m3g/m3gcore11/src/m3g_morphingmesh.c
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Wed, 31 Mar 2010 23:34:07 +0300
branchRCL_3
changeset 26 15986eb6c500
parent 0 5d03bc08d59c
permissions -rw-r--r--
Revision: 201010 Kit: 201013

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


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

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

#include "m3g_morphingmesh.h"
#include "m3g_memory.h"
#include "m3g_animationtrack.h"

#define WEIGHT_SHIFT        ( 8 )
#define WEIGHT_SCALE        ( 1 << WEIGHT_SHIFT )
#define WEIGHT_ROUND_PLUS   ( WEIGHT_SCALE / 4 )
#define WEIGHT_ROUND_MINUS  ( 0 )

/*!
 * \internal
 * \brief offsetof macro
 */
#include <stddef.h>

static M3Gbool m3gMorph(MorphingMesh *momesh);
static M3Gbool m3gCreateClones(VertexBuffer *vertices, VertexBuffer *morphed);
static void m3gDeleteClones(VertexBuffer *morphed);

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

/*!
 * \internal
 * \brief Destroys this MorphingMesh object.
 *
 * \param obj MorphingMesh object
 */
static void m3gDestroyMorphingMesh(Object *obj)
{
    M3Gint i;
    MorphingMesh *momesh = (MorphingMesh *) obj;
    M3G_VALIDATE_OBJECT(momesh);

    for (i = 0; i < momesh->numTargets; i++) {
        M3G_ASSIGN_REF(momesh->targets[i], NULL);
    }

    M3G_ASSIGN_REF(momesh->morphed, NULL);

    {
        Interface *m3g = M3G_INTERFACE(momesh); 
        m3gFree(m3g, momesh->targets);
        m3gFree(m3g, momesh->weights);
        m3gFree(m3g, momesh->floatWeights);
    }

    m3gDestroyMesh(obj);
}

/*!
 * \internal
 * \brief Overloaded Node method.
 *
 * Setup morphing mesh render. Morph and call Mesh
 * render setup.
 *
 * \param self MorphingMesh object
 * \param toCamera transform to camera
 * \param alphaFactor total alpha factor
 * \param caller caller node
 * \param renderQueue RenderQueue
 *
 * \retval M3G_TRUE continue render setup
 * \retval M3G_FALSE abort render setup
 */
static M3Gbool m3gMorphingMeshSetupRender(Node *self,
                                          const Node *caller,
                                          SetupRenderState *s,
                                          RenderQueue *renderQueue)
{
    MorphingMesh *momesh = (MorphingMesh *)self;
    M3G_UNREF(caller);
    m3gIncStat(M3G_INTERFACE(self), M3G_STAT_RENDER_NODES, 1);
    
    momesh->dirtyState = M3G_TRUE;

    if ((self->enableBits & NODE_RENDER_BIT) != 0 &&
        (self->scope & renderQueue->scope) != 0) {

        /* Try view frustum culling */

#       if defined(M3G_ENABLE_VF_CULLING)
        m3gUpdateCullingMask(s, renderQueue->camera, &momesh->bbox);
        if (s->cullMask == 0) {
            m3gIncStat(M3G_INTERFACE(self),
                       M3G_STAT_RENDER_NODES_CULLED, 1);
            return M3G_TRUE;
        }
#       endif
        
        /* No dice, must morph & render */
        
        M3G_BEGIN_PROFILE(M3G_INTERFACE(momesh), M3G_PROFILE_MORPH);
        if (!m3gMorph(momesh)) {
            return M3G_FALSE;
        }
        M3G_END_PROFILE(M3G_INTERFACE(momesh), M3G_PROFILE_MORPH);

        return m3gQueueMesh((Mesh*) self, &s->toCamera, renderQueue);
    }
    return M3G_TRUE;
}

/*!
 * \internal
 * \brief Overloaded Node method.
 *
 * Renders one submesh.
 *
 * \param self MorphingMesh object
 * \param ctx current render context
 * \param patchIndex submesh index
 */
static void m3gMorphingMeshDoRender(Node *self,
                                    RenderContext *ctx,
                                    const Matrix *toCamera,
                                    M3Gint patchIndex)
{
    MorphingMesh *momesh = (MorphingMesh *)self;
    Mesh *mesh = (Mesh *)self;

    m3gDrawMesh(ctx,
                momesh->morphed,
                mesh->indexBuffers[patchIndex],
                mesh->appearances[patchIndex],
                toCamera,
                mesh->totalAlphaFactor + 1,
                self->scope);
}

/*!
 * \internal
 * \brief Overloaded Node method.
 *
 * Morph and forward call Mesh internal ray intersect.
 *
 * \param self      MorphingMesh object
 * \param mask      pick scope mask
 * \param ray       pick ray
 * \param ri        RayIntersection object
 * \param toGroup   transform to originating group
 * \retval          M3G_TRUE    continue pick
 * \retval          M3G_FALSE   abort pick
 */
static M3Gbool m3gMorphingMeshRayIntersect( Node *self,
                                            M3Gint mask,
                                            M3Gfloat *ray,
                                            RayIntersection *ri,
                                            Matrix *toGroup)
{
    MorphingMesh *momesh = (MorphingMesh *)self;
    M3G_BEGIN_PROFILE(M3G_INTERFACE(momesh), M3G_PROFILE_MORPH);
    if (!m3gMorph(momesh)) {
        return M3G_FALSE;
    }
    M3G_END_PROFILE(M3G_INTERFACE(momesh), M3G_PROFILE_MORPH);
    return m3gMeshRayIntersectInternal(&momesh->mesh, momesh->morphed, mask, ray, ri, toGroup);
}

