m3g/m3gcore11/src/m3g_vertexarray.c
author Gareth Stockwell <gareth.stockwell@accenture.com>
Fri, 05 Nov 2010 17:31:20 +0000
branchbug235_bringup_0
changeset 215 097e92a68d68
parent 0 5d03bc08d59c
child 116 171fae344dd4
permissions -rw-r--r--
Added GLES 1.x spinning cube-rendering code to eglbringuptest The coordinate, color and index data are uploaded to server-side buffers by the CGLES1Cube::KhrSetup function. CGLES1Cube::KhrPaint just sets the view matrix and issues a draw command. Which demo to display can be selected by passing its name on the command line, e.g. eglbringuptest vgline eglbringuptest gles1cube If no name is provided, the application defaults to vgline.

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


/*!
 * \internal
 * \file
 * \brief VertexArray implementation
 *
 */

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

#include "m3g_vertexarray.h"

#define DIRTY_ALPHA_FACTOR (-1)

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

/*!
 * \internal
 * \brief Signals that the contents of an array have changed
 */
static M3G_INLINE void m3gInvalidateArray(VertexArray *array)
{
    array->cachedAlphaFactor = DIRTY_ALPHA_FACTOR;
    array->rangeMin = 1;
    array->rangeMax = 0;
    
    ++array->timestamp;
}
    
/*----------------------------------------------------------------------
 * Internal functions
 *--------------------------------------------------------------------*/

/*!
 * \internal
 * \brief Destroys this VertexArray object.
 *
 * \param obj VertexArray object
 */
static void m3gDestroyVertexArray(Object *obj)
{
    VertexArray *array = (VertexArray *) obj;
    M3G_VALIDATE_OBJECT(array);
    M3G_ASSERT(array->numLocks == 0);
    {
        Interface *m3g = M3G_INTERFACE(array);
        m3gFreeObject(m3g, array->data);
        m3gFreeObject(m3g, array->cachedColors);
    }
    m3gDestroyObject(&array->object);
}

/*!
 * \internal
 * \brief Sends color array to OpenGL.
 *
 * \note Alpha scaling currently prevents an array from being used for
 * anything else while it is being bound as a color array.
 *
 * \param array         VertexArray object
 * \param alphaFactor   1.16 alpha factor in [0, 0x10000]
 */
static void m3gLockColorArray(const VertexArray *array, M3Gint alphaFactor)
{
    Interface *m3g = M3G_INTERFACE(array);
    M3G_VALIDATE_OBJECT(array);
    M3G_ASSERT(!array->mapCount);
    M3G_ASSERT(array->numLocks == 0);
    M3G_ASSERT(m3gInRange(alphaFactor, 0, 0x10000));

    /* With an alpha factor of 1.0, we can just load up the original data */
    
    if (alphaFactor >= 0x10000) {
        GLenum type = array->elementType;
        if (type >= GL_BYTE && type <= GL_UNSIGNED_SHORT) {
            type |= 0x01; /* force type to unsigned for GL */
        }
        glColorPointer(type == GL_UNSIGNED_BYTE ? 4 : array->elementSize,
                       type,
                       array->stride,
                       m3gMapObject(m3g, array->data));
    }
    else {

        /* With a non-unit alpha factor, we may need to update the
         * cached pre-scaled colors. */

        M3Gubyte* const cache = (M3Gubyte *)
            m3gMapObject(m3g, array->cachedColors);
            
        if (array->cachedAlphaFactor != alphaFactor) {
            M3Gubyte *dst = cache;
            int i, n;

            M3G_VALIDATE_MEMBLOCK(cache);
            
            /* Scale the colors, converting from the source format */

            n = array->vertexCount;
            
            /* Byte colors are always padded to 4 bytes per entry,
             * with the implicit alpha set to 0xFF for RGB colors, so
             * we can do a near-straight copy. */
            
            switch (array->elementType) {
            case GL_BYTE:
            case GL_UNSIGNED_BYTE:
            {   
                const M3Gubyte *src = (M3Gubyte *)m3gMapObject(m3g,
                                                               array->data);
                for (i = 0; i < n; ++i) {
                    *dst++ = *src++;
                    *dst++ = *src++;
                    *dst++ = *src++;
                    {
                        M3Guint tmp = *src++ * (M3Guint) alphaFactor;
                        *dst++ = (M3Gubyte)(tmp >> 16);
                    }
                }
                m3gUnmapObject(m3g, array->data);
                break;
            }
            default:
                M3G_ASSERT(M3G_FALSE);
            }
            
            ((VertexArray*)array)->cachedAlphaFactor = alphaFactor;
        }
        
        /* We now have the scaled colors in the cache, so just set the
         * pointer there */
        
        glColorPointer(4, GL_UNSIGNED_BYTE, 0, cache);
    }
    M3G_ASSERT_GL;
    
    ++((VertexArray*)array)->numLocks;
}

