m3g/m3gcore11/src/m3g_vertexbuffer.c
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Mon, 04 Oct 2010 02:31:51 +0300
changeset 194 18f84489a694
parent 152 9f1c3fea0f87
permissions -rw-r--r--
Revision: 201039 Kit: 201039

/*
* 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: Vertex buffer implementation
*
*/


/*!
 * \internal
 * \file
 * \brief Vertex buffer implementation
 */

#ifndef M3G_CORE_INCLUDE
#   error included by m3g_core.c; do not compile separately.
#endif

#include "m3g_vertexbuffer.h"
#include "m3g_vertexarray.h"

#include "m3g_appearance.h"
#include "m3g_node.h" /* for NODE_ALPHA_FACTOR_BITS */

/*----------------------------------------------------------------------
 * Private functions
 *--------------------------------------------------------------------*/

/*----------------------------------------------------------------------
 * Internal functions
 *--------------------------------------------------------------------*/

/*!
 * \internal
 * \brief Deletes a vertex buffer
 *
 * \param obj VertexBuffer object
 */
static void m3gDestroyVertexBuffer(Object *obj)
{
    M3Gint i;
    VertexBuffer *buffer = (VertexBuffer *) obj;
    M3G_VALIDATE_OBJECT(buffer);

    if (buffer->locked) {
        M3G_ASSERT(M3G_FALSE);
        m3gReleaseVertexBuffer(buffer);
    }

    M3G_ASSIGN_REF(buffer->vertices, NULL);
    M3G_ASSIGN_REF(buffer->normals, NULL);
    M3G_ASSIGN_REF(buffer->colors, NULL);
    for(i = 0; i < M3G_NUM_TEXTURE_UNITS; i++) {
        M3G_ASSIGN_REF(buffer->texCoords[i], NULL);
    }
    
    m3gDestroyObject(&buffer->object);
}

/*!
 * \internal
 * \brief Applies the scale and bias values of a vertex buffer to the
 * current GL state
 *
 * The scale and bias transformations are applied to the existing
 * values in the GL_MODELVIEW and GL_TEXTURE matrix stacks.
 *
 * \param buffer VertexBuffer object
 */
static void m3gApplyScaleAndBias(const VertexBuffer *buffer)
{
    M3G_VALIDATE_OBJECT(buffer);
    
    glMatrixMode(GL_TEXTURE);
    {
        M3Gint i;
        for (i = 0; i < M3G_NUM_TEXTURE_UNITS; ++i) {
            if (buffer->texCoords[i] != NULL) {
                glActiveTexture((GLenum)(GL_TEXTURE0 + i));
                glTranslatef(buffer->texCoordBias[i][0],
                             buffer->texCoordBias[i][1],
                             buffer->texCoordBias[i][2]);
                glScalef(buffer->texCoordScale[i],
                         buffer->texCoordScale[i],
                         buffer->texCoordScale[i]);
            }
        }
    }

    glMatrixMode(GL_MODELVIEW);
    if (buffer->vertices != NULL) {
        glTranslatef(buffer->vertexBias[0],
                     buffer->vertexBias[1],
                     buffer->vertexBias[2]);
        glScalef(buffer->vertexScale,
                 buffer->vertexScale,
                 buffer->vertexScale);
    }
}

/*!
 * \internal
 * \brief Locks a vertex buffer for subsequent rendering
 *
 * \param buffer        VertexBuffer object
 * \param alphaFactor   alpha factor as 1.16 fixed point
 */