/*!
 * \internal
 * \brief Morphes short type vertex arrays.
 *
 * \param momesh        MorphingMesh object
 * \param dsta          destination VertexArray object
 * \param base          base VertexArray object
 * \param arrayOffset   offset to VertexArray object inside Mesh object
 * \retval M3G_TRUE     morph ok
 * \retval M3G_FALSE    morph failed, exception raised
 */
static M3Gbool m3gMorphShorts(MorphingMesh *momesh, VertexArray *dsta, VertexArray *base, M3Gint arrayOffset)
{
    M3Gint i;
    M3Gint sum, sw;
    M3Gshort *dst, *src;
    M3Gshort j, numMsTargets = 0;
    M3Gshort **srct = m3gAllocTemp(M3G_INTERFACE(momesh), sizeof(M3Gshort *) * momesh->numTargets * 2);
    M3Gshort *msTargets = (M3Gshort *) (srct + momesh->numTargets);

    if (msTargets == NULL) {
        return M3G_FALSE;
    }

    /* Check that target arrays are in same format */
    for (j = 0; j < momesh->numTargets; j++) {
        VertexArray **va;
        va = (VertexArray **)(((M3Gbyte *)momesh->targets[j]) + arrayOffset);
        if (!m3gIsCompatible(*va, base)) {
            /* Unmap all arrays */
            for (j = 0; j < numMsTargets; j++) {
                m3gUnmapVertexArray(*(VertexArray **)(((M3Gbyte *)momesh->targets[msTargets[j]]) + arrayOffset));
            }
            m3gFreeTemp(M3G_INTERFACE(momesh));
            m3gRaiseError(M3G_INTERFACE(momesh), M3G_INVALID_OPERATION);
            return M3G_FALSE;
        }
        /* Use only most significant targets*/
        if (momesh->weights[j] != 0) {
            msTargets[numMsTargets] = j;
            srct[numMsTargets] = (M3Gshort *) m3gMapVertexArrayReadOnly(*va);
            numMsTargets++;
        }
    }

    dst = (M3Gshort *) m3gMapVertexArray(dsta);
    src = (M3Gshort *) m3gMapVertexArrayReadOnly(base);
    sw = momesh->sumWeights;

    for (i = 0; i < base->vertexCount * base->elementSize; i++) {
        sum = src[i] * sw;
        for (j = 0; j < numMsTargets; j++) {
            sum += srct[j][i] * momesh->weights[msTargets[j]];
        }
        /* Round */
        if (sum >= 0) {
            sum += WEIGHT_ROUND_PLUS;
        }
        else {
            sum -= WEIGHT_ROUND_MINUS;
        }
        dst[i] = (M3Gshort)(sum >> WEIGHT_SHIFT);
    }

    /* Unmap all arrays */
    for (j = 0; j < numMsTargets; j++) {
        m3gUnmapVertexArray(*(VertexArray **)(((M3Gbyte *)momesh->targets[msTargets[j]]) + arrayOffset));
    }

    m3gUnmapVertexArray(base);
    m3gUnmapVertexArray(dsta);
    
    m3gFreeTemp(M3G_INTERFACE(momesh));
    return M3G_TRUE;
}

/*!
 * \internal
 * \brief Morphes byte type vertex arrays.
 *
 * \param momesh        MorphingMesh object
 * \param dsta          destination VertexArray object
 * \param base          base VertexArray object
 * \param arrayOffset   offset to VertexArray object inside Mesh object
 * \retval M3G_TRUE     morph ok
 * \retval M3G_FALSE    morph failed, exception raised
 */
static M3Gbool m3gMorphBytes(MorphingMesh *momesh, VertexArray *dsta, VertexArray *base, M3Gint arrayOffset)
{
    M3Gint i;
    M3Gint sum, sw;
    M3Gbyte *dst, *src;
    M3Gint skip;
    M3Gshort j, numMsTargets = 0;
    M3Gbyte **srct = m3gAllocTemp(M3G_INTERFACE(momesh), sizeof(M3Gbyte *) * momesh->numTargets * 2);
    M3Gshort *msTargets = (M3Gshort *) (srct + momesh->numTargets);

    if (msTargets == NULL) {
        return M3G_FALSE;
    }

    /* Check that target arrays are in same format */
    for (j = 0; j < momesh->numTargets; j++) {
        VertexArray **va;
        va = (VertexArray **)(((M3Gbyte *)momesh->targets[j]) + arrayOffset);
        if (!m3gIsCompatible(*va, base)) {
            /* Unmap all arrays */
            for (j = 0; j < numMsTargets; j++) {
                m3gUnmapVertexArray(*(VertexArray **)(((M3Gbyte *)momesh->targets[msTargets[j]]) + arrayOffset));
            }
            m3gFreeTemp(M3G_INTERFACE(momesh));
            m3gRaiseError(M3G_INTERFACE(momesh), M3G_INVALID_OPERATION);
            return M3G_FALSE;
        }
        /* Use only most significant targets*/
        if (momesh->weights[j] != 0) {
            msTargets[numMsTargets] = j;
            srct[numMsTargets] = (M3Gbyte *) m3gMapVertexArrayReadOnly(*va);
            numMsTargets++;
        }
    }

    dst = (M3Gbyte *) m3gMapVertexArray(dsta);
    src = (M3Gbyte *) m3gMapVertexArrayReadOnly(base);
    sw = momesh->sumWeights;
    skip = base->elementSize;

    for (i = 0; i < base->vertexCount * base->stride; i++) {
        if ((i & 3) >= skip) continue;
        sum = src[i] * sw;
        for (j = 0; j < numMsTargets; j++) {
            sum += srct[j][i] * momesh->weights[msTargets[j]];
        }
        /* Round */
        if (sum >= 0) {
            sum += WEIGHT_ROUND_PLUS;
        }
        else {
            sum -= WEIGHT_ROUND_MINUS;
        }
        dst[i] = (M3Gbyte)(sum >> WEIGHT_SHIFT);
    }

    /* Unmap all arrays */
    for (j = 0; j < numMsTargets; j++) {
        m3gUnmapVertexArray(*(VertexArray **)(((M3Gbyte *)momesh->targets[msTargets[j]]) + arrayOffset));
    }

    m3gUnmapVertexArray(base);
    m3gUnmapVertexArray(dsta);

    m3gFreeTemp(M3G_INTERFACE(momesh));
    return M3G_TRUE;
}