/*!
 * \internal
 * \brief Creates the color cache required for alpha factors
 */
static M3Gbool m3gCreateAlphaColorCache(VertexArray *array)
{
    M3G_VALIDATE_OBJECT(array);
    M3G_ASSERT(array->cachedColors == 0);
    
    /* There are always four bytes per color entry */
    
    array->cachedColors = m3gAllocObject(M3G_INTERFACE(array),
                                         4 * array->vertexCount);
    
    return (array->cachedColors != 0);
}

/*!
 * \internal
 * \brief Sends normal array to OpenGL.
 *
 * \param array VertexArray object
 */
static void m3gLockNormalArray(const VertexArray *array)
{
    M3G_VALIDATE_OBJECT(array);
    M3G_ASSERT(!array->mapCount);

    glNormalPointer(array->elementType, array->stride,
                    m3gMapObject(M3G_INTERFACE(array), array->data));
    M3G_ASSERT_GL;
    
    ++((VertexArray*)array)->numLocks;
}

/*!
 * \internal
 * \brief Sends texture coordinate array to OpenGL.
 *
 * \param array VertexArray object
 */
static void m3gLockTexCoordArray(const VertexArray *array)
{
    M3G_VALIDATE_OBJECT(array);
    M3G_ASSERT(!array->mapCount);

    glTexCoordPointer(array->elementSize,
                      array->elementType,
                      array->stride,
                      m3gMapObject(M3G_INTERFACE(array), array->data));
    M3G_ASSERT_GL;
    
    ++((VertexArray*)array)->numLocks;
}

/*!
 * \internal
 * \brief Sends vertex array to OpenGL.
 *
 * \param array VertexArray object
 */
static void m3gLockVertexArray(const VertexArray *array)
{
    M3G_VALIDATE_OBJECT(array);
    M3G_ASSERT(!array->mapCount);

    glVertexPointer(array->elementSize,
                    array->elementType,
                    array->stride,
                    m3gMapObject(M3G_INTERFACE(array), array->data));
    M3G_ASSERT_GL;
    
    ++((VertexArray*)array)->numLocks;
}

/*!
 * \internal
 * \brief Decreases array lock count.
 *
 * \param array VertexArray object
 */
static void m3gUnlockArray(const VertexArray *array)
{
    M3G_VALIDATE_OBJECT(array);
    M3G_ASSERT(array->numLocks > 0);
    
    m3gUnmapObject(M3G_INTERFACE(array), array->data);
    
    --((VertexArray*)array)->numLocks;
}

/*!
 * \internal
 * \brief Clones a VertexArray.
 *
 * Used by MorphingMesh.
 *
 * \param array VertexArray object
 * \return cloned VertexArray object
 *
 */