static void m3gLockVertexBuffer(const VertexBuffer *buffer,
                                M3Gint alphaFactor)
{
    M3G_VALIDATE_OBJECT(buffer);
    M3G_ASSERT(!buffer->locked);

    if (buffer->colors != NULL) {
        glEnableClientState(GL_COLOR_ARRAY);
        m3gLockColorArray(buffer->colors, alphaFactor);
    }
    else {
        GLfixed r = buffer->defaultColor.r;
        GLfixed g = buffer->defaultColor.g;
        GLfixed b = buffer->defaultColor.b;
        GLfixed a = buffer->defaultColor.a * alphaFactor;

        r = (r << 8) + r + (r >> 7);
        g = (g << 8) + g + (g >> 7);
        b = (b << 8) + b + (b >> 7);
        a = (a >> (NODE_ALPHA_FACTOR_BITS - 8))
            + (a >> NODE_ALPHA_FACTOR_BITS)
            + (a >> (NODE_ALPHA_FACTOR_BITS + 7));
        
        glDisableClientState(GL_COLOR_ARRAY);
        glColor4x(r, g, b, a);
    }

    if (buffer->normals != NULL) {
        glEnableClientState(GL_NORMAL_ARRAY);
        m3gLockNormalArray(buffer->normals);
    }
    else {
        glDisableClientState(GL_NORMAL_ARRAY);
    }

    if (buffer->vertices != NULL) {
        glEnableClientState(GL_VERTEX_ARRAY);
        m3gLockVertexArray(buffer->vertices);
    }
    else {
        glDisableClientState(GL_VERTEX_ARRAY);
    }
    
    {
        M3Gint i;
        for (i = 0; i < M3G_NUM_TEXTURE_UNITS; ++i) {
            const VertexArray *array = buffer->texCoords[i];
            glClientActiveTexture(GL_TEXTURE0 + i);
            glActiveTexture(GL_TEXTURE0 + i);
            if (array != NULL) {
                glEnableClientState(GL_TEXTURE_COORD_ARRAY);
                m3gLockTexCoordArray(array);
            }
            else {
                glDisableClientState(GL_TEXTURE_COORD_ARRAY);
            }    
        }
    }
    
    ((VertexBuffer*)buffer)->locked = M3G_TRUE;
}

/*!
 * \internal
 * \brief Releases a vertex buffer
 *
 * \param buffer        VertexBuffer object
 */
static void m3gReleaseVertexBuffer(const VertexBuffer *buffer)
{
    M3G_VALIDATE_OBJECT(buffer);
    M3G_ASSERT(buffer->locked);

    if (buffer->colors != NULL) {
        m3gUnlockArray(buffer->colors);
    }
    if (buffer->normals != NULL) {
        m3gUnlockArray(buffer->normals);
    }
    if (buffer->vertices != NULL) {
        m3gUnlockArray(buffer->vertices);
    }
    {
        M3Gint i;
        for (i = 0; i < M3G_NUM_TEXTURE_UNITS; ++i) {
            const VertexArray *array = buffer->texCoords[i];
            if (array != NULL) {
                m3gUnlockArray(array);
            }
        }
    }
    
    ((VertexBuffer*)buffer)->locked = M3G_FALSE;
}

/*!
 * \internal
 * \brief Gets a vertex position. Scale and bias
 * are not applied.
 *
 * \param buffer    VertexBuffer object
 * \param idx       index of coordinate
 * \param v         vector to fill in
 * \retval          M3G_TRUE get ok
 * \retval          M3G_FALSE no such vertex
 */
static M3Gbool m3gGetVertex(const VertexBuffer *buffer, M3Gint idx, M3GVec3 *v)
{
    M3Gfloat vert[3];
    M3Gbool res = m3gGetCoordinates(buffer->vertices, 3, idx, vert);
    if (res == M3G_TRUE)
        v->x = vert[0]; v->y = vert[1]; v->z = vert[2];

    return res;
}

/*!
 * \internal
 * \brief Gets a normal coordinate.
 *
 * \param buffer    VertexBuffer object
 * \param idx       index of coordinate
 * \param v         vector to fill in
 * \retval          M3G_TRUE get ok
 * \retval          M3G_FALSE no such vertex
 */
static M3Gbool m3gGetNormal(const VertexBuffer *buffer, M3Gint idx, M3GVec3 *v)
{
    M3Gfloat norm[3];
    M3Gbool res = m3gGetCoordinates(buffer->normals, 3, idx, norm);
    if (res == M3G_TRUE)
        v->x = norm[0]; v->y = norm[1]; v->z = norm[2];

    return res;
}

/*!
 * \internal
 * \brief Gets a texture coordinate, used in pick routines.
 * Scale and bias are applied to the coordinates.
 *
 * \param buffer    VertexBuffer object
 * \param idx       index of coordinate
 * \param unit      texturing unit
 * \param v         vector to fill in
 * \retval          M3G_TRUE get ok
 * \retval          M3G_FALSE no such vertex
 */
static M3Gbool m3gGetTexCoord(const VertexBuffer *buffer, M3Gint idx, M3Gint unit, M3GVec3 *v)
{
    M3Gfloat texcoord[2];
    M3Gbool res = m3gGetCoordinates(buffer->texCoords[unit], 2, idx, texcoord);

    if (res == M3G_TRUE)
        v->x = texcoord[0]; v->y = texcoord[1];

    v->x = m3gMul(v->x, buffer->texCoordScale[unit]);
    v->y = m3gMul(v->y, buffer->texCoordScale[unit]);

    v->x = m3gAdd(v->x, buffer->texCoordBias[unit][0]);
    v->y = m3gAdd(v->y, buffer->texCoordBias[unit][1]);

    v->z = 0.f;

    return res;
}