/*!
 * \internal
 * \brief Morphes byte type color vertex arrays.
 *
 * \param momesh        MorphingMesh object
 * \param dsta          destination VertexArray object
 * \param base          base VertexArray object
 * \param arrayOffset   offset to VertexArray object inside Mesh object
 * \retval M3G_TRUE     morph ok
 * \retval M3G_FALSE    morph failed, exception raised
 */
static M3Gbool m3gMorphColorBytes(MorphingMesh *momesh, VertexArray *dsta, VertexArray *base, M3Gint arrayOffset)
{
    M3Gint i;
    M3Gint sum, sw;
    M3Gubyte *dst, *src;
    M3Gint skip;
    M3Gshort j, numMsTargets = 0;
    M3Gubyte **srct = m3gAllocTemp(M3G_INTERFACE(momesh), sizeof(M3Gubyte *) * momesh->numTargets * 2);
    M3Gshort *msTargets = (M3Gshort *) (srct + momesh->numTargets);


    if (msTargets == NULL) {
        return M3G_FALSE;
    }

    /* Check that target arrays are in same format */
    for (j = 0; j < momesh->numTargets; j++) {
        VertexArray **va;
        va = (VertexArray **)(((M3Gbyte *)momesh->targets[j]) + arrayOffset);
        if (!m3gIsCompatible(*va, base)) {
            /* Unmap all arrays */
            for (j = 0; j < numMsTargets; j++) {
                m3gUnmapVertexArray(*(VertexArray **)(((M3Gbyte *)momesh->targets[msTargets[j]]) + arrayOffset));
            }
            m3gFreeTemp(M3G_INTERFACE(momesh));
            m3gRaiseError(M3G_INTERFACE(momesh), M3G_INVALID_OPERATION);
            return M3G_FALSE;
        }
        /* Use only most significant targets*/
        if (momesh->weights[j] != 0) {
            msTargets[numMsTargets] = j;
            srct[numMsTargets] = (M3Gubyte *) m3gMapVertexArrayReadOnly(*va);
            numMsTargets++;
        }
    }

    dst = (M3Gubyte *) m3gMapVertexArray(dsta);
    src = (M3Gubyte *) m3gMapVertexArray(base);
    sw = momesh->sumWeights;
    skip = base->elementSize;

    for (i = 0; i < base->vertexCount * base->stride; i++) {
        if ((i & 3) >= skip) continue;
        sum = src[i] * sw;
        for (j = 0; j < numMsTargets; j++) {
            sum += srct[j][i] * momesh->weights[msTargets[j]];
        }

        /* Round */
        sum += WEIGHT_ROUND_PLUS;
        /* Clamp */
        if (sum > 255 * WEIGHT_SCALE) sum = 255 * WEIGHT_SCALE;

        dst[i] = (M3Gubyte) (sum >> WEIGHT_SHIFT);
    }

    /* Unmap all arrays */
    for (j = 0; j < numMsTargets; j++) {
        m3gUnmapVertexArray(*(VertexArray **)(((M3Gbyte *)momesh->targets[msTargets[j]]) + arrayOffset));
    }

    m3gUnmapVertexArray(base);
    m3gUnmapVertexArray(dsta);
    
    m3gFreeTemp(M3G_INTERFACE(momesh));
    return M3G_TRUE;
}

/*!
 * \internal
 * \brief Morphes all MorphingMesh vertex arrays. That
 * is positions, normals, texture coordinates and colors.
 *
 * \param momesh        MorphingMesh object
 * \retval M3G_TRUE     morph ok
 * \retval M3G_FALSE    morph failed, exception raised
 */