static VertexArray *m3gCloneVertexArray(const VertexArray *array)
{
	VertexArray *clone;
	Interface *m3g = M3G_INTERFACE(array);
	
    M3G_VALIDATE_OBJECT(array);
    M3G_ASSERT(!array->mapCount);

	clone = (VertexArray *) m3gAlloc(m3g, sizeof(VertexArray));
	if (clone == NULL) {
        return NULL;
    }

	m3gCopy(clone, array, sizeof(VertexArray));
    m3gInitObject((Object*) clone, m3g, M3G_CLASS_VERTEX_ARRAY);

	clone->data = m3gAllocObject(m3g, array->vertexCount * array->stride);

	if (!clone->data) {
        m3gDestroyObject((Object*) clone);
		m3gFree(m3g, clone);
		return NULL;
	}

	m3gCopy(m3gMapObject(m3g, clone->data),
            m3gMapObject(m3g, array->data),
            array->vertexCount * array->stride);
    m3gUnmapObject(m3g, clone->data);
    m3gUnmapObject(m3g, array->data);

	return clone;
}

/*!
 * \internal
 * \brief Gets array vertex count.
 *
 * \param array VertexArray object
 * \return number of vertices
 */
static M3Gint m3gGetArrayVertexCount(const VertexArray *array)
{
    return array->vertexCount;
}

/*!
 * \internal
 * \brief Returns the minimum and maximum value stored in the array
 */
static void m3gGetArrayValueRange(const VertexArray *array,
                                  M3Gint *minValue, M3Gint *maxValue)
{
    Interface *m3g = M3G_INTERFACE(array);
    
    if (array->rangeMin > array->rangeMax) {
        M3Gint count = array->elementSize * array->vertexCount;
        M3Gint minVal = 0, maxVal = 0;
        
        if (count > 0) {
            switch (array->elementType) {
            case GL_BYTE:
            {
                const GLbyte *src = (const GLbyte*) m3gMapObject(m3g,
                                                                 array->data);
                const M3Gint c = array->elementSize;
                const M3Gint skip = array->stride - c;
                minVal = maxVal = (M3Gint) *src++;
                while (count) {
                    M3Gint i;
                    for (i = 0; i < c; ++i) {
                        M3Gint v = (M3Gint) *src++;
                        minVal = M3G_MIN(minVal, v);
                        maxVal = M3G_MAX(maxVal, v);
                    }
                    count -= c;
                    src += skip;
                }
                break;
            }
            case GL_UNSIGNED_BYTE:
            {
                const GLubyte *src = (const GLubyte*) m3gMapObject(m3g,
                                                                   array->data);
                const M3Gint c = array->elementSize;
                const M3Gint skip = array->stride - c;
                minVal = maxVal = (M3Gint) *src++;
                while (count) {
                    M3Gint i;
                    for (i = 0; i < c; ++i) {
                        M3Gint v = (M3Gint) *src++;
                        minVal = M3G_MIN(minVal, v);
                        maxVal = M3G_MAX(maxVal, v);
                    }
                    count -= c;
                    src += skip;
                }
                break;
            }
            case GL_SHORT:
            {
                const GLshort *src = (const GLshort*)
                    m3gMapObject(m3g, array->data);
                minVal = maxVal = (M3Gint) *src++;
                while (--count) {
                    M3Gint v = (M3Gint) *src++;
                    minVal = M3G_MIN(minVal, v);
                    maxVal = M3G_MAX(maxVal, v);
                }
                break;
            }
            case GL_UNSIGNED_SHORT:
            {
                const GLushort *src = (const GLushort*)
                    m3gMapObject(m3g, array->data);
                minVal = maxVal = (M3Gint) *src++;
                while (--count) {
                    M3Gint v = (M3Gint) *src++;
                    minVal = M3G_MIN(minVal, v);
                    maxVal = M3G_MAX(maxVal, v);
                }
                break;
            }
            default:
                M3G_ASSERT(M3G_FALSE);
            }
        }
        m3gUnmapObject(m3g, array->data);

        M3G_ASSERT(m3gInRange(minVal, -32768, 32767));
        M3G_ASSERT(m3gInRange(maxVal, -32768, 32767));
        
        ((VertexArray*)array)->rangeMin = (M3Gshort) minVal;
        ((VertexArray*)array)->rangeMax = (M3Gshort) maxVal;
    }
    
    *minValue = array->rangeMin;
    *maxValue = array->rangeMax;
}

