m3g/m3gcore11/src/m3g_vertexarray.c
changeset 0 5d03bc08d59c
child 116 171fae344dd4
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/m3g/m3gcore11/src/m3g_vertexarray.c	Tue Feb 02 01:47:50 2010 +0200
@@ -0,0 +1,1058 @@
+/*
+* 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
+