static M3Gbool m3gMorph(MorphingMesh *momesh)
{
    M3Gint i, j;
    M3Gint nullValues;

    if (momesh->cloneArrayMask != m3gGetArrayMask(momesh->base)) {
        if (!m3gCreateClones(momesh->base, momesh->morphed)) {
            return M3G_FALSE;
        }
        momesh->cloneArrayMask = m3gGetArrayMask(momesh->base);
        momesh->dirtyState = M3G_TRUE;
    }

    if (momesh->dirtyState == M3G_FALSE) return M3G_TRUE;

    if (momesh->base->vertices != NULL) {
        nullValues = 0;
        for (i = 0; i < momesh->numTargets; i++) {
            if (momesh->targets[i]->vertices == NULL) {
                nullValues++;
            }           
        }

        if (nullValues != momesh->numTargets && nullValues != 0) {
            m3gRaiseError(M3G_INTERFACE(momesh), M3G_INVALID_OPERATION);
            return M3G_FALSE;
        }

        if (nullValues == 0) {
            if (momesh->base->vertices->elementType == M3G_GLTYPE(M3G_SHORT)) {
                if (!m3gMorphShorts( momesh,
                                     momesh->morphed->vertices,
                                     momesh->base->vertices,
                                     offsetof(VertexBuffer, vertices) )) return M3G_FALSE;
            }
            else {
                if (!m3gMorphBytes(  momesh,
                                     momesh->morphed->vertices,
                                     momesh->base->vertices,
                                     offsetof(VertexBuffer, vertices) )) return M3G_FALSE;
            }
        }
        else {
            m3gCopy(m3gMapVertexArray(momesh->morphed->vertices),
                    m3gMapVertexArrayReadOnly(momesh->base->vertices),
                    momesh->base->vertices->stride * momesh->base->vertices->vertexCount );
            m3gUnmapVertexArray(momesh->base->vertices);
            m3gUnmapVertexArray(momesh->morphed->vertices);
        }
    }
    else {
        for (i = 0; i < momesh->numTargets; i++) {
            if (momesh->targets[i]->vertices != NULL) {
                m3gRaiseError(M3G_INTERFACE(momesh), M3G_INVALID_OPERATION);
                return M3G_FALSE;
            }           
        }
    }

    if (momesh->base->normals != NULL) {
        nullValues = 0;
        for (i = 0; i < momesh->numTargets; i++) {
            if (momesh->targets[i]->normals == NULL) {
                nullValues++;
            }           
        }

        if (nullValues != momesh->numTargets && nullValues != 0) {
            m3gRaiseError(M3G_INTERFACE(momesh), M3G_INVALID_OPERATION);
            return M3G_FALSE;
        }

        if (nullValues == 0) {
            if (momesh->base->normals->elementType == M3G_GLTYPE(M3G_SHORT)) {
                if (!m3gMorphShorts( momesh,
                                     momesh->morphed->normals,
                                     momesh->base->normals,
                                     offsetof(VertexBuffer, normals) )) return M3G_FALSE;
            }
            else {
                if (!m3gMorphBytes(  momesh,
                                     momesh->morphed->normals,
                                     momesh->base->normals,
                                     offsetof(VertexBuffer, normals) )) return M3G_FALSE;
            }
        }
        else {
            m3gCopy(m3gMapVertexArray(momesh->morphed->normals),
                    m3gMapVertexArrayReadOnly(momesh->base->normals),
                    momesh->base->normals->stride * momesh->base->normals->vertexCount );
            m3gUnmapVertexArray(momesh->base->normals);
            m3gUnmapVertexArray(momesh->morphed->normals);
        }
    }
    else {
        for (i = 0; i < momesh->numTargets; i++) {
            if (momesh->targets[i]->normals != NULL) {
                m3gRaiseError(M3G_INTERFACE(momesh), M3G_INVALID_OPERATION);
                return M3G_FALSE;
            }           
        }
    }
    
    if (momesh->base->colors != NULL) {
        nullValues = 0;
        for (i = 0; i < momesh->numTargets; i++) {
            if (momesh->targets[i]->colors == NULL) {
                nullValues++;
            }           
        }
        
        if (nullValues != momesh->numTargets && nullValues != 0) {
            m3gRaiseError(M3G_INTERFACE(momesh), M3G_INVALID_OPERATION);
            return M3G_FALSE;
        }

        if (nullValues == 0) {
            /* Only byte colors are supported */
            if (!m3gMorphColorBytes( momesh,
                                     momesh->morphed->colors,
                                     momesh->base->colors,
                                     offsetof(VertexBuffer, colors) )) return M3G_FALSE;
        }
        else {
            m3gCopy(m3gMapVertexArray(momesh->morphed->colors),
                    m3gMapVertexArrayReadOnly(momesh->base->colors),
                    momesh->base->colors->stride * momesh->base->colors->vertexCount );
            m3gUnmapVertexArray(momesh->base->colors);
            m3gUnmapVertexArray(momesh->morphed->colors);
        }
    }
    else {
        /* Morph default color */   
        M3Gint r, g, b, a;
        
        for (i = 0; i < momesh->numTargets; i++) {
            if (momesh->targets[i]->colors != NULL) {
                m3gRaiseError(M3G_INTERFACE(momesh), M3G_INVALID_OPERATION);
                return M3G_FALSE;
            }           
        }

        r = momesh->base->defaultColor.r * momesh->sumWeights;
        g = momesh->base->defaultColor.g * momesh->sumWeights;
        b = momesh->base->defaultColor.b * momesh->sumWeights;
        a = momesh->base->defaultColor.a * momesh->sumWeights;

        for (i = 0; i < momesh->numTargets; i++) {
            r += momesh->targets[i]->defaultColor.r * momesh->weights[i];
            g += momesh->targets[i]->defaultColor.g * momesh->weights[i];
            b += momesh->targets[i]->defaultColor.b * momesh->weights[i];
            a += momesh->targets[i]->defaultColor.a * momesh->weights[i];
        }       

        /* Clamp values*/
        if (r < 0) r = 0;
        if (g < 0) g = 0;
        if (b < 0) b = 0;
        if (a < 0) a = 0;

        if (r > 255 * WEIGHT_SCALE) r = 255 * WEIGHT_SCALE;
        if (g > 255 * WEIGHT_SCALE) g = 255 * WEIGHT_SCALE;
        if (b > 255 * WEIGHT_SCALE) b = 255 * WEIGHT_SCALE;
        if (a > 255 * WEIGHT_SCALE) a = 255 * WEIGHT_SCALE;

        momesh->morphed->defaultColor.r = (GLubyte) (r >> WEIGHT_SHIFT);
        momesh->morphed->defaultColor.g = (GLubyte) (g >> WEIGHT_SHIFT);
        momesh->morphed->defaultColor.b = (GLubyte) (b >> WEIGHT_SHIFT);
        momesh->morphed->defaultColor.a = (GLubyte) (a >> WEIGHT_SHIFT);
    }

    for (i = 0; i < M3G_NUM_TEXTURE_UNITS; i++) {
        if (momesh->base->texCoords[i] != NULL) {
            nullValues = 0;
            for (j = 0; j < momesh->numTargets; j++) {
                if (momesh->targets[j]->texCoords[i] == NULL) {
                    nullValues++;
                }           
            }

            if (nullValues != momesh->numTargets && nullValues != 0) {
                m3gRaiseError(M3G_INTERFACE(momesh), M3G_INVALID_OPERATION);
                return M3G_FALSE;
            }
    
            if (nullValues == 0) {
                if (momesh->base->texCoords[i]->elementType == M3G_GLTYPE(M3G_SHORT)) {
                    if (!m3gMorphShorts( momesh,
                                         momesh->morphed->texCoords[i],
                                         momesh->base->texCoords[i],
                                         offsetof(VertexBuffer, texCoords) + i * sizeof(VertexBuffer *) )) return M3G_FALSE;
                }
                else {
                    if (!m3gMorphBytes(  momesh,
                                         momesh->morphed->texCoords[i],
                                         momesh->base->texCoords[i],
                                         offsetof(VertexBuffer, texCoords) + i * sizeof(VertexBuffer *) )) return M3G_FALSE;
                }
            }
            else {
                m3gCopy(m3gMapVertexArray(momesh->morphed->texCoords[i]),
                        m3gMapVertexArrayReadOnly(momesh->base->texCoords[i]),
                        momesh->base->texCoords[i]->stride * momesh->base->texCoords[i]->vertexCount );
                m3gUnmapVertexArray(momesh->base->texCoords[i]);
                m3gUnmapVertexArray(momesh->morphed->texCoords[i]);
            }
        }
        else {
            for (j = 0; j < momesh->numTargets; j++) {
                if (momesh->targets[j]->texCoords[i] != NULL) {
                    m3gRaiseError(M3G_INTERFACE(momesh), M3G_INVALID_OPERATION);
                    return M3G_FALSE;
                }           
            }
        }
    }

    momesh->dirtyState = M3G_FALSE;
    return M3G_TRUE;
}

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

