m3g/m3gcore11/src/m3g_vertexarray.c
author hgs
Fri, 24 Sep 2010 16:14:28 +0300
changeset 187 9f66f99ee56f
parent 0 5d03bc08d59c
child 164 25ffed67c7ef
permissions -rw-r--r--
201026

/*
* 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:
            /* always padded to 4 bytes */
            array->stride = 4;
            break;
        case M3G_SHORT:
            array->stride = size * sizeof(M3Gshort);
            break;
        }

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