/*
 *  dmphgeom.c - delta morph geometry data and morph targets
 */

/*===========================================================================*
 *--- Include files ---------------------------------------------------------*
 *===========================================================================*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "rpplugin.h"
#include <rpdbgerr.h>
#include <rwcore.h>
#include <rpworld.h>

#include "rpdmorph.h"
#include "dmphgeom.h"

#if (!defined(DOXYGEN))
static const char rcsid[] __RWUNUSED__ =
   "@@@@(#)$Id: dmphgeom.c,v 1.7 2001/10/01 16:44:36 jamesa Exp $";
#endif /* (!defined(DOXYGEN)) */

/*===========================================================================*
 *--- Types -----------------------------------------------------------------*
 *===========================================================================*/

/*--- DMorphTargetBinary -------------------------------------------------*/
typedef struct DMorphTargetBinary DMorphTargetBinary;
struct DMorphTargetBinary
{
    RwUInt32           flags;           /* Type RpGeometryFlag */
    RwUInt32           lockFlags;       /* Type RpGeometryLockMode */
    RwUInt32           numCodeElements;
    RwUInt32           numDataElements;
};

/*===========================================================================*
 *--- Global variables ------------------------------------------------------*
 *===========================================================================*/

RwInt32 rpDMorphGeometryDataOffset = 0;

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

/*--- DMorphGeometryCtor ---------------------------------------------------
 */
static void *
DMorphGeometryCtor( void *object,
                    RwInt32 offset __RWUNUSED__,
                    RwInt32 size   __RWUNUSED__ )
{
    RWFUNCTION(RWSTRING("DMorphGeometryCtor"));
    RWASSERT(object);

    *RPDMORPHGEOMETRYGETDATA(object) = (rpDMorphGeometryData *)NULL;

    RWRETURN(object);
}

/*--- DMorphGeometryDtor ---------------------------------------------------
 */
static void *
DMorphGeometryDtor( void *object,
                    RwInt32 offset __RWUNUSED__,
                    RwInt32 size   __RWUNUSED__ )
{
    rpDMorphGeometryData  *geometryData;

    RWFUNCTION(RWSTRING("DMorphGeometryDtor"));
    RWASSERT(object);

    geometryData = *RPDMORPHGEOMETRYGETDATA(object);
    if( geometryData )
    {
        if( geometryData->numDMorphTargets > 0 )
        {
            RpDMorphGeometryDestroyDMorphTargets((RpGeometry *)object);
        }
    }

    *RPDMORPHGEOMETRYGETDATA(object) = (rpDMorphGeometryData *)NULL;

    RWRETURN(object);
}

/*--- DMorphGeometryCopy ---------------------------------------------------
 */
static void *
DMorphGeometryCopy( void *dstObject,
                    const void *srcObject __RWUNUSEDRELEASE__,
                    RwInt32 offset __RWUNUSED__,
                    RwInt32 size   __RWUNUSED__ )
{
    RWFUNCTION(RWSTRING("DMorphGeometryCopy"));
    RWASSERT(dstObject);
    RWASSERT(srcObject);

    /* A geometry can not be copied so leave this empty and there ain't
     * nothing to do anyway */

    RWRETURN(dstObject);
}

/*--- DMorphGeometryStreamRead ---------------------------------------------
 *
 *  TODO: DMorphGeometryStreamRead and Write not endian safe
 */
static RwStream *
DMorphGeometryStreamRead( RwStream *stream,
                          RwInt32 binaryLength   __RWUNUSED__,
                          void *object,
                          RwInt32 offsetInObject __RWUNUSED__,
                          RwInt32 sizeInObject   __RWUNUSED__ )
{
    rpDMorphGeometryData *geometryData;

    RpGeometry *geometry;

    RwUInt32   numDMorphTargets;
    RwUInt32   i;

    RWFUNCTION(RWSTRING("DMorphGeometryStreamRead"));
    RWASSERT(stream);
    RWASSERT(object);

    /* Grab the geometry and extension data... */
    geometry = (RpGeometry *)object;

    /* rpDMorphGeometryData: numDMorphTargets */
    RwStreamReadInt32( stream,
                       (RwInt32 *)&numDMorphTargets,
                       sizeof(RwUInt32) );

    RpDMorphGeometryCreateDMorphTargets( geometry,
                                         numDMorphTargets );

    /* Grab the geometry extension data... */
    geometryData = *RPDMORPHGEOMETRYGETDATA(geometry);
    RWASSERT(geometryData);

    for( i = 0; i < geometryData->numDMorphTargets; i++ )
    {
        RpDMorphTarget     *dMorphTarget;
        DMorphTargetBinary binary;

        RwUInt32       length;
        RwUInt32       size;
        /*RpGeometryFlag flags;*/
        RwUInt32       flags;

        void *memory;

        /* Make things a little simpler. */
        dMorphTarget = &(geometryData->dMorphTargets[i]);

        /* DMorphTarget: length of name */
        RwStreamReadInt32(stream,
                          (RwInt32 *)&length,
                          sizeof(RwUInt32) );

        if( 0 != length )
        {
            dMorphTarget->name = (RwChar *)RwMalloc(length * sizeof(RwChar));
            RWASSERT(dMorphTarget->name);

            /* DMorphTarget: name */
            RwStreamRead( stream,
                          (void *)dMorphTarget->name,
                          length );
        }
        else
        {
            dMorphTarget->name = (RwChar *)NULL;
        }

        /* DMorphTarget: binary data */
        RwStreamReadInt32(stream,
                          (RwInt32 *)&binary,
                          sizeof(DMorphTargetBinary) );

        dMorphTarget->flags =               binary.flags;
        dMorphTarget->lockFlags =           binary.lockFlags;
        dMorphTarget->rle.numCodeElements = binary.numCodeElements;
        dMorphTarget->rle.numDataElements = binary.numDataElements;

        /* DMorphTraget: RLE: codeElements */
        dMorphTarget->rle.code = (RwUInt8 *)
            RwMalloc( dMorphTarget->rle.numCodeElements *
                      sizeof(RwUInt8) );
        RWASSERT(dMorphTarget->rle.code);

        RwStreamRead( stream,
                      (void *)(dMorphTarget->rle.code),
                      dMorphTarget->rle.numCodeElements * sizeof(RwUInt8) );

        /* DMorphTarget: RLE: dataElements */
        flags = dMorphTarget->flags;

        size = (((0!=(flags & rpGEOMETRYPOSITIONS)) * sizeof(RwV3d))
               +((0!=(flags & rpGEOMETRYNORMALS))   * sizeof(RwV3d))
               +((0!=(flags & rpGEOMETRYPRELIT))    * sizeof(RwRGBA))
               +((0!=(flags & rpGEOMETRYTEXTURED))  * sizeof(RwTexCoords)))
               * (dMorphTarget->rle.numDataElements);

        dMorphTarget->rle.data = (rpDMorphRLEDelta *)RwMalloc( size );
        RWASSERT(dMorphTarget->rle.data);

        RwStreamReadReal( stream,
                          (RwReal *)(dMorphTarget->rle.data),
                          size );

        /* DMorphTarget: Bounding Sphere */
        RwStreamReadReal(stream,
                         (RwReal *)(&(dMorphTarget->boundingSphere)),
                         sizeof(RwSphere));

        /* Assign DMorphGeometry pointers. */
        memory = (void *)dMorphTarget->rle.data;
        if(flags & rpGEOMETRYPOSITIONS)
        {
            dMorphTarget->vertices = (RwV3d *)memory;
            memory = (void *)((RwV3d *)memory
                              + dMorphTarget->rle.numDataElements);
        }
        if(flags & rpGEOMETRYNORMALS)
        {
            dMorphTarget->normals = (RwV3d *)memory;
            memory = (void *)((RwV3d *)memory
                              + dMorphTarget->rle.numDataElements);
        }
        if(flags & rpGEOMETRYPRELIT)
        {
            dMorphTarget->preLightColors = (RwRGBA *)memory;
            memory = (void *)((RwRGBA *)memory
                              + dMorphTarget->rle.numDataElements);
        }
        if(flags & rpGEOMETRYTEXTURED)
        {
            dMorphTarget->texCoords = (RwTexCoords *)memory;
        }

        /* Extra data */
        dMorphTarget->geometry = geometry;
    }

    /* OK, we're all done */
    RWRETURN(stream);
}