/*!
 * \internal
 * \brief Compares attributes of two vertex arrays.
 *
 * \param array         VertexArray object
 * \param other         VertexArray object
 * \retval M3G_TRUE     arrays are compatible
 * \retval M3G_FALSE    arrays are not compatible
 */
static M3Gbool m3gIsCompatible(const VertexArray *array, const VertexArray *other)
{
    return( other != NULL &&
            other->elementType == array->elementType &&
            other->elementSize == array->elementSize &&
            other->vertexCount == array->vertexCount);
}

/*!
 * \internal
 * \brief Overloaded Object3D method.
 *
 * \param originalObj original VertexArray object
 * \param cloneObj pointer to cloned VertexArray object
 * \param pairs array for all object-duplicate pairs
 * \param numPairs number of pairs
 */
static M3Gbool m3gVertexArrayDuplicate(const Object *originalObj,
                                       Object **cloneObj,
                                       Object **pairs,
                                       M3Gint *numPairs)
{
    VertexArray *clone = m3gCloneVertexArray((VertexArray *)originalObj);
    if (!clone) {
        return M3G_FALSE;
    }
    *cloneObj = (Object*) clone;
    return m3gObjectDuplicate(originalObj, cloneObj, pairs, numPairs);
}

/*!
 * \internal
 * \brief Gets array timestamp.
 *
 * \param array VertexArray object
 * \return timestamp
 */
static M3Gint m3gGetArrayTimestamp(const VertexArray *array)
{
    return array->timestamp;
}

/*!
 * \internal
 * \brief Gets array bounding box as shorts.
 *
 * \param array VertexArray object
 * \param boundingBox   array to fill in
 *                      \arg [0] = minX
 *                      \arg [1] = minY
 *                      \arg [2] = minZ
 *                      \arg [3] = maxX
 *                      \arg [4] = maxY
 *                      \arg [5] = maxZ
 */
static void m3gGetArrayBoundingBox(const VertexArray *array, M3Gshort *boundingBox)
{
    Interface *m3g = M3G_INTERFACE(array);
    M3Gint i;
    M3Gshort minX, minY, minZ;
    M3Gshort maxX, maxY, maxZ;
    M3Gbyte *bptr;
    M3Gshort *sptr;
    
    /* Only support 3 component arrays */
    if (array->elementSize != 3 || array->vertexCount == 0) {
        return;
    }

    switch(array->elementType) {
    case M3G_GLTYPE(M3G_BYTE):
    case M3G_GLTYPE(M3G_UBYTE):
        bptr = (M3Gbyte *) m3gMapObject(m3g, array->data);

        minX = maxX = bptr[0];
        minY = maxY = bptr[1];
        minZ = maxZ = bptr[2];
        bptr += 4;

        for (i = 0; i < array->vertexCount - 1; i++) {
            if (bptr[0] < minX) minX = bptr[0];
            if (bptr[0] > maxX) maxX = bptr[0];
            if (bptr[1] < minY) minY = bptr[1];
            if (bptr[1] > maxY) maxY = bptr[1];
            if (bptr[2] < minZ) minZ = bptr[2];
            if (bptr[2] > maxZ) maxZ = bptr[2];
            bptr += 4;
        }
        break;

    case M3G_GLTYPE(M3G_SHORT):
    case M3G_GLTYPE(M3G_USHORT):
        sptr = (M3Gshort *) m3gMapObject(m3g, array->data);

        minX = maxX = sptr[0];
        minY = maxY = sptr[1];
        minZ = maxZ = sptr[2];
        sptr += 3;

        for (i = 0; i < array->vertexCount - 1; i++) {
            if (sptr[0] < minX) minX = sptr[0];
            if (sptr[0] > maxX) maxX = sptr[0];
            if (sptr[1] < minY) minY = sptr[1];
            if (sptr[1] > maxY) maxY = sptr[1];
            if (sptr[2] < minZ) minZ = sptr[2];
            if (sptr[2] > maxZ) maxZ = sptr[2];
            sptr += 3;
        }
        break;

    default: /* Error */
        M3G_ASSERT(0);
        return;
    }

    m3gUnmapObject(m3g, array->data);

    boundingBox[0] = minX;
    boundingBox[1] = minY;
    boundingBox[2] = minZ;
    boundingBox[3] = maxX;
    boundingBox[4] = maxY;
    boundingBox[5] = maxZ;
}

