/*
 *
 * VRML to RW converter plug-in
 */

/****************************************************************************
 *                                                                         
 * VRML 2.0 to RW3.0 Converter
 * Copyright (C) 1998 Criterion Technologies
 *
 * Author  : Damian Scallan 
 *
 * Module  : Utilities.c
 *                                                                         
 * Purpose : Useful expoter functions which will eventually move into RW tools
 *                                                                         
 ****************************************************************************/

/****************************************************************************
 Includes
 */
#include <math.h>
#include <string.h>

#include "rpplugin.h"
#include <rwcore.h>
#include <rtworld.h>
#include <rpdbgerr.h>
#include "rpvrmlanim.h"
#include "rpvrml.h"
#include "utilities.h"

static const char __RWUNUSED__ rcsid[] =
    "@@(#)$Id: utilities.c,v 1.55 2001/08/17 14:57:17 johns Exp $";

extern void         _rwFrameSyncHierarchyLTM(RwFrame * frame);

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   clump frame normalization functions

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

static RwObject    *
doObjects(RwObject * object, void *pData)
{
    RwMatrix           *LTM = (RwMatrix *) pData;

    RWFUNCTION(RWSTRING("doObjects"));

    if (object->type == rpATOMIC)
    {
        RpAtomic           *atomic = (RpAtomic *) object;
        RpGeometry         *geometry;

        geometry = RpAtomicGetGeometry(atomic);
        if (geometry)
        {
            RpGeometryTransform(geometry, LTM);
        }
    }
    RWRETURN(object);
}

static RwFrame     *
NormalizeFrame(RwFrame * frame)
{
    RwFrame            *parentFrame;
    RwMatrix            LTM;
    RwMatrix           *modelling;
    RwV3d              *childPos;
    RwV3d               pos;
    RwV3d               origin = { 0.0f, 0.0f, 0.0f };

    RWFUNCTION(RWSTRING("NormalizeFrame"));

    LTM = frame->ltm;
    modelling = &frame->modelling;
    childPos = RwMatrixGetPos(&LTM);

    parentFrame = RwFrameGetParent(frame);
    if (parentFrame)
    {
        RwMatrix           *parentLTM;
        RwV3d              *parentPos;

        parentLTM = &parentFrame->ltm;
        parentPos = RwMatrixGetPos(parentLTM);
        RwV3dSub(&pos, childPos, parentPos);
    }
    else
    {
        pos = *childPos;
    }

    LTM.pos = origin;

    RwFrameForAllObjects(frame, doObjects, &LTM);
    RwMatrixSetIdentity(modelling);
    RwMatrixTranslate(modelling, &pos, rwCOMBINEREPLACE);

    RWRETURN((frame));
}

static RwFrame     *
NormalizeFrameHierarchy(RwFrame * frame, void *__RWUNUSED__ pData)
{
    RWFUNCTION(RWSTRING("NormalizeFrameHierarchy"));

    /* do normalization */
    NormalizeFrame(frame);

    RwFrameForAllChildren(frame, NormalizeFrameHierarchy, NULL);

    RWRETURN((frame));
}

RpClump            *
ClumpFrameNormalize(RpClump * clump)
{

    RwFrame            *frame;

    RWFUNCTION(RWSTRING("ClumpFrameNormalize"));

    frame = RpClumpGetFrame(clump);
    _rwFrameSyncHierarchyLTM(frame);

    RWRETURN((clump));
}

static RwObject    *
isObjectAtomic(RwObject * object, void *pData)
{

    RwBool             *haveAtomic = (RwBool *) pData;

    RWFUNCTION(RWSTRING("isObjectAtomic"));

    if (RwObjectGetType(object) == rpATOMIC)
    {
        *haveAtomic = TRUE;

        RWRETURN(((RwObject *)NULL));
    }

    RWRETURN((object));
}

static              RwBool
FrameHaveAtomics(RwFrame * frame)
{

    RwBool              haveAtomics = FALSE;

    RWFUNCTION(RWSTRING("FrameHaveAtomics"));

    RwFrameForAllObjects(frame, isObjectAtomic, &haveAtomics);

    RWRETURN((haveAtomics));
}

static RwFrame     *
redoMM(RwFrame * frame, void *pData)
{

    RwMatrix           *parentsMM = (RwMatrix *) pData;
    RwMatrix           *modelling = RwFrameGetMatrix(frame);

    RWFUNCTION(RWSTRING("redoMM"));

    RwMatrixMultiply(modelling, modelling, parentsMM);

    RWRETURN((frame));
}

static RwFrame     *
addChildren(RwFrame * frame, void *pData)
{

    RwFrame            *parentFrame = (RwFrame *) pData;

    RWFUNCTION(RWSTRING("addChildren"));

    RwFrameAddChild(parentFrame, frame);

    RWRETURN((frame));
}