/*--- DMorphGeometrySteamWrite ---------------------------------------------
 */
static RwStream *
DMorphGeometryStreamWrite( RwStream *stream,
                           RwInt32 binaryLength   __RWUNUSED__,
                           const void *object,
                           RwInt32 offsetInObject __RWUNUSED__,
                           RwInt32 sizeInObject   __RWUNUSED__ )
{
    const rpDMorphGeometryData *geometryData;

    const RpGeometry *geometry;

    RwUInt32   i;

    RWFUNCTION(RWSTRING("DMorphGeometryStreamWrite"));
    RWASSERT(stream);
    RWASSERT(object);

    /* Grab the geometry and extension data... */
    geometry = (const RpGeometry *)object;
    geometryData = *RPDMORPHGEOMETRYGETCONSTDATA(geometry);
    RWASSERT(geometryData);

    /* Lets store the geometry data. */
    RwStreamWrite( stream,
                   (const RwInt32 *)&(geometryData->numDMorphTargets),
                   sizeof(RwUInt32));

    /* Lets store the dmorphtarget data. */
    for( i = 0; i < geometryData->numDMorphTargets; i++ )
    {
        const RpDMorphTarget     *dMorphTarget;
              DMorphTargetBinary binary;

        /*RpGeometryFlag flags;*/
        RwUInt32       flags;
        RwUInt32       size;
        RwUInt32       length;

        /* Make things a little simpler. */
        dMorphTarget = &(geometryData->dMorphTargets[i]);

        binary.flags            = dMorphTarget->flags;
        binary.lockFlags        = dMorphTarget->lockFlags;
        binary.numCodeElements  = dMorphTarget->rle.numCodeElements;
        binary.numDataElements  = dMorphTarget->rle.numDataElements;

        flags = dMorphTarget->flags;
        size = (((0!=(flags & rpGEOMETRYPOSITIONS)) * sizeof(RwV3d))
               +((0!=(flags & rpGEOMETRYNORMALS))   * sizeof(RwV3d))
               +((0!=(flags & rpGEOMETRYPRELIT))    * sizeof(RwRGBA))
               +((0!=(flags & rpGEOMETRYTEXTURED))  * sizeof(RwTexCoords)))
               * (dMorphTarget->rle.numDataElements);

        length = (NULL != dMorphTarget->name)
               ? rwstrlen(dMorphTarget->name) + 1 : 0;

        /* DMorphTarget: name length */
        RwStreamWrite( stream,
                       (void *)(&length),
                       sizeof(RwUInt32) );

        if( 0 != length )
        {
            /* DMorphTarget: name */
            RwStreamWrite( stream,
                           (void *)(dMorphTarget->name),
                           length );
        }

        /* DMorphTarget: binary */
        RwStreamWrite( stream,
                       (void *)(&binary),
                       sizeof(DMorphTargetBinary) );

        /* DMorphTraget: RLE: codeElements */
        RwStreamWrite( stream,
                       (void *)(dMorphTarget->rle.code),
                       dMorphTarget->rle.numCodeElements * sizeof(RwUInt8) );

        /* DMorphTraget: RLE: dataElements */
        RwStreamWrite( stream,
                       (void *)(dMorphTarget->rle.data),
                       size );

        /* DMorphTarget: Bounding Sphere */
        RwStreamWrite( stream,
                       (const RwReal *)(&(dMorphTarget->boundingSphere)),
                       sizeof(RwSphere));
    }

    /* OK, we're all done */
    RWRETURN(stream);
}

/*--- DMorphGeometryStreamSize ---------------------------------------------
 */