/*!
 * \internal
 * \brief Gets a coordinate from vertex array.
 *
 * \param va            VertexArray object
 * \param elementCount  elemens in coordinate
 * \param idx           index of coordinate
 * \param v             vector to fill in
 * \retval              M3G_TRUE get ok
 * \retval              M3G_FALSE no such vertex
 */
static M3Gbool m3gGetCoordinates(VertexArray *va,
                                 M3Gint elementCount,
                                 M3Gint idx,
                                 M3Gfloat *v)
{
    Interface *m3g;
    M3Gbyte *bptr;
    M3Gshort *sptr;
    int i;
    
    if (!va) {
        return M3G_FALSE;
    }

    m3g = M3G_INTERFACE(va);

    switch (va->elementType) {
    case M3G_GLTYPE(M3G_BYTE):
    case M3G_GLTYPE(M3G_UBYTE):
        idx *= 4;
        bptr = (M3Gbyte *) m3gMapObject(m3g, va->data);
        bptr += idx;
        for (i = 0; i < elementCount; ++i) {
            *v++ = *bptr++;
        }
        break;

    case M3G_GLTYPE(M3G_SHORT):
    case M3G_GLTYPE(M3G_USHORT):
        idx *= elementCount;
        sptr = (M3Gshort *) m3gMapObject(m3g, va->data);
        sptr += idx;
        for (i = 0; i < elementCount; ++i) {
            *v++ = *sptr++;
        }
        break;
    }

    m3gUnmapObject(m3g, va->data);
    return M3G_TRUE;
}

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

static const ObjectVFTable m3gvf_VertexArray = {
    m3gObjectApplyAnimation,
    m3gObjectIsCompatible,
    m3gObjectUpdateProperty,
    m3gObjectDoGetReferences,
    m3gObjectFindID,
    m3gVertexArrayDuplicate,
    m3gDestroyVertexArray
};
        

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

/*!
 * \brief Creates a VertexArray object.
 *
 * \param interface     M3G interface
 * \param count         Count of vertices
 * \param size          Size of each element [2, 4]
 * \param type          Type of elements
 * \retval VertexArray new VertexArray object
 * \retval NULL VertexArray creating failed
 */

/*@access M3Ginterface@*/
/*@access M3GVertexArray@*/
M3G_API M3GVertexArray m3gCreateVertexArray(M3GInterface interface,
                                            M3Gsizei count,
                                            M3Gint size,
                                            M3Gdatatype type)
{
    Interface *m3g = (Interface *) interface;
    M3G_VALIDATE_INTERFACE(m3g);
    
    /* Check errors */
    if (count < 1 || count > 65535 ||
        size < 2 || size > 4 ||
        (type != M3G_BYTE && type != M3G_SHORT)) {
        m3gRaiseError(m3g, M3G_INVALID_VALUE);
        return NULL;
    }

    {
        /* Allocate the array object and its data buffer */
        
        VertexArray *array = m3gAllocZ(m3g, (M3Gsizei) sizeof(VertexArray));
        if (!array) {
            return NULL;
        }

        switch (type) {
        case M3G_BYTE:
        case M3G_UBYTE:
            /* always padded to 4 bytes */
            array->stride = 4;
            break;
        case M3G_SHORT:
        case M3G_USHORT:
            array->stride = size * sizeof(M3Gshort);
            break;
        default:
            m3gFree(m3g, array);
            m3gRaiseError(m3g, M3G_INVALID_ENUM);
            return NULL;
        }

        /* Alloc and initialize all values to zero */
        array->data = m3gAllocObject(m3g, count * array->stride);
        if (!array->data) {
            m3gFree(m3g, array);
            return NULL;
        }
        else {
            void *ptr = m3gMapObject(m3g, array->data);
            m3gZero(ptr, count * array->stride);
            m3gUnmapObject(m3g, array->data);
        }

        m3gInitObject(&array->object, m3g, M3G_CLASS_VERTEX_ARRAY);

        array->elementType = M3G_GLTYPE(type);
        array->elementSize = size;
        array->vertexCount = count;
        m3gInvalidateArray(array);
        
        return (M3GVertexArray) array;
    }
}

