Преглед изворни кода

Merge pull request #1127 from sgrenier/next

Minor fixes and features
Steve Grenier пре 12 година
родитељ
комит
807720a229

+ 126 - 97
gameplay/src/PhysicsController.cpp

@@ -11,6 +11,7 @@
 #undef new
 #endif
 #include "BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h"
+#include "BulletCollision/CollisionShapes/btShapeHull.h"
 #ifdef GAMEPLAY_MEM_LEAK_DETECTION
 #define new DEBUG_NEW
 #endif
@@ -762,7 +763,7 @@ static void computeCenterOfMass(const Vector3& center, const Vector3& scale, Vec
     centerOfMassOffset->negate();
 }
 
-PhysicsCollisionShape* PhysicsController::createShape(Node* node, const PhysicsCollisionShape::Definition& shape, Vector3* centerOfMassOffset)
+PhysicsCollisionShape* PhysicsController::createShape(Node* node, const PhysicsCollisionShape::Definition& shape, Vector3* centerOfMassOffset, bool dynamic)
 {
     GP_ASSERT(node);
 
@@ -887,7 +888,7 @@ PhysicsCollisionShape* PhysicsController::createShape(Node* node, const PhysicsC
     case PhysicsCollisionShape::SHAPE_MESH:
         {
             // Build mesh from passed in shape.
-            collisionShape = createMesh(shape.data.mesh, scale);
+            collisionShape = createMesh(shape.data.mesh, scale, dynamic);
         }
         break;
 
@@ -1055,34 +1056,10 @@ PhysicsCollisionShape* PhysicsController::createHeightfield(Node* node, HeightFi
     return shape;
 }
 
-PhysicsCollisionShape* PhysicsController::createMesh(Mesh* mesh, const Vector3& scale)
+PhysicsCollisionShape* PhysicsController::createMesh(Mesh* mesh, const Vector3& scale, bool dynamic)
 {
     GP_ASSERT(mesh);
 
-    // Only support meshes with triangle list primitive types.
-    bool triMesh = true;
-    if (mesh->getPartCount() > 0)
-    {
-        for (unsigned int i = 0; i < mesh->getPartCount(); ++i)
-        {
-            if (mesh->getPart(i)->getPrimitiveType() != Mesh::TRIANGLES)
-            {
-                triMesh = false;
-                break;
-            }
-        }
-    }
-    else
-    {
-        triMesh = mesh->getPrimitiveType() == Mesh::TRIANGLES;
-    }
-
-    if (!triMesh)
-    {
-        GP_ERROR("Mesh rigid bodies are currently only supported on meshes with TRIANGLES primitive type.");
-        return NULL;
-    }
-
     // The mesh must have a valid URL (i.e. it must have been loaded from a Bundle)
     // in order to fetch mesh data for computing mesh rigid body.
     if (strlen(mesh->getUrl()) == 0)
@@ -1091,6 +1068,36 @@ PhysicsCollisionShape* PhysicsController::createMesh(Mesh* mesh, const Vector3&
         return NULL;
     }
 
+    if (!dynamic)
+    {
+        // Static meshes use btBvhTriangleMeshShape and therefore only support triangle mesh shapes.
+        // Dynamic meshes are approximated with a btConvexHullShape (convex wrapper on cloud of vertices)
+        // and therefore can support any primitive type.
+        bool triMesh = true;
+        if (mesh->getPartCount() > 0)
+        {
+            for (unsigned int i = 0; i < mesh->getPartCount(); ++i)
+            {
+                if (mesh->getPart(i)->getPrimitiveType() != Mesh::TRIANGLES)
+                {
+                    triMesh = false;
+                    break;
+                }
+            }
+        }
+        else
+        {
+            triMesh = mesh->getPrimitiveType() == Mesh::TRIANGLES;
+        }
+
+        if (!triMesh)
+        {
+            GP_ERROR("Mesh rigid bodies are currently only supported on meshes with TRIANGLES primitive type.");
+            return NULL;
+        }
+    }
+
+    // Read mesh data from URL
     Bundle::MeshData* data = Bundle::readMeshData(mesh->getUrl());
     if (data == NULL)
     {
@@ -1112,96 +1119,118 @@ PhysicsCollisionShape* PhysicsController::createMesh(Mesh* mesh, const Vector3&
     for (unsigned int i = 0; i < data->vertexCount; i++)
     {
         v.set(*((float*)&data->vertexData[i * vertexStride + 0 * sizeof(float)]),
-              *((float*)&data->vertexData[i * vertexStride + 1 * sizeof(float)]),
-              *((float*)&data->vertexData[i * vertexStride + 2 * sizeof(float)]));
+                *((float*)&data->vertexData[i * vertexStride + 1 * sizeof(float)]),
+                *((float*)&data->vertexData[i * vertexStride + 2 * sizeof(float)]));
         v *= m;
         memcpy(&(shapeMeshData->vertexData[i * 3]), &v, sizeof(float) * 3);
     }
 
-    btTriangleIndexVertexArray* meshInterface = bullet_new<btTriangleIndexVertexArray>();
+    btCollisionShape* collisionShape = NULL;
+    btTriangleIndexVertexArray* meshInterface = NULL;
 
-    size_t partCount = data->parts.size();
-    if (partCount > 0)
+    if (dynamic)
+    {
+        // For dynamic meshes, use a btConvexHullShape approximation
+        btConvexHullShape* originalConvexShape = bullet_new<btConvexHullShape>(shapeMeshData->vertexData, data->vertexCount, sizeof(float)*3);
+
+        // Create a hull approximation for better performance
+	    btShapeHull* hull = bullet_new<btShapeHull>(originalConvexShape);
+	    hull->buildHull(originalConvexShape->getMargin());
+	    collisionShape = bullet_new<btConvexHullShape>((btScalar*)hull->getVertexPointer(), hull->numVertices());
+
+        SAFE_DELETE(hull);
+        SAFE_DELETE(originalConvexShape);
+    }
+    else
     {
-        PHY_ScalarType indexType = PHY_UCHAR;
-        int indexStride = 0;
-        Bundle::MeshPartData* meshPart = NULL;
-        for (size_t i = 0; i < partCount; i++)
+        // For static meshes, use btBvhTriangleMeshShape
+        meshInterface = bullet_new<btTriangleIndexVertexArray>();
+
+        size_t partCount = data->parts.size();
+        if (partCount > 0)
         {
-            meshPart = data->parts[i];
-            GP_ASSERT(meshPart);
+            PHY_ScalarType indexType = PHY_UCHAR;
+            int indexStride = 0;
+            Bundle::MeshPartData* meshPart = NULL;
+            for (size_t i = 0; i < partCount; i++)
+            {
+                meshPart = data->parts[i];
+                GP_ASSERT(meshPart);
 
-            switch (meshPart->indexFormat)
+                switch (meshPart->indexFormat)
+                {
+                case Mesh::INDEX8:
+                    indexType = PHY_UCHAR;
+                    indexStride = 1;
+                    break;
+                case Mesh::INDEX16:
+                    indexType = PHY_SHORT;
+                    indexStride = 2;
+                    break;
+                case Mesh::INDEX32:
+                    indexType = PHY_INTEGER;
+                    indexStride = 4;
+                    break;
+                default:
+                    GP_ERROR("Unsupported index format (%d).", meshPart->indexFormat);
+                    SAFE_DELETE(meshInterface);
+                    SAFE_DELETE_ARRAY(shapeMeshData->vertexData);
+                    SAFE_DELETE(shapeMeshData);
+                    SAFE_DELETE(data);
+                    return NULL;
+                }
+
+                // Move the index data into the rigid body's local buffer.
+                // Set it to NULL in the MeshPartData so it is not released when the data is freed.
+                shapeMeshData->indexData.push_back(meshPart->indexData);
+                meshPart->indexData = NULL;
+
+                // Create a btIndexedMesh object for the current mesh part.
+                btIndexedMesh indexedMesh;
+                indexedMesh.m_indexType = indexType;
+                indexedMesh.m_numTriangles = meshPart->indexCount / 3; // assume TRIANGLES primitive type
+                indexedMesh.m_numVertices = meshPart->indexCount;
+                indexedMesh.m_triangleIndexBase = (const unsigned char*)shapeMeshData->indexData[i];
+                indexedMesh.m_triangleIndexStride = indexStride*3;
+                indexedMesh.m_vertexBase = (const unsigned char*)shapeMeshData->vertexData;
+                indexedMesh.m_vertexStride = sizeof(float)*3;
+                indexedMesh.m_vertexType = PHY_FLOAT;
+
+                // Add the indexed mesh data to the mesh interface.
+                meshInterface->addIndexedMesh(indexedMesh, indexType);
+            }
+        }
+        else
+        {
+            // Generate index data for the mesh locally in the rigid body.
+            unsigned int* indexData = new unsigned int[data->vertexCount];
+            for (unsigned int i = 0; i < data->vertexCount; i++)
             {
-            case Mesh::INDEX8:
-                indexType = PHY_UCHAR;
-                indexStride = 1;
-                break;
-            case Mesh::INDEX16:
-                indexType = PHY_SHORT;
-                indexStride = 2;
-                break;
-            case Mesh::INDEX32:
-                indexType = PHY_INTEGER;
-                indexStride = 4;
-                break;
-            default:
-                GP_ERROR("Unsupported index format (%d).", meshPart->indexFormat);
-                SAFE_DELETE(meshInterface);
-                SAFE_DELETE_ARRAY(shapeMeshData->vertexData);
-                SAFE_DELETE(shapeMeshData);
-                SAFE_DELETE(data);
-                return NULL;
+                indexData[i] = i;
             }
+            shapeMeshData->indexData.push_back((unsigned char*)indexData);
 
-            // Move the index data into the rigid body's local buffer.
-            // Set it to NULL in the MeshPartData so it is not released when the data is freed.
-            shapeMeshData->indexData.push_back(meshPart->indexData);
-            meshPart->indexData = NULL;
-
-            // Create a btIndexedMesh object for the current mesh part.
+            // Create a single btIndexedMesh object for the mesh interface.
             btIndexedMesh indexedMesh;
-            indexedMesh.m_indexType = indexType;
-            indexedMesh.m_numTriangles = meshPart->indexCount / 3; // assume TRIANGLES primitive type
-            indexedMesh.m_numVertices = meshPart->indexCount;
-            indexedMesh.m_triangleIndexBase = (const unsigned char*)shapeMeshData->indexData[i];
-            indexedMesh.m_triangleIndexStride = indexStride*3;
+            indexedMesh.m_indexType = PHY_INTEGER;
+            indexedMesh.m_numTriangles = data->vertexCount / 3; // assume TRIANGLES primitive type
+            indexedMesh.m_numVertices = data->vertexCount;
+            indexedMesh.m_triangleIndexBase = shapeMeshData->indexData[0];
+            indexedMesh.m_triangleIndexStride = sizeof(unsigned int);
             indexedMesh.m_vertexBase = (const unsigned char*)shapeMeshData->vertexData;
             indexedMesh.m_vertexStride = sizeof(float)*3;
             indexedMesh.m_vertexType = PHY_FLOAT;
 
-            // Add the indexed mesh data to the mesh interface.
-            meshInterface->addIndexedMesh(indexedMesh, indexType);
+            // Set the data in the mesh interface.
+            meshInterface->addIndexedMesh(indexedMesh, indexedMesh.m_indexType);
         }
-    }
-    else
-    {
-        // Generate index data for the mesh locally in the rigid body.
-        unsigned int* indexData = new unsigned int[data->vertexCount];
-        for (unsigned int i = 0; i < data->vertexCount; i++)
-        {
-            indexData[i] = i;
-        }
-        shapeMeshData->indexData.push_back((unsigned char*)indexData);
-
-        // Create a single btIndexedMesh object for the mesh interface.
-        btIndexedMesh indexedMesh;
-        indexedMesh.m_indexType = PHY_INTEGER;
-        indexedMesh.m_numTriangles = data->vertexCount / 3; // assume TRIANGLES primitive type
-        indexedMesh.m_numVertices = data->vertexCount;
-        indexedMesh.m_triangleIndexBase = shapeMeshData->indexData[0];
-        indexedMesh.m_triangleIndexStride = sizeof(unsigned int);
-        indexedMesh.m_vertexBase = (const unsigned char*)shapeMeshData->vertexData;
-        indexedMesh.m_vertexStride = sizeof(float)*3;
-        indexedMesh.m_vertexType = PHY_FLOAT;
-
-        // Set the data in the mesh interface.
-        meshInterface->addIndexedMesh(indexedMesh, indexedMesh.m_indexType);
+
+        // Create our collision shape object and store shapeMeshData in it.
+        collisionShape = bullet_new<btBvhTriangleMeshShape>(meshInterface, true);
     }
 
     // Create our collision shape object and store shapeMeshData in it.
-    PhysicsCollisionShape* shape =
-        new PhysicsCollisionShape(PhysicsCollisionShape::SHAPE_MESH, bullet_new<btBvhTriangleMeshShape>(meshInterface, true), meshInterface);
+    PhysicsCollisionShape* shape = new PhysicsCollisionShape(PhysicsCollisionShape::SHAPE_MESH, collisionShape, meshInterface);
     shape->_shapeData.meshData = shapeMeshData;
 
     _shapes.push_back(shape);

+ 2 - 2
gameplay/src/PhysicsController.h

@@ -419,7 +419,7 @@ private:
 
     // Creates a collision shape for the given node and gameplay shape definition.
     // Populates 'centerOfMassOffset' with the correct calculated center of mass offset.
-    PhysicsCollisionShape* createShape(Node* node, const PhysicsCollisionShape::Definition& shape, Vector3* centerOfMassOffset);
+    PhysicsCollisionShape* createShape(Node* node, const PhysicsCollisionShape::Definition& shape, Vector3* centerOfMassOffset, bool dynamic);
     
     // Creates a box collision shape.
     PhysicsCollisionShape* createBox(const Vector3& extents, const Vector3& scale);
@@ -434,7 +434,7 @@ private:
     PhysicsCollisionShape* createHeightfield(Node* node, HeightField* heightfield, Vector3* centerOfMassOffset);
 
     // Creates a triangle mesh collision shape.
-    PhysicsCollisionShape* createMesh(Mesh* mesh, const Vector3& scale);
+    PhysicsCollisionShape* createMesh(Mesh* mesh, const Vector3& scale, bool dynamic);
 
     // Destroys a collision shape created through PhysicsController
     void destroyShape(PhysicsCollisionShape* shape);

+ 1 - 1
gameplay/src/PhysicsGhostObject.cpp

@@ -14,7 +14,7 @@ PhysicsGhostObject::PhysicsGhostObject(Node* node, const PhysicsCollisionShape::
     GP_ASSERT(physicsController);
 
     // Create and set the collision shape for the ghost object.
-    _collisionShape = physicsController->createShape(node, shape, &centerOfMassOffset);
+    _collisionShape = physicsController->createShape(node, shape, &centerOfMassOffset, false);
     GP_ASSERT(_collisionShape);
 
     // Create the ghost object.

+ 2 - 2
gameplay/src/PhysicsRigidBody.cpp

@@ -18,7 +18,7 @@ PhysicsRigidBody::PhysicsRigidBody(Node* node, const PhysicsCollisionShape::Defi
 
     // Create our collision shape.
     Vector3 centerOfMassOffset;
-    _collisionShape = Game::getInstance()->getPhysicsController()->createShape(node, shape, &centerOfMassOffset);
+    _collisionShape = Game::getInstance()->getPhysicsController()->createShape(node, shape, &centerOfMassOffset, parameters.mass != 0.0f);
     GP_ASSERT(_collisionShape && _collisionShape->getShape());
 
     // Create motion state object.
@@ -28,7 +28,7 @@ PhysicsRigidBody::PhysicsRigidBody(Node* node, const PhysicsCollisionShape::Defi
     // inertia. However, if the collision shape is a triangle mesh, we don't calculate 
     // inertia since Bullet doesn't currently support this.
     btVector3 localInertia(0.0, 0.0, 0.0);
-    if (parameters.mass != 0.0 && _collisionShape->getType() != PhysicsCollisionShape::SHAPE_MESH)
+    if (parameters.mass != 0.0)
         _collisionShape->getShape()->calculateLocalInertia(parameters.mass, localInertia);
 
     // Create the Bullet physics rigid body object.

+ 51 - 2
gameplay/src/RenderState.cpp

@@ -18,6 +18,7 @@
 #define RS_STENCIL_WRITE 256
 #define RS_STENCIL_FUNC 512
 #define RS_STENCIL_OP 1024
+#define RS_FRONT_FACE 2048
 
 #define RS_ALL_ONES 0xFFFFFFFF
 
@@ -513,7 +514,7 @@ void RenderState::cloneInto(RenderState* renderState, NodeCloneContext& context)
 RenderState::StateBlock::StateBlock()
     : _cullFaceEnabled(false), _depthTestEnabled(false), _depthWriteEnabled(true), _depthFunction(RenderState::DEPTH_LESS),
       _blendEnabled(false), _blendSrc(RenderState::BLEND_ONE), _blendDst(RenderState::BLEND_ZERO),
-	  _stencilTestEnabled(false), _stencilWrite(RS_ALL_ONES), 
+      _cullFaceSide(CULL_FACE_SIDE_BACK), _frontFace(FRONT_FACE_CCW), _stencilTestEnabled(false), _stencilWrite(RS_ALL_ONES), 
 	  _stencilFunction(RenderState::STENCIL_ALWAYS), _stencilFunctionRef(0), _stencilFunctionMask(RS_ALL_ONES), 
 	  _stencilOpSfail(RenderState::STENCIL_OP_KEEP), _stencilOpDpfail(RenderState::STENCIL_OP_KEEP), _stencilOpDppass(RenderState::STENCIL_OP_KEEP),
       _bits(0L)
@@ -578,6 +579,11 @@ void RenderState::StateBlock::bindNoRestore()
         GL_ASSERT( glCullFace((GLenum)_cullFaceSide) );
         _defaultState->_cullFaceSide = _cullFaceSide;
     }
+    if ((_bits & RS_FRONT_FACE) && (_frontFace != _defaultState->_frontFace))
+    {
+        GL_ASSERT( glFrontFace((GLenum)_frontFace) );
+        _defaultState->_frontFace = _frontFace;
+    }
     if ((_bits & RS_DEPTH_TEST) && (_depthTestEnabled != _defaultState->_depthTestEnabled))
     {
         if (_depthTestEnabled) 
@@ -667,6 +673,12 @@ void RenderState::StateBlock::restore(long stateOverrideBits)
         _defaultState->_bits &= ~RS_CULL_FACE_SIDE;
         _defaultState->_cullFaceSide = RenderState::CULL_FACE_SIDE_BACK;
     }
+    if (!(stateOverrideBits & RS_FRONT_FACE) && (_defaultState->_bits & RS_FRONT_FACE))
+    {
+        GL_ASSERT( glFrontFace((GLenum)GL_CCW) );
+        _defaultState->_bits &= ~RS_FRONT_FACE;
+        _defaultState->_frontFace = RenderState::FRONT_FACE_CCW;
+    }
     if (!(stateOverrideBits & RS_DEPTH_TEST) && (_defaultState->_bits & RS_DEPTH_TEST))
     {
         GL_ASSERT( glDisable(GL_DEPTH_TEST) );
@@ -742,6 +754,7 @@ void RenderState::StateBlock::cloneInto(StateBlock* state)
     state->_blendSrc = _blendSrc;
     state->_blendDst = _blendDst;
     state->_cullFaceSide = _cullFaceSide;
+    state->_frontFace = _frontFace;
 	state->_stencilTestEnabled = _stencilTestEnabled;
 	state->_stencilWrite = _stencilWrite;
 	state->_stencilFunction = _stencilFunction;
@@ -882,11 +895,29 @@ static RenderState::CullFaceSide parseCullFaceSide(const char* value)
         return RenderState::CULL_FACE_SIDE_FRONT_AND_BACK;
     else
     {
-        GP_ERROR("Unsupported cull face side value (%s). Will default to BACK if errors are treated as warnings)", value);
+        GP_ERROR("Unsupported cull face side value (%s). Will default to BACK if errors are treated as warnings.", value);
         return RenderState::CULL_FACE_SIDE_BACK;
     }
 }
 
+static RenderState::FrontFace parseFrontFace(const char* value)
+{
+    GP_ASSERT(value);
+
+    // Convert string to uppercase for comparison
+    std::string upper(value);
+    std::transform(upper.begin(), upper.end(), upper.begin(), (int(*)(int))toupper);
+    if (upper == "CCW")
+        return RenderState::FRONT_FACE_CCW;
+    else if (upper == "CW")
+        return RenderState::FRONT_FACE_CW;
+    else
+    {
+        GP_ERROR("Unsupported front face side value (%s). Will default to CCW if errors are treated as warnings.", value);
+        return RenderState::FRONT_FACE_CCW;
+    }
+}
+
 static RenderState::StencilFunction parseStencilFunc(const char* value)
 {
     GP_ASSERT(value);
@@ -971,6 +1002,10 @@ void RenderState::StateBlock::setState(const char* name, const char* value)
     {
         setCullFaceSide(parseCullFaceSide(value));
     }
+    else if (strcmp(name, "frontFace") == 0)
+    {
+        setFrontFace(parseFrontFace(value));
+    }
     else if (strcmp(name, "depthTest") == 0)
     {
         setDepthTest(parseBoolean(value));
@@ -1089,6 +1124,20 @@ void RenderState::StateBlock::setCullFaceSide(CullFaceSide side)
     }
 }
 
+void RenderState::StateBlock::setFrontFace(FrontFace winding)
+{
+    _frontFace = winding;
+    if (_frontFace == FRONT_FACE_CCW)
+    {
+        // Default front face
+        _bits &= ~RS_FRONT_FACE;
+    }
+    else
+    {
+        _bits |= RS_FRONT_FACE;
+    }
+}
+
 void RenderState::StateBlock::setDepthTest(bool enabled)
 {
     _depthTestEnabled = enabled;

+ 22 - 0
gameplay/src/RenderState.h

@@ -180,6 +180,17 @@ public:
         CULL_FACE_SIDE_FRONT_AND_BACK = GL_FRONT_AND_BACK
     };
 
+    /**
+     * Defines the winding of vertices in faces that are considered front facing.
+     *
+     * The initial front face mode is set to FRONT_FACE_CCW.
+     */
+    enum FrontFace
+    {
+        FRONT_FACE_CW,
+        FRONT_FACE_CCW
+    };
+
 	/**
      * Defines the supported stencil compare functions.
 	 * 
@@ -280,10 +291,20 @@ public:
          * Sets the side of the facets to cull.
          *
          * When not explicitly set, the default is to cull back-facing facets.
+         *
          * @param side The side to cull.
          */
         void setCullFaceSide(CullFaceSide side);
 
+        /**
+         * Sets the winding for front facing polygons.
+         *
+         * By default, counter-clockwise wound polygons are considered front facing.
+         *
+         * @param winding The winding for front facing polygons.
+         */
+        void setFrontFace(FrontFace winding);
+
         /**
          * Toggles depth testing.
          *
@@ -396,6 +417,7 @@ public:
         Blend _blendSrc;
         Blend _blendDst;
         CullFaceSide _cullFaceSide;
+        FrontFace _frontFace;
 		bool _stencilTestEnabled;
 		unsigned int _stencilWrite;
 		StencilFunction _stencilFunction;

+ 18 - 7
gameplay/src/SceneLoader.cpp

@@ -94,15 +94,10 @@ Scene* SceneLoader::loadInternal(const char* url)
         SceneNodeProperty::TRANSLATE);
     applyNodeProperties(sceneProperties, SceneNodeProperty::COLLISION_OBJECT);
 
-    // Apply node tags (TODO : update for child nodes)
+    // Apply node tags
     for (size_t i = 0, sncount = _sceneNodes.size(); i < sncount; ++i)
     {
-        SceneNode& sceneNode = _sceneNodes[i];
-        for (std::map<std::string, std::string>::const_iterator itr = sceneNode._tags.begin(); itr != sceneNode._tags.end(); ++itr)
-        {
-            for (size_t n = 0, ncount = sceneNode._nodes.size(); n < ncount; ++n)
-                sceneNode._nodes[n]->setTag(itr->first.c_str(), itr->second.c_str());
-        }
+        applyTags(_sceneNodes[i]);
     }
 
     // Set active camera
@@ -156,6 +151,22 @@ Scene* SceneLoader::loadInternal(const char* url)
     return _scene;
 }
 
+void SceneLoader::applyTags(SceneNode& sceneNode)
+{
+    // Apply tags for this scene node
+    for (std::map<std::string, std::string>::const_iterator itr = sceneNode._tags.begin(); itr != sceneNode._tags.end(); ++itr)
+    {
+        for (size_t n = 0, ncount = sceneNode._nodes.size(); n < ncount; ++n)
+            sceneNode._nodes[n]->setTag(itr->first.c_str(), itr->second.c_str());
+    }
+
+    // Process children
+    for (size_t i = 0, count = sceneNode._children.size(); i < count; ++i)
+    {
+        applyTags(sceneNode._children[i]);
+    }
+}
+
 void SceneLoader::addSceneAnimation(const char* animationID, const char* targetID, const char* url)
 {
     std::string urlStr = url ? url : "";

+ 2 - 0
gameplay/src/SceneLoader.h

@@ -83,6 +83,8 @@ private:
 
     Scene* loadInternal(const char* url);
 
+    void applyTags(SceneNode& sceneNode);
+
     void addSceneAnimation(const char* animationID, const char* targetID, const char* url);
 
     void addSceneNodeProperty(SceneNode& sceneNode, SceneNodeProperty::Type type, const char* url = NULL, int index = 0);

+ 9 - 0
gameplay/src/Texture.cpp

@@ -39,6 +39,11 @@
 #define ATC_RGBA_INTERPOLATED_ALPHA_AMD 0x87EE
 #endif
 
+// ETC1 (OES_compressed_ETC1_RGB8_texture) : All OpenGL ES chipsets
+#ifndef ETC1_RGB8
+#define ETC1_RGB8 0x8D64
+#endif
+
 namespace gameplay
 {
 
@@ -624,6 +629,10 @@ Texture* Texture::createCompressedDDS(const char* path)
             format = internalFormat = ATC_RGBA_INTERPOLATED_ALPHA_AMD;
             bytesPerBlock = 16;
             break;
+        case ('E'|('T'<<8)|('C'<<16)|('1'<<24)):
+            format = internalFormat = ETC1_RGB8;
+            bytesPerBlock = 8;
+            break;
         default:
             GP_ERROR("Unsupported compressed texture format (%d) for DDS file '%s'.", header.ddspf.dwFourCC, path);
             SAFE_DELETE_ARRAY(mipLevels);

+ 2 - 2
samples/browser/res/common/physics.physics

@@ -34,8 +34,8 @@ collisionObject capsule
 collisionObject duck
 {
     type = RIGID_BODY
-    shape = BOX
-    mass = 1.0
+    shape = MESH
+    mass = 5.0
     friction = 1.0
     restitution = 0.0
     linearDamping = 0.5