static RwInt32
DMorphGeometryStreamSize( const void *object,
                          RwInt32 offsetInObject __RWUNUSED__,
                          RwInt32 sizeInObject   __RWUNUSED__ )
{
    const rpDMorphGeometryData *geometryData;

    const RpGeometry *geometry;

    RwUInt32 size;
    RwUInt32 i;

    RWFUNCTION(RWSTRING("DMorphGeometryStreamSize"));

    /* Set the default size. */
    size = 0;

    /* Grab the geometry and extension data... */
    geometry = (const RpGeometry *)object;
    geometryData = *RPDMORPHGEOMETRYGETCONSTDATA(geometry);

    /* Does the geometry have any dmorphing data? */
    if( NULL != geometryData )
    {
        /* GeometryData: size */
        size += sizeof(RwUInt32);

        /* DMorphTarget: size */
        for( i = 0; i < geometryData->numDMorphTargets; i++ )
        {
            const RpDMorphTarget *dMorphTarget;

            /*RpGeometryFlag flags;*/
            RwUInt32 flags;

            dMorphTarget = &(geometryData->dMorphTargets[i]);
            flags = dMorphTarget->flags;

            /* DMorphTarget name length: size */
            size += sizeof(RwUInt32);

            if( NULL != dMorphTarget->name )
            {
                /* DMorphTarget name: size */
                size += rwstrlen(dMorphTarget->name) + 1;
            }

            /* DMorphTarget binary: size */
            size += sizeof(DMorphTargetBinary);

            /* DMorphTraget RLE codeElements: size */
            size += dMorphTarget->rle.numCodeElements;

            /* DMorphTarget RLE dataElemeents: size */
            size += (((0!=(flags & rpGEOMETRYPOSITIONS))* sizeof(RwV3d))
                    +((0!=(flags & rpGEOMETRYNORMALS))  * sizeof(RwV3d))
                    +((0!=(flags & rpGEOMETRYPRELIT))   * sizeof(RwRGBA))
                    +((0!=(flags & rpGEOMETRYTEXTURED)) * sizeof(RwTexCoords)))
                    *(dMorphTarget->rle.numDataElements);

            /* DMorphTarget BoundingSphere: size */
            size += sizeof(RwSphere);
        }
    }

    /* OK, we're all done */
    RWRETURN(size);
}

/*--- DMorphTargetCalcBoundingSphere ---------------------------------------
 * Copied from old morph system - dodgy in my opinion. (JA)
 */
static RwSphere *
DMorphTargetCalcBoundingSphere( const RwV3d *vertices,
                                RwUInt32 numVerts,
                                RwSphere *boundingSphere )
{
    RwUInt32    i;
    RwSphere    sphere;
    RwReal      sphere_radius = (RwReal)(0);
    RwBBox      boundBox;

    RWFUNCTION(RWSTRING("DMorphTargetCalcBoundingSphere"));
    RWASSERT(vertices);
    RWASSERT(boundingSphere);

    /* Find the median (sort of) by doing a bounding box,
     * then using the center of the box.
     */
    RwBBoxCalculate(&boundBox, vertices, numVerts);
    RwV3dAdd(&sphere.center, &boundBox.inf, &boundBox.sup);
    RwV3dScale(&sphere.center, &sphere.center, (RwReal) (0.5));

    /* Find the radius (we do this in square space). */
    for (i = 0; i < numVerts; i++)
    {
        RwReal              nDist;
        RwV3d               vTmp;

        RwV3dSub(&vTmp, &vertices[i], &sphere.center);
        nDist = RwV3dDotProduct(&vTmp, &vTmp);

        if (nDist > sphere_radius)
        {
            sphere_radius = nDist;
        }
    }

    /* Now do the root */
    if (sphere_radius > (RwReal) (0))
    {
        rwSqrtMacro(sphere_radius, sphere_radius);
    }

    /* Add 0.1% for numerical inaccuracy */
    sphere.radius = sphere_radius * ((RwReal)(1.001));

    /* Save off result */
    (*boundingSphere) = sphere;

    /* All done */
    RWRETURN(boundingSphere);
}

/*--- DMorphGenerateRwV3dDelta ---------------------------------------------
 */
static rpDMorphRLEDelta *
DMorphGenerateV3dDelta( const rpDMorphRLEData  *baseData,
                        const rpDMorphRLEData  *data,
                              rpDMorphRLEDelta *delta )
{
    const RwV3d *baseDataV3d;
    const RwV3d *dataV3d;
          RwV3d *deltaV3d;

    RWFUNCTION(RWSTRING("DMorphGenerateV3dDelta"));
    RWASSERT(baseData);
    RWASSERT(data);
    RWASSERT(delta);

    baseDataV3d = (const RwV3d *)baseData;
    dataV3d     = (const RwV3d *)data;
    deltaV3d    = (RwV3d *)delta;

    if( ( baseDataV3d->z == dataV3d->z ) &&
        ( baseDataV3d->y == dataV3d->y ) &&
        ( baseDataV3d->x == dataV3d->x ) )
    {
        /* Then base and data are the same, i.e. delta ~ zero. */
        deltaV3d->z = 0;
        deltaV3d->y = 0;
        deltaV3d->x = 0;

        RWRETURN(NULL);
    }
    else
    {
        /* Set the delta and return it. */
        deltaV3d->z = dataV3d->z - baseDataV3d->z;
        deltaV3d->y = dataV3d->y - baseDataV3d->y;
        deltaV3d->x = dataV3d->x - baseDataV3d->x;

        RWRETURN(delta);
    }
}

/*--- DMorphGenerateRwRGBADelta --------------------------------------------
 */
static rpDMorphRLEDelta *
DMorphGenerateRGBADelta( const rpDMorphRLEData  *baseData,
                         const rpDMorphRLEData  *data,
                               rpDMorphRLEDelta *delta )
{
    const RwRGBA *baseDataRGBA;
    const RwRGBA *dataRGBA;
          RwRGBA *deltaRGBA;

    RWFUNCTION(RWSTRING("DMorphGenerateRGBADelta"));
    RWASSERT(baseData);
    RWASSERT(data);
    RWASSERT(delta);

    baseDataRGBA = (const RwRGBA *)baseData;
    dataRGBA     = (const RwRGBA *)data;
    deltaRGBA    = (RwRGBA *)delta;

    if( ( baseDataRGBA->alpha == dataRGBA->alpha ) &&
        ( baseDataRGBA->blue  == dataRGBA->blue  ) &&
        ( baseDataRGBA->green == dataRGBA->green ) &&
        ( baseDataRGBA->red   == dataRGBA->red   ) )
    {
        /* Then base and data are the same, i.e. delta ~ zero. */
        deltaRGBA->alpha = 0;
        deltaRGBA->blue  = 0;
        deltaRGBA->green = 0;
        deltaRGBA->red   = 0;

        RWRETURN(NULL);
    }
    else
    {
        /* Set the delta and return it. */
        deltaRGBA->alpha = dataRGBA->alpha - baseDataRGBA->alpha;
        deltaRGBA->blue  = dataRGBA->blue  - baseDataRGBA->blue;
        deltaRGBA->green = dataRGBA->green - baseDataRGBA->green;
        deltaRGBA->red   = dataRGBA->red   - baseDataRGBA->red;

        RWRETURN(delta);
    }
}