static RwFrame     *
removeDummies(RwFrame * frame, void *pData)
{

    RpClump            *clump = (RpClump *) pData;
    RwBool              removeFrame = FALSE;

    RWFUNCTION(RWSTRING("removeDummies"));

    /* does the frame have any attached atomics */
    if (!FrameHaveAtomics(frame))
    {
        RwFrame            *parentFrame;
        RwMatrix           *modelling;

        /* multiply modeling matrix with childrens modelling matrix */
        modelling = RwFrameGetMatrix(frame);
        RwFrameForAllChildren(frame, redoMM, modelling);

        parentFrame = RwFrameGetParent(frame);
        if (parentFrame)
        {
            /* remove children frames */
            RwFrameRemoveChild(frame);
#if (0)

#endif /* (0) */
            /* attach children to parent */
            RwFrameForAllChildren(frame, addChildren, parentFrame);
#if (0)
            RwFrameForAllChildren(frame, removeChildren, NULL);
#endif /* (0) */
            frame->next = (RwFrame *)NULL;
            frame->child = (RwFrame *)NULL;

            removeFrame = TRUE;
        }
    }

    RwFrameForAllChildren(frame, removeDummies, clump);

    if (removeFrame)
    {
        RwFrameDestroy(frame);
        frame = (RwFrame *)NULL;
    }

    RWRETURN((frame));
}

RpClump            *
ClumpRemoveDummyFrames(RpClump * clump)
{

    RwFrame            *frame;

    RWFUNCTION(RWSTRING("ClumpRemoveDummyFrames"));

    frame = RpClumpGetFrame(clump);
    frame = removeDummies(frame, clump);

#if (0)
    RpClumpSetFrame(clump, frame);
#endif /* (0) */

    RwFrameGetLTM(frame);

    RWRETURN((clump));
}

/********************* crease angle code **************************************/

typedef struct TriangleVertices TriangleVertices;
struct TriangleVertices
{
    int                 triInd;
    int                 vertInd;
    TriangleVertices   *sameAs;
};

typedef struct Edge Edge;
struct Edge
{
    int                 startInd;
    int                 endInd;
};

typedef struct WingedTriangle WingedTriangle;
struct WingedTriangle
{
    TriangleVertices   *triindices[3];
    Edge                edges[3];
    RwV3d               faceNormal;
    WingedTriangle     *wingtris[3];
};

#if (0)

/*
 * Prototype in "utilities.h"
 */
extern RpGeometry  *_rpVrmlGeometryAddCreaseNormals(RpGeometry *
                                                    geometry,
                                                    RwReal creaseAngle);

#endif /* (0) */

static RwBool       EdgeFindTriangle(Edge * edge,
                                     WingedTriangle * triangles,
                                     int numTriangles,
                                     WingedTriangle ** wingNieghbour,
                                     int *edgeInd);

static RwBool       ChainEquivalentTriangleVertices(WingedTriangle *
                                                    wt1, int ed1,
                                                    WingedTriangle *
                                                    wt2, int ed2,
                                                    TriangleVertices *
                                                    triIndices);

static RwV3d       *CreateRemappedVertices(TriangleVertices *
                                           triIndices,
                                           int numTriangleVertices,
                                           RwV3d * vertices,
                                           RwV3d * newVertices);

static RwRGBA      *CreateRemappedColourVertices(TriangleVertices *
                                                 triIndices,
                                                 int
                                                 numTriangleVertices,
                                                 RwRGBA * colVerts,
                                                 RwRGBA * newColVerts);

static RwTexCoords *CreateRemappedTextureVertices(TriangleVertices *
                                                  triIndices,
                                                  int
                                                  numTriangleVertices,
                                                  RwTexCoords *
                                                  texVerts,
                                                  RwTexCoords *
                                                  newTexVerts);

static RwBool       RemapTriFaces(RpGeometry * geometry,
                                  RpGeometry * newGeometry,
                                  WingedTriangle * wingTris,
                                  int numTriangles,
                                  TriangleVertices * triIndices);

static int          RemapTriangleVertices(TriangleVertices * triIndices,
                                          int numTriangleVertices);

static RpAtomic    *
AddCreaseNoramls(RpAtomic * atomic, void *pData)
{

    RpGeometry         *geometry;
    RwReal              creaseAngle = *(RwReal *) pData;

    RWFUNCTION(RWSTRING("AddCreaseNoramls"));

    geometry = RpAtomicGetGeometry(atomic);

    if (geometry)
    {
        RpGeometry         *newGeometry;

        newGeometry =
            _rpVrmlGeometryAddCreaseNormals(geometry, creaseAngle);
        if (newGeometry)
        {
            RpGeometryDestroy(geometry);
            RpAtomicSetGeometry(atomic, newGeometry, 0);
        }
    }

    RWRETURN((atomic));
}

