/****************************************************************************
 *                                                                          *
 *  Module  :   patchgen.c                                                  *
 *                                                                          *
 *  Purpose :   Support for the creation of RW patches from a set of ctrl   *
 *              points.                                                     *
 *                                                                          *
 ****************************************************************************/

/****************************************************************************
 Includes
 */

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

/* RW includes */
#include "rpplugin.h"
#include <rpdbgerr.h>
#include <rwcore.h>
#include <rpworld.h>

#include "rpplugin.h"
#include "rtbezpat.h"
#include "rppatch310.h"
#include "patchvar.h"
#include "patchgen.h"
#include "patchop.h"

#if (!defined(DOXYGEN))
static const char   rcsid[] __RWUNUSED__ =
    "@@@@(#)$Id: patchgen.c,v 1.23 2001/09/28 12:32:10 johns Exp $";
#endif /* (!defined(DOXYGEN)) */

/*-------------------------------------------------------------------*/

static RpPatch     *
PatchTriPatchInterpNormal(RpPatch * patch, RpPatchNode * patchNode)
{
    RtBezierMatrix      T000;
    RtBezierMatrix      T045;
    RtBezierMatrix      T090;
    RtBezierMatrix      T135;
    RtBezierMatrix      Q;
    RtBezierMatrix      T;
    RwInt32             i;
    RwInt32             j;
    RwInt32             k;
    RwV3d              *nrm;
    RwV3d              *pos;

    RWFUNCTION(RWSTRING("PatchTriPatchInterpNormal"));

    pos = patch->cpt;
    nrm = patch->nrm;

    /* Load Triangle control points */
    k = 0;
    for (j = 0; j <= 3; j++)
    {
        for (i = 0; i <= (3 - j); i++)
        {
            RtBezierV4d        *const dst = &T[j][i];
            const RwV3d        *const src = &pos[patchNode->idx[k++]];

            dst->x = src->x;
            dst->y = src->y;
            dst->z = src->z;
        }
    }

    /* Find equivalent Quadrilaterial control points */
    RtBezierQuadFromTriangle(Q, T);

#if (0 && defined(RWDEBUG) && defined(RWVERBOSE))
    for (j = 0; j <= 3; j++)
    {
        for (i = 0; i <= (3 - j); i++)
        {
            const RtBezierV4d  *const t = &T[j][i];
            const RtBezierV4d  *const q = &Q[j][i];

            RWMESSAGE((" (%d,%d) [ %.2f %.2f %.2f ] [ %.2f %.2f %.2f]",
                       j, i, t->x, t->y, t->z, q->x, q->y, q->z));
        }
    }
#endif /* (0 && defined(RWDEBUG) && defined(RWVERBOSE)) */

    /* Find Quadrilaterial Tangent control points */
    RtBezierQuadTangent(T000, T090, ((RwReal) 0), Q);
    RtBezierQuadTangent(T045, T135, rwPIOVER2 * 0.5, Q);

    /* Find Normals points */
    k = 0;
    for (j = 0; j <= 3; j++)
    {
        const RwReal        u = ((RwReal) j) / 3;

        for (i = 0; i <= (3 - j); i++)
        {
            const RwReal        v = ((RwReal) i) / 3;
            RwV3d              *const dst = &nrm[patchNode->idx[k++]];
            RwV3d               Tan000;
            RwV3d               Tan045;
            RwV3d               Tan090;
            RwV3d               Tan135;
            RwV3d               N;
            RwReal              factor;
            RwReal              candidate;

            dst->x = 0;
            dst->y = 0;
            dst->z = 0;
            factor = 0;

            RtBezierQuadMatrixSampleMacro(&Tan000, T000, u, v);
            RtBezierQuadMatrixSampleMacro(&Tan045, T045, u, v);
            RtBezierQuadMatrixSampleMacro(&Tan090, T090, u, v);
            RtBezierQuadMatrixSampleMacro(&Tan135, T135, u, v);

            RwV3dCrossProductMacro(&N, &Tan000, &Tan045);
            candidate = RwV3dDotProduct(&N, &N);
            if (factor < candidate)
            {
                factor = candidate;
                candidate = (RwReal) sqrt(((RwReal) 1) / candidate);
                RwV3dScaleMacro(dst, &N, candidate);
            }

            RwV3dCrossProductMacro(&N, &Tan000, &Tan090);
            candidate = RwV3dDotProduct(&N, &N);
            if (factor < candidate)
            {
                factor = candidate;
                candidate = (RwReal) sqrt(((RwReal) 1) / candidate);
                RwV3dScaleMacro(dst, &N, candidate);
            }

            RwV3dCrossProductMacro(&N, &Tan000, &Tan135);
            candidate = RwV3dDotProduct(&N, &N);
            if (factor < candidate)
            {
                factor = candidate;
                candidate = (RwReal) sqrt(((RwReal) 1) / candidate);
                RwV3dScaleMacro(dst, &N, candidate);
            }

            RwV3dCrossProductMacro(&N, &Tan045, &Tan090);
            candidate = RwV3dDotProduct(&N, &N);
            if (factor < candidate)
            {
                factor = candidate;
                candidate = (RwReal) sqrt(((RwReal) 1) / candidate);
                RwV3dScaleMacro(dst, &N, candidate);
            }

            RwV3dCrossProductMacro(&N, &Tan045, &Tan135);
            candidate = RwV3dDotProduct(&N, &N);
            if (factor < candidate)
            {
                factor = candidate;
                candidate = (RwReal) sqrt(((RwReal) 1) / candidate);
                RwV3dScaleMacro(dst, &N, candidate);
            }

            RwV3dCrossProductMacro(&N, &Tan090, &Tan135);
            candidate = RwV3dDotProduct(&N, &N);
            if (factor < candidate)
            {
                factor = candidate;
                candidate = (RwReal) sqrt(((RwReal) 1) / candidate);
                RwV3dScaleMacro(dst, &N, candidate);
            }

#if (0 && defined(RWDEBUG) && defined(RWVERBOSE))
            RWMESSAGE((" %f [ %.4f %.4f %.4f ]",
                       factor, dst->x, dst->y, dst->z));
#endif /* (0 && defined(RWDEBUG) && defined(RWVERBOSE)) */
        }
    }

    RWRETURN(patch);
}