/*--- DMorphGenerateRwTexCoordsDelta ---------------------------------------
 */
static rpDMorphRLEDelta *
DMorphGenerateTexCoordsDelta( const rpDMorphRLEData  *baseData,
                              const rpDMorphRLEData  *data,
                                    rpDMorphRLEDelta *delta )
{
    const RwTexCoords *baseDataTexCoords;
    const RwTexCoords *dataTexCoords;
          RwTexCoords *deltaTexCoords;

    RWFUNCTION(RWSTRING("DMorphGenerateTexCoordsDelta"));
    RWASSERT(baseData);
    RWASSERT(data);
    RWASSERT(delta);

    baseDataTexCoords = (const RwTexCoords *)baseData;
    dataTexCoords     = (const RwTexCoords *)data;
    deltaTexCoords    = (RwTexCoords *)delta;

    if( ( baseDataTexCoords->v == dataTexCoords->v ) &&
        ( baseDataTexCoords->u == dataTexCoords->u ) )
    {
        /* Then base and data are the same, i.e. delta ~ zero. */
        deltaTexCoords->v = 0;
        deltaTexCoords->u = 0;

        RWRETURN(NULL);
    }
    else
    {
        /* Set the delta and return it. */
        deltaTexCoords->v = dataTexCoords->v - baseDataTexCoords->v;
        deltaTexCoords->u = dataTexCoords->u - baseDataTexCoords->u;

        RWRETURN(delta);
    }
}

/*--- DMorphRLEGenerate ----------------------------------------------------
 */
static RwUInt32
DMorphRLEGenerate( const RwV3d          *baseVertices,
                   const RwV3d          *vertices,
                   const RwV3d          *baseNormals,
                   const RwV3d          *normals,
                   const RwRGBA         *basePreLightColors,
                   const RwRGBA         *preLightColors,
                   const RwTexCoords    *baseTexCoords,
                   const RwTexCoords    *texCoords,
                         RpGeometryFlag flags,
                         rpDMorphRLE   *rle )
{
    RwUInt8  *code;

    RWFUNCTION(RWSTRING("DMorphRLEGenerate"));
    RWASSERT(rle);

    /* Lets check we don't compress anything naughty. */
#if defined(RWDEBUG)
    if(flags & rpGEOMETRYPOSITIONS)
    {
        RWASSERT(baseVertices);
        RWASSERT(vertices);
    }
    if(flags & rpGEOMETRYNORMALS)
    {
        RWASSERT(baseNormals);
        RWASSERT(normals);
    }
    if(flags & rpGEOMETRYPRELIT)
    {
        RWASSERT(basePreLightColors);
        RWASSERT(preLightColors);
    }
    if(flags & rpGEOMETRYTEXTURED)
    {
        RWASSERT(baseTexCoords);
        RWASSERT(texCoords);
    }
#endif /* defined(RWDEBUG) */

    /* Malloc sufficent space for an evil code. */
    code = (RwUInt8 *)RwMalloc(sizeof(RwUInt8) * rle->numCodeElements);
    if( NULL != code )
    {
        RwV3d       deltaVertices;
        RwV3d       deltaNormals;
        RwRGBA      deltaPreLightColors;
        RwTexCoords deltaTexCoords;

        RwUInt32 i, k;
        RwUInt8  j;

        RwBool   delta, pattern, search;

        /* Assume we are searching for deltas for now. */
        pattern = TRUE;

        /* Lets make sure we process all the elements. */
        for( i = 0, j = 0, k = 0; i < rle->numCodeElements; k++, j = 0 )
        {
            /* Lets search .... */
            search = TRUE;
            /* ... but not forgetting to restrict our lists to 127.
             *        (doubly not forgetting that 0-126 is 127 elememts)
             */

            /* We search until j gets to 126, ie a full packet ...
             * or our pattern changes.
             */
            while(search)
            {
                /* Assume we don't have a delta until prompted otherwise. */
                delta = FALSE;

                if(flags & rpGEOMETRYPOSITIONS)
                {
                    delta |=
                        ( DMorphGenerateV3dDelta( &baseVertices[i],
                                                  &vertices[i],
                                                  &deltaVertices )
                          != NULL );
                }
                if(flags & rpGEOMETRYNORMALS)
                {
                    delta |=
                        ( DMorphGenerateV3dDelta( &baseNormals[i],
                                                  &normals[i],
                                                  &deltaNormals )
                         != NULL );
                }
                if(flags & rpGEOMETRYPRELIT)
                {
                    delta |=
                        ( DMorphGenerateRGBADelta( &basePreLightColors[i],
                                                   &preLightColors[i],
                                                   &deltaPreLightColors )
                          != NULL );
                }
                if(flags & rpGEOMETRYTEXTURED)
                {
                    delta |=
                        ( DMorphGenerateTexCoordsDelta( &baseTexCoords[i],
                                                        &texCoords[i],
                                                        &deltaTexCoords )
                          != NULL );
                }

                /* Does the delta match our pattern? */
                if(delta == pattern)
                {
                    /* Then increase our packet size. */
                    j++;

                    /* Is our packet full? */
                    if(j == rpDMORPHRLEMAXPACKETSIZE)
                    {
                        /* Then we've reached the end of this chain. */
                        search = FALSE;
                    }

                    /* Either way - we've finished with the present element. */
                    i++;

                    /* Increase our number of data elements. */
                    if( delta )
                    {
                        rle->numDataElements++;
                    }
                }
                else
                {
                    /* We've reached the end of this chain. */
                    search = FALSE;
                }

                /* Check we haven't run out of elements. */
                if(i == rle->numCodeElements)
                {
                    break;
                }
            }

            /* Ok. We're generated a little packet from the elements. */
            code[k] = (((RwUInt8)pattern) << 7) + j;

            /* But maybe we're looking for for something new now. */
            pattern = delta;
        }

        /* Hopefully we now have a code full of info. */
        rle->code = (RwUInt8 *)RwMalloc(sizeof(RwUInt8) * k);
        if( NULL != rle->code )
        {
            /* Lets make a copy of it. */
            memcpy(rle->code, code, k);

            /* And record the number of actual elements. */
            rle->numCodeElements = k;
        }

        /* Free the original code array as we are finished with it. */
        RwFree(code);
    }

    /* All done */
    RWRETURN(rle->numCodeElements);
}