/*!
 * \internal
 * \brief Gets vertex buffer positions bounding box.
 *
 * The bounding box is returned as floats, with scale and bias
 * applied.
 *
 * \param buffer        VertexBuffer object
 * \param boundingBox   bounding box float array
 */
static void m3gGetBoundingBox(VertexBuffer *vb, AABB *boundingBox)
{
    /* If timestamp is changed, refresh bounding box */
    
    if (vb->vertices && (m3gGetArrayTimestamp(vb->vertices)
                         != vb->verticesTimestamp)) {
        M3Gshort ab[6];
        vb->verticesTimestamp = m3gGetArrayTimestamp(vb->vertices);
        m3gGetArrayBoundingBox(vb->vertices, ab);
        
        vb->bbox.min[0] = m3gMadd(ab[0], vb->vertexScale, vb->vertexBias[0]);
        vb->bbox.min[1] = m3gMadd(ab[1], vb->vertexScale, vb->vertexBias[1]);
        vb->bbox.min[2] = m3gMadd(ab[2], vb->vertexScale, vb->vertexBias[2]);
        vb->bbox.max[0] = m3gMadd(ab[3], vb->vertexScale, vb->vertexBias[0]);
        vb->bbox.max[1] = m3gMadd(ab[4], vb->vertexScale, vb->vertexBias[1]);
        vb->bbox.max[2] = m3gMadd(ab[5], vb->vertexScale, vb->vertexBias[2]);

        /* Flip the bounding box if the scale was negative */
        
        if (vb->vertexScale < 0) {
            M3Gint i;
            for (i = 0; i < 3; ++i) {
                M3Gfloat t = vb->bbox.min[i];
                vb->bbox.min[i] = vb->bbox.max[i];
                vb->bbox.max[i] = t;
            }
        }
    }
    *boundingBox = vb->bbox;
}

/*!
 * \internal
 * \brief Gets vertex buffer timestamp.
 *
 * \param buffer        VertexBuffer object
 * \return timestamp
 */
static M3Gint m3gGetTimestamp(const VertexBuffer *buffer)
{
    if (buffer->vertices &&
        m3gGetArrayTimestamp(buffer->vertices) != buffer->verticesTimestamp) {
        return buffer->timestamp + 1;
    }
    return buffer->timestamp;
}

/**
 * Updates the vertex count bookkeeping when setting vertex
 * arrays.
 *
 * \param buffer        VertexBuffer object
 * \param oldArray      VertexArray object
 * \param newArray      VertexArray object
 * \param maskBit       array mask bit
 */
static void m3gUpdateArray(VertexBuffer *buffer,
                           VertexArray *oldArray,
                           VertexArray *newArray,
                           M3Gbitmask maskBit)
{
    M3Gint change = (oldArray == NULL && newArray != NULL) ?  1 :
        (oldArray != NULL && newArray == NULL) ? -1 :
        0;

    /* If adding or replacing an array, set the initial vertex count,
     * or compare the new array against the current vertex count */
    
    if (newArray != NULL) {
    	if (buffer->arrayCount == 0 || (buffer->arrayCount == 1
                                        && change == 0)) {
    		buffer->vertexCount = m3gGetArrayVertexCount(newArray);
    	}
    	else if (m3gGetArrayVertexCount(newArray) != buffer->vertexCount) {
            m3gRaiseError(M3G_INTERFACE(buffer), M3G_INVALID_VALUE);
            return;
    	}
    }

    /* Update the array bitmask */
    
    if (newArray != NULL) {
        buffer->arrayMask |= maskBit;
    }
    else {
        buffer->arrayMask &= ~maskBit;
    }

    /* Update the array count, and reset the vertex count to zero if
     * no arrays remain */
    
    buffer->arrayCount += change;
    if (buffer->arrayCount == 0) {
        M3G_ASSERT(buffer->arrayMask == 0);
        buffer->vertexCount = 0;
    }
}

/*!
 * \internal
 * \brief Overloaded Object3D method
 *
 * \param self VertexBuffer object
 * \param references array of reference objects
 * \return number of references
 */