/*-------------------------------------------------------------------*/

static RpPatch     *
PatchQuadPatchInterpNormal(RpPatch * patch, RpPatchNode * patchNode)
{
    RtBezierMatrix      T000;
    RtBezierMatrix      T045;
    RtBezierMatrix      T090;
    RtBezierMatrix      T135;
    RtBezierMatrix      Q;
    RwInt32             i;
    RwInt32             j;
    RwInt32             k;
    RwV3d              *nrm;
    RwV3d              *pos;

    RWFUNCTION(RWSTRING("PatchQuadPatchInterpNormal"));

    pos = patch->cpt;
    nrm = patch->nrm;

    /* Load Quadrilaterial control points */
    k = 0;
    for (j = 0; j <= 3; j++)
    {
        for (i = 0; i <= 3; i++)
        {
            RtBezierV4d        *const dst = &Q[j][i];
            const RwV3d        *const src = &pos[patchNode->idx[k++]];

            dst->x = src->x;
            dst->y = src->y;
            dst->z = src->z;
        }
    }

    /* Find Quadrilaterial Tangent control points */
    RtBezierQuadTangent(T000, T090, ((RwReal) 0), Q);
    RtBezierQuadTangent(T045, T135, rwPIOVER2 * 0.5, Q);

    /* Find Normals points */
    k = 0;
    for (j = 0; j <= 3; j++)
    {
        const RwReal        u = ((RwReal) j) / 3;

        for (i = 0; i <= 3; i++)
        {
            const RwReal        v = ((RwReal) i) / 3;
            RwV3d              *const dst = &nrm[patchNode->idx[k++]];
            RwV3d               Tan000;
            RwV3d               Tan045;
            RwV3d               Tan090;
            RwV3d               Tan135;
            RwV3d               N;
            RwReal              factor;
            RwReal              candidate;

            dst->x = 0;
            dst->y = 0;
            dst->z = 0;
            factor = 0;

            RtBezierQuadMatrixSampleMacro(&Tan000, T000, u, v);
            RtBezierQuadMatrixSampleMacro(&Tan045, T045, u, v);
            RtBezierQuadMatrixSampleMacro(&Tan090, T090, u, v);
            RtBezierQuadMatrixSampleMacro(&Tan135, T135, u, v);

            RwV3dCrossProductMacro(&N, &Tan000, &Tan045);
            candidate = RwV3dDotProduct(&N, &N);
            if (factor < candidate)
            {
                factor = candidate;
                candidate = (RwReal) sqrt(((RwReal) 1) / candidate);
                RwV3dScaleMacro(dst, &N, candidate);
            }

            RwV3dCrossProductMacro(&N, &Tan000, &Tan090);
            candidate = RwV3dDotProduct(&N, &N);
            if (factor < candidate)
            {
                factor = candidate;
                candidate = (RwReal) sqrt(((RwReal) 1) / candidate);
                RwV3dScaleMacro(dst, &N, candidate);
            }

            RwV3dCrossProductMacro(&N, &Tan000, &Tan135);
            candidate = RwV3dDotProduct(&N, &N);
            if (factor < candidate)
            {
                factor = candidate;
                candidate = (RwReal) sqrt(((RwReal) 1) / candidate);
                RwV3dScaleMacro(dst, &N, candidate);
            }

            RwV3dCrossProductMacro(&N, &Tan045, &Tan090);
            candidate = RwV3dDotProduct(&N, &N);
            if (factor < candidate)
            {
                factor = candidate;
                candidate = (RwReal) sqrt(((RwReal) 1) / candidate);
                RwV3dScaleMacro(dst, &N, candidate);
            }

            RwV3dCrossProductMacro(&N, &Tan045, &Tan135);
            candidate = RwV3dDotProduct(&N, &N);
            if (factor < candidate)
            {
                factor = candidate;
                candidate = (RwReal) sqrt(((RwReal) 1) / candidate);
                RwV3dScaleMacro(dst, &N, candidate);
            }

            RwV3dCrossProductMacro(&N, &Tan090, &Tan135);
            candidate = RwV3dDotProduct(&N, &N);
            if (factor < candidate)
            {
                factor = candidate;
                candidate = (RwReal) sqrt(((RwReal) 1) / candidate);
                RwV3dScaleMacro(dst, &N, candidate);
            }

#if (0 && defined(RWDEBUG) && defined(RWVERBOSE))
            RWMESSAGE((" %f [ %.4f %.4f %.4f ]",
                       factor, dst->x, dst->y, dst->z));
#endif /* (0 && defined(RWDEBUG) && defined(RWVERBOSE)) */
        }
    }

    RWRETURN(patch);
}