/*--- DMorphRLEGenerateDelta -----------------------------------------------
 */
static rpDMorphRLEDelta *
DMorphRLEGenerateDelta( rpDMorphRLEData                  *baseData,
                        rpDMorphRLEData                  *data,
                        RwUInt32                       size,
                        rpDMorphRLEGenerateDeltaCallBack delta,
                        rpDMorphRLEDelta                 *memory,
                        rpDMorphRLE                      *rle )
{
    RwUInt32 i, j, l;
    RwUInt8  k;

    RWFUNCTION(RWSTRING("DMorphRLEGenerateDelta"));
    RWASSERT(baseData);
    RWASSERT(data);
    RWASSERT(delta);
    RWASSERT(memory);
    RWASSERT(rle);
    RWASSERT(0 < size);

    /*
     * i: element in the code array
     * j: element in the baseData and data arrays
     * k: loop to process all the deltas for each code delta element
     * l: element in the target delta array
     */
    for( i = 0, j = 0, l = 0; i < rle->numCodeElements; i++ )
    {
        if( (rle->code[i] & 0x80) )
        {
            /* We've got data! */
            for( k = 0; k < (rle->code[i] & 0x7f); k++, j++, l++ )
            {
                delta( (rpDMorphRLEData *) ((char *)baseData + (j * size)),
                       (rpDMorphRLEData *) ((char *)data     + (j * size)),
                       (rpDMorphRLEDelta *)((char *)memory   + (l * size)) );
            }
        }
        else
        {
            j += (rle->code[i] & 0x7f);
        }
    }

    /* All done */
    RWRETURN(memory);
}

/*===========================================================================*
 *--- Plugin Internal Functions ---------------------------------------------*
 *===========================================================================*/

RwBool
_rpDMorphGeometryPluginAttach(void)
{
    RwInt32 offset;

    RWFUNCTION(RWSTRING("_rpDMorphGeometryPluginAttach"));

    /* Register the plugin with the geometry. */
    rpDMorphGeometryDataOffset =
        RpGeometryRegisterPlugin( sizeof(rpDMorphGeometryData *),
                                  rwID_DMORPHPLUGIN,
                                  DMorphGeometryCtor,
                                  DMorphGeometryDtor,
                                  DMorphGeometryCopy);

    if( 0 > rpDMorphGeometryDataOffset )
    {
        RWRETURN(FALSE);
    }

    /* Register the plugin with the geometry streams. */
    offset = RpGeometryRegisterPluginStream( rwID_DMORPHPLUGIN,
                                             DMorphGeometryStreamRead,
                                             DMorphGeometryStreamWrite,
                                             DMorphGeometryStreamSize );
    if ( 0 > offset )
    {
        RWRETURN(FALSE);
    }

    RWRETURN(TRUE);
}

/*===========================================================================*
 *--- Plugin API Functions --------------------------------------------------*
 *===========================================================================*/

/*--- DMorphGeometry DMorphTarget functions ---------------------------------*/

/**
 * \ingroup rpdmorph
 * \ref RpDMorphGeometryCreateDMorphTargets is used to
 * create space for a number of delta morph targets.
 * Once the base geometry has been setup,
 * \ref RpDMorphGeometryAddDMorphTarget should be used to
 * add delta morph targets to the base.
 * When the delta morph targets are finished with they can
 * be removed with \ref RpDMorphGeometryRemoveDMorphTarget
 * and emptied with
 * \ref RpDMorphGeometryDestroyDMorphTargets.
 *
 * The world and delta morph plugins must be attached
 * before using this function. The header file rpdmorph.h
 * is required.
 *
 * \param geometry  Pointer to the base geometry.
 * \param number    The number of delta morph targets to
 *                  create.
 *
 * \return Returns NULL if there is an error, or otherwise
 *         a pointer to the base geometry.
 *
 * \see RpDMorphGeometryDestroyDMorphTargets
 * \see RpDMorphGeometryAddDMorphTarget
 * \see RpDMorphGeometryRemoveDMorphTarget
 */
RpGeometry *
RpDMorphGeometryCreateDMorphTargets( RpGeometry *geometry,
                                     RwUInt32 number )
{
    rpDMorphGeometryData **dataExtension;
    rpDMorphGeometryData *geometryData;
    RpDMorphTarget     *dMorphTargets;

    RwUInt32              size;

    RWAPIFUNCTION(RWSTRING("RpDMorphGeometryCreateDMorphTargets"));
    RWASSERT(rpDMorphModule.numInstances);
    RWASSERT(geometry);
    RWASSERT(0 < number);

    /* Get our geometry extension data. */
    dataExtension = RPDMORPHGEOMETRYGETDATA(geometry);

    /* Create rpDMorphGeometryData. */
    size = sizeof(rpDMorphGeometryData);
    geometryData = *dataExtension = (rpDMorphGeometryData *)RwMalloc(size);
    if(NULL == geometryData)
    {
        RWRETURN((RpGeometry *)NULL);
    }

    /* Setup rpDMorphGeometryData. */
    memset(geometryData, 0, size);
    geometryData->numDMorphTargets = number;

    /* Create DMorphTargets and morph value array */
    size = (sizeof(RwReal) + sizeof(RpDMorphTarget)) * number;
    dMorphTargets = (RpDMorphTarget *)RwMalloc(size);
    if( NULL == dMorphTargets )
    {
        RwFree(geometryData);
        geometryData = (rpDMorphGeometryData *)NULL;

        RWRETURN((RpGeometry *)NULL);
    }

    /* Setup the DMorphTargets and morph values */
    memset(dMorphTargets, 0, size);
    geometryData->dMorphTargets = dMorphTargets;
    geometryData->currValues = (RwReal *) (dMorphTargets + number);

    RWRETURN(geometry);
}