/*!
 * \internal
 * \brief Overloaded Node method
 */
static M3Gint m3gMorphingMeshGetBBox(Node *self, AABB *bbox)
{
    MorphingMesh *m = (MorphingMesh*) self;
    
    if (m->base->vertices) {
        if (self->dirtyBits & NODE_BBOX_BIT) {

            /* Blend target and base mesh bounding boxes to estimate the
             * real bounding box of the morphed mesh */
        
            M3Gint i;
            M3Gint vMin, vMax;
            M3Gfloat sumWeights = (M3Gfloat)m->sumWeights / WEIGHT_SCALE;

            /* Get base mesh bounding box */

            m3gGetArrayValueRange(m->base->vertices, &vMin, &vMax);
            if (sumWeights < 0) {
                M3Gint t = vMin;
                vMin = vMax;
                vMax = t;
            }
            
            for (i = 0; i < 3; ++i) {
                bbox->min[i] = (M3Gfloat) vMin;
                bbox->max[i] = (M3Gfloat) vMax;
            }
            
            /* Blend with target bounding boxes */
            
            for (i = 0; i < m->numTargets; i++) {
                if (m->targets[i]->vertices) {
                    M3Gfloat w = m->floatWeights[i];

                    m3gGetArrayValueRange(m->targets[i]->vertices,
                                          &vMin, &vMax);
                    if (w < 0) {
                        M3Gint t = vMin;
                        vMin = vMax;
                        vMax = t;
                    }
                    
                    bbox->min[0] = m3gMadd((M3Gfloat) vMin, w, bbox->min[0]);
                    bbox->min[1] = m3gMadd((M3Gfloat) vMin, w, bbox->min[1]);
                    bbox->min[2] = m3gMadd((M3Gfloat) vMin, w, bbox->min[2]);
                    bbox->max[0] = m3gMadd((M3Gfloat) vMax, w, bbox->max[0]);
                    bbox->max[1] = m3gMadd((M3Gfloat) vMax, w, bbox->max[1]);
                    bbox->max[2] = m3gMadd((M3Gfloat) vMax, w, bbox->max[2]);
                }
            }

            /* Apply scale and bias, and flip the min-max values if
             * the scale is negative */
            {
                const VertexBuffer *vb = m->base;
                for (i = 0; i < 3; ++i) {
                    
                    bbox->min[i] = m3gMadd(bbox->min[i], vb->vertexScale,
                                           vb->vertexBias[i]);
                    bbox->max[i] = m3gMadd(bbox->max[i], vb->vertexScale,
                                           vb->vertexBias[i]);
                    
                    if (bbox->min[i] > bbox->max[i]) {
                        M3Gfloat t = bbox->min[i];
                        bbox->min[i] = bbox->max[i];
                        bbox->max[i] = t;
                    }
                }
            }
            
            m->bbox = *bbox;
        }
        else {
            *bbox = m->bbox;
        }

        /* Estimate a cost of 6 times normal bbox check, as we're
         * dynamically computing the box every time... */
        
        return 6 * VFC_BBOX_COST + VFC_NODE_OVERHEAD;
    }
    return 0; /* no vertices, nothing to render */
}