static M3Gint m3gVertexBufferDoGetReferences(Object *self, Object **references)
{
    VertexBuffer *vb = (VertexBuffer *)self;
    M3Gint i, num = m3gObjectDoGetReferences(self, references);
    if (vb->vertices != NULL) {
        if (references != NULL)
            references[num] = (Object *)vb->vertices;
        num++;
    }
    if (vb->normals != NULL) {
        if (references != NULL)
            references[num] = (Object *)vb->normals;
        num++;
    }
    if (vb->colors != NULL) {
        if (references != NULL)
            references[num] = (Object *)vb->colors;
        num++;
    }
    for (i = 0; i < M3G_NUM_TEXTURE_UNITS; i++) {
        if (vb->texCoords[i] != NULL) {
            if (references != NULL)
                references[num] = (Object *)vb->texCoords[i];
            num++;
        }
    }
    return num;
}

/*!
 * \internal
 * \brief Overloaded Object3D method
 */
static Object *m3gVertexBufferFindID(Object *self, M3Gint userID)
{
    int i;
    VertexBuffer *vb = (VertexBuffer *)self;
    Object *found = m3gObjectFindID(self, userID);
    
    if (!found && vb->vertices != NULL) {
        found = m3gFindID((Object*) vb->vertices, userID);
    }
    if (!found && vb->normals != NULL) {
        found = m3gFindID((Object*) vb->normals, userID);
    }
    if (!found && vb->colors != NULL) {
        found = m3gFindID((Object*) vb->colors, userID);
    }
    for (i = 0; !found && i < M3G_NUM_TEXTURE_UNITS; ++i) {
        if (vb->texCoords[i] != NULL) {
            found = m3gFindID((Object*) vb->texCoords[i], userID);
        }
    }
    return found;
}

/*!
 * \internal
 * \brief Duplicates vertex buffer data and array configuration
 */
static void m3gDuplicateVertexBufferData(VertexBuffer *clone,
                                         const VertexBuffer *original)
{
    M3Gint i;
    
    clone->vertexScale = original->vertexScale;
    m3gCopy(clone->vertexBias, original->vertexBias, 3 * sizeof(GLfloat));
    clone->defaultColor = original->defaultColor;
    clone->locked = original->locked;
    clone->vertexCount = original->vertexCount;
    clone->arrayCount = original->arrayCount;
    clone->arrayMask = original->arrayMask;
    clone->timestamp = original->timestamp;
    
    for (i = 0; i < M3G_NUM_TEXTURE_UNITS; i++) {
        clone->texCoordScale[i] = original->texCoordScale[i];
        clone->texCoordBias[i][0] = original->texCoordBias[i][0];
        clone->texCoordBias[i][1] = original->texCoordBias[i][1];
        clone->texCoordBias[i][2] = original->texCoordBias[i][2];
        M3G_ASSIGN_REF(clone->texCoords[i], original->texCoords[i]);
    }
    M3G_ASSIGN_REF(clone->colors, original->colors);
    M3G_ASSIGN_REF(clone->normals, original->normals);
    M3G_ASSIGN_REF(clone->vertices, original->vertices);
}

/*!
 * \internal
 * \brief Overloaded Object3D method
 *
 * \param originalObj original VertexBuffer object
 * \param cloneObj pointer to cloned VertexBuffer object
 * \param pairs array for all object-duplicate pairs
 * \param numPairs number of pairs
 */
static M3Gbool m3gVertexBufferDuplicate(const Object *originalObj,
                                        Object **cloneObj,
                                        Object **pairs,
                                        M3Gint *numPairs)
{
    VertexBuffer *original = (VertexBuffer*) originalObj;
    VertexBuffer *clone;
    M3G_ASSERT(*cloneObj == NULL); /* no derived classes */

    /* Create the clone object */
    
    clone = (VertexBuffer*) m3gCreateVertexBuffer(originalObj->interface);
    if (!clone) {
        return M3G_FALSE;
    }
    *cloneObj = (Object *)clone;

    /* Duplicate base class data */
    
    if (!m3gObjectDuplicate(originalObj, cloneObj, pairs, numPairs)) {
        return M3G_FALSE;
    }

    /* Duplicate our own data */

    m3gDuplicateVertexBufferData(clone, original);
    return M3G_TRUE;
}

/*!
 * \internal
 * \brief Overloaded Object3D method.
 *
 * \param property      animation property
 * \retval M3G_TRUE     property supported
 * \retval M3G_FALSE    property not supported
 */