/**
 * \ingroup rpdmorph
 * \ref RpDMorphGeometryDestroyDMorphTargets is used to
 * destroy the base geometry's delta morph targets. This
 * should only be used after a call to
 * \ref RpDMorphGeometryCreateDMorphTargets. Any delta
 * morph targets that have not been removed will be
 * automatically removed.
 *
 * The world and delta morph plugins must be attached
 * before using this function. The header file rpdmorph.h
 * is required.
 *
 * \param geometry  Pointer to the base geometry.
 *
 * \return Returns NULL if there is an error, or otherwise
 *         a pointer to the base geometry.
 *
 * \see RpDMorphGeometryCreateDMorphTargets
 * \see RpDMorphGeometryAddDMorphTarget
 * \see RpDMorphGeometryRemoveDMorphTarget
 */
RpGeometry *
RpDMorphGeometryDestroyDMorphTargets( RpGeometry *geometry )
{
    /* This destroys all the geometry extension data */
    rpDMorphGeometryData **dataExtension;
    rpDMorphGeometryData *geometryData;

    RwUInt32 i;

    RWAPIFUNCTION(RWSTRING("RpDMorphGeometryDestroyDMorphTargets"));
    RWASSERT(rpDMorphModule.numInstances);
    RWASSERT(geometry);

    /* Get our geometry extension data. */
    dataExtension = RPDMORPHGEOMETRYGETDATA(geometry);
    RWASSERT(dataExtension);

    geometryData = *dataExtension;
    RWASSERT(geometryData);
    RWASSERT(geometryData->dMorphTargets);

    /* Free up the DMorphTargets data. */
    for( i=0; i < geometryData->numDMorphTargets; i++ )
    {
        RpDMorphGeometryRemoveDMorphTarget( geometry, i );
    }

    /* Free up the DMorphGeometry data (also morph value array) */
    RwFree(geometryData->dMorphTargets);

    RwFree(geometryData);

    geometryData = (rpDMorphGeometryData *)NULL;

    RWRETURN(geometry);
}

/**
 * \ingroup rpdmorph
 * \ref RpDMorphGeometryAddDMorphTarget is used to add
 * a delta morph target to the base geometry at the
 * specified index.
 *
 * The flags determine what geometry elements the delta
 * morph target will contain. The adding process
 * calculates the delta from the given data and the base.
 * The delta is stored in a compressed format as much of
 * the delta will be identical to the base and hence
 * resolve to a zero delta.
 *
 * The world and delta morph plugins must be attached
 * before using this function. The header file rpdmorph.h
 * is required.
 *
 * \param geometry        Pointer to the base geometry.
 * \param index           Index where the delta should be
 *                        created.
 * \param vertices        Pointer to the delta's vertices
 *                        or NULL if the delta is not
 *                        morphing the positions.
 * \param normals         Pointer to the delta's normals
 *                        or NULL if the delta is not
 *                        morphing the normals.
 * \param preLightColors  Pointer to the delta's prelight
 *                        colors or NULL if the delta is
 *                        not morphing the prelight colors.
 * \param texCoords       Pointer to the delta's texture
 *                        coordinates or NULL if the delta
 *                        is not morphing the texture
 *                        coordinates.
 *
 * \return Returns NULL if there is an error, or otherwise
 *         a pointer to the base geometry.
 *
 * \see RpDMorphGeometryRemoveDMorphTarget
 * \see RpDMorphGeometryDestroyDMorphTargets
 * \see RpDMorphGeometryCreateDMorphTargets
 */
