m3g/m3gcore11/src/m3g_mesh.c
author Faisal Memon <faisal.memon@nokia.com>
Thu, 09 Sep 2010 18:17:30 +0100
branchNewGraphicsArchitecture
changeset 175 c03ff11acab3
parent 0 5d03bc08d59c
permissions -rw-r--r--
Export new s4 egl extensions bitmap for the SF wiki

/*
* 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;
}