m3g/m3gcore11/src/m3g_mesh.c
changeset 0 5d03bc08d59c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/m3g/m3gcore11/src/m3g_mesh.c	Tue Feb 02 01:47:50 2010 +0200
@@ -0,0 +1,839 @@
+/*
+* Copyright (c) 2003 Nokia Corporation and/or its subsidiary(-ies).
+* All rights reserved.
+* This component and the accompanying materials are made available
+* under the terms of the License "Eclipse Public License v1.0"
+* which accompanies this distribution, and is available
+* at the URL "http://www.eclipse.org/legal/epl-v10.html".
+*
+* Initial Contributors:
+* Nokia Corporation - initial contribution.
+*
+* Contributors:
+*
+* Description: Mesh implementation
+*
+*/
+
+
+/*!
+ * \internal
+ * \file
+ * \brief Mesh implementation
+ */
+
+#ifndef M3G_CORE_INCLUDE
+#   error included by m3g_core.c; do not compile separately.
+#endif
+
+#include "m3g_mesh.h"
+#include "m3g_memory.h"
+
+/*----------------------------------------------------------------------
+ * Internal functions
+ *--------------------------------------------------------------------*/
+
+/*!
+ * \internal
+ * \brief Destroys this Mesh object.
+ *
+ * \param obj Mesh object
+ */
+static void m3gDestroyMesh(Object *obj)
+{
+    M3Gint i;
+    Mesh *mesh = (Mesh *) obj;
+    M3G_VALIDATE_OBJECT(mesh);
+
+    for (i = 0; i < mesh->trianglePatchCount; ++i) {
+        M3G_ASSIGN_REF(mesh->indexBuffers[i], NULL);
+        M3G_ASSIGN_REF(mesh->appearances[i], NULL);
+    }
+    M3G_ASSIGN_REF(mesh->vertexBuffer, NULL);
+
+	{
+		Interface *m3g = M3G_INTERFACE(mesh);
+		m3gFree(m3g, mesh->indexBuffers);
+		m3gFree(m3g, mesh->appearances);
+	}
+
+    m3gIncStat(M3G_INTERFACE(obj), M3G_STAT_RENDERABLES, -1);
+    
+    m3gDestroyNode(obj);
+}
+
+/*!
+ * \internal
+ * \brief Insert a mesh into a rendering queue
+ */
+static M3Gbool m3gQueueMesh(Mesh *mesh, const Matrix *toCamera,
+                            RenderQueue *renderQueue)
+{
+    M3Gint i;
+
+    /* Fetch the cumulative alpha factor for this node */
+    
+    mesh->totalAlphaFactor =
+        (M3Gushort) m3gGetTotalAlphaFactor((Node*) mesh, renderQueue->root);
+        
+    /* Insert each submesh into the rendering queue */
+            
+    for (i = 0; i < mesh->trianglePatchCount; i++) {
+        if (mesh->appearances[i] != NULL) {
+            if (!m3gInsertDrawable(M3G_INTERFACE(mesh),
+                                   renderQueue,
+                                   (Node*) mesh,
+                                   toCamera,
+                                   i,
+                                   m3gGetAppearanceSortKey(mesh->appearances[i])))
+                return M3G_FALSE;
+        }
+    }
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Overloaded Node method.
+ *
+ * Setup mesh rendering by adding all submeshes to
+ * the render queue.
+ *
+ * \param self Mesh object
+ * \param toCamera transform to camera
+ * \param alphaFactor total alpha factor
+ * \param caller caller node
+ * \param renderQueue RenderQueue
+ *
+ * \retval M3G_TRUE continue render setup
+ * \retval M3G_FALSE abort render setup
+ */
+static M3Gbool m3gMeshSetupRender(Node *self,
+                                  const Node *caller,
+                                  SetupRenderState *s,
+                                  RenderQueue *renderQueue)
+{
+	Mesh *mesh = (Mesh *)self;
+    M3G_UNREF(caller);
+    m3gIncStat(M3G_INTERFACE(self), M3G_STAT_RENDER_NODES, 1);
+    
+	if ((self->enableBits & NODE_RENDER_BIT) != 0 &&
+        (self->scope & renderQueue->scope) != 0) {
+
+        /* Check view frustum culling */
+        
+#       if defined(M3G_ENABLE_VF_CULLING)
+        AABB bbox;
+        m3gGetBoundingBox(mesh->vertexBuffer, &bbox);
+        m3gUpdateCullingMask(s, renderQueue->camera, &bbox);
+        if (s->cullMask == 0) {
+            m3gIncStat(M3G_INTERFACE(self),
+                       M3G_STAT_RENDER_NODES_CULLED, 1);
+            return M3G_TRUE;
+        }
+#       endif
+
+        /* No dice, let's render... */
+
+        return m3gQueueMesh(mesh, &s->toCamera, renderQueue);
+    }
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Overloaded Node method.
+ *
+ * Renders one submesh.
+ *
+ * \param self Mesh object
+ * \param ctx current render context
+ * \param patchIndex submesh index
+ */
+static void m3gMeshDoRender(Node *self,
+                            RenderContext *ctx,
+                            const Matrix *toCamera,
+                            M3Gint patchIndex)
+{
+    Mesh *mesh = (Mesh *)self;
+
+	m3gDrawMesh(ctx,
+                mesh->vertexBuffer,
+                mesh->indexBuffers[patchIndex],
+                mesh->appearances[patchIndex],
+                toCamera,
+                mesh->totalAlphaFactor + 1,
+                self->scope);
+}
+
+/*!
+ * \internal
+ * \brief Internal equivalent routine called
+ * by m3gMeshRayIntersect.
+ *
+ * \param mesh      Mesh object
+ * \param vertices  VertexBuffer object used in calculations
+ * \param mask      pick scope mask
+ * \param ray       pick ray
+ * \param ri        RayIntersection object
+ * \param toGroup   transform to originating group
+ * \retval          M3G_TRUE    continue pick
+ * \retval          M3G_FALSE   abort pick
+ */
+static M3Gbool m3gMeshRayIntersectInternal(	Mesh *mesh,
+                                            VertexBuffer *vertices,
+            								M3Gint mask,
+            								M3Gfloat *ray,
+            								RayIntersection *ri,
+            								Matrix *toGroup)
+{
+    Vec3 v0, v1, v2, tuv;
+    Vec4 transformed, p0, p1;
+    M3Gint indices[4] = { 0, 0, 0, 0 }; 
+    M3Gint i, j, k, cullMode;
+    Matrix t;   /* Reused as texture transform */
+
+    if (vertices == NULL ||
+         mesh->appearances == NULL ||
+         mesh->indexBuffers == NULL ||
+         (((Node *)mesh)->scope & mask) == 0) {
+        return M3G_TRUE;
+    }
+
+    if (vertices->vertices == NULL) {
+        m3gRaiseError(M3G_INTERFACE(mesh), M3G_INVALID_OPERATION);
+        return M3G_FALSE;
+    }
+
+    p0.x = ray[0];
+    p0.y = ray[1];
+    p0.z = ray[2];
+    p0.w = 1.f;
+
+    p1.x = ray[3];
+    p1.y = ray[4];
+    p1.z = ray[5];
+    p1.w = 1.f;
+
+    m3gCopyMatrix(&t, toGroup);
+    M3G_BEGIN_PROFILE(M3G_INTERFACE(mesh), M3G_PROFILE_TRANSFORM_INVERT);
+    if (!m3gInvertMatrix(&t)) {
+        m3gRaiseError(M3G_INTERFACE(mesh), M3G_ARITHMETIC_ERROR);
+        return M3G_FALSE;
+    }
+    M3G_END_PROFILE(M3G_INTERFACE(mesh), M3G_PROFILE_TRANSFORM_INVERT);
+    m3gTransformVec4(&t, &p0);
+    m3gTransformVec4(&t, &p1);
+    
+    m3gScaleVec3((Vec3*) &p0, m3gRcp(p0.w));
+    m3gScaleVec3((Vec3*) &p1, m3gRcp(p1.w));
+
+    m3gSubVec4(&p1, &p0);
+
+    /* Quick bounding box test for Meshes */
+    if (m3gGetClass((Object *)mesh) == M3G_CLASS_MESH) {
+        AABB boundingBox;
+        m3gGetBoundingBox(vertices, &boundingBox);
+
+        if (!m3gIntersectBox((Vec3*) &p0, (Vec3*) &p1, &boundingBox)) {
+            return M3G_TRUE;
+        }
+    }
+
+    /* Apply the inverse of the vertex scale and bias to the ray */
+    
+    if (!IS_ZERO(vertices->vertexScale)) {
+        const Vec3 *bias = (const Vec3*) vertices->vertexBias;
+        M3Gfloat ooScale = m3gRcp(vertices->vertexScale);
+        m3gSubVec3((Vec3*) &p0, bias);
+        m3gScaleVec3((Vec3*) &p0, ooScale);
+        m3gScaleVec3((Vec3*) &p1, ooScale); /* direction vector -> no bias */
+    }
+    else {
+        m3gRaiseError(M3G_INTERFACE(mesh), M3G_ARITHMETIC_ERROR);
+        return M3G_FALSE;
+    }
+    
+    /* Go through all submeshes */
+    for (i = 0; i < mesh->trianglePatchCount; i++) {
+        /* Do not pick submeshes with null appearance */
+        if (mesh->appearances[i] == NULL ||
+            mesh->indexBuffers[i] == NULL) continue;
+
+        /* Validate indices versus vertex buffer */
+        if (m3gGetMaxIndex(mesh->indexBuffers[i]) >= m3gGetNumVertices(vertices)) {
+            m3gRaiseError(M3G_INTERFACE(mesh), M3G_INVALID_OPERATION);
+            return M3G_FALSE;
+        }
+
+        if (mesh->appearances[i]->polygonMode != NULL) {
+            cullMode = m3gGetWinding(mesh->appearances[i]->polygonMode) == M3G_WINDING_CCW ? 0 : 1;
+            switch(m3gGetCulling(mesh->appearances[i]->polygonMode)) {
+            case M3G_CULL_FRONT: cullMode ^= 1; break;
+            case M3G_CULL_NONE:  cullMode  = 2; break;
+            }
+        }
+        else {
+            cullMode = 0;
+        }
+
+        /* Go through all triangels */
+        for (j = 0; m3gGetIndices(mesh->indexBuffers[i], j, indices); j++) {
+            /* Ignore zero area triangles */
+            if ( indices[0] == indices[1] ||
+                 indices[0] == indices[2] ||
+                 indices[1] == indices[2]) continue;
+
+            m3gGetVertex(vertices, indices[0], &v0);
+            m3gGetVertex(vertices, indices[1], &v1);
+            m3gGetVertex(vertices, indices[2], &v2);
+
+            if (m3gIntersectTriangle((Vec3*)&p0, (Vec3*)&p1, &v0, &v1, &v2, &tuv, indices[3] ^ cullMode)) {
+                /* Check that we are going to fill this intersection */
+                if (tuv.x <= 0.f || tuv.x >= ri->tMin) continue;
+
+                /* Fill in to RayIntersection */
+                ri->tMin = tuv.x;
+                ri->distance = tuv.x;
+                ri->submeshIndex = i;
+                ri->intersected = (Node *)mesh;
+
+                /* Fetch normal */
+                if (m3gGetNormal(vertices, indices[0], &v0)) {
+                    m3gGetNormal(vertices, indices[1], &v1);
+                    m3gGetNormal(vertices, indices[2], &v2);
+
+                    ri->normal[0] = m3gAdd(
+                        m3gMul(v0.x, m3gSub(1.f, m3gAdd(tuv.y, tuv.z))),
+                        m3gAdd(
+                            m3gMul(v1.x, tuv.y),
+                            m3gMul(v2.x, tuv.z)));
+                    
+                    ri->normal[1] = m3gAdd(
+                        m3gMul(v0.y, m3gSub(1.f, m3gAdd(tuv.y, tuv.z))),
+                        m3gAdd(
+                            m3gMul(v1.y, tuv.y),
+                            m3gMul(v2.y, tuv.z)));
+                    
+                    ri->normal[2] = m3gAdd(
+                        m3gMul(v0.z, m3gSub(1.f, m3gAdd(tuv.y, tuv.z))),
+                        m3gAdd(
+                            m3gMul(v1.z, tuv.y),
+                            m3gMul(v2.z, tuv.z)));
+                }
+                else {
+                    ri->normal[0] = 0.f;
+                    ri->normal[1] = 0.f;
+                    ri->normal[2] = 1.f;
+                }
+            
+                /* Fetch texture coordinates for each unit */
+                for (k = 0; k < M3G_NUM_TEXTURE_UNITS; k++) {
+                    if (m3gGetTexCoord(vertices, indices[0], k, &v0)) {
+                        m3gGetTexCoord(vertices, indices[1], k, &v1);
+                        m3gGetTexCoord(vertices, indices[2], k, &v2);
+
+                        /* Calculate transformed S and T */
+                        transformed.x = m3gAdd(
+                            m3gMul(v0.x, m3gSub(1.f, m3gAdd(tuv.y, tuv.z))),
+                            m3gAdd(
+                                m3gMul(v1.x, tuv.y),
+                                m3gMul(v2.x, tuv.z)));
+                        
+                        transformed.y = m3gAdd(
+                            m3gMul(v0.y, m3gSub(1.f, m3gAdd(tuv.y, tuv.z))),
+                            m3gAdd(
+                                m3gMul(v1.y, tuv.y),
+                                m3gMul(v2.y, tuv.z)));
+                        
+                        transformed.z = 0;
+                        transformed.w = 1;
+
+                        /* Transform and * 1/w */
+                        if (mesh->appearances[i]->texture[k] != NULL) {
+                            m3gGetCompositeTransform((Transformable *)mesh->appearances[i]->texture[k], &t);
+                            m3gTransformVec4(&t, &transformed);
+                            m3gScaleVec4(&transformed, m3gRcp(transformed.w));
+                        }
+
+                        ri->textureS[k] = transformed.x;
+                        ri->textureT[k] = transformed.y;
+                    }
+                    else {
+                        ri->textureS[k] = 0.f;
+                        ri->textureT[k] = 0.f;
+                    }
+                }
+            }
+        }
+    }
+
+    return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Overloaded Node method.
+ *
+ * Just forward call internal ray intersect.
+ *
+ * \param self      Mesh object
+ * \param mask      pick scope mask
+ * \param ray       pick ray
+ * \param ri        RayIntersection object
+ * \param toGroup   transform to originating group
+ * \retval          M3G_TRUE    continue pick
+ * \retval          M3G_FALSE   abort pick
+ */
+static M3Gbool m3gMeshRayIntersect( Node *self,
+    								M3Gint mask,
+    								M3Gfloat *ray,
+    								RayIntersection *ri,
+    								Matrix *toGroup)
+{
+    Mesh *mesh = (Mesh *)self;
+    return m3gMeshRayIntersectInternal(mesh, mesh->vertexBuffer, mask, ray, ri, toGroup);
+}
+
+/*!
+ * \internal
+ * \brief Initializes a Mesh object. See specification
+ * for default values.
+ *
+ * \param m3g                   M3G interface
+ * \param mesh                  Mesh object
+ * \param hVertices             VertexBuffer object
+ * \param hTriangles            array of IndexBuffer objects
+ * \param hAppearances          array of Appearance objects
+ * \param trianglePatchCount    number of submeshes
+ * \param vfTable               virtual function table
+ * \retval                      M3G_TRUE success
+ * \retval                      M3G_FALSE failed
+ */
+static M3Gbool m3gInitMesh(Interface *m3g,
+                           Mesh *mesh,
+                           M3GVertexBuffer hVertices,
+                           M3GIndexBuffer *hTriangles,
+                           M3GAppearance *hAppearances,
+                           M3Gint trianglePatchCount,
+                           M3GClass classID)
+{
+    M3Gint i;
+	
+    /* Out of memory if more than 65535 triangle patches */
+    if (trianglePatchCount > 65535) {
+        m3gRaiseError(m3g, M3G_OUT_OF_MEMORY);
+        return M3G_FALSE;
+    }
+
+	for (i = 0; i < trianglePatchCount; i++) {
+		if (hTriangles[i] == NULL) {
+			m3gRaiseError(m3g, M3G_NULL_POINTER);
+            return M3G_FALSE;
+		}
+	}
+
+	mesh->indexBuffers =
+        m3gAllocZ(m3g, sizeof(IndexBuffer *) * trianglePatchCount);
+	if (!mesh->indexBuffers) {
+		return M3G_FALSE;
+	}
+
+	mesh->appearances =
+        m3gAllocZ(m3g, sizeof(Appearance *) * trianglePatchCount);
+	if (!mesh->appearances) {
+		m3gFree(m3g, mesh->indexBuffers);
+		return M3G_FALSE;
+	}
+
+	/* Mesh is derived from node */
+	m3gInitNode(m3g, &mesh->node, classID);
+    mesh->node.hasRenderables = M3G_TRUE;
+    mesh->node.dirtyBits |= NODE_BBOX_BIT;
+
+    for (i = 0; i < trianglePatchCount; i++) {
+        M3G_ASSIGN_REF(mesh->indexBuffers[i], hTriangles[i]);
+    }
+	
+	if (hAppearances != NULL) {
+        for (i = 0; i < trianglePatchCount; i++) {
+            M3G_ASSIGN_REF(mesh->appearances[i], hAppearances[i]);
+        }
+	}
+	else {
+		m3gZero(mesh->appearances, sizeof(Appearance *) * trianglePatchCount);
+    }
+	
+    M3G_ASSIGN_REF(mesh->vertexBuffer, hVertices);
+	mesh->trianglePatchCount = (M3Gshort) trianglePatchCount;
+
+    m3gIncStat(M3G_INTERFACE(mesh), M3G_STAT_RENDERABLES, 1);
+    
+	return M3G_TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Overloaded Object3D method.
+ *
+ * \param self Mesh object
+ * \param references array of reference objects
+ * \return number of references
+ */
+static M3Gint m3gMeshDoGetReferences(Object *self, Object **references)
+{
+    Mesh *mesh = (Mesh *)self;
+    M3Gint i, num = m3gObjectDoGetReferences(self, references);
+    if (references != NULL)
+        references[num] = (Object *)mesh->vertexBuffer;
+    num++;
+    for (i = 0; i < mesh->trianglePatchCount; i++) {
+        if (mesh->indexBuffers[i] != NULL) {
+            if (references != NULL)
+                references[num] = (Object *)mesh->indexBuffers[i];
+            num++;
+        }
+        if (mesh->appearances[i] != NULL) {
+            if (references != NULL)
+                references[num] = (Object *)mesh->appearances[i];
+            num++;
+        }
+    }
+    return num;
+}
+
+/*!
+ * \internal
+ * \brief Overloaded Object3D method.
+ */
+static Object *m3gMeshFindID(Object *self, M3Gint userID)
+{
+    int i;
+    Mesh *mesh = (Mesh *)self;
+    Object *found = m3gObjectFindID(self, userID);
+
+    if (!found) {
+        found = m3gFindID((Object*) mesh->vertexBuffer, userID);
+    }    
+    for (i = 0; !found && i < mesh->trianglePatchCount; ++i) {
+        if (mesh->indexBuffers[i] != NULL) {
+            found = m3gFindID((Object*) mesh->indexBuffers[i], userID);
+        }
+        if (!found && mesh->appearances[i] != NULL) {
+            found = m3gFindID((Object*) mesh->appearances[i], userID);
+        }
+    }
+    return found;
+}
+
+/*!
+ * \internal
+ * \brief Overloaded Object3D method.
+ *
+ * \param originalObj original Mesh object
+ * \param cloneObj pointer to cloned Mesh object
+ * \param pairs array for all object-duplicate pairs
+ * \param numPairs number of pairs
+ */
+static M3Gbool m3gMeshDuplicate(const Object *originalObj,
+                                Object **cloneObj,
+                                Object **pairs,
+                                M3Gint *numPairs)
+{
+    /* Create the clone if it doesn't exist; otherwise we'll be all
+     * set by the derived class(es) and can just call through to the
+     * base class */
+    
+    if (*cloneObj == NULL) {
+        Mesh *original = (Mesh *)originalObj;
+        Mesh *clone = (Mesh *)m3gCreateMesh(originalObj->interface,
+                                            original->vertexBuffer,
+                                            original->indexBuffers,
+                                            original->appearances,
+                                            original->trianglePatchCount);
+        *cloneObj = (Object *)clone;
+        if (*cloneObj == NULL) {
+            return M3G_FALSE;
+        }
+    }
+    
+    return m3gNodeDuplicate(originalObj, cloneObj, pairs, numPairs);
+}
+
+/*!
+ * \internal
+ * \brief Overloaded Object3D method.
+ *
+ * \param self Mesh object
+ * \param time current world time
+ * \return minimum validity
+ */
+static M3Gint m3gMeshApplyAnimation(Object *self, M3Gint time)
+{
+    M3Gint validity, minValidity;
+    Mesh *mesh = (Mesh *)self;
+    Object *vb;
+    M3G_VALIDATE_OBJECT(mesh);
+
+    minValidity = m3gObjectApplyAnimation(self, time);
+
+    vb = (Object *) mesh->vertexBuffer;
+
+    if (vb != NULL && minValidity > 0) {
+        validity = M3G_VFUNC(Object, vb, applyAnimation)(vb, time);
+        minValidity = M3G_MIN(validity, minValidity);
+    }
+    
+    if (mesh->appearances != NULL) {
+        Object *app;
+        M3Gint i, n;
+        n = mesh->trianglePatchCount;
+        
+        for (i = 0; i < n && minValidity > 0; ++i) {
+            app = (Object *) mesh->appearances[i];
+            if (app != NULL) {
+                validity = M3G_VFUNC(Object, app, applyAnimation)(app, time);
+                minValidity = M3G_MIN(validity, minValidity);
+            }
+        }
+    }
+
+    return minValidity;
+}
+
+/*!
+ * \internal
+ * \brief Overloaded Node method
+ */
+static M3Gint m3gMeshGetBBox(Node *self, AABB *bbox)
+{
+    Mesh *mesh = (Mesh *) self;
+    VertexBuffer *vb = mesh->vertexBuffer;
+
+    if (vb->vertices) {
+        m3gGetBoundingBox(vb, bbox);
+        return VFC_BBOX_COST + VFC_NODE_OVERHEAD;
+    }
+    else {
+        return 0;
+    }
+}
+
+/*!
+ * \internal
+ * \brief Overloaded Node method
+ */
+static M3Gbool m3gMeshValidate(Node *self, M3Gbitmask stateBits, M3Gint scope)
+{
+    Mesh *mesh = (Mesh *) self;
+    VertexBuffer *vb = mesh->vertexBuffer;
+    int i;
+
+    if ((scope & self->scope) != 0) {
+        if (stateBits & self->enableBits) {
+            
+            /* Validate vertex buffer components */
+            
+            for (i = 0; i < mesh->trianglePatchCount; ++i) {
+                Appearance *app = mesh->appearances[i];
+                if (app) {
+                    if (!m3gValidateVertexBuffer(
+                            vb, app, m3gGetMaxIndex(mesh->indexBuffers[i]))) {
+                        m3gRaiseError(M3G_INTERFACE(mesh), M3G_INVALID_OPERATION);
+                        return M3G_FALSE;
+                    }
+                }
+            }
+    
+            /* Invalidate cached vertex stuff if source buffer changed */
+            {
+                M3Gint vbTimestamp = m3gGetTimestamp(vb);
+                if (mesh->vbTimestamp != vbTimestamp) {
+                    m3gInvalidateNode(self, NODE_BBOX_BIT);
+                    mesh->vbTimestamp = vbTimestamp;
+                }
+            }
+            
+            return m3gNodeValidate(self, stateBits, scope);
+        }
+    }
+    return M3G_TRUE;
+}
+
+#if 0
+/*!
+ * \internal
+ * \brief Computes the estimated rendering cost for this Mesh node
+ */
+static M3Gint m3gMeshRenderingCost(const Mesh *mesh)
+{
+    /* Since we're using strips, just assume that each vertex
+     * generates a new triangle... */
+    
+    return
+        mesh->vertexBuffer->vertexCount * (VFC_VERTEX_COST +
+                                           VFC_TRIANGLE_COST) +
+        mesh->trianglePatchCount * VFC_RENDERCALL_OVERHEAD +
+        VFC_NODE_OVERHEAD;
+}
+#endif
+
+/*----------------------------------------------------------------------
+ * Virtual function table
+ *--------------------------------------------------------------------*/
+
+static const NodeVFTable m3gvf_Mesh = {
+    {
+        {
+            m3gMeshApplyAnimation,
+            m3gNodeIsCompatible,
+            m3gNodeUpdateProperty,
+            m3gMeshDoGetReferences,
+            m3gMeshFindID,
+            m3gMeshDuplicate,
+            m3gDestroyMesh
+        }
+    },
+    m3gNodeAlign,
+    m3gMeshDoRender,
+    m3gMeshGetBBox,
+    m3gMeshRayIntersect,
+    m3gMeshSetupRender,
+    m3gNodeUpdateDuplicateReferences,
+    m3gMeshValidate
+};
+
+
+/*----------------------------------------------------------------------
+ * Public API functions
+ *--------------------------------------------------------------------*/
+
+/*!
+ * \brief Creates a Mesh object.
+ *
+ * \param interface             M3G interface
+ * \param hVertices             VertexBuffer object
+ * \param hTriangles            array of IndexBuffer objects
+ * \param hAppearances          array of Appearance objects
+ * \param trianglePatchCount    number of submeshes
+ * \retval Mesh new Mesh object
+ * \retval NULL Mesh creating failed
+ */
+M3G_API M3GMesh m3gCreateMesh(M3GInterface interface,
+                              M3GVertexBuffer hVertices,
+                              M3GIndexBuffer *hTriangles,
+                              M3GAppearance *hAppearances,
+                              M3Gint trianglePatchCount)
+{
+    Interface *m3g = (Interface *) interface;
+    M3G_VALIDATE_INTERFACE(m3g);
+
+	{
+		Mesh *mesh = m3gAllocZ(m3g, sizeof(Mesh));
+
+		if (mesh != NULL) {
+    		if (!m3gInitMesh(m3g, mesh,
+                             hVertices, hTriangles, hAppearances,
+                             trianglePatchCount,
+                             M3G_CLASS_MESH)) {
+    			m3gFree(m3g, mesh);
+    			return NULL;
+    		}
+		}
+
+		return (M3GMesh)mesh;
+	}
+}
+
+/*!
+ * \brief Sets submesh appearance.
+ *
+ * \param handle                Mesh object
+ * \param appearanceIndex       submesh index
+ * \param hAppearance           Appearance object
+ */
+M3G_API void m3gSetAppearance(M3GMesh handle,
+                              M3Gint appearanceIndex,
+                              M3GAppearance hAppearance)
+{
+	Mesh *mesh = (Mesh *)handle;
+    M3G_VALIDATE_OBJECT(mesh);
+	
+	if (appearanceIndex < 0 || appearanceIndex >= mesh->trianglePatchCount) {
+		m3gRaiseError(M3G_INTERFACE(mesh), M3G_INVALID_INDEX);
+        return;
+	}
+
+    M3G_ASSIGN_REF(mesh->appearances[appearanceIndex], (Appearance *) hAppearance);
+}
+
+/*!
+ * \brief Gets submesh appearance.
+ *
+ * \param handle                Mesh object
+ * \param idx                   submesh index
+ * \return                      Appearance object
+ */
+M3G_API M3GAppearance m3gGetAppearance(M3GMesh handle,
+                                       M3Gint idx)
+{
+	Mesh *mesh = (Mesh *)handle;
+    M3G_VALIDATE_OBJECT(mesh);
+	
+	if (idx < 0 || idx >= mesh->trianglePatchCount) {
+		m3gRaiseError(M3G_INTERFACE(mesh), M3G_INVALID_INDEX);
+        return NULL;
+	}
+
+    return mesh->appearances[idx];
+}
+
+/*!
+ * \brief Gets submesh index buffer.
+ *
+ * \param handle                Mesh object
+ * \param idx                   submesh index
+ * \return                      IndexBuffer object
+ */
+M3G_API M3GIndexBuffer m3gGetIndexBuffer(M3GMesh handle,
+                                         M3Gint idx)
+{
+	Mesh *mesh = (Mesh *)handle;
+    M3G_VALIDATE_OBJECT(mesh);
+	
+	if (idx < 0 || idx >= mesh->trianglePatchCount) {
+		m3gRaiseError(M3G_INTERFACE(mesh), M3G_INVALID_INDEX);
+        return NULL;
+	}
+
+    return mesh->indexBuffers[idx];
+}
+
+/*!
+ * \brief Gets VertexBuffer.
+ *
+ * \param handle                Mesh object
+ * \return                      VertexBuffer object
+ */
+M3G_API M3GVertexBuffer m3gGetVertexBuffer(M3GMesh handle)
+{
+	Mesh *mesh = (Mesh *)handle;
+    M3G_VALIDATE_OBJECT(mesh);
+
+    return mesh->vertexBuffer;
+}
+
+/*!
+ * \brief Gets submesh count.
+ *
+ * \param handle                Mesh object
+ * \return                      submesh count
+ */
+M3G_API M3Gint m3gGetSubmeshCount(M3GMesh handle)
+{
+	Mesh *mesh = (Mesh *)handle;
+    M3G_VALIDATE_OBJECT(mesh);
+
+    return mesh->trianglePatchCount;
+}
+