static M3Gbool m3gVertexBufferIsCompatible(M3Gint property)
{
    switch (property) {
    case M3G_ANIM_ALPHA:
    case M3G_ANIM_COLOR:
        return M3G_TRUE;
    default:
        return m3gObjectIsCompatible(property);
    }
}

/*!
 * \internal
 * \brief Overloaded Object3D method
 *
 * \param self          VertexBuffer object
 * \param property      animation property
 * \param valueSize     size of value array
 * \param value         value array
 */
static void m3gVertexBufferUpdateProperty(Object *self,
                                          M3Gint property,
                                          M3Gint valueSize,
                                          const M3Gfloat *value)
{
    VertexBuffer *buffer = (VertexBuffer *) self;
    M3G_VALIDATE_OBJECT(buffer);
    M3G_ASSERT_PTR(value);

    switch (property) {
    case M3G_ANIM_ALPHA:
        M3G_ASSERT(valueSize >= 1);
        buffer->defaultColor.a =
            (GLubyte)m3gAdd(m3gMul(255.f, m3gClampFloat(value[0], 0.f, 1.f)), 0.5f);
        break;
    case M3G_ANIM_COLOR:
        M3G_ASSERT(valueSize >= 3);
        buffer->defaultColor.r =
            (GLubyte)m3gAdd(m3gMul(255.f, m3gClampFloat(value[0], 0.f, 1.f)), 0.5f);
        buffer->defaultColor.g =
            (GLubyte)m3gAdd(m3gMul(255.f, m3gClampFloat(value[1], 0.f, 1.f)), 0.5f);
        buffer->defaultColor.b =
            (GLubyte)m3gAdd(m3gMul(255.f, m3gClampFloat(value[2], 0.f, 1.f)), 0.5f);
        break;
    default:
        m3gObjectUpdateProperty(self, property, valueSize, value);
    }
}

/*!
 * \internal
 * \brief Checks that a vertex buffer can be properly rendered
 *
 * The vertex format required by \c app is compared against the vertex
 * arrays included in \c vb, and \c vb is checked to have at least \c
 * maxIndex vertex entries.
 *
 * \param vb        VertexBuffer object
 * \param app       Appearance object
 * \param maxIndex  maximum index in index buffer
 * \retval M3G_TRUE  valid state
 * \retval M3G_FALSE invalid state
 */
static M3Gbool m3gValidateVertexBuffer(const VertexBuffer *vb,
                                       const Appearance *app,
                                       M3Gsizei maxIndex)
{
    M3Gbitmask reqMask;
    M3G_UNREF(app);
    reqMask = M3G_POSITION_BIT;
    if ((m3gGetArrayMask(vb) & reqMask) != reqMask) {
        return M3G_FALSE;
    }
    return (m3gGetNumVertices(vb) > maxIndex);
}

/*!
 * \internal
 * \brief Sets a vertex buffer up for subsequent vertex modification
 *
 * A specified subset of the arrays of \c srcBuffer will be replicated
 * in \c buffer, without copying the contents.  The others will be
 * copied as references only.
 *
 * \param buffer       the modified buffer
 * \param srcBuffer    the source buffer
 * \param arrayMask    bitmask of arrays to modify
 * \param createArrays M3G_TRUE to create the arrays specified by
 *                     \c arrayMask, M3G_FALSE to leave them NULL
 * \retval M3G_TRUE success
 * \retval M3G_FALSE out of memory
 */
static M3Gbool m3gMakeModifiedVertexBuffer(VertexBuffer *buffer,
                                           const VertexBuffer *srcBuffer,
                                           M3Gbitmask arrayMask,
                                           M3Gbool createArrays)
{
    M3G_VALIDATE_OBJECT(buffer);
    M3G_VALIDATE_OBJECT(srcBuffer);
    {
        Interface *m3g = M3G_INTERFACE(buffer);
        VertexArray *array;
        int i;

        /* First, just copy the data from the other buffer */

        m3gDuplicateVertexBufferData(buffer, srcBuffer);
        
        /* Now, override the specified arrays: release the existing
         * array, and either allocate a new one or leave as NULL,
         * depending on the value of the createArrays flag */

#       define MODIFY_ARRAY(bit, name)                          \
            if (arrayMask & (bit)) {                            \
                array = NULL;                                   \
                if (srcBuffer->name && createArrays) {          \
                    array = m3gCreateVertexArray(               \
                        m3g,                                    \
                        srcBuffer->name->vertexCount,           \
                        srcBuffer->name->elementSize,           \
                        srcBuffer->name->elementType == GL_SHORT ? M3G_SHORT : M3G_BYTE); \
                    if (!array) {                               \
                        return M3G_FALSE;                       \
                    }                                           \
                }                                               \
                m3gUpdateArray(buffer, buffer->name, array, bit); \
                M3G_ASSIGN_REF(buffer->name, array);            \
            }

        MODIFY_ARRAY(M3G_POSITION_BIT, vertices);
        MODIFY_ARRAY(M3G_COLOR_BIT, colors);
        MODIFY_ARRAY(M3G_NORMAL_BIT, normals);
        
        for (i = 0; i < M3G_NUM_TEXTURE_UNITS; ++i) {
            MODIFY_ARRAY(M3G_TEXCOORD0_BIT << i, texCoords[i]);
        }

#       undef MODIFY_ARRAY

        return M3G_TRUE;
    }
}