/*-------------------------------------------------------------------*/

static RpMeshHeader *
PatchCreateMeshHeader(RpPatch * patch, RwInt32 __RWUNUSED__ flag)
{
    RpPatchNode        *patchNode;
    RpPatchMaterial    *patchMat;
    RpMeshHeader       *meshHdr;
    RpMesh             *mesh;
    RwInt32             numMat;
    RwInt32             numIdx;
    RwInt32             totalNumIdx;
    RwInt32             meshSize;
    RwInt32             i;
    RxVertexIndex      *meshIdx;

    RWFUNCTION(RWSTRING("PatchCreateMeshHeader"));

    meshHdr = (RpMeshHeader *) NULL;

    if (patch != NULL)
    {
        totalNumIdx = patch->numIndex;

        /* Calc number of meshes. 
         * This is NOT the number of patches, 
         * but number of patch meshes, sorted by material.
         */
        numMat = 0;
        for (patchMat = patch->patchMat;
             patchMat != NULL; patchMat = patchMat->next)
        {
            numMat++;
        }

        /* And generate an output mesh
         * (allow a bit per material for alignment) */
        meshSize = ((sizeof(RpMeshHeader)) +
                    (sizeof(RpMesh) * numMat) +
                    (sizeof(RxVertexIndex) * totalNumIdx));

        meshHdr = _rpMeshHeaderCreate(meshSize);

        meshHdr->flags = rpMESHHEADERPOINTLIST;

        meshHdr->numMeshes = (RwUInt16) numMat;
        meshHdr->serialNum = _rpMeshGetNextSerialNumber();
        meshHdr->firstMeshOffset = 0;

        mesh = (RpMesh *) (meshHdr + 1);
        meshIdx = (RxVertexIndex *) (mesh + numMat);
        totalNumIdx = 0;

        /* Start here */
        for (patchMat = patch->patchMat; patchMat;
             patchMat = patchMat->next)
        {
            /*
             * Create the quad patch.
             */
            mesh->indices = meshIdx;
            numIdx = 0;

            for (patchNode = patchMat->quadPatch;
                 patchNode != NULL; patchNode = patchNode->next)

            {
                for (i = 0; i < 16; i++)
                {
                    *meshIdx++ = patchNode->idx[i];
                    numIdx++;
                }

                /* PATCHBOX("PatchQuadPatchInterpNormal"); */
                PatchQuadPatchInterpNormal(patch, patchNode);
            }

            for (patchNode = patchMat->triPatch;
                 patchNode != NULL; patchNode = patchNode->next)
            {
                for (i = 0; i < 16; i++)
                {
                    *meshIdx++ = patchNode->idx[i];
                    numIdx++;
                }

                /* PATCHBOX("PatchTriPatchInterpNormal"); */
                PatchTriPatchInterpNormal(patch, patchNode);
            }

            mesh->material = patchMat->mat;
            mesh->numIndices = numIdx;

            mesh++;

            totalNumIdx += numIdx;
        }

        meshHdr->totalIndicesInMesh = totalNumIdx;
    }

    RWRETURN(meshHdr);
}