static TriangleVertices *
TriangleVerticesArrayCreate(int numTriangles)
{

    int                 numTriangleVertices;
    TriangleVertices   *tv;

    RWFUNCTION(RWSTRING("TriangleVerticesArrayCreate"));

    /* allocate */
    numTriangleVertices = numTriangles * 3;
    tv = (TriangleVertices *) RwMalloc(numTriangleVertices *
                                       sizeof(TriangleVertices));
    if (!tv)
    {
        RWRETURN(((TriangleVertices *)NULL));
    }

    RWRETURN((tv));
}

static TriangleVertices *
TriangleVerticesArrayInit(TriangleVertices * tv,
                          RpTriangle * triangles, int numTriangles)
{
    int                 triangleNum;
    int                 index = 0;

    RWFUNCTION(RWSTRING("TriangleVerticesArrayInit"));

    /* initialize */
    for (triangleNum = 0; triangleNum < numTriangles; triangleNum++)
    {
        int                 i;

        for (i = 0; i < 3; i++)
        {
            tv->triInd = index;
            tv->vertInd = triangles->vertIndex[i];
            tv->sameAs = (TriangleVertices *)NULL;

            index++;
            tv++;
        }

        triangles++;
    }

    RWRETURN((tv));
}

static void
TriangleVerticesArrayDestroy(TriangleVertices * tv)
{
    RWFUNCTION(RWSTRING("TriangleVerticesArrayDestroy"));

    RwFree(tv);

    RWRETURNVOID();
}

static WingedTriangle *
WingedTriangleArrayCreate(int numWingedTriangles)
{

    WingedTriangle     *wts;

    RWFUNCTION(RWSTRING("WingedTriangleArrayCreate"));

    wts =
        (WingedTriangle *) RwMalloc(numWingedTriangles *
                                    sizeof(WingedTriangle));
    if (!wts)
    {
        RWRETURN(((WingedTriangle *)NULL));
    }

    RWRETURN((wts));
}

static WingedTriangle *
WingedTriangleArrayInit(WingedTriangle * wts,
                        TriangleVertices * tv, int numWingedTriangles)
{
    int                 triangleNum;

    RWFUNCTION(RWSTRING("WingedTriangleArrayInit"));

    for (triangleNum = 0; triangleNum < numWingedTriangles;
         triangleNum++)
    {
        int                 i;

        for (i = 0; i < 3; i++)
        {
            wts->triindices[i] = tv;
            wts->wingtris[i] = (WingedTriangle *)NULL;

            tv++;
        }

        wts++;
    }

    RWRETURN((wts));
}

static WingedTriangle *
WingedTriangleArraySortEdges(WingedTriangle * wts,
                             int numWingedTriangles)
{

    int                 triangleNum;

    RWFUNCTION(RWSTRING("WingedTriangleArraySortEdges"));

    for (triangleNum = 0; triangleNum < numWingedTriangles;
         triangleNum++)
    {
        int                 i;

        for (i = 0; i < 3; i++)
        {
            Edge               *edge;
            int                 eds, ede;

            edge = &wts->edges[i];
            eds = wts->triindices[i]->vertInd;
            ede = wts->triindices[(i + 1) % 3]->vertInd;

            if (eds > ede)
            {
                eds ^= ede;
                ede ^= eds;
                eds ^= ede;
            }

            edge->startInd = eds;
            edge->endInd = ede;
        }

        wts++;
    }

    RWRETURN((wts));
}

static WingedTriangle *
WingedTriangleArrayComputeFaceNormals(WingedTriangle * wts,
                                      RwV3d * vertices,
                                      int numWingedTriangles)
{
    int                 triangleNum;

    RWFUNCTION(RWSTRING("WingedTriangleArrayComputeFaceNormals"));

    for (triangleNum = 0; triangleNum < numWingedTriangles;
         triangleNum++)
    {
        RwInt32             ind;
        RwV3d              *pt1, *pt2;
        RwV3d               edge1, edge2;
        RwV3d              *faceNormal;

        ind = wts->triindices[0]->vertInd;
        pt1 = &vertices[ind];
        ind = wts->triindices[1]->vertInd;
        pt2 = &vertices[ind];
        RwV3dSub(&edge1, pt1, pt2);

        ind = wts->triindices[1]->vertInd;
        pt1 = &vertices[ind];
        ind = wts->triindices[2]->vertInd;
        pt2 = &vertices[ind];
        RwV3dSub(&edge2, pt1, pt2);

        faceNormal = &wts->faceNormal;
        RwV3dCrossProduct(faceNormal, &edge1, &edge2);
        _rwV3dNormalize(faceNormal, faceNormal);

        wts++;
    }

    RWRETURN((wts));
}