/*----------------------------------------------------------------------
 * Virtual function table
 *--------------------------------------------------------------------*/

static const ObjectVFTable m3gvf_VertexBuffer = {
    m3gObjectApplyAnimation,
    m3gVertexBufferIsCompatible,
    m3gVertexBufferUpdateProperty,
    m3gVertexBufferDoGetReferences,
    m3gVertexBufferFindID,
    m3gVertexBufferDuplicate,
    m3gDestroyVertexBuffer
};


/*----------------------------------------------------------------------
 * Public API functions
 *--------------------------------------------------------------------*/

/*!
 * \brief Creates an empty vertex buffer
 *
 * \param interface    M3G interface
 * \retval VertexBuffer new VertexBuffer object
 * \retval NULL VertexBuffer creating failed
 */
/*@access M3Ginterface@*/
/*@access M3Gobject@*/
M3G_API M3GVertexBuffer m3gCreateVertexBuffer(M3GInterface interface)
{
    Interface *m3g = (Interface *) interface;
    M3G_VALIDATE_INTERFACE(m3g);
    
    {
        VertexBuffer *buffer = m3gAllocZ(m3g, sizeof(VertexBuffer));

        if (buffer != NULL) {
            m3gInitObject(&buffer->object, m3g, M3G_CLASS_VERTEX_BUFFER);
        
            /* Set default color to white */
            buffer->defaultColor.r = (GLubyte) 0xFF;
            buffer->defaultColor.g = (GLubyte) 0xFF;
            buffer->defaultColor.b = (GLubyte) 0xFF;
            buffer->defaultColor.a = (GLubyte) 0xFF;
        }

        return (M3GVertexBuffer) buffer;
    }
}

/*!
 * \brief Sets the color array of a vertex buffer
 *
 * \param hBuffer   VertexBuffer object
 * \param hArray    VertexArray object
 */
/*@access M3Gobject@*/
M3G_API void m3gSetColorArray(M3GVertexBuffer hBuffer, M3GVertexArray hArray)
{
    VertexBuffer *buffer = (VertexBuffer *) hBuffer;
    VertexArray *array = (VertexArray *) hArray;
    M3G_VALIDATE_OBJECT(buffer);
    
    if (array != NULL) {
        M3G_VALIDATE_OBJECT(array);

        /* Check for errors */
        if (!m3gInRange(array->elementSize, 3, 4) ||
            array->elementType != M3G_GLTYPE(M3G_BYTE)) {
            m3gRaiseError(M3G_INTERFACE(array), M3G_INVALID_VALUE);
            return;
        }
    }
    
    m3gUpdateArray(buffer, buffer->colors, array, M3G_COLOR_BIT);
    M3G_ASSIGN_REF(buffer->colors, array);
    ++buffer->timestamp;
}

/*!
 * \brief Sets the normal array of a vertex buffer
 *
 * \param hBuffer   VertexBuffer object
 * \param hArray    VertexArray object
 */
/*@access M3Gobject@*/
M3G_API void m3gSetNormalArray(M3GVertexBuffer hBuffer, M3GVertexArray hArray)
{
    VertexBuffer *buffer = (VertexBuffer *) hBuffer;
    VertexArray *array = (VertexArray *) hArray;
    M3G_VALIDATE_OBJECT(buffer);
    if (array != NULL) {
        M3G_VALIDATE_OBJECT(array);

        /* Check for errors */
        if (array->elementSize != 3) {
            m3gRaiseError(M3G_INTERFACE(array), M3G_INVALID_VALUE);
            return;
        }
    }
    
    m3gUpdateArray(buffer, buffer->normals, array, M3G_NORMAL_BIT);
    M3G_ASSIGN_REF(buffer->normals, array);
    ++buffer->timestamp;
}