/*!
 * \brief Returns the data layout parameters for a vertex array
 *
 * This gives the format of the data mapped to user memory with \c
 * m3gMapVertexArray.
 *
 * \param handle  array handle
 * \param count   pointer for number of vertices (output)
 * \param size    pointer for components per vertex (output)
 * \param type    pointer to data element type (output)
 * \param stride  pointer to stride, i.e. number of bytes from
 *                the beginning of one vertex to the next (output)
 */
M3G_API void m3gGetVertexArrayParams(M3GVertexArray handle,
                                     M3Gsizei *count,
                                     M3Gint *size,
                                     M3Gdatatype *type,
                                     M3Gsizei *stride)
{
    VertexArray *array = (VertexArray *) handle;
    M3G_VALIDATE_OBJECT(array);

    if (count) {
        *count = array->vertexCount;
    }
    if (size) {
        *size = array->elementSize;
    }
    if (type) {
        *type = (M3Gdatatype) M3G_M3GTYPE(array->elementType);
    }
    if (stride) {
        *stride = array->stride;
    }
}

/*!
 * \brief Maps the data of a vertex array to application memory
 *
 * The contents of the array will remain mapped to application memory
 * until a matching \c m3gUnMapVertexArray call. While mapped to user
 * memory, the array can not be used for rendering.
 *
 * Deleting a mapped array will also implicitly unmap it.
 *
 * \param handle handle of the array to map
 * \return pointer to the array data
 */
M3G_API void *m3gMapVertexArray(M3GVertexArray handle)
{
    void *ptr = (void*) m3gMapVertexArrayReadOnly(handle);
    if (ptr) {
        m3gInvalidateArray((VertexArray*) handle);
    }
    return ptr;
}

/*!
 * \brief Maps a vertex array for reading only
 *
 * This is the same as m3gMapVertexArray, but maps the array for
 * reading only, allowing internal optimizations.
 *
 */
M3G_API const void *m3gMapVertexArrayReadOnly(M3GVertexArray handle)
{
    VertexArray *array = (VertexArray *) handle;
    M3G_VALIDATE_OBJECT(array);
    
    if (array->numLocks > 0) {
        m3gRaiseError(M3G_INTERFACE(array), M3G_INVALID_OPERATION);
        return NULL;
    }
    
    ++array->mapCount;
    return m3gMapObject(M3G_INTERFACE(array), array->data);
}

/*!
 * \brief Releases an array mapped to user memory
 *
 * The pointer obtained with a preceding \c m3gMapVertexArray call
 * will not be valid after unmapping the array.
 *
 * \param handle handle of the array to release
 */
M3G_API void m3gUnmapVertexArray(M3GVertexArray handle)
{
    VertexArray *array = (VertexArray *) handle;
    M3G_VALIDATE_OBJECT(array);
    M3G_ASSERT(array->mapCount);

    m3gUnmapObject(M3G_INTERFACE(array), array->data);
    --array->mapCount;
}

/*!
 * \brief Set a range of vertex array elements
 *
 * \param handle array handle
 * \param first  index of first vertex to set
 * \param count  number of total vertices to set
 * \param srcLength length of source data
 * \param type  data type of source data
 * \param src   source data
 */
