فهرست منبع

Adds support for heightfield collision shapes (including needed bullet_new in DebugNew.h).
Fixes a couple bugs in SceneLoader (allows for no 'physics' element in the .scene file and fixes a memory leak with Node stitching).
Fixes wireframe drawing for models when indices are of type INDEX16 or INDEX32.
Refactors some of the functionality of the Texture class into the Texture::Image class to support loading image data without immediately storing it on the GPU.
Adds missing function to Transform.

Chris Culy 14 سال پیش
والد
کامیت
2f846925ae

+ 1 - 0
gameplay-encoder/src/Mesh.cpp

@@ -233,6 +233,7 @@ void Mesh::generateHeightmap(const char* filename)
         png_write_row(png_ptr, row);
     }
 
+    png_write_end(png_ptr, NULL);
     DEBUGPRINT_VARG("> Saved heightmap: %s\n", filename);
 
 error:

+ 14 - 1
gameplay/src/DebugNew.h

@@ -39,7 +39,7 @@ void operator delete[] (void* p, const char* file, int line) throw();
 #endif
 
 // Since Bullet overrides new, we define custom functions to allocate Bullet objects that undef
-// 'new' before allocation and redefine it to our custom version afterwards (we support 0-2 parameter constructors).
+// 'new' before allocation and redefine it to our custom version afterwards (we support 0-2, 9 parameter constructors).
 template<typename T> T* bullet_new()
 {
 #ifdef GAMEPLAY_MEM_LEAK_DETECTION
@@ -76,4 +76,17 @@ template<typename T, typename T1, typename T2> T* bullet_new(T1 t1, T2 t2)
 #endif
 }
 
+template<typename T, typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8, typename T9> 
+T* bullet_new(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8, T9 t9)
+{
+#ifdef GAMEPLAY_MEM_LEAK_DETECTION
+#undef new
+    T* t = new T(t1, t2, t3, t4, t5, t6, t7, t8, t9);
+#define new DEBUG_NEW
+    return t;
+#else
+    return new T(t1, t2, t3, t4, t5, t6, t7, t8, t9);
+#endif
+}
+
 #endif

+ 28 - 10
gameplay/src/Model.cpp