/*!
 * \brief Sets the texture coordinate array of a vertex buffer
 *
 * \param hBuffer   VertexBuffer object
 * \param unit      texturing unit
 * \param hArray    VertexArray object
 * \param scale     scale
 * \param bias      bias array
 * \param biasLength bias array length
 */
/*@access M3Gobject@*/
M3G_API void m3gSetTexCoordArray(M3GVertexBuffer hBuffer,
                                 M3Gint unit,
                                 M3GVertexArray hArray,
                                 M3Gfloat scale, M3Gfloat *bias,
                                 M3Gint biasLength)
{
    VertexBuffer *buffer = (VertexBuffer *) hBuffer;
    VertexArray *array = (VertexArray *) hArray;

    M3G_VALIDATE_OBJECT(buffer);
    if (array != NULL) {
        M3G_VALIDATE_OBJECT(array);
    }

    /* Check errors */
    
    if (unit < 0 || unit >= M3G_NUM_TEXTURE_UNITS) {
        m3gRaiseError(M3G_INTERFACE(buffer), M3G_INVALID_INDEX);
        return;
    }

    if (array != NULL) {
        if (array->elementSize != 2 && array->elementSize != 3) {
            m3gRaiseError(M3G_INTERFACE(buffer), M3G_INVALID_VALUE);
            return;
        }
        if (bias != NULL && biasLength < array->elementSize) {
            m3gRaiseError(M3G_INTERFACE(buffer), M3G_INVALID_VALUE);
            return;
        }
    }

    m3gUpdateArray(buffer, buffer->texCoords[unit], array, M3G_TEXCOORD0_BIT << unit);
    M3G_ASSIGN_REF(buffer->texCoords[unit], array);

    if (array != NULL && bias != NULL) {
        buffer->texCoordBias[unit][0] = bias[0];
        buffer->texCoordBias[unit][1] = bias[1];
        if (biasLength > 2) {
            buffer->texCoordBias[unit][2] = bias[2];
        }
    }
    else {
        buffer->texCoordBias[unit][0] = 0.f;
        buffer->texCoordBias[unit][1] = 0.f;
        buffer->texCoordBias[unit][2] = 0.f;
    }

    buffer->texCoordScale[unit] = scale;

    ++buffer->timestamp;
}

/*!
 * \brief Sets the vertex array of a vertex buffer
 *
 * \param hBuffer   VertexBuffer object
 * \param hArray    VertexArray object
 * \param scale     scale
 * \param bias      bias array
 * \param biasLength bias array length
 */
/*@access M3Gobject@*/
M3G_API void m3gSetVertexArray(M3GVertexBuffer hBuffer,
                               M3GVertexArray hArray,
                               M3Gfloat scale,
                               M3Gfloat *bias, M3Gint biasLength)
{
    VertexBuffer *buffer = (VertexBuffer *) hBuffer;
    VertexArray *array = (VertexArray *) hArray;
    M3G_VALIDATE_OBJECT(buffer);
    if (array != NULL) {
        M3G_VALIDATE_OBJECT(array);
    }

    if (array != NULL && array->elementSize != 3) {
        m3gRaiseError(M3G_INTERFACE(buffer), M3G_INVALID_VALUE);
        return;
    }

    if (array != NULL && bias != NULL && biasLength < 3) {
        m3gRaiseError(M3G_INTERFACE(buffer), M3G_INVALID_VALUE);
        return;
    }

    m3gUpdateArray(buffer, buffer->vertices, array, M3G_POSITION_BIT);
    M3G_ASSIGN_REF(buffer->vertices, array);

    if (array != NULL && bias != NULL) {
        buffer->vertexBias[0] = bias[0];
        buffer->vertexBias[1] = bias[1];
        buffer->vertexBias[2] = bias[2];
    }
    else {
        buffer->vertexBias[0] = 0.f;
        buffer->vertexBias[1] = 0.f;
        buffer->vertexBias[2] = 0.f;
    }
    buffer->vertexScale = scale;

    /* Make sure we invalidate the cached bounding box */
    
    ++buffer->timestamp;
    if (array != NULL) {
        buffer->verticesTimestamp = ~m3gGetArrayTimestamp(array); /*lint !e502 ok for signed */
    }
}

/*!
 * \brief Sets the default color of a vertex buffer
 *
 * \param handle    VertexBuffer object
 * \param ARGB      default color as ARGB
 */