RpGeometry *
RpDMorphGeometryAddDMorphTarget( RpGeometry     *geometry,
                                 RwUInt32       index,
                                 RwV3d          *vertices,
                                 RwV3d          *normals,
                                 RwRGBA         *preLightColors,
                                 RwTexCoords    *texCoords,
                                 RpGeometryFlag flags )
{
    rpDMorphGeometryData *geometryData;
    RpDMorphTarget     *dMorphTarget;

    RwV3d       *baseVertices;
    RwV3d       *baseNormals;
    RwTexCoords *baseTexCoords;
    RwRGBA      *basePreLightColors;

    RwUInt32 size;
    void     *memory;

    RWAPIFUNCTION(RWSTRING("RpDMorphGeometryAddDMorphTarget"));
    RWASSERT(rpDMorphModule.numInstances);
    RWASSERT(geometry);

    /* Get our geometry extension data. */
    geometryData = *RPDMORPHGEOMETRYGETDATA(geometry);
    RWASSERT(geometryData);
    RWASSERT(geometryData->numDMorphTargets > index);

    dMorphTarget = &(geometryData->dMorphTargets[index]);

    /* Lets check we only morph something the base has. */
#if defined(RWDEBUG)
    {
        RpGeometryFlag geometryFlags =
            (RpGeometryFlag)RpGeometryGetFlags(geometry);

        if(flags & rpGEOMETRYPOSITIONS)
        {
            RWASSERT(geometryFlags & rpGEOMETRYPOSITIONS);
        }
        if(flags & rpGEOMETRYNORMALS)
        {
            RWASSERT(geometryFlags & rpGEOMETRYNORMALS);
        }
        if(flags & rpGEOMETRYPRELIT)
        {
            RWASSERT(geometryFlags & rpGEOMETRYPRELIT);
        }
        if(flags & rpGEOMETRYTEXTURED)
        {
            RWASSERT(geometryFlags & rpGEOMETRYTEXTURED);
        }
    }
#endif /* defined(RWDEBUG) */

    /* Lets get the base morph information. */
    baseVertices = RPDMORPHGEOMETRYGETVERTICES(geometry);
    baseNormals = RPDMORPHGEOMETRYGETNORMALS(geometry);
    basePreLightColors = RPDMORPHGEOMETRYGETPRELIGHTCOLORS(geometry);
    baseTexCoords = RPDMORPHGEOMETRYGETTEXCOORDS(geometry);

    /* Lets help the generator by suggesting the maximum number of elements. */
    dMorphTarget->rle.numCodeElements =
        RPDMORPHGEOMETRYGETNUMBEROFVERTICES(geometry);

    /* Lets generate the control block to remove the zero deltas. */
    DMorphRLEGenerate( baseVertices,       vertices,
                       baseNormals,        normals,
                       basePreLightColors, preLightColors,
                       baseTexCoords,      texCoords,
                       flags,              &(dMorphTarget->rle) );
    RWASSERT(dMorphTarget->rle.code);

    /* Lets calculate the amount of memory necessary for the RLE deltas. */
    size = (((0!=(flags & rpGEOMETRYPOSITIONS)) * sizeof(RwV3d))
           +((0!=(flags & rpGEOMETRYNORMALS))   * sizeof(RwV3d))
           +((0!=(flags & rpGEOMETRYPRELIT))    * sizeof(RwRGBA))
           +((0!=(flags & rpGEOMETRYTEXTURED))  * sizeof(RwTexCoords)))
           * (dMorphTarget->rle.numDataElements);

    /* Get the memory for the RLE deltas. */
    memory = dMorphTarget->rle.data = RwMalloc(size);
    if(NULL == dMorphTarget->rle.data)
    {
        RWRETURN((RpGeometry *)NULL);
    }

    /* Setup the DMorphTarget flags. */
    dMorphTarget->flags = flags;
    dMorphTarget->lockFlags = (RpGeometryLockMode)0;
    dMorphTarget->geometry = geometry;

    /* Assign delta values. */
    if(flags & rpGEOMETRYPOSITIONS)
    {
        /* Calculate fully morphed bounding sphere. */
        DMorphTargetCalcBoundingSphere( vertices,
                                        geometry->numVertices,
                                        &dMorphTarget->boundingSphere );

        dMorphTarget->vertices = (RwV3d *)
            DMorphRLEGenerateDelta( (rpDMorphRLEData *)baseVertices,
                                    (rpDMorphRLEData *)vertices,
                                    sizeof(RwV3d),
                                    DMorphGenerateV3dDelta,
                                    (rpDMorphRLEDelta *)memory,
                                    &(dMorphTarget->rle) );

        memory = (void *)((RwV3d *)memory + dMorphTarget->rle.numDataElements);
        dMorphTarget->lockFlags |= rpGEOMETRYLOCKVERTICES;
    }
    else
    {
        /* Make a copy of the bounding sphere. */
        RwSphereAssign( &(dMorphTarget->boundingSphere),
                        &(RPDMORPHGEOMETRYGETBOUNDINGSPHERE(geometry)) );
    }

    if(flags & rpGEOMETRYNORMALS)
    {
        dMorphTarget->normals = (RwV3d *)
            DMorphRLEGenerateDelta( (rpDMorphRLEData *)baseNormals,
                                    (rpDMorphRLEData *)normals,
                                    sizeof(RwV3d),
                                    DMorphGenerateV3dDelta,
                                    (rpDMorphRLEDelta *)memory,
                                    &(dMorphTarget->rle) );

        memory = (void *)((RwV3d *)memory + dMorphTarget->rle.numDataElements);
        dMorphTarget->lockFlags += rpGEOMETRYLOCKNORMALS;
    }
    if(flags & rpGEOMETRYPRELIT)
    {
        dMorphTarget->preLightColors = (RwRGBA *)
            DMorphRLEGenerateDelta( (rpDMorphRLEData *)basePreLightColors,
                                    (rpDMorphRLEData *)preLightColors,
                                    sizeof(RwRGBA),
                                    DMorphGenerateRGBADelta,
                                    (rpDMorphRLEDelta *)memory,
                                    &(dMorphTarget->rle) );

        memory = (void *)((RwRGBA *)memory +
                          dMorphTarget->rle.numDataElements);
        dMorphTarget->lockFlags += rpGEOMETRYLOCKPRELIGHT;
    }
    if(flags & rpGEOMETRYTEXTURED)
    {
        dMorphTarget->texCoords = (RwTexCoords *)
            DMorphRLEGenerateDelta( (rpDMorphRLEData *)baseTexCoords,
                                    (rpDMorphRLEData *)texCoords,
                                    sizeof(RwTexCoords),
                                    DMorphGenerateTexCoordsDelta,
                                    (rpDMorphRLEDelta *)memory,
                                    &(dMorphTarget->rle) );
        dMorphTarget->lockFlags += rpGEOMETRYLOCKTEXCOORDS;
    }

    RWRETURN(geometry);
}

/**
 * \ingroup rpdmorph
 * \ref RpDMorphGeometryRemoveDMorphTarget is used to
 * remove the index delta morph target from the base
 * geometry. Once removed the index is then available
 * for a new delta morph target.
 *
 * The world and delta morph plugins must be attached
 * before using this function. The header file rpdmorph.h
 * is required.
 *
 * \param geometry  Pointer to the base geometry.
 * \param index     Index of the delta morph target.
 *
 * \return Returns NULL if there is an error, or otherwise
 *         a pointer to the base geometry.
 *
 * \see RpDMorphGeometryDestroyDMorphTargets
 * \see RpDMorphGeometryCreateDMorphTargets
 * \see RpDMorphGeometryAddDMorphTarget
 */
RpGeometry *
RpDMorphGeometryRemoveDMorphTarget( RpGeometry *geometry,
                                    RwUInt32 index )
{
    rpDMorphGeometryData *geometryData;
    RpDMorphTarget     *dMorphTarget;

    RWAPIFUNCTION(RWSTRING("RpDMorphGeometryRemoveDMorphTarget"));
    RWASSERT(rpDMorphModule.numInstances);
    RWASSERT(geometry);

    /* Get our geometry extension data. */
    geometryData = *RPDMORPHGEOMETRYGETDATA(geometry);
    RWASSERT(geometryData);
    RWASSERT(index < geometryData->numDMorphTargets);

    dMorphTarget = &(geometryData->dMorphTargets[index]);

    /* Free up the DMorphTargets data. */
    if(dMorphTarget->name)
    {
        RwFree(dMorphTarget->name);
    }

    if(dMorphTarget->rle.code)
    {
        RwFree(dMorphTarget->rle.code);
    }

    if(dMorphTarget->rle.data)
    {
        /* This automatically frees the vertices, normals, etc. */
        RwFree(dMorphTarget->rle.data);
    }

    /* Clean the DMorphTarget. */
    memset(dMorphTarget, 0, sizeof(RpDMorphTarget));

    RWRETURN(geometry);
}

/**
 * \ingroup rpdmorph
 * \ref RpDMorphGeometryGetDMorphTarget is used to
 * get the indexed delta morph target of the base geometry.
 *
 * The world and delta morph plugins must be attached
 * before using this function. The header file rpdmorph.h
 * is required.
 *
 * \param geometry  Pointer to the base geometry.
 * \param index     Index of the delta morph target.
 *
 * \return Returns NULL if there is an error, or otherwise
 *         a pointer to the delta morph target.
 *
 * \see RpDMorphGeometryCreateDMorphTargets
 * \see RpDMorphGeometryGetNumDMorphTargets
 */