M3G_API void m3gSetVertexArrayElements(M3GVertexArray handle,
                                       M3Gint first, M3Gsizei count,
                                       M3Gsizei srcLength,
                                       M3Gdatatype type,
                                       const void *src)
{
    VertexArray *array = (VertexArray *) handle;
    M3G_VALIDATE_OBJECT(array);

    M3G_ASSERT(array->numLocks == 0);

    /* Check errors */
    if (array->mapCount) {
        m3gRaiseError(M3G_INTERFACE(array), M3G_INVALID_OPERATION);
        return;
    }
    if (src == NULL) {
        m3gRaiseError(M3G_INTERFACE(array), M3G_NULL_POINTER);
        return;
    }
    if (first < 0 || first + count > array->vertexCount) {
        m3gRaiseError(M3G_INTERFACE(array), M3G_INVALID_INDEX);
        return;
    }
    if (count < 0 || srcLength < count * array->elementSize) {
        m3gRaiseError(M3G_INTERFACE(array), M3G_INVALID_VALUE);
        return;
    }

    /* Copy source data according to destination array type */
    {
        int values = count * array->elementSize;
        
        switch (array->elementType) {
        case GL_BYTE:
        case GL_UNSIGNED_BYTE:
            if (type != M3G_BYTE) {
                m3gRaiseError(M3G_INTERFACE(array), M3G_INVALID_OPERATION);
                return;
            }
            else {
                GLubyte *dst =
                    ((GLubyte *)m3gMapObject(M3G_INTERFACE(array),
                                             array->data))
                    + first * array->stride;
                GLubyte *srcByte = (GLubyte *) src;

                M3G_ASSERT(array->elementSize >= 2 && array->elementSize <= 4);
                M3G_ASSERT(array->stride == 4);
                
                while (values > 0) {
                    *dst++ = *srcByte++;
                    *dst++ = *srcByte++;
                    *dst++ = (M3Gubyte)((array->elementSize >= 3) ? *srcByte++ : 0x00);
                    *dst++ = (M3Gubyte)((array->elementSize == 4) ? *srcByte++ : 0xFF);
                    values -= array->elementSize;
                }
            }
            break;
        
        case GL_SHORT:
        case GL_UNSIGNED_SHORT:
            if (type != M3G_SHORT) {
                m3gRaiseError(M3G_INTERFACE(array), M3G_INVALID_OPERATION);
                return;
            }
            else {
                GLushort *dst =
                    ((GLushort *)m3gMapObject(M3G_INTERFACE(array),
                                              array->data))
                    + first * array->stride / 2;
                GLushort *srcShort = (GLushort *) src;
                M3G_ASSERT(array->stride == (GLsizei)(array->elementSize * sizeof(*dst)));
                
                while (values--) {
                    *dst++ = *srcShort++;
                }
            }
            break;

        default:
            M3G_ASSERT(0);      /* fatal internal error */
        }
    }

    m3gUnmapObject(M3G_INTERFACE(array), array->data);
    m3gInvalidateArray(array);
}

/*!
 * \brief Get a range of vertex array elements
 *
 * \param handle array handle
 * \param first  index of first vertex to set
 * \param count  number of total vertices to set
 * \param dstLength length of destination data
 * \param type  data type of destination data
 * \param dst   destination data
 */