/*@access M3Gobject@*/

M3G_API void m3gSetVertexDefaultColor(M3GVertexBuffer handle, M3Guint ARGB)
{
    VertexBuffer *buffer = (VertexBuffer *) handle;
    M3G_VALIDATE_OBJECT(buffer);    

    buffer->defaultColor.b = (GLubyte)(ARGB);
    buffer->defaultColor.g = (GLubyte)(ARGB >> 8);
    buffer->defaultColor.r = (GLubyte)(ARGB >> 16);
    buffer->defaultColor.a = (GLubyte)(ARGB >> 24);
    ++buffer->timestamp;
}

/*!
 * \brief Gets the default color of a vertex buffer
 * \param handle    VertexBuffer object
 * \return          default color as ARGB
 */
/*@access M3Gobject@*/

M3G_API M3Guint m3gGetVertexDefaultColor(M3GVertexBuffer handle)
{
	unsigned ARGB;
    VertexBuffer *buffer = (VertexBuffer *) handle;
    M3G_VALIDATE_OBJECT(buffer);    

	ARGB = buffer->defaultColor.a;
	ARGB <<= 8;
	ARGB |= buffer->defaultColor.r;
	ARGB <<= 8;
	ARGB |= buffer->defaultColor.g;
	ARGB <<= 8;
	ARGB |= buffer->defaultColor.b;

	return ARGB;
}

/*!
 * \brief Gets vertex array of a vertex buffer
 *
 * \param handle    VertexBuffer object
 * \param which     which array to get
 *                  \arg M3G_GET_POSITIONS
 *                  \arg M3G_GET_NORMALS
 *                  \arg M3G_GET_COLORS
 *                  \arg M3G_GET_TEXCOORDS0
 *                  \arg M3G_GET_TEXCOORDS0 + 1
 * \param scaleBias array for scale and bias (s, bx, by, bz)
 * \param sbLength  length of scale bias array
 */

/*@access M3Gobject@*/
M3G_API M3GVertexArray m3gGetVertexArray(M3GVertexBuffer handle,
                                         M3Gint which,
                                         M3Gfloat *scaleBias, M3Gint sbLength)
{
    M3Gint tci = 1;
    VertexBuffer *buffer = (VertexBuffer *) handle;
    M3G_VALIDATE_OBJECT(buffer);

    switch(which) {
    case M3G_GET_POSITIONS:
        if (scaleBias != NULL && sbLength < 4) {
            m3gRaiseError(M3G_INTERFACE(buffer), M3G_INVALID_VALUE);
            return 0;
        }

        if (scaleBias != NULL) {
            scaleBias[0] = buffer->vertexScale;
            scaleBias[1] = buffer->vertexBias[0];
            scaleBias[2] = buffer->vertexBias[1];
            scaleBias[3] = buffer->vertexBias[2];
        }
        return buffer->vertices;
    case M3G_GET_NORMALS: return buffer->normals;
    case M3G_GET_COLORS:  return buffer->colors;
    case M3G_GET_TEXCOORDS0:
        --tci;
        /* Flow through */
    case M3G_GET_TEXCOORDS0 + 1:
        if (buffer->texCoords[tci] != NULL) {
            if (scaleBias != NULL && sbLength < (buffer->texCoords[tci]->elementSize + 1)) {
                m3gRaiseError(M3G_INTERFACE(buffer), M3G_INVALID_VALUE);
                return 0;
            }
    
            if (scaleBias != NULL) {
                scaleBias[0] = buffer->texCoordScale[tci];
                scaleBias[1] = buffer->texCoordBias[tci][0];
                scaleBias[2] = buffer->texCoordBias[tci][1];
                if (buffer->texCoords[tci]->elementSize > 2) {
                    scaleBias[3] = buffer->texCoordBias[tci][2];
                }
            }
        }
        return buffer->texCoords[tci];
    default:
        m3gRaiseError(M3G_INTERFACE(buffer), M3G_INVALID_VALUE);
        break;
    }

    return 0; /* Error */
}

/*!
 * \brief Gets vertex count of a vertex buffer
 *
 * \param handle    VertexBuffer object
 * \return vertex count
 */

/*@access M3Gobject@*/
M3G_API M3Gint  m3gGetVertexCount(M3GVertexBuffer handle)
{
    VertexBuffer *buffer = (VertexBuffer *) handle;
    M3G_VALIDATE_OBJECT(buffer);    

    return buffer->vertexCount;    
}