/*!
 * \internal
 * \brief Overloaded Object3D method.
 *
 * \param self          MorphingMesh object
 * \param property      animation property
 * \param valueSize     size of value array
 * \param value         value array
 */
static void m3gMorphingMeshUpdateProperty(Object *self,
                                          M3Gint property,
                                          M3Gint valueSize,
                                          const M3Gfloat *value)
{
    M3Gint i;
    MorphingMesh *mMesh = (MorphingMesh *)self;
    M3G_VALIDATE_OBJECT(mMesh);
    M3G_ASSERT_PTR(value);
    
    switch (property) {
    case M3G_ANIM_MORPH_WEIGHTS:
        mMesh->sumWeights = WEIGHT_SCALE;
        mMesh->dirtyState = M3G_TRUE;

        for (i = 0; i < mMesh->numTargets; i++) {
            /* Value array can have less or more elements than
               numTargets is. If less, weights after the last
               element are set to 0. If more, weights after
               numTargets are ignored. */
            if (i < valueSize) {
                mMesh->floatWeights[i] = value[i];
                mMesh->weights[i] = m3gRoundToInt(m3gMul(value[i], WEIGHT_SCALE));
                mMesh->sumWeights -= mMesh->weights[i];
            }
            else
                mMesh->weights[i] = 0;
        }
        m3gInvalidateNode((Node*)self, NODE_BBOX_BIT);
        break;
    default:
        m3gNodeUpdateProperty(self, property, valueSize, value);
    }
}

/*!
 * \internal
 * \brief Deletes all internal vertex arrays.
 *
 * \param morphed       VertexBuffer object that contains
 *                      the internal arrays.
 */
static void m3gDeleteClones(VertexBuffer *morphed)
{
    M3Gint i;

    M3G_ASSIGN_REF(morphed->vertices, NULL);
    M3G_ASSIGN_REF(morphed->normals, NULL);
    M3G_ASSIGN_REF(morphed->colors, NULL);

    for (i = 0; i < M3G_NUM_TEXTURE_UNITS; i++) {
        M3G_ASSIGN_REF(morphed->texCoords[i], NULL);
    }
}

/*!
 * \internal
 * \brief Creates all internal vertex arrays.
 *
 * \param vertices      VertexBuffer object that contains
 *                      the original arrays.
 * \param morphed       VertexBuffer object that contains
 *                      the internal arrays.
 * \retval M3G_TRUE     clones created
 * \retval M3G_FALSE    clone creation failed
 */
static M3Gbool m3gCreateClones(VertexBuffer *vertices, VertexBuffer *morphed)
{
    M3Gint i, refCount;

    /* Delete old clone arrays */
    m3gDeleteClones(morphed);

    /* Clone all attributes but arrays, reference count and animtracks. Because
       morphed is an internal buffer it cannot have those attributes copied.
       If they would be copied it would cause a memory leak when morphed array
       is destroyed. */
    refCount = morphed->object.refCount;
    m3gCopy(morphed, vertices, sizeof(VertexBuffer));
    morphed->object.refCount = refCount;
    morphed->object.animTracks = NULL;
    morphed->vertices = NULL;
    morphed->normals = NULL;
    morphed->colors = NULL;
    for (i = 0; i < M3G_NUM_TEXTURE_UNITS; i++) {
        morphed->texCoords[i] = NULL;
    }

    if (vertices->vertices != NULL) {
        M3G_ASSIGN_REF(morphed->vertices, m3gCloneVertexArray(vertices->vertices));
        if (morphed->vertices == NULL) {
            return M3G_FALSE;
        }
    }

    if (vertices->normals != NULL) {
        M3G_ASSIGN_REF(morphed->normals, m3gCloneVertexArray(vertices->normals));
        if (morphed->normals == NULL) {
            return M3G_FALSE;
        }
    }

    if (vertices->colors != NULL) {
        M3G_ASSIGN_REF(morphed->colors, m3gCloneVertexArray(vertices->colors));
        if (morphed->colors == NULL) {
            return M3G_FALSE;
        }
    }

    for (i = 0; i < M3G_NUM_TEXTURE_UNITS; i++) {
        if (vertices->texCoords[i] != NULL) {
            M3G_ASSIGN_REF(morphed->texCoords[i], m3gCloneVertexArray(vertices->texCoords[i]));
            if (morphed->texCoords[i] == NULL) {
                return M3G_FALSE;
            }
        }   
    }

    return M3G_TRUE;
}

/*!
 * \internal
 * \brief Overloaded Object3D method.
 *
 * \param self MorphingMesh object
 * \param references array of reference objects
 * \return number of references
 */
static M3Gint m3gMorphingMeshDoGetReferences(Object *self, Object **references)
{
    MorphingMesh *mmesh = (MorphingMesh *)self;
    M3Gint i, num = m3gMeshDoGetReferences(self, references);
    for (i = 0; i < mmesh->numTargets; i++) {
        if (mmesh->targets[i] != NULL) {
            if (references != NULL)
                references[num] = (Object *)mmesh->targets[i];
            num++;
        }
    }
    return num;
}