static WingedTriangle *
WingedTriangleArrayFindNieghbours(WingedTriangle * wts,
                                  int numWingedTriangles)
{
    int                 triangleNum;

    RWFUNCTION(RWSTRING("WingedTriangleArrayFindNieghbours"));

    for (triangleNum = 0; triangleNum < numWingedTriangles;
         triangleNum++)
    {
        int                 remainingTriangles;
        int                 i;

        remainingTriangles = numWingedTriangles - triangleNum;

        for (i = 0; i < 3; i++)
        {
            if (!wts->wingtris[i])
            {
                Edge               *edge = (Edge *)NULL;
                WingedTriangle     *wingNieghbour = (WingedTriangle *)NULL;
                int                 edgeIndex;

                edge = &wts->edges[i];
                if (EdgeFindTriangle(edge,
                                     wts + 1,
                                     remainingTriangles - 1,
                                     &wingNieghbour, &edgeIndex))
                {
                    /* setup the new nieghbours */
                    wts->wingtris[i] = wingNieghbour;
                    wingNieghbour->wingtris[edgeIndex] = wts;
                }
            }
        }

        wts++;
    }

    RWRETURN((wts));
}

static WingedTriangle *
WingedTriangleArrayWeldNieghbours(WingedTriangle * wts,
                                  TriangleVertices * tv,
                                  int numWingedTriangles,
                                  float creaseAngle)
{
    int                 triangleNum;

    RWFUNCTION(RWSTRING("WingedTriangleArrayWeldNieghbours"));
    for (triangleNum = 0; triangleNum < numWingedTriangles;
         triangleNum++)
    {
        RwV3d              *faceNormal;
        int                 i;

        faceNormal = &wts->faceNormal;

        for (i = 0; i < 3; i++)
        {
            RwReal              angle;
            RwV3d              *nieghbourFaceNormal;
            WingedTriangle     *wingNieghbour;

            wingNieghbour = wts->wingtris[i];
            if (wingNieghbour)
            {
                nieghbourFaceNormal = &wingNieghbour->faceNormal;

                angle =
                    RwV3dDotProduct(faceNormal, nieghbourFaceNormal);
                angle = (RwReal) RwACos(angle);
                angle *= (180.0f / 3.141592654f);
                if (angle <= creaseAngle)
                {
                    Edge               *edge;
                    int                 nieghbourEdgeInd;

                    edge = &wts->edges[i];

                    /* get the common edge */
                    EdgeFindTriangle(edge,
                                     wingNieghbour,
                                     1, &wingNieghbour,
                                     &nieghbourEdgeInd);

                    /* mark as equivalent */
                    ChainEquivalentTriangleVertices(wts, i,
                                                    wingNieghbour,
                                                    nieghbourEdgeInd,
                                                    tv);
                }
            }
        }

        wts++;
    }

    RWRETURN((wts));
}

static void
WingedTriangleArrayDestroy(WingedTriangle * wts)
{
    RWFUNCTION(RWSTRING("WingedTriangleArrayDestroy"));

    RwFree(wts);
    RWRETURNVOID();
}

static RpGeometry  *
VrmlGeometryFromWingedTriangleArray(RpGeometry * geometry,
                                    WingedTriangle * wts,
                                    TriangleVertices * tv,
                                    int numWingedTriangles)
{
    RpGeometry         *newGeometry;
    int                 numNewVerts;
    RwUInt32            flags;

    RWFUNCTION(RWSTRING("VrmlGeometryFromWingedTriangleArray"));

    flags = RpGeometryGetFlags(geometry);
    flags |= rpGEOMETRYNORMALS;

    numNewVerts = RemapTriangleVertices(tv, numWingedTriangles * 3);
    newGeometry =
        RpGeometryCreate(numNewVerts, numWingedTriangles, flags);
    if (newGeometry)
    {
        RpMorphTarget      *morphTarget, *newMorphTarget;
        RwV3d              *vertices, *newVertices;

        morphTarget = RpGeometryGetMorphTarget(geometry, 0);
        vertices = RpMorphTargetGetVertices(morphTarget);

        newMorphTarget = RpGeometryGetMorphTarget(newGeometry, 0);
        newVertices = RpMorphTargetGetVertices(newMorphTarget);

        /* remap the vertices */
        CreateRemappedVertices(tv, numWingedTriangles * 3, vertices,
                               newVertices);

        /* remap any vertex prelight valuses */
        if (flags & rpGEOMETRYPRELIT)
        {
            RwRGBA             *preLitCols, *newPreLitCols;

            preLitCols = RpGeometryGetPreLightColors(geometry);
            newPreLitCols = RpGeometryGetPreLightColors(newGeometry);
            CreateRemappedColourVertices(tv, numWingedTriangles * 3,
                                         preLitCols, newPreLitCols);
        }

        /* reamp any vertex colour values */
#ifdef USE_COLOUREDVERTS
        if (flags & rpGEOMETRYCOLORED)
        {
            RwRGBA             *colours, *newColours;

            colours = RpGeometryGetVertexColors(geometry);
            newColours = RpGeometryGetVertexColors(newGeometry);
            CreateRemappedColourVertices(tv, numWingedTriangles * 3,
                                         colours, newColours);
        }
#endif /* USE_COLOUREDVERTS */

        /* copy any polygon textures */
        if (flags & rpGEOMETRYTEXTURED)
        {
            /* remap any vertex texture values */
            RwTexCoords        *texVerts, *newTexVerts;

            texVerts = RpGeometryGetVertexTexCoords(geometry, rwTEXTURECOORDINATEINDEX0);
            newTexVerts = RpGeometryGetVertexTexCoords(newGeometry, rwTEXTURECOORDINATEINDEX0);
            CreateRemappedTextureVertices(tv, numWingedTriangles * 3,
                                          texVerts, newTexVerts);
        }

        RemapTriFaces(geometry, newGeometry, wts, numWingedTriangles,
                      tv);

        newMorphTarget->boundingSphere = morphTarget->boundingSphere;
        RpGeometryUnlock(newGeometry);

        RWRETURN((newGeometry));
    }

    RWRETURN(((RpGeometry *)NULL));
}