@@ -219,22 +219,24 @@ void Model::setNode(Node* node)
 void Model::draw(bool wireframe)
 {
     wireframe &= (_mesh->getPrimitiveType() == Mesh::TRIANGLES) | (_mesh->getPrimitiveType() == Mesh::TRIANGLE_STRIP);
-    unsigned int count = _mesh->getPartCount();
-    if (count == 0)
+    unsigned int partCount = _mesh->getPartCount();
+    if (partCount == 0)
     {
         // No mesh parts (index buffers).
         if (_material)
         {
             Technique* technique = _material->getTechnique();
-            for (unsigned int i = 0, count = technique->getPassCount(); i < count; ++i)
+            unsigned int techniqueCount = technique->getPassCount();
+            for (unsigned int i = 0; i < techniqueCount; ++i)
             {
                 Pass* pass = technique->getPass(i);
                 pass->bind();
                 if (wireframe)
                 {
-                    for (unsigned int i = 0, count = _mesh->getVertexCount(); i < count; i += 3)
+                    unsigned int vertexCount = _mesh->getVertexCount();
+                    for (unsigned int j = 0; j < vertexCount; j += 3)
                     {
-                        GL_ASSERT( glDrawArrays(GL_LINE_LOOP, i, 3) );
+                        GL_ASSERT( glDrawArrays(GL_LINE_LOOP, j, 3) );
                     }
                 }
                 else
@@ -247,7 +249,7 @@ void Model::draw(bool wireframe)
     }
     else
     {
-        for (unsigned int i = 0; i < count; ++i)
+        for (unsigned int i = 0; i < partCount; ++i)
         {
             MeshPart* part = _mesh->getPart(i);
 
@@ -265,16 +267,32 @@ void Model::draw(bool wireframe)
             if (material)
             {
                 Technique* technique = material->getTechnique();
-                for (unsigned int i = 0, count = technique->getPassCount(); i < count; ++i)
+                unsigned int techniqueCount = technique->getPassCount();
+                for (unsigned int j = 0; j < techniqueCount; ++j)
                 {
-                    Pass* pass = technique->getPass(i);
+                    Pass* pass = technique->getPass(j);
                     pass->bind();
                     GL_ASSERT( glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, part->_indexBuffer) );
                     if (wireframe)
                     {
-                        for (unsigned int i = 0, count = part->getIndexCount(); i < count; i += 3)
+                        unsigned int indexCount = part->getIndexCount();
+                        unsigned int indexSize = 0;
+                        switch (part->getIndexFormat())
+                        {
+                        case Mesh::INDEX8:
+                            indexSize = 1;
+                            break;
+                        case Mesh::INDEX16:
+                            indexSize = 2;
+                            break;
+                        case Mesh::INDEX32:
+                            indexSize = 4;
+                            break;
+                        }
+
+                        for (unsigned int k = 0; k < indexCount; k += 3)
                         {
-                            GL_ASSERT( glDrawElements(GL_LINE_LOOP, 3, part->getIndexFormat(), (const GLvoid*)i) );
+                            GL_ASSERT( glDrawElements(GL_LINE_LOOP, 3, part->getIndexFormat(), ((const GLvoid*)(k*indexSize))) );
                         }
                     }
                     else

+ 196 - 4
gameplay/src/PhysicsRigidBody.cpp

@@ -4,6 +4,8 @@
 #include "PhysicsMotionState.h"
 #include "PhysicsRigidBody.h"
 
+#include "BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h"
+
 namespace gameplay
 {
 
@@ -11,14 +13,18 @@ const int PhysicsRigidBody::Listener::DIRTY         = 0x01;
 const int PhysicsRigidBody::Listener::COLLISION     = 0x02;
 const int PhysicsRigidBody::Listener::REGISTERED    = 0x04;
 
-// Internal value used for creating mesh rigid bodies.
+// Internal values used for creating mesh and heightfield rigid bodies.
 #define SHAPE_MESH ((PhysicsRigidBody::Type)(PhysicsRigidBody::SHAPE_NONE + 1))
+#define SHAPE_HEIGHTFIELD ((PhysicsRigidBody::Type)(PhysicsRigidBody::SHAPE_NONE + 2))
+
+// Helper function for calculating heights from heightmap (image) or heightfield data.
+static float calculateHeight(float* data, unsigned int width, unsigned int height, float x, float y);
 
 PhysicsRigidBody::PhysicsRigidBody(Node* node, PhysicsRigidBody::Type type, float mass, 
         float friction, float restitution, float linearDamping, float angularDamping)
         : _shape(NULL), _body(NULL), _node(node), _listeners(NULL), _angularVelocity(NULL),
         _anisotropicFriction(NULL), _gravity(NULL), _linearVelocity(NULL), _vertexData(NULL),
-        _indexData(NULL)
+        _indexData(NULL), _heightfieldData(NULL), _inverse(NULL), _inverseIsDirty(true)
 {
     switch (type)
     {
@@ -55,6 +61,88 @@ PhysicsRigidBody::PhysicsRigidBody(Node* node, PhysicsRigidBody::Type type, floa
     Game::getInstance()->getPhysicsController()->addRigidBody(this);
 }
 
+PhysicsRigidBody::PhysicsRigidBody(Node* node, Texture::Image* image, float mass,
+    float friction, float restitution, float linearDamping, float angularDamping)
+        : _shape(NULL), _body(NULL), _node(node), _listeners(NULL), _angularVelocity(NULL),
+        _anisotropicFriction(NULL), _gravity(NULL), _linearVelocity(NULL), _vertexData(NULL),
+        _indexData(NULL), _heightfieldData(NULL), _inverse(NULL), _inverseIsDirty(true)
+{
+    // Get the width, length and minimum and maximum height of the heightfield.
+    const BoundingBox& box = node->getModel()->getMesh()->getBoundingBox();
+    float width = box.max.x - box.min.x;
+    float minHeight = box.min.y;
+    float maxHeight = box.max.y;
+    float length = box.max.z - box.min.z;
+
+    // Get the size in bytes of a pixel (we ensure that the image's
+    // pixel format is actually supported before calling this constructor).
+    unsigned int pixelSize = 0;
+    switch (image->getFormat())
+    {
+        case Texture::RGB888:
+            pixelSize = 3;
+            break;
+        case Texture::RGBA8888:
+            pixelSize = 4;
+            break;
+    }
+
+    // Calculate the heights for each pixel.
+    float* data = new float[image->getWidth() * image->getHeight()];
+    for (unsigned int x = 0; x < image->getWidth(); x++)
+    {
+        for (unsigned int y = 0; y < image->getHeight(); y++)
+        {
+            data[x + y * image->getWidth()] = ((((float)image->getData()[(x + y * image->getHeight()) * pixelSize + 0]) +
+                ((float)image->getData()[(x + y * image->getHeight()) * pixelSize + 1]) +
+                ((float)image->getData()[(x + y * image->getHeight()) * pixelSize + 2])) / 768.0f) * (maxHeight - minHeight) + minHeight;
+        }
+    }
+
+    // Generate the heightmap data needed for physics (one height per world unit).
+    unsigned int sizeWidth = width;
+    unsigned int sizeHeight = length;
+    _width = sizeWidth + 1;
+    _height = sizeHeight + 1;
+    _heightfieldData = new float[_width * _height];
+    unsigned int heightIndex = 0;
+    float widthImageFactor = (float)(image->getWidth() - 1) / sizeWidth;
+    float heightImageFactor = (float)(image->getHeight() - 1) / sizeHeight;
+    float x = 0.0f;
+    float z = 0.0f;
+    for (unsigned int row = 0, z = 0.0f; row <= sizeHeight; row++, z += 1.0f)
+    {
+        for (unsigned int col = 0, x = 0.0f; col <= sizeWidth; col++, x += 1.0f)
+        {
+            heightIndex = row * _width + col;
+            _heightfieldData[heightIndex] = calculateHeight(data, image->getWidth(), image->getHeight(), x * widthImageFactor, (sizeHeight - z) * heightImageFactor);
+        }
+    }
+    SAFE_DELETE_ARRAY(data);
+
+    // Create the heightfield collision shape.
+    _shape = bullet_new<btHeightfieldTerrainShape>(_width, _height, _heightfieldData,
+        1.0f, minHeight, maxHeight, 1, PHY_FLOAT, false);
+
+    // Offset the heightmap's center of mass according to the way that Bullet calculates the origin 
+    // of its heightfield collision shape; see documentation for the btHeightfieldTerrainShape for more info.
+    Vector3 s;
+    node->getWorldMatrix().getScale(&s);
+    Vector3 c (0.0f, -(maxHeight - (0.5f * (maxHeight - minHeight))) / s.y, 0.0f);
+
+    // Create the Bullet rigid body.
+    if (c.lengthSquared() > MATH_EPSILON)
+        _body = createRigidBodyInternal(_shape, mass, node, friction, restitution, linearDamping, angularDamping, &c);
+    else
+        _body = createRigidBodyInternal(_shape, mass, node, friction, restitution, linearDamping, angularDamping);
+
+    // Add the rigid body to the physics world.
+    Game::getInstance()->getPhysicsController()->addRigidBody(this);
+
+    // Add the rigid body as a listener on the node's transform.
+    _node->addListener(this);
+}
+
 PhysicsRigidBody::~PhysicsRigidBody()
 {
     // Clean up all constraints linked to this rigid body.
@@ -88,6 +176,8 @@ PhysicsRigidBody::~PhysicsRigidBody()
     {
         SAFE_DELETE_ARRAY(_indexData[i]);
     }
+    SAFE_DELETE_ARRAY(_heightfieldData);
+    SAFE_DELETE(_inverse);
 }
 
 void PhysicsRigidBody::addCollisionListener(Listener* listener, PhysicsRigidBody* body)
@@ -202,6 +292,7 @@ PhysicsRigidBody* PhysicsRigidBody::create(Node* node, Properties* properties)
     bool kinematic = false;
     Vector3* gravity = NULL;
     Vector3* anisotropicFriction = NULL;
+    const char* imagePath = NULL;
 
     // Load the defined properties.
     properties->rewind();
@@ -217,6 +308,8 @@ PhysicsRigidBody* PhysicsRigidBody::create(Node* node, Properties* properties)
                 type = SHAPE_SPHERE;
             else if (typeStr == "MESH")
                 type = SHAPE_MESH;
+            else if (typeStr == "HEIGHTFIELD")
+                type = SHAPE_HEIGHTFIELD;
             else
             {
                 WARN_VARG("Could not create rigid body; unsupported value for rigid body type: '%s'.", typeStr.c_str());
@@ -257,6 +350,10 @@ PhysicsRigidBody* PhysicsRigidBody::create(Node* node, Properties* properties)
             anisotropicFriction = new Vector3();
             properties->getVector3(NULL, anisotropicFriction);
         }
+        else if (strcmp(name, "image") == 0)
+        {
+            imagePath = properties->getString();
+        }
     }
 
     // If the rigid body type is equal to mesh, check that the node's mesh's primitive type is supported.
@@ -281,7 +378,32 @@ PhysicsRigidBody* PhysicsRigidBody::create(Node* node, Properties* properties)
     }
 
     // Create the rigid body.
-    PhysicsRigidBody* body = new PhysicsRigidBody(node, type, mass, friction, restitution, linearDamping, angularDamping);
+    PhysicsRigidBody* body = NULL;
+    if (imagePath == NULL)
+    {
+        body = new PhysicsRigidBody(node, type, mass, friction, restitution, linearDamping, angularDamping);
+    }
+    else
+    {
+        // Load the image data from the given file path.
+        Texture::Image* image = Texture::Image::create(imagePath);
+        if (!image)
+            return NULL;
+
+        // Ensure that the image's pixel format is supported.
+        switch (image->getFormat())
+        {
+            case Texture::RGB888:
+            case Texture::RGBA8888:
+                break;
+            default:
+                WARN_VARG("Heightmap: pixel format is not supported: %d", image->getFormat());
+                return NULL;
+        }
+
+        body = new PhysicsRigidBody(node, image, mass, friction, restitution, linearDamping, angularDamping);
+        SAFE_RELEASE(image);
+    }
 
     // Set any initially defined properties.
     if (kinematic)
@@ -298,6 +420,40 @@ PhysicsRigidBody* PhysicsRigidBody::create(Node* node, Properties* properties)
     return body;
 }
 
+
+float PhysicsRigidBody::getHeight(float x, float y) const
+{
+    // This function is only supported for heightfield rigid bodies.
+    if (_shape->getShapeType() != TERRAIN_SHAPE_PROXYTYPE)
+    {
+        WARN("Attempting to get the height of a non-heightfield rigid body.");
+        return 0.0f;
+    }
+
+    // Calculate the correct x, y position relative to the heightfield data.
+    if (_inverseIsDirty)
+    {
+        if (_inverse == NULL)
+            _inverse = new Matrix();
+
+        _node->getWorldMatrix().invert(_inverse);
+        _inverseIsDirty = false;
+    }
+
+    Vector3 v = (*_inverse) * Vector3(x, 0.0f, y);
+    x = (v.x + (0.5f * (_width - 1))) * _width / (_width - 1);
+    y = (v.z + (0.5f * (_height - 1))) * _height / (_height - 1);
+
+    // Check that the x, y position is within the bounds.
+    if (x < 0.0f || x > _width || y < 0.0f || y > _height)
+    {
+        WARN_VARG("Attempting to get height at point '%f, %f', which is outside the range of the heightfield with width %d and height %d.", x, y, _width, _height);
+        return 0.0f;
+    }
+
+    return calculateHeight(_heightfieldData, _width, _height, x, y);
+}
+
 btRigidBody* PhysicsRigidBody::createRigidBodyInternal(btCollisionShape* shape, float mass, Node* node,
                                                        float friction, float restitution, float linearDamping, float angularDamping, 
                                                        const Vector3* centerOfMassOffset)
@@ -340,7 +496,12 @@ void PhysicsRigidBody::removeConstraint(PhysicsConstraint* constraint)
 
 bool PhysicsRigidBody::supportsConstraints()
 {
-    return _shape->getShapeType() != TRIANGLE_MESH_SHAPE_PROXYTYPE;
+    return _shape->getShapeType() != TRIANGLE_MESH_SHAPE_PROXYTYPE && _shape->getShapeType() != TERRAIN_SHAPE_PROXYTYPE;
+}
+
+void PhysicsRigidBody::transformChanged(Transform* transform, long cookie)
+{
+    _inverseIsDirty = true;
 }
 
 PhysicsRigidBody::CollisionPair::CollisionPair(PhysicsRigidBody* rbA, PhysicsRigidBody* rbB)
@@ -392,4 +553,35 @@ btScalar PhysicsRigidBody::CollidesWithCallback::addSingleResult(btManifoldPoint
     return 0.0f;
 }
 
+float calculateHeight(float* data, unsigned int width, unsigned int height, float x, float y)
+{
+    unsigned int x1 = x;
+    unsigned int y1 = y;
+    unsigned int x2 = x1 + 1;
+    unsigned int y2 = y1 + 1;
+    float tmp;
+    float xFactor = modf(x, &tmp);
+    float yFactor = modf(y, &tmp);
+    float xFactorI = 1.0f - xFactor;
+    float yFactorI = 1.0f - yFactor;
+
+    if (x2 >= width && y2 >= height)
+    {
+        return data[x1 + y1 * width];
+    }
+    else if (x2 >= width)
+    {
+        return data[x1 + y1 * width] * yFactorI + data[x1 + y2 * width] * yFactor;
+    }
+    else if (y2 >= height)
+    {
+        return data[x1 + y1 * width] * xFactorI + data[x2 + y1 * width] * xFactor;
+    }
+    else
+    {
+        return data[x1 + y1 * width] * xFactorI * yFactorI + data[x1 + y2 * width] * xFactorI * yFactor + 
+            data[x2 + y2 * width] * xFactor * yFactor + data[x2 + y1 * width] * xFactor * yFactorI;
+    }
+}
+
 }

+ 34 - 1
gameplay/src/PhysicsRigidBody.h

@@ -15,7 +15,7 @@ class PhysicsConstraint;
 /**
  * Defines a class for physics rigid bodies.
  */
-class PhysicsRigidBody
+class PhysicsRigidBody : public Transform::Listener
 {
     friend class Node;
     friend class PhysicsConstraint;
@@ -190,6 +190,15 @@ public:
      */
     inline const Vector3& getGravity() const;
 
+    /**
+     * Gets the height at the given point (only for rigid bodies of type HEIGHTFIELD).
+     * 
+     * @param x The x position.
+     * @param y The y position.
+     * @return The height at the given point.
+     */
+    float getHeight(float x, float y) const;
+
     /**
      * Gets the rigid body's linear damping.
      * 
@@ -300,6 +309,22 @@ private:
     PhysicsRigidBody(Node* node, PhysicsRigidBody::Type type, float mass, float friction = 0.5,
         float restitution = 0.0, float linearDamping = 0.0, float angularDamping = 0.0);
 
+    /**
+     * Creates a heightfield rigid body.
+     * 
+     * @param node The node to create the heightfield rigid body for; note that the node must have
+     *      a model attached to it prior to creating a rigid body for it.
+     * @param image The heightfield image.
+     * @param mass The mass of the rigid body, in kilograms.
+     * @param friction The friction of the rigid body (non-zero values give best simulation results).
+     * @param restitution The restitution of the rigid body (this controls the bounciness of
+     *      the rigid body; use zero for best simulation results).
+     * @param linearDamping The percentage of linear velocity lost per second (between 0.0 and 1.0).
+     * @param angularDamping The percentage of angular velocity lost per second (between 0.0 and 1.0).
+     */
+    PhysicsRigidBody(Node* node, Texture::Image* image, float mass, float friction = 0.5,
+        float restitution = 0.0, float linearDamping = 0.0, float angularDamping = 0.0);
+
     /**
      * Destructor.
      */
@@ -345,6 +370,9 @@ private:
     // Whether or not the rigid body supports constraints fully.
     bool supportsConstraints();
 
+    // Used for implementing getHeight() when the heightfield has a transform that can change.
+    void transformChanged(Transform* transform, long cookie);
+
     // Internal class used to implement the collidesWith(PhysicsRigidBody*) function.
     struct CollidesWithCallback : public btCollisionWorld::ContactResultCallback
     {
@@ -366,6 +394,11 @@ private:
     mutable Vector3* _linearVelocity;
     float* _vertexData;
     std::vector<unsigned char*> _indexData;
+    float* _heightfieldData;
+    unsigned int _width;
+    unsigned int _height;
+    mutable Matrix* _inverse;
+    mutable bool _inverseIsDirty;
 };
 
 }

+ 71 - 32
gameplay/src/SceneLoader.cpp

@@ -77,7 +77,7 @@ Scene* SceneLoader::load(const char* filePath)
     while (true)
     {
         Properties* ns = sceneProperties->getNextNamespace();
-        if (strcmp(ns->getNamespace(), "physics") == 0)
+        if (ns == NULL || strcmp(ns->getNamespace(), "physics") == 0)
         {
             physics = ns;
             break;
@@ -184,7 +184,7 @@ void SceneLoader::addSceneNodeProperty(SceneNodeProperty::Type type, const char*
 
 void SceneLoader::applyNodeProperties(const Scene* scene, const Properties* sceneProperties)
 {
-    // Apply all of the remaining scene node properties.
+    // Apply all of the remaining scene node properties except rigid body (we apply that last).
     for (unsigned int i = 0; i < _nodeProperties.size(); i++)
     {
         // If the referenced node doesn't exist in the scene, then we
@@ -254,36 +254,8 @@ void SceneLoader::applyNodeProperties(const Scene* scene, const Properties* scen
                 break;
             }
             case SceneNodeProperty::RIGIDBODY:
-            {
-                // If the scene file specifies a rigid body model, use it for creating the rigid body.
-                Properties* np = sceneProperties->getNamespace(_nodeProperties[i]._nodeID);
-                const char* name = NULL;
-                if (np && (name = np->getString("rigidbodymodel")))
-                {
-                    Node* modelNode = scene->findNode(name);
-                    if (!modelNode)
-                        WARN_VARG("Node '%s' does not exist; attempting to use its model for rigid body creation.", name);
-                    else
-                    {
-                        if (!modelNode->getModel())
-                            WARN_VARG("Node '%s' does not have a model; attempting to use its model for rigid body creation.", name);
-                        else
-                        {
-                            // Set the specified model during physics rigid body creation.
-                            Model* model = node->getModel();
-                            node->setModel(modelNode->getModel());
-                            node->setPhysicsRigidBody(p);
-                            node->setModel(model);
-                        }
-                    }
-                }
-                else if (!node->getModel())
-                    WARN_VARG("Attempting to set a rigid body on node '%s', which has no model.", _nodeProperties[i]._nodeID);
-                else
-                    node->setPhysicsRigidBody(p);
-
+                // Process this last in a separate loop to allow scale, translate, rotate to be applied first.
                 break;
-            }
             default:
                 // This cannot happen.
                 break;
@@ -322,8 +294,74 @@ void SceneLoader::applyNodeProperties(const Scene* scene, const Properties* scen
                 break;
             }
         }
+    }
+
+    // Process rigid body properties.
+    for (unsigned int i = 0; i < _nodeProperties.size(); i++)
+    {
+        if (_nodeProperties[i]._type == SceneNodeProperty::RIGIDBODY)
+        {
+            // If the referenced node doesn't exist in the scene, then we
+            // can't do anything so we skip to the next scene node property.
+            Node* node = scene->findNode(_nodeProperties[i]._nodeID);
+            if (!node)
+            {
+                WARN_VARG("Attempting to set a property for node '%s', which does not exist in the scene.", _nodeProperties[i]._nodeID);
+                continue;
+            }
+
+            // Check to make sure the referenced properties object was loaded properly.
+            Properties* p = _propertiesFromFile[_nodeProperties[i]._file];
+            if (!p)
+            {
+                WARN_VARG("The referenced node data in file '%s' failed to load.", _nodeProperties[i]._file.c_str());
+                continue;
+            }
 
-        
+            // If a specific namespace within the file was specified, load that namespace.
+            if (_nodeProperties[i]._id.size() > 0)
+            {
+                p = p->getNamespace(_nodeProperties[i]._id.c_str());
+                if (!p)
+                {
+                    WARN_VARG("The referenced node data at '%s#%s' failed to load.", _nodeProperties[i]._file.c_str(), _nodeProperties[i]._id.c_str());
+                    continue;
+                }
+            }
+            else
+            {
+                // Otherwise, use the first namespace.
+                p->rewind();
+                p = p->getNextNamespace();
+            }
+
+            // If the scene file specifies a rigid body model, use it for creating the rigid body.
+            Properties* np = sceneProperties->getNamespace(_nodeProperties[i]._nodeID);
+            const char* name = NULL;
+            if (np && (name = np->getString("rigidbodymodel")))
+            {
+                Node* modelNode = scene->findNode(name);
+                if (!modelNode)
+                    WARN_VARG("Node '%s' does not exist; attempting to use its model for rigid body creation.", name);
+                else
+                {
+                    if (!modelNode->getModel())
+                        WARN_VARG("Node '%s' does not have a model; attempting to use its model for rigid body creation.", name);
+                    else
+                    {
+                        // Set the specified model during physics rigid body creation.
+                        Model* model = node->getModel();
+                        node->setModel(modelNode->getModel());
+                        node->setPhysicsRigidBody(p);
+                        node->setModel(model);
+                    }
+                }
+            }
+            else if (!node->getModel())
+                WARN_VARG("Attempting to set a rigid body on node '%s', which has no model.", _nodeProperties[i]._nodeID);
+            else
+                node->setPhysicsRigidBody(p);
+        }
     }
 }
 
@@ -365,6 +403,7 @@ void SceneLoader::applyNodeUrls(Scene* scene)
                         {
                             node->setId(_nodeProperties[i]._nodeID);
                             scene->addNode(node);
+                            SAFE_RELEASE(node);
                         }
                         
                         SAFE_RELEASE(tmpPackage);

+ 108 - 97
gameplay/src/Texture.cpp

@@ -67,7 +67,9 @@ Texture* Texture::create(const char* path, bool generateMipmaps)
         case 4:
             if (tolower(ext[1]) == 'p' && tolower(ext[2]) == 'n' && tolower(ext[3]) == 'g')
             {
-                texture = loadPNG(path, generateMipmaps);
+                Image* image = Image::create(path);
+                texture = create(image, generateMipmaps);
+                SAFE_RELEASE(image);
             }
             break;
         }
@@ -88,103 +90,9 @@ Texture* Texture::create(const char* path, bool generateMipmaps)
     return NULL;
 }
 
-Texture* Texture::loadPNG(const char* path, bool generateMipmaps)
+Texture* Texture::create(Image* image, bool generateMipmaps)
 {
-    // Open the file.
-    FILE* fp = FileSystem::openFile(path, "rb");
-    if (fp == NULL)
-    {
-        return NULL;
-    }
-
-    // Verify PNG signature.
-    unsigned char sig[8];
-    if (fread(sig, 1, 8, fp) != 8 || png_sig_cmp(sig, 0, 8) != 0)
-    {
-        LOG_ERROR_VARG("Texture is not a valid PNG: %s", path);
-        fclose(fp);
-        return NULL;
-    }
-
-    // Initialize png read struct (last three parameters use stderr+longjump if NULL).
-    png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
-    if (png == NULL)
-    {
-        fclose(fp);
-        return NULL;
-    }
-
-    // Initialize info struct.
-    png_infop info = png_create_info_struct(png);
-    if (info == NULL)
-    {
-        fclose(fp);
-        png_destroy_read_struct(&png, NULL, NULL);
-        return NULL;
-    }
-
-    // Set up error handling (required without using custom error handlers above).
-    if (setjmp(png_jmpbuf(png)))
-    {
-        fclose(fp);
-        png_destroy_read_struct(&png, &info, NULL);
-        return NULL;
-    }
-
-    // Initialize file io.
-    png_init_io(png, fp);
-
-    // Indicate that we already read the first 8 bytes (signature).
-    png_set_sig_bytes(png, 8);
-
-    // Read the entire image into memory.
-    png_read_png(png, info, PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND, NULL);
-
-    unsigned int width = png_get_image_width(png, info);
-    unsigned int height = png_get_image_height(png, info);
-    png_byte colorType = png_get_color_type(png, info);
-    Format format;
-
-    switch (colorType)
-    {
-    case PNG_COLOR_TYPE_RGBA:
-        format = RGBA8888;
-        break;
-
-    case PNG_COLOR_TYPE_RGB:
-        format = RGB888;
-        break;
-
-    default:
-        LOG_ERROR_VARG("Unsupported PNG color type (%d) for texture: %s", (int)colorType, path);
-        fclose(fp);
-        png_destroy_read_struct(&png, &info, NULL);
-        return NULL;
-    }
-
-    unsigned int stride = png_get_rowbytes(png, info);
-
-    // Allocate image data.
-    unsigned char* data = new unsigned char[stride * height];
-
-    // Read rows into image data.
-    png_bytepp rows = png_get_rows(png, info);
-    for (unsigned int i = 0; i < height; ++i)
-    {
-        memcpy(data+(stride * (height-1-i)), rows[i], stride);
-    }
-
-    // Clean up.
-    png_destroy_read_struct(&png, &info, NULL);
-    fclose(fp);
-
-    // Create texture.
-    Texture* texture = create(format, width, height, data, generateMipmaps);
-
-    // Free temporary data.
-    SAFE_DELETE_ARRAY(data);
-
-    return texture;
+    return create(image->_format, image->_width, image->_height, image->_data, generateMipmaps);
 }
 
 Texture* Texture::create(Format format, unsigned int width, unsigned int height, unsigned char* data, bool generateMipmaps)
@@ -325,4 +233,107 @@ void Texture::Sampler::bind()
     GL_ASSERT( glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (GLenum)_magFilter) );
 }
 
+Texture::Image* Texture::Image::create(const char* path)
+{
+    // Open the file.
+    FILE* fp = FileSystem::openFile(path, "rb");
+    if (fp == NULL)
+    {
+        return NULL;
+    }
+
+    // Verify PNG signature.
+    unsigned char sig[8];
+    if (fread(sig, 1, 8, fp) != 8 || png_sig_cmp(sig, 0, 8) != 0)
+    {
+        LOG_ERROR_VARG("Texture is not a valid PNG: %s", path);
+        fclose(fp);
+        return NULL;
+    }
+
+    // Initialize png read struct (last three parameters use stderr+longjump if NULL).
+    png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+    if (png == NULL)
+    {
+        fclose(fp);
+        return NULL;
+    }
+
+    // Initialize info struct.
+    png_infop info = png_create_info_struct(png);
+    if (info == NULL)
+    {
+        fclose(fp);
+        png_destroy_read_struct(&png, NULL, NULL);
+        return NULL;
+    }
+
+    // Set up error handling (required without using custom error handlers above).
+    if (setjmp(png_jmpbuf(png)))
+    {
+        fclose(fp);
+        png_destroy_read_struct(&png, &info, NULL);
+        return NULL;
+    }
+
+    // Initialize file io.
+    png_init_io(png, fp);
+
+    // Indicate that we already read the first 8 bytes (signature).
+    png_set_sig_bytes(png, 8);
+
+    // Read the entire image into memory.
+    png_read_png(png, info, PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND, NULL);
+
+    Image* image = new Image();
+    image->_width = png_get_image_width(png, info);
+    image->_height = png_get_image_height(png, info);
+
+    png_byte colorType = png_get_color_type(png, info);
+    switch (colorType)
+    {
+    case PNG_COLOR_TYPE_RGBA:
+        image->_format = RGBA8888;
+        break;
+
+    case PNG_COLOR_TYPE_RGB:
+        image->_format = RGB888;
+        break;
+
+    default:
+        LOG_ERROR_VARG("Unsupported PNG color type (%d) for texture: %s", (int)colorType, path);
+        fclose(fp);
+        png_destroy_read_struct(&png, &info, NULL);
+        return NULL;
+    }
+
+    unsigned int stride = png_get_rowbytes(png, info);
+
+    // Allocate image data.
+    image->_data = new unsigned char[stride * image->_height];
+
+    // Read rows into image data.
+    png_bytepp rows = png_get_rows(png, info);
+    for (unsigned int i = 0; i < image->_height; ++i)
+    {
+        memcpy(image->_data+(stride * (image->_height-1-i)), rows[i], stride);
+    }
+
+    // Clean up.
+    png_destroy_read_struct(&png, &info, NULL);
+    fclose(fp);
+
+    return image;
+}
+
+Texture::Image::Image()
+{
+    // Unused
+}
+
+Texture::Image::~Image()
+{
+    SAFE_DELETE_ARRAY(_data);
+}
+
 }

+ 66 - 2
gameplay/src/Texture.h

@@ -119,6 +119,67 @@ public:
         Filter _magFilter;
     };
 
+    /**
+     * Loads image data (currently only supports PNG files).
+     */
+    class Image : public Ref
+    {
+        friend class Texture;
+
+    public:
+        /**
+         * Creates an image from the image file at the given path.
+         * 
+         * @param path The path to the image file.
+         * @return The newly created image.
+         */
+        static Image* create(const char* path);
+
+        /**
+         * Gets the image's raw pixel data.
+         * 
+         * @return The image's pixel data.
+         */
+        inline unsigned char* getData() { return _data; }
+
+        /**
+         * Gets the image's format.
+         * 
+         * @return The image's format.
+         */
+        inline Format getFormat() { return _format; }
+
+        /**
+         * Gets the height of the image.
+         * 
+         * @return The height of the image.
+         */
+        inline unsigned int getHeight() { return _height; }
+        
+        /**
+         * Gets the width of the image.
+         * 
+         * @return The width of the image.
+         */
+        inline unsigned int getWidth() { return _width; }
+
+    private:
+        /**
+         * Constructor.
+         */
+        Image();
+        
+        /**
+         * Destructor.
+         */
+        ~Image();
+
+        unsigned char* _data;
+        Format _format;
+        unsigned int _height;
+        unsigned int _width;
+    };
+
     /**
      * Creates a texture from the given image resource.
      *
@@ -129,6 +190,11 @@ public:
      */
     static Texture* create(const char* path, bool generateMipmaps = false);
 
+    /**
+     * Creates a texture from the given image.
+     */
+    static Texture* create(Image* image, bool generateMipmaps = false);
+
     /**
      * Creates a texture from the given texture data.
      */
@@ -196,8 +262,6 @@ private:
      */
     virtual ~Texture();
 
-    static Texture* loadPNG(const char* path, bool generateMipmaps);
-
     std::string _path;
     TextureHandle _handle;
     unsigned int _width;

+ 6 - 0
gameplay/src/Transform.cpp

@@ -402,6 +402,12 @@ void Transform::setRotation(const Matrix& rotation)
     dirty();
 }
 
+void Transform::setRotation(const Vector3& axis, float angle)
+{
+    _rotation.set(axis, angle);
+    dirty();
+}
+
 void Transform::setTranslation(const Vector3& translation)
 {
     _translation.set(translation);