/*-------------------------------------------------------------------*/

/**
 * \ingroup rppatch310
 * \ref RpPatchAtomicSetPatchGeometry is used to attach the patch and
 * geometry to the specified atomic.
 *
 * A geometry created from a patch must be attached using this function
 * and not with RpAtomicSetGeometry. Otherwise the RpAtomic will be treated
 * as a polygonal atomic and not a patch atomic.
 *
 * The patch plugin must be attached before using this function.
 *
 * \param atomic    The atomic.
 * \param patch     The patch.
 * \param geom      The geometry.
 *
 * \return The atomic if successful, NULL otherwise.
 *
 * \see RpPatchCreate
 * \see RpPatchCreateGeometry.
 * \see RpPatchAddControlIndices.
 */

RpAtomic           *
RpPatchAtomicSetPatchGeometry(RpAtomic * atomic, RpPatch * patch,
                              RpGeometry * geom)
{
    RWAPIFUNCTION(RWSTRING("RpPatchAtomicSetPatchGeometry"));

    RWASSERT(atomic);

    if (geom != NULL)
    {
        RpPatchAtomicExt   *patchAtomicExt;

        RpAtomicSetGeometry(atomic, geom, 0);

        patchAtomicExt = (RpPatchAtomicExt *)
            RPPATCHPROP(atomic, rpPatchGlobals.atomicExtOffset);

        patchAtomicExt->version = RPPATCHVERSION;
        patchAtomicExt->flag |= RPPATCHATOMICFLAGPATCH;

        patchAtomicExt->numQuadPatch = patch->numQuadPatch;
        patchAtomicExt->numTriPatch = patch->numTriPatch;

        patchAtomicExt->patch = patch;
    }

    RWRETURN(atomic);
}