RpGeometry         *
_rpVrmlGeometryAddCreaseNormals(RpGeometry * geometry,
                                RwReal creaseAngle)
{

    RpGeometry         *newGeometry;
    RwInt32             numTriangles;
    RpTriangle         *triangles;
    RpMorphTarget      *morphTarget;
    RwV3d              *vertices;
    TriangleVertices   *triIndices;
    WingedTriangle     *wingTriangles;

    RWFUNCTION(RWSTRING("_rpVrmlGeometryAddCreaseNormals"));

    numTriangles = RpGeometryGetNumTriangles(geometry);
    triangles = RpGeometryGetTriangles(geometry);
    /* get the vertices for calculating face normals */
    morphTarget = RpGeometryGetMorphTarget(geometry, 0);
    vertices = RpMorphTargetGetVertices(morphTarget);

    /* create the crease info structures */
    triIndices = TriangleVerticesArrayCreate(numTriangles * 3);
    if (!triIndices)
    {
        RWRETURN(((RpGeometry *)NULL));
    }

    wingTriangles = WingedTriangleArrayCreate(numTriangles);
    if (!wingTriangles)
    {
        TriangleVerticesArrayDestroy(triIndices);

        RWRETURN(((RpGeometry *)NULL));
    }

    TriangleVerticesArrayInit(triIndices, triangles, numTriangles);

    WingedTriangleArrayInit(wingTriangles, triIndices, numTriangles);

    WingedTriangleArraySortEdges(wingTriangles, numTriangles);

    WingedTriangleArrayComputeFaceNormals(wingTriangles, vertices,
                                          numTriangles);

    /* find each wing triangles three nieghbours */
    WingedTriangleArrayFindNieghbours(wingTriangles, numTriangles);

    WingedTriangleArrayWeldNieghbours(wingTriangles, triIndices,
                                      numTriangles, creaseAngle);

    newGeometry =
        VrmlGeometryFromWingedTriangleArray(geometry, wingTriangles,
                                            triIndices, numTriangles);

    TriangleVerticesArrayDestroy(triIndices);
    WingedTriangleArrayDestroy(wingTriangles);

    RtGeometryCalculateVertexNormals(newGeometry);

    RWRETURN((newGeometry));
}

static              RwBool
EdgeFindTriangle(Edge * edge,
                 WingedTriangle * triangles,
                 int numTriangles,
                 WingedTriangle ** wingNieghbour, int *edgeInd)
{
    int                 triNum;

    RWFUNCTION(RWSTRING("EdgeFindTriangle"));

    for (triNum = 0; triNum < numTriangles; triNum++)
    {
        int                 i;

        for (i = 0; i < 3; i++)
        {
            Edge               *edgeDst;

            edgeDst = &triangles->edges[i];

            if ((edge->startInd == edgeDst->startInd) &&
                (edge->endInd == edgeDst->endInd))
            {
                *wingNieghbour = triangles;
                *edgeInd = i;

                RWRETURN((TRUE));
            }
        }

        triangles++;
    }

    RWRETURN((FALSE));
}