/*!
 * \internal
 * \brief Overloaded Object3D method.
 */
static Object *m3gMorphingMeshFindID(Object *self, M3Gint userID)
{
    int i;
    MorphingMesh *mmesh = (MorphingMesh *)self;
    Object *found = m3gMeshFindID(self, userID);
    
    for (i = 0; !found && i < mmesh->numTargets; ++i) {
        if (mmesh->targets[i] != NULL) {
            found = m3gFindID((Object*) mmesh->targets[i], userID);
        }
    }
    return found;
}

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

    /* Create the clone object */
    
    clone = (MorphingMesh*) m3gCreateMorphingMesh(originalObj->interface,
                                                  original->mesh.vertexBuffer,
                                                  original->targets,
                                                  original->mesh.indexBuffers,
                                                  original->mesh.appearances,
                                                  original->mesh.trianglePatchCount,
                                                  original->numTargets);
    if (!clone) {
        return M3G_FALSE;
    }
    *cloneObj = (Object *)clone;

    /* Duplicate base class data and if that succeeds, our own */
    
    if (m3gMeshDuplicate(originalObj, cloneObj, pairs, numPairs)) {
        m3gSetWeights(clone, original->floatWeights, original->numTargets);
        return M3G_TRUE;
    }
    else {
        return M3G_FALSE;
    }
}

/*!
 * \internal
 * \brief Overloaded Node method
 */
static M3Gbool m3gMorphingMeshValidate(Node *self, M3Gbitmask stateBits, M3Gint scope)
{
    MorphingMesh *mesh = (MorphingMesh*) self;
    AABB bbox;
    int i;
    M3G_BEGIN_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_VFC_UPDATE);

    /* Always compute a new bounding box for morphing meshes --
     * otherwise we would have to maintain timestamps for all source
     * vertex buffers */
    
    m3gMorphingMeshGetBBox(self, &bbox);

    /* Invalidate enclosing bounding boxes if new box is larger */
    
    for (i = 0; i < 3; ++i) {
        if (bbox.min[i] < mesh->bbox.min[i] ||
            bbox.max[i] > mesh->bbox.max[i]) {
            m3gInvalidateNode(self, NODE_BBOX_BIT);
            break;
        }
    }
    mesh->bbox = bbox;
    
    M3G_END_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_VFC_UPDATE);
    return m3gMeshValidate(self, stateBits, scope);
}

/*!
 * \internal
 * \brief Initializes a MorphingMesh object. See specification
 * for default values.
 *
 * \param m3g                   M3G interface
 * \param momesh                MorphingMesh object
 * \param hVertices             VertexBuffer object
 * \param hTargets              array of morph target VertexBuffer objects
 * \param hTriangles            array of IndexBuffer objects
 * \param hAppearances          array of Appearance objects
 * \param trianglePatchCount    number of submeshes
 * \param targetCount           number of morph targets
 * \retval                      M3G_TRUE success
 * \retval                      M3G_FALSE failed
 */
static M3Gbool m3gInitMorphingMesh( Interface *m3g,
                                    MorphingMesh *momesh,
                                    M3GVertexBuffer hVertices,
                                    M3GVertexBuffer *hTargets,
                                    M3GIndexBuffer *hTriangles,
                                    M3GAppearance *hAppearances,
                                    M3Gint trianglePatchCount,
                                    M3Gint targetCount )
{
    M3Gint i;

    VertexBuffer *morphed;

    /* Check target validities */
    for (i = 0; i < targetCount; i++) {
        if (hTargets[i] == NULL) {
            m3gRaiseError(m3g, M3G_NULL_POINTER);
            return M3G_FALSE;
        }
    }

    /* Create morphed vertex buffer */
    morphed = (VertexBuffer *)m3gCreateVertexBuffer(m3g);
    if (morphed == NULL) {
        return M3G_FALSE;
    }

    M3G_ASSIGN_REF(momesh->morphed, morphed);

    /* Create initial vertex array clones */
    if (!m3gCreateClones(hVertices, morphed)) {
        M3G_ASSIGN_REF(momesh->morphed, NULL);
        return M3G_FALSE;
    }

    /* MorphingMesh is derived from Mesh */
    if (!m3gInitMesh(m3g, &momesh->mesh,
                     hVertices, hTriangles, hAppearances,
                     trianglePatchCount,
                     M3G_CLASS_MORPHING_MESH)) {
        M3G_ASSIGN_REF(momesh->morphed, NULL);
        return M3G_FALSE;        
    }

    momesh->targets = m3gAllocZ(m3g, sizeof(VertexBuffer *) * targetCount);
    momesh->weights = m3gAllocZ(m3g, sizeof(M3Gint) * targetCount);
    momesh->floatWeights = m3gAllocZ(m3g, sizeof(M3Gfloat) * targetCount);

    /* Check for errors */
    if (!momesh->targets || !momesh->weights || !momesh->floatWeights) {
        m3gDestroyMesh((Object *) &momesh->mesh); /* already init'd above */
        m3gFree(m3g, momesh->targets);
        m3gFree(m3g, momesh->weights);
        m3gFree(m3g, momesh->floatWeights);
        M3G_ASSIGN_REF(momesh->morphed, NULL);
        return M3G_FALSE;    
    }

    /* Assign references */
    for (i = 0; i < targetCount; i++) {
        M3G_ASSIGN_REF(momesh->targets[i], hTargets[i]);
    }
    
    momesh->base = hVertices;
    momesh->numTargets = targetCount;
    momesh->sumWeights = WEIGHT_SCALE;
    momesh->dirtyState = M3G_TRUE;
    momesh->cloneArrayMask = m3gGetArrayMask(hVertices);

    return M3G_TRUE;
}

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