/*-------------------------------------------------------------------*/

/**
 * \ingroup rppatch310
 * \ref RpPatchAddControlIndices is used add a set of control point
 * indices to a patch.
 * 
 * A patch object can consist of a multiple sets of control points
 * together which form a surface. Each set consists of 16 control points
 * identified by an index array to support sharing of control points.
 * 
 * The flag identify the set as either quad or triangular. Quad sets
 * requires 16 valid indices and a triangular requiring 10 with the
 * remainder unused.
 * 
 * The index array is copied internally and is not reference.
 * 
 * The patch plugin must be attached before using this function.
 * 
 * \param patch     The patch.
 * \param indices   The array of indices values.
 * \param mat       The material
 * \param flag      The patch flag.
 *
 * \return The patch if successful, NULL otherwise.
 *
 * \see RpPatchCreate
 * \see RpPatchCreateGeometry.
 * \see RpPatchAtomicSetPatchGeometry.
 */

RpPatch            *
RpPatchAddControlIndices(RpPatch * patch, RxVertexIndex * indices,
                         RpMaterial * mat, RwInt32 flag)
{
    RwInt32             i;
    RpPatch            *result;
    RpPatchNode        *patchNode;
    RpPatchMaterial    *patchMat;

    RWAPIFUNCTION(RWSTRING("RpPatchAddControlIndices"));

    /* Assume failure until the end. */
    result = (RpPatch *) NULL;

    /* Check if the material is already in the list. */

    for (patchMat = patch->patchMat;
         patchMat != NULL; patchMat = patchMat->next)

    {
        if (patchMat->mat == mat)
        {
            break;
        }
    }

    /* Not found. Create a new entry in the mat list. */
    if (patchMat == NULL)
    {
        patchMat = (RpPatchMaterial *)
            RwFreeListAlloc(rpPatchGlobals.patchMaterialFreeList);

        if (patchMat != NULL)
        {
            memset(patchMat, 0, sizeof(RpPatchMaterial));

            patchMat->mat = mat;
            patchMat->quadPatch = (RpPatchNode *) NULL;
            patchMat->triPatch = (RpPatchNode *) NULL;

            patchMat->next = patch->patchMat;
            patch->patchMat = patchMat;
        }
        else
        {
            /* Memory exhausion. */
            RWRETURN((RpPatch *) NULL);
        }
    }

    patchNode = (RpPatchNode *)
        RwFreeListAlloc(rpPatchGlobals.patchNodeFreeList);

    if (patchNode != NULL)
    {
        memset(patchNode, 0, sizeof(RpPatchNode));

        /* Copy the indices. */
        for (i = 0; i < 16; i++)
            patchNode->idx[i] = indices[i];

        patchNode->next = (RpPatchNode *) NULL;
        patchNode->flag = flag;
    }
    else
    {
        /* Memory exhausion. */
        RWRETURN((RpPatch *) NULL);
    }

    /* Add the new patch to the material. */
    if (flag & RPPATCHFLAGQUAD)
    {
        patchNode->next = patchMat->quadPatch;
        patchMat->quadPatch = patchNode;
        patchMat->numQuadPatch++;
        patch->numQuadPatch++;
    }
    else
    {
        patchNode->next = patchMat->triPatch;
        patchMat->triPatch = patchNode;
        patchMat->numTriPatch++;
        patch->numTriPatch++;
    }

    /* Increment the patch and index count. */
    patch->numIndex += RPPATCHNUMCONTROLINDICES;

    result = patch;

    RWRETURN(result);

}

/*-------------------------------------------------------------------*/