static              RwBool
ChainEquivalentTriangleVertices(WingedTriangle * wt1,
                                int ed1, WingedTriangle * wt2, int ed2,
                                TriangleVertices * triIndices)
{
    int                 startInd1, startInd2;
    int                 endInd1, endInd2;
    TriangleVertices   *quivInds;

    RWFUNCTION(RWSTRING("ChainEquivalentTriangleVertices"));

    startInd1 = wt1->triindices[ed1]->triInd;
    startInd2 = wt1->triindices[(ed1 + 1) % 3]->triInd;

    endInd1 = wt2->triindices[(ed2 + 1) % 3]->triInd;
    endInd2 = wt2->triindices[ed2]->triInd;

    if (startInd1 < endInd1)
    {
        startInd1 ^= endInd1;
        endInd1 ^= startInd1;
        startInd1 ^= endInd1;
    }

    {
        quivInds = &triIndices[startInd1];
        while (quivInds->sameAs)
        {
            quivInds = quivInds->sameAs;
        }

        startInd1 = quivInds->triInd;

        if (startInd1 < endInd1)
        {
            startInd1 ^= endInd1;
            endInd1 ^= startInd1;
            startInd1 ^= endInd1;
        }

        /* don't allow self reference */
        if (startInd1 != endInd1)
        {
            triIndices[startInd1].sameAs = &triIndices[endInd1];
        }
    }

    if (startInd2 < endInd2)
    {
        startInd2 ^= endInd2;
        endInd2 ^= startInd2;
        startInd2 ^= endInd2;
    }

    {
        quivInds = &triIndices[startInd2];
        while (quivInds->sameAs)
        {
            quivInds = quivInds->sameAs;
        }

        startInd2 = quivInds->triInd;

        if (startInd2 < endInd2)
        {
            startInd2 ^= endInd2;
            endInd2 ^= startInd2;
            startInd2 ^= endInd2;
        }

        /* don't allow self reference */
        if (startInd2 != endInd2)
        {
            triIndices[startInd2].sameAs = &triIndices[endInd2];
        }
    }

    RWRETURN((TRUE));
}

static int
RemapTriangleVertices(TriangleVertices * triIndices,
                      int numTriangleVertices)
{

    int                 vertNum;
    int                 numRemappedVerts = 0;

    RWFUNCTION(RWSTRING("RemapTriangleVertices"));

    for (vertNum = 0; vertNum < numTriangleVertices; vertNum++)
    {
        if (!triIndices->sameAs)
        {
            triIndices->triInd = numRemappedVerts;
            numRemappedVerts++;
        }

        triIndices++;
    }

    RWRETURN((numRemappedVerts));
}

static RwV3d       *
CreateRemappedVertices(TriangleVertices * triIndices,
                       int numTriangleVertices,
                       RwV3d * vertices, RwV3d * newVertices)
{
    RwV3d              *currentVert;
    int                 vertNum;

    RWFUNCTION(RWSTRING("CreateRemappedVertices"));

    currentVert = newVertices;

    for (vertNum = 0; vertNum < numTriangleVertices; vertNum++)
    {
        if (!triIndices->sameAs)
        {
            *currentVert = vertices[triIndices->vertInd];
            currentVert++;
        }
        triIndices++;
    }

    RWRETURN((newVertices));
}

static RwRGBA      *
CreateRemappedColourVertices(TriangleVertices * triIndices,
                             int numTriangleVertices,
                             RwRGBA * colVerts, RwRGBA * newColVerts)
{
    RwRGBA             *currentColVert;
    int                 vertNum;

    RWFUNCTION(RWSTRING("CreateRemappedColourVertices"));

    currentColVert = newColVerts;

    for (vertNum = 0; vertNum < numTriangleVertices; vertNum++)
    {
        if (!triIndices->sameAs)
        {
            *currentColVert = colVerts[triIndices->vertInd];
            currentColVert++;
        }
        triIndices++;
    }

    RWRETURN((newColVerts));
}

static RwTexCoords *
CreateRemappedTextureVertices(TriangleVertices * triIndices,
                              int numTriangleVertices,
                              RwTexCoords * texVerts,
                              RwTexCoords * newTexVerts)
{
    RwTexCoords        *currentTexVert;
    int                 vertNum;

    RWFUNCTION(RWSTRING("CreateRemappedTextureVertices"));

    currentTexVert = newTexVerts;

    for (vertNum = 0; vertNum < numTriangleVertices; vertNum++)
    {
        if (!triIndices->sameAs)
        {
            *currentTexVert = texVerts[triIndices->vertInd];
            currentTexVert++;
        }
        triIndices++;
    }

    RWRETURN((newTexVerts));
}