static const NodeVFTable m3gvf_MorphingMesh = {
    {
        {
            m3gMeshApplyAnimation,
            m3gMorphingMeshIsCompatible,
            m3gMorphingMeshUpdateProperty,
            m3gMorphingMeshDoGetReferences,
            m3gMorphingMeshFindID,
            m3gMorphingMeshDuplicate,
            m3gDestroyMorphingMesh
        }
    },
    m3gNodeAlign,
    m3gMorphingMeshDoRender,
    m3gMorphingMeshGetBBox,
    m3gMorphingMeshRayIntersect,
    m3gMorphingMeshSetupRender,
    m3gNodeUpdateDuplicateReferences,
    m3gMorphingMeshValidate
};


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

/*!
 * \brief Creates a MorphingMesh object.
 *
 * \param interface             M3G interface
 * \param hVertices             VertexBuffer object
 * \param hTargets              array of morph target VertexBuffer objects
 * \param hTriangles            array of IndexBuffer objects
 * \param hAppearances          array of Appearance objects
 * \param trianglePatchCount    number of submeshes
 * \param targetCount           number of morph targets
 * \retval Mesh new MorphingMesh object
 * \retval NULL MorphingMesh creating failed
 */
M3G_API M3GMorphingMesh m3gCreateMorphingMesh(M3GInterface interface,
                                              M3GVertexBuffer hVertices,
                                              M3GVertexBuffer *hTargets,
                                              M3GIndexBuffer *hTriangles,
                                              M3GAppearance *hAppearances,
                                              M3Gint trianglePatchCount,
                                              M3Gint targetCount)
{
    Interface *m3g = (Interface *) interface;
    M3G_VALIDATE_INTERFACE(m3g);

    {
        MorphingMesh *momesh = m3gAllocZ(m3g, sizeof(MorphingMesh));
    
        if (momesh != NULL) {
            if (!m3gInitMorphingMesh(   m3g,
                                        momesh,
                                        hVertices, hTargets,
                                        hTriangles, hAppearances,
                                        trianglePatchCount, targetCount)) {
                m3gFree(m3g, momesh);
                return NULL;
            }
        }

        return (M3GMorphingMesh)momesh;
    }
}

/*!
 * \brief Set morph weights.
 *
 * \param handle                MorphingMesh object
 * \param weights               array of weights
 * \param numWeights            number of weights in array
 */
M3G_API void m3gSetWeights(M3GMorphingMesh handle,
                           M3Gfloat *weights,
                           M3Gint numWeights)
{
    MorphingMesh *momesh = (MorphingMesh *)handle;
    M3G_VALIDATE_OBJECT(momesh);
    
    if (numWeights >= momesh->numTargets) {
        M3Gint i;
        momesh->dirtyState = M3G_TRUE;
        momesh->sumWeights = WEIGHT_SCALE;
        for (i = 0; i < momesh->numTargets; i++) {
            momesh->floatWeights[i] = weights[i];
            momesh->weights[i] = m3gRoundToInt(m3gMul(weights[i], WEIGHT_SCALE));
            momesh->sumWeights -= momesh->weights[i];
        }
        m3gInvalidateNode((Node*)momesh, NODE_BBOX_BIT);
    }
    else {
        m3gRaiseError(M3G_INTERFACE(momesh), M3G_INVALID_VALUE);
    }
}

/*!
 * \brief Get morph weights.
 *
 * \param handle                MorphingMesh object
 * \param weights               array of weights to fill in
 * \param numWeights            max number of weights in array
 */
M3G_API void m3gGetWeights(M3GMorphingMesh handle,
                           M3Gfloat *weights,
                           M3Gint numWeights)
{
    MorphingMesh *momesh = (MorphingMesh *)handle;
    M3G_VALIDATE_OBJECT(momesh);
    
    if (numWeights >= momesh->numTargets) {
        M3Gint i;
        for (i = 0; i < momesh->numTargets; i++) {
            weights[i] = momesh->floatWeights[i];
        }
    }
    else {
        m3gRaiseError(M3G_INTERFACE(momesh), M3G_INVALID_VALUE);
    }
}

/*!
 * \brief Get morph target.
 *
 * \param handle                MorphingMesh object
 * \param idx                   target index
 * \return                      VertexBuffer object
 */
M3G_API M3GVertexBuffer m3gGetMorphTarget(M3GMorphingMesh handle, M3Gint idx)
{
    MorphingMesh *momesh = (MorphingMesh *)handle;
    M3G_VALIDATE_OBJECT(momesh);

    if (idx < 0 || idx >= momesh->numTargets) {
        m3gRaiseError(M3G_INTERFACE(momesh), M3G_INVALID_INDEX);
        return NULL;
    }

    return momesh->targets[idx];
}

/*!
 * \brief Get morph target count.
 *
 * \param handle                MorphingMesh object
 * \return                      number of morph targets
 */
M3G_API M3Gint m3gGetMorphTargetCount(M3GMorphingMesh handle)
{
    MorphingMesh *momesh = (MorphingMesh *)handle;
    M3G_VALIDATE_OBJECT(momesh);

    return momesh->numTargets;
}