static void
PatchCreateGeometry(RpPatch * patch, RpGeometry * geom,
                    RpMeshHeader * meshHdr)
{

    RwInt32             i, j;
    RpPatchMaterial    *patchMat;
    RpPatchGeomExt     *patchGeom;
    RpMorphTarget      *morphTgt;
    RwInt32             numControlPoints;

    RWFUNCTION(RWSTRING("PatchCreateGeometry"));

    RWASSERT(patch != NULL);

    numControlPoints = patch->numControlPoints;

    patchGeom = (RpPatchGeomExt *)
        RPPATCHPROP(geom, rpPatchGlobals.geomExtOffset);

    /* Build the material list. */
    for (patchMat = patch->patchMat;
         patchMat != NULL; patchMat = patchMat->next)

    {
        RpMaterial         *const mat = patchMat->mat;

        if (mat)
        {
            _rpMaterialListAppendMaterial(&geom->matList, mat);
        }
    }

    /* Set up the geometry. */
    geom->mesh = meshHdr;

    geom->numTriangles = 0;
    geom->triangles = (RpTriangle *) NULL;

    geom->numVertices = numControlPoints;
    geom->numMorphTargets = 1;

    /* Copy over the control points. */
    morphTgt = geom->morphTarget;

    for (i = 0; i < numControlPoints; i++)
    {
        morphTgt->verts[i] = patch->cpt[i];
        morphTgt->normals[i] = patch->nrm[i];
    }

    /* Smooth the nrms. */
    _rpPatchSmoothNormal(patch, morphTgt->normals);

    /* Copy any tex coords. */
    for (j = 0; j < rwMAXTEXTURECOORDS; j++)
    {
        const RwTexCoords  *const src_texcoords = patch->texCoords[j];
        RwTexCoords        *const dst_texcoords = geom->texCoords[j];

        if (src_texcoords)
        {
            for (i = 0; i < numControlPoints; i++)
            {
                dst_texcoords[i] = src_texcoords[i];
            }
        }
        else
        {
            geom->texCoords[j] = (RwTexCoords *) NULL;
        }
    }

    /* Copy any prelit. */
    if (patch->preLit)
    {
        RwRGBA             *const prelit = geom->preLitLum;

        for (i = 0; i < numControlPoints; i++)
        {
            prelit[i] = patch->preLit[i];
        }
    }

    patchGeom->flag |= RPPATCHATOMICFLAGPATCH;

    RWRETURNVOID();
}

/*-------------------------------------------------------------------*/

/**
 * \ingroup rppatch310
 * \ref RpPatchCreateGeometry is used to create a RpGeometry from a RpPatch.
 * A RpPatch object is not directly renderable. It must be converted
 * to a RpGeometry format and assigned to a RpAtomic.
 *
 * The return geometry is not a triangular mesh but a mesh of the patch's
 * control points.  This mesh is used to generate a triangular mesh
 * during instancing for rendering. A patch object pipeline is
 * used to perform the instancing.
 *
 * The patch plugin must be attached before using this function.
 *
 * \param patch     The patch.
 * \param flag      The flag, identifical to RpGeometry's flag.
 *
 * \return The patch if successful, NULL otherwise.
 *
 * \see RpPatchCreate
 * \see RpPatchAddControlIndices.
 * \see RpPatchAtomicSetPatchGeometry.
 */
RpGeometry         *
RpPatchCreateGeometry(RpPatch * patch, RwInt32 flag)
{
    RpGeometry         *geom;
    RwInt32             numControlPoints;

    RWAPIFUNCTION(RWSTRING("RpPatchCreateGeometry"));

    RWASSERT(patch != NULL);

    numControlPoints = patch->numControlPoints;

    geom = RpGeometryCreate(numControlPoints, 0,
                            (flag | rpGEOMETRYTRISTRIP));

    if (geom != NULL)
    {
        RpMeshHeader       *meshHdr;

        /* Build the mesh. */
        meshHdr = PatchCreateMeshHeader(patch, flag);

        if (meshHdr != NULL)
        {

            PatchCreateGeometry(patch, geom, meshHdr);
        }
    }

    RWRETURN(geom);
}