static              RwBool
RemapTriFaces(RpGeometry * geometry,
              RpGeometry * newGeometry,
              WingedTriangle * wingTris,
              int numTriangles,
              TriangleVertices * __RWUNUSED__ triIndices)
{
    RpTriangle         *triangles;
    RpTriangle         *newTriangles;
    int                 triNum;

    RWFUNCTION(RWSTRING("RemapTriFaces"));

    triangles = RpGeometryGetTriangles(geometry);
    newTriangles = RpGeometryGetTriangles(newGeometry);

    for (triNum = 0; triNum < numTriangles; triNum++)
    {
        int                 i;
        RwUInt16            ind[3];
        RpMaterial         *material;

        for (i = 0; i < 3; i++)
        {
            TriangleVertices   *triInd;
            TriangleVertices   *equivList;

            triInd = wingTris->triindices[i];

            equivList = triInd;
            while (equivList->sameAs)
            {
                equivList = equivList->sameAs;
            }

            ind[i] = equivList->triInd;

            triInd++;
        }

        RpGeometryTriangleSetVertexIndices(newGeometry, newTriangles,
                                           ind[0], ind[1], ind[2]);
        material = RpGeometryTriangleGetMaterial(geometry, triangles);
        RpGeometryTriangleSetMaterial(newGeometry, newTriangles,
                                      material);

        triangles++;
        newTriangles++;
        wingTris++;
    }

    RWRETURN((TRUE));
}

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   initial Field Block stuff

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

MemBlock           *MemBlock_Create(int size);
int                 MemBlock_Destroy(MemBlock * mb);

FieldBlock         *
FieldBlock_Create(int elementSize, int numElements)
{
    FieldBlock         *fb;

    RWFUNCTION(RWSTRING("FieldBlock_Create"));

    fb = (FieldBlock *) RwMalloc(sizeof(FieldBlock));
    if (!fb)
    {
        RWRETURN(((FieldBlock *)NULL));
    }

    fb->currentBlock = (MemBlock *) fb;
    fb->delBlock = (MemBlock *)NULL;
    fb->currentElement = (void *) fb;
    fb->elementSize = elementSize;
    fb->numElements = numElements;
    fb->blockSize = elementSize * numElements;
    fb->numUsedElements = 0;

    RWRETURN((fb));
}

int
FieldBlock_Destroy(FieldBlock * fb)
{
    MemBlock           *currentMemBlock;

    RWFUNCTION(RWSTRING("FieldBlock_Destroy"));

    /* remove any linked blocks */
    currentMemBlock = fb->currentBlock;
    while (currentMemBlock != (MemBlock *) fb)
    {
        MemBlock           *nextMemBlock;

        nextMemBlock = currentMemBlock->nextBlock;
        MemBlock_Destroy(currentMemBlock);
        currentMemBlock = nextMemBlock;
    }

    RwFree(fb);

    RWRETURN((1));
}

void               *
FieldBlock_GetElement(FieldBlock * fb)
{
    void               *element;

    RWFUNCTION(RWSTRING("FieldBlock_GetElement"));

    if (fb->currentElement == fb)
    {
        /* create a new block */
        MemBlock           *newMemBlock;

        newMemBlock = MemBlock_Create(fb->blockSize);
        if (!newMemBlock)
        {
            RWRETURN((NULL));
        }

        fb->currentElement = newMemBlock->mem;

        /* link it in */

        /* initial case */
        if (fb->currentBlock == (MemBlock *) fb)
        {
            newMemBlock->nextBlock = (MemBlock *) fb;
        }
        else
        {
            newMemBlock->nextBlock = fb->currentBlock;
        }

        fb->currentBlock = newMemBlock;
    }

    element = fb->currentElement;
    fb->currentElement = (char *) fb->currentElement + fb->elementSize;

    if ((char *) fb->currentElement >= ((char *) fb->currentBlock->mem
                                        + fb->blockSize))
    {
        fb->currentElement = fb;
    }

    fb->numUsedElements++;
    fb->currentBlock->numUsedElements++;

    RWRETURN((element));
}

int
FieldBlock_ReleaseElement(FieldBlock * fb)
{
    RWFUNCTION(RWSTRING("FieldBlock_ReleaseElement"));

    /* used just for debugging purposes */
    fb->numUsedElements--;

    RWRETURN((1));
}

int
FieldBlock_Compact(FieldBlock * fb)
{

    int                 newBlockSize = 0;
    MemBlock           *mb;
    MemBlock           *currentBlock;
    void               *mem;

    RWFUNCTION(RWSTRING("FieldBlock_Compact"));

    /* create one large block from the list of blocks */

    /* compute the size of the new block */
    /* should try and be clever ang get the exact fit sometime */
    currentBlock = fb->currentBlock;
    while (currentBlock != (MemBlock *) fb)
    {
        newBlockSize += fb->blockSize;
    }

    mb = MemBlock_Create(newBlockSize);
    if (!mb)
    {
        RWRETURN((0));
    }

    mem = mb->mem;
    currentBlock = fb->currentBlock;
    while (currentBlock != (MemBlock *) fb)
    {
        memcpy(mem, currentBlock->mem, fb->blockSize);
        MemBlock_Destroy(currentBlock);
    }

    /* setup the new values */
    fb->currentBlock = mb;
    mb->nextBlock = (MemBlock *) fb;
    fb->delBlock = mb;
    mb->numUsedElements = fb->numUsedElements;

    RWRETURN((1));
}