M3G_API void m3gGetVertexArrayElements(M3GVertexArray handle,
                                       M3Gint first, M3Gsizei count,
                                       M3Gsizei dstLength, M3Gdatatype type, void *dst)
{
    VertexArray *array = (VertexArray *) handle;
    M3G_VALIDATE_OBJECT(array);

    M3G_ASSERT(array->numLocks == 0);

    /* Check errors */
    if (array->mapCount) {
        m3gRaiseError(M3G_INTERFACE(array), M3G_INVALID_OPERATION);
        return;
    }
    if (dst == NULL) {
        m3gRaiseError(M3G_INTERFACE(array), M3G_NULL_POINTER);
        return;
    }
    if (first < 0 || first + count > array->vertexCount) {
        m3gRaiseError(M3G_INTERFACE(array), M3G_INVALID_INDEX);
        return;
    }
    if (count < 0 || dstLength < count * array->elementSize) {
        m3gRaiseError(M3G_INTERFACE(array), M3G_INVALID_VALUE);
        return;
    }

    /* Data according to destination array type */
    {
        int values = count * array->elementSize;
        
        switch (array->elementType) {
        case GL_BYTE:
        case GL_UNSIGNED_BYTE:
            if (type != M3G_BYTE) {
                m3gRaiseError(M3G_INTERFACE(array), M3G_INVALID_OPERATION);
                return;
            }
            else {
                GLubyte *src =
                    ((GLubyte *)m3gMapObject(M3G_INTERFACE(array),
                                             array->data))
                    + first * array->stride;
                GLubyte *dstByte = (GLubyte *) dst;

                M3G_ASSERT(array->elementSize >= 2 && array->elementSize <= 4);
                M3G_ASSERT(array->stride == 4);
                
                while (values > 0) {
                    *dstByte++ = src[0];
                    *dstByte++ = src[1];
                    if (array->elementSize >= 3) {
                        *dstByte++ = src[2];
                    }
                    if (array->elementSize == 4) {
                        *dstByte++ = src[3];
                    }
                    src += 4;
                    values -= array->elementSize;
                }
            }
            break;
        
        case GL_SHORT:
        case GL_UNSIGNED_SHORT:
            if (type != M3G_SHORT) {
                m3gRaiseError(M3G_INTERFACE(array), M3G_INVALID_OPERATION);
                return;
            }
            else {
                GLushort *src =
                    ((GLushort *)m3gMapObject(M3G_INTERFACE(array),
                                              array->data))
                    + first * array->stride / 2;
                GLushort *dstShort = (GLushort *) dst;
                M3G_ASSERT(array->stride == (GLsizei)(array->elementSize * sizeof(*src)));
                
                while (values--) {
                    *dstShort++ = *src++;
                }
            }
            break;

        default:
            M3G_ASSERT(0);      /* fatal internal error */
        }
    }

    m3gUnmapObject(M3G_INTERFACE(array), array->data);
}

/*!
 * \brief Transform vertex array with
 * given transform and w.
 *
 * \param handle        array handle
 * \param transform     transform
 * \param out           output array to fill in
 * \param outLength     length of the output array
 * \param w             use w
 */
M3G_API void m3gTransformArray(M3GVertexArray handle,
                               M3GMatrix *transform,
                               M3Gfloat *out, M3Gint outLength,
                               M3Gbool w)
{
    M3Gbyte *bptr;
    M3Gshort *sptr;
    M3Gfloat *outPtr = out;
    M3Gint i;
    M3GVec4 vec;
    VertexArray *array = (VertexArray *) handle;
    M3G_VALIDATE_OBJECT(array);

    /* Check for errors */
    if (outLength < (4 * array->vertexCount) ||
        array->elementSize == 4) {
        m3gRaiseError(M3G_INTERFACE(array), M3G_INVALID_VALUE);
        return;
    }

    switch(array->elementType) {
        case GL_BYTE:
        case GL_UNSIGNED_BYTE:
            bptr = (M3Gbyte *)m3gMapObject(M3G_INTERFACE(array), array->data);

            for (i = 0; i < array->vertexCount * 4; i += 4) {
                vec.x = bptr[i + 0];
                vec.y = bptr[i + 1];
                vec.z = 0;
                if (array->elementSize == 3) {
                    vec.z = bptr[i + 2];
                }
                vec.w = (M3Gfloat)w;

                m3gTransformVec4(transform, &vec);

                *outPtr++ = vec.x;
                *outPtr++ = vec.y;
                *outPtr++ = vec.z;
                *outPtr++ = vec.w;
            }
            break;

        case GL_SHORT:
        case GL_UNSIGNED_SHORT:
            sptr = (M3Gshort *)m3gMapObject(M3G_INTERFACE(array), array->data);

            for (i = 0; i < array->vertexCount * array->elementSize; i += array->elementSize) {
                vec.x = sptr[i + 0];
                vec.y = sptr[i + 1];
                vec.z = 0;
                if (array->elementSize == 3) {
                    vec.z = sptr[i + 2];
                }
                vec.w = (M3Gfloat)w;

                m3gTransformVec4(transform, &vec);

                *outPtr++ = vec.x;
                *outPtr++ = vec.y;
                *outPtr++ = vec.z;
                *outPtr++ = vec.w;
            }
            break;
    }
    m3gUnmapObject(M3G_INTERFACE(array), array->data);
}

#undef DIRTY_ALPHA_FACTOR