RpDMorphTarget *
RpDMorphGeometryGetDMorphTarget( const RpGeometry *geometry,
                                 RwUInt32 index )
{
    const rpDMorphGeometryData *geometryData;

    RWAPIFUNCTION(RWSTRING("RpDMorphGeometryGetDMorphTarget"));
    RWASSERT(rpDMorphModule.numInstances);
    RWASSERT(geometry);

    /* Get our geometry extension data. */
    geometryData = *RPDMORPHGEOMETRYGETCONSTDATA(geometry);
    RWASSERT(geometryData);
    RWASSERT(index < geometryData->numDMorphTargets);

    /* Offer up the DMorphTarget at index. */
    RWRETURN(&(geometryData->dMorphTargets[index]));
}

/**
 * \ingroup rpdmorph
 * \ref RpDMorphGeometryGetNumDMorphTargets is used to
 * get the number of delta morph targets that were created
 * by a call to \ref RpDMorphGeometryCreateDMorphTargets.
 *
 * The world and delta morph plugins must be attached
 * before using this function. The header file rpdmorph.h
 * is required.
 *
 * \param geometry  Pointer to the base geometry.
 *
 * \return Returns the number of delta morph targets.
 *
 * \see RpDMorphGeometryCreateDMorphTargets
 * \see RpDMorphGeometryGetDMorphTarget
 */
RwUInt32
RpDMorphGeometryGetNumDMorphTargets( const RpGeometry *geometry )
{
    const rpDMorphGeometryData *geometryData;

    RWAPIFUNCTION(RWSTRING("RpDMorphGeometryGetNumDMorphTargets"));
    RWASSERT(rpDMorphModule.numInstances);
    RWASSERT(geometry);

    /* Get our geometry extension data. */
    geometryData = *RPDMORPHGEOMETRYGETCONSTDATA(geometry);

    if( NULL != geometryData )
    {
        /* Return the number of DMorphTargets. */
        RWRETURN(geometryData->numDMorphTargets);
    }
    else
    {
        /* Return zero. */
        RWRETURN(0);
    }
}

/*--- DMorphTarget functions -----------------------------------------------*/

/**
 * \ingroup rpdmorph
 * \ref RpDMorphTargetGetBoundingSphere is used to
 * get the bounding sphere of the delta morph target.
 * The bounding sphere returned is returned as if the
 * delta morph had been fully applied.
 *
 * The world and delta morph plugins must be attached
 * before using this function. The header file rpdmorph.h
 * is required.
 *
 * \param dMorphTarget  Pointer to the delta morph target.
 *
 * \return Returns NULL if there is an error, or otherwise
 *         a pointer to the bounding sphere.
 *
 * \see RpDMorphGeometryAddDMorphTarget
 */
const RwSphere *
RpDMorphTargetGetBoundingSphere( const RpDMorphTarget *dMorphTarget )
{
    RWAPIFUNCTION(RWSTRING("RpDMorphTargetGetBoundingSphere"));
    RWASSERT(rpDMorphModule.numInstances);
    RWASSERT(dMorphTarget);

    /* Offer up the DMorphTargets bounding sphere. */
    RWRETURN(&(dMorphTarget->boundingSphere));
}

/**
 * \ingroup rpdmorph
 * \ref RpDMorphTargetSetName is used to set the name
 * of the delta morph target.
 *
 * The world and delta morph plugins must be attached
 * before using this function. The header file rpdmorph.h
 * is required.
 *
 * \param dMorphTarget  Pointer to the delta morph target.
 *
 * \return Returns NULL if there is an error, or otherwise
 *         a pointer to the delta morph target.
 *
 * \see RpDMorphTargetGetName
 */
RpDMorphTarget *
RpDMorphTargetSetName( RpDMorphTarget *dMorphTarget,
                       RwChar *name )
{
    RWAPIFUNCTION(RWSTRING("RpDMorphTargetSetName"));
    RWASSERT(rpDMorphModule.numInstances);
    RWASSERT(dMorphTarget);
    RWASSERT(name);

    /* Lets make a copy of the name for the DMorphTarget. */
    rwstrdup(dMorphTarget->name, name );
    RWASSERT(dMorphTarget->name);

    RWRETURN(dMorphTarget);
}

/**
 * \ingroup rpdmorph
 * \ref RpDMorphTargetGetName is used to get the name
 * of the delta morph target.
 *
 * The world and delta morph plugins must be attached
 * before using this function. The header file rpdmorph.h
 * is required.
 *
 * \param dMorphTarget  Pointer to the delta morph target.
 *
 * \return Returns NULL if there is an error, or otherwise
 *         a pointer to the name.
 *
 * \see RpDMorphTargetSetName
 */
RwChar *
RpDMorphTargetGetName( RpDMorphTarget *dMorphTarget )
{
    RWAPIFUNCTION(RWSTRING("RpDMorphTargetGetName"));
    RWASSERT(rpDMorphModule.numInstances);
    RWASSERT(dMorphTarget);

    /* Offer up the DMorphTargets name. */
    RWRETURN(dMorphTarget->name);
}

/**
 * \ingroup rpdmorph
 * \ref RpDMorphTargetGetFlags is used to get elements
 * of geometry that the delta morph target contains.
 *
 * The world and delta morph plugins must be attached
 * before using this function. The header file rpdmorph.h
 * is required.
 *
 * \param dMorphTarget  Pointer to the delta morph target.
 *
 * \return Returns the geometry flags of the delta morph
 *         target.
 *
 * \see RpGeometryGetFlags
 * \see RpDMorphGeometryAddDMorphTarget
 */
RpGeometryFlag
RpDMorphTargetGetFlags( RpDMorphTarget *dMorphTarget )
{
    RWAPIFUNCTION(RWSTRING("RpDMorphTargetGetFlags"));
    RWASSERT(rpDMorphModule.numInstances);
    RWASSERT(dMorphTarget);

    /* Offer up the DMorphTargets flags. */
    RWRETURN((RpGeometryFlag)dMorphTarget->flags);
}