MemBlock           *
MemBlock_Create(int size)
{

    MemBlock           *mb;

    RWFUNCTION(RWSTRING("MemBlock_Create"));

    mb = (MemBlock *) RwMalloc(sizeof(MemBlock));
    if (!mb)
    {
        RWRETURN(((MemBlock *)NULL));
    }

    mb->mem = RwMalloc(size);
    if (!mb->mem)
    {
        RwFree(mb);

        RWRETURN(((MemBlock *)NULL));
    }

#if (0)
    mb->currentElement = mb->mem;
#endif /* (0) */
    mb->numUsedElements = 0;

    RWRETURN((mb));
}

int
MemBlock_Destroy(MemBlock * mb)
{
    RWFUNCTION(RWSTRING("MemBlock_Destroy"));

    RwFree(mb->mem);
    RwFree(mb);

    RWRETURN((1));
}

/* Matrix utilities */

RwMatrix           *
MatrixSetAtPos(RwMatrix * matrix, RwV3d * dir, RwV3d * pos)
{

    RwV3d               u, v, at, up, right;
    RwReal              dot, len;
    RwMatrix            matrixRot, matrixTran;

    RWFUNCTION(RWSTRING("MatrixSetAtPos"));

    /* proposed up vector */
    u.x = (RwReal) (0);
    u.y = (RwReal) (1);
    u.z = (RwReal) (0);

    /* find the real up */
    at = *dir;
    _rwV3dNormalize(&at, &at);
    dot = RwV3dDotProduct(&u, &at);
    RwV3dScale(&v, &at, dot);
    RwV3dSub(&up, &u, &v);

    len = RwV3dNormalize(&up, &up);
    if (len < (RwReal) (0.00001))
    {
        /* at must be near vertical */
        up.x = (RwReal) (0);
        up.y = (RwReal) (0);
        up.z = (RwReal) (-1);  /* wrong */
    }

    /* find right */
    RwV3dCrossProduct(&right, &at, &up);

    /* set the matrix's new orientation */
    RwMatrixSetIdentity(&matrixRot);
    *RwMatrixGetUp(&matrixRot) = up;
    *RwMatrixGetAt(&matrixRot) = at;
    *RwMatrixGetRight(&matrixRot) = right;

    /* RwMatrixUpdate(&matrixRot); */
    RwMatrixOptimize(&matrixRot, RWMATRIXOPTIMIZETOLERANCE);

    /* set the matrix's new position */
    RwMatrixSetIdentity(&matrixTran);
    RwMatrixTranslate(&matrixTran, pos, rwCOMBINEREPLACE);

    /* combine the translation abd rotation */
    RwMatrixSetIdentity(matrix);
    RwMatrixMultiply(matrix, &matrixTran, &matrixRot);

    RWRETURN((matrix));
}

/* finds an arbitary prependicular vector to the orginal */
RwV3d              *
VerctorPerpendicular(RwV3d * out, RwV3d * in)
{

    RwV3d               u, v, at, up;
    RwReal              dot, len;

    RWFUNCTION(RWSTRING("VerctorPerpendicular"));

    /* proposed perpendicular vector */
    u.x = (RwReal) (0);
    u.y = (RwReal) (0);
    u.z = (RwReal) (1);

    /* find the real perpendicular */
    up = *in;
    _rwV3dNormalize(&up, &up);
    dot = RwV3dDotProduct(&u, &up);
    RwV3dScale(&v, &up, dot);
    RwV3dSub(&at, &u, &v);

    len = RwV3dNormalize(&at, &at);
    if (len < (RwReal) (0.00001))
    {
        /* at must be near horizontal */
        up.x = (RwReal) (0);
        up.y = (RwReal) (1);
        up.z = (RwReal) (0);
    }

    *out = at;

    RWRETURN((out));
}

/**
 * \ingroup rpvrml
 * \ref RpVrmlClumpAddCreaseNormals
 * is used to determine which edges in the clump should be smoothly
 * shaded and which should have a sharp crease. If the positive angle 
 * between surface normals on adjacent faces is greater than the crease
 * angle, then the edge between those two faces will be creased, otherwise
 * it will be smooth shaded.
 * 
 * The VRML plug-in must be attached before using this function.
 * \param clump  to the clump.
 * \param creaseAngle  A RwReal value equal to the crease angle.
 * \return
 * pointer to the clump if successful or NULL if there is an
 * error.
 * \see RpVrmlClumpRead
 * \see RpVrmlPluginAttach
 */
RpClump            *
RpVrmlClumpAddCreaseNormals(RpClump * clump, RwReal creaseAngle)
{
    RWAPIFUNCTION(RWSTRING("RpVrmlClumpAddCreaseNormals"));

    RpClumpForAllAtomics(clump, AddCreaseNoramls, &creaseAngle);

    RWRETURN((clump));
}
