/* 
 *
 * Functionality for 2D rendering
 *
 * Copyright (c) 1998 Criterion Software Ltd.
 */

/****************************************************************************
 *                                                                          *
 *  Module  :   stroke.c                                                    *
 *                                                                          *
 *  Purpose :   stroke paths                                                *
 *                                                                          *
 ****************************************************************************/

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

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

#include <rwcore.h>
#include <rpdbgerr.h>

#include "path.h"
#include "gstate.h"
#include "brush.h"
#include "stroke.h"
#include "rt2d.h"

#if (defined(SKY2_DRVMODEL_H))
#include "ps2stroke.h"
#endif /* (defined(SKY2_DRVMODEL_H)) */

static const char   rcsid[] __RWUNUSED__ =
    "@@(#)$Id: stroke.c,v 1.40 2001/08/30 11:35:37 johns Exp $";

/****************************************************************************
 Local Types
 */

/****************************************************************************
 Local (Static) Prototypes
 */

/****************************************************************************
 Local Defines
 */

/****************************************************************************
 Globals (across program)
 */

/****************************************************************************
 Local (static) Globals
 */
#if (0)
static FILE        *cho;
#endif /* (0) */

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

   Functions

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

/****************************************************************************/

#ifdef AMB_SPECIFIC
#ifdef RWDEBUG
static void
WireRender2d(RWIM3DVERTEX * vertex, int vcount,
             RWIMVERTEXINDEX * topo, int icount)
{
    RWIM3DVERTEX        local[256];
    int                 i;

    RWFUNCTION(RWSTRING("WireRender2d"));

    for (i = 0; i < vcount; i++)
    {
        local[i] = vertex[i];
        RWIM3DVERTEXSetRGBA(&(local[i]), 255, 255, 255, 255);
    }

    if (RwIm3DTransform(local, vcount, _rt2dCTMGet(), 0))
    {
        RwRenderStateSet(rwRENDERSTATETEXTURERASTER, NULL);
        RwIm3DRenderIndexedPrimitive(rwPRIMTYPEPOLYLINE, topo, icount);
        RwIm3DEnd();
    }

    RWRETURNVOID();
}
#endif /* RWDEBUG */
#endif /* AMB_SPECIFIC */

/****************************************************************************/
static Rt2dPath    *
DefaultPathStroke2d(Rt2dPath * path, Rt2dBrush * brush)
{
    Rt2dPath           *flat;
    rt2dPathNode       *pnode;
    rt2dPathNode       *firstnode;
    RwInt32             vcount, i, vindex;
    RWIM3DVERTEX       *vdst;
    RwReal              oobaseu, halfwidth, layerDepth, inset;

#if (0)
    rt2dPathNode       *corner = NULL;
#endif /* (0) */

    RWFUNCTION(RWSTRING("DefaultPathStroke2d"));

    /* NULL path is valid */
    if (path)
    {
        RwV2d               pos;
        rt2dShadeParameters sp;
        RwV2d               avgnormal;
        RwMatrix           *ctm = _rt2dCTMGet();
        RwInt32             red;
        RwInt32             green;
        RwInt32             blue;
        RwInt32             alpha;

        layerDepth = Rt2dGlobals.layerDepth;
        halfwidth = brush->halfwidth;
        inset = path->inset;

        /* we'll be using the flattened path */
        flat = path;
        if (!path->flat)
        {
            flat = _rt2dScratchPath();
            _rt2dSubPathFlatten(flat, path);
        }

        firstnode = pnode =
            (rt2dPathNode *) rwSListGetArray(flat->segments);
        vcount = rwSListGetNumEntries(flat->segments);

        if (brush->texture)
        {
            const RwTextureFilterMode filterMode =
                RwTextureGetFilterMode(brush->texture);
            const RwTextureAddressMode addressingMode =
                RwTextureGetAddressing(brush->texture);

            RwRenderStateSet(rwRENDERSTATETEXTURERASTER,
                             (void *)
                             RwTextureGetRaster(brush->texture));
            RwRenderStateSet(rwRENDERSTATETEXTUREFILTER,
                             (void *) filterMode);
            RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS,
                             (void *) addressingMode);
        }
        else
        {
            RwRenderStateSet(rwRENDERSTATETEXTURERASTER, NULL);
        }

        oobaseu = 1.0f / pnode[vcount - 1].dist;
        vdst = brush->vertex;
        vindex = 0;
        for (i = 0; i < vcount; i++, pnode++)
        {
            /* need to flush? (save space for last two vertices) */
            if (vindex >= 256 - 2)
            {
                /* render */
                if (RwIm3DTransform
                    (brush->vertex, vindex, ctm,
                    (brush->texture ? rwIM3D_VERTEXUV : 0) |
                    Rt2dGlobals.TransformFlags))
                {
                    RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRISTRIP,
                                                 Rt2dGlobals.topo,
                                                 vindex);
                    RwIm3DEnd();

#ifdef AMB_SPECIFIC
#ifdef RWDEBUG
                    WireRender2d(brush->vertex, vindex,
                                 Rt2dGlobals.topo, vindex);
#endif
#endif
                }

                /* start new strip */
                vdst = brush->vertex;
                vindex = 0;

                /* bump back to previous vertex */
                i--;
                pnode--;
            }

            /* corner normal */
            RwV2dScale(&avgnormal, &pnode->normal, (halfwidth + inset));

            RwV2dSub(&pos, &pnode->pos, &avgnormal);
            RWIM3DVERTEXSetPos(vdst, pos.x, pos.y, layerDepth);

            if (brush->calcFields & FIELDRGBA)
            {
                RwRGBARealScale(&sp.col, &brush->dtop.col,
                                pnode->dist * oobaseu);
                RwRGBARealAdd(&sp.col, &brush->top.col, &sp.col);

                red = (RwInt32) sp.col.red;
                green = (RwInt32) sp.col.green;
                blue = (RwInt32) sp.col.blue;
                alpha = (RwInt32) sp.col.alpha;

                RWIM3DVERTEXSetRGBA(vdst,
                                    (RwUInt8) red,
                                    (RwUInt8) green,
                                    (RwUInt8) blue, (RwUInt8) alpha);
            }
            else
            {
                RWIM3DVERTEXSetRGBA(vdst,
                                    (RwUInt8) brush->top.col.red,
                                    (RwUInt8) brush->top.col.green,
                                    (RwUInt8) brush->top.col.blue,
                                    (RwUInt8) brush->top.col.alpha);
            }

            if (brush->calcFields & FIELDUV)
            {
                RwV2dScale(&sp.uv, &brush->dtop.uv,
                           pnode->dist * oobaseu);
                RwV2dAdd(&sp.uv, &brush->top.uv, &sp.uv);
                RWIM3DVERTEXSetU(vdst, sp.uv.x);
                RWIM3DVERTEXSetV(vdst, sp.uv.y);
            }

            vdst++;

            RwV2dScale(&avgnormal, &pnode->normal, (halfwidth - inset));

            RwV2dAdd(&pos, &pnode->pos, &avgnormal);
            RWIM3DVERTEXSetPos(vdst, pos.x, pos.y, layerDepth);

            if (brush->calcFields & FIELDRGBA)
            {
                RwRGBARealScale(&sp.col, &brush->dbottom.col,
                                pnode->dist * oobaseu);
                RwRGBARealAdd(&sp.col, &brush->bottom.col, &sp.col);

                red = (RwInt32) sp.col.red;
                green = (RwInt32) sp.col.green;
                blue = (RwInt32) sp.col.blue;
                alpha = (RwInt32) sp.col.alpha;

                RWIM3DVERTEXSetRGBA(vdst,
                                    (RwUInt8) red,
                                    (RwUInt8) green,
                                    (RwUInt8) blue, (RwUInt8) alpha);
            }
            else
            {
                RWIM3DVERTEXSetRGBA(vdst,
                                    (RwUInt8) brush->bottom.col.red,
                                    (RwUInt8) brush->bottom.col.green,
                                    (RwUInt8) brush->bottom.col.blue,
                                    (RwUInt8) brush->bottom.col.alpha);
            }

            if (brush->calcFields & FIELDUV)
            {
                RwV2dScale(&sp.uv, &brush->dbottom.uv,
                           pnode->dist * oobaseu);
                RwV2dAdd(&sp.uv, &brush->bottom.uv, &sp.uv);
                RWIM3DVERTEXSetU(vdst, sp.uv.x);
                RWIM3DVERTEXSetV(vdst, sp.uv.y);
            }

            vdst++;

            vindex += 2;
        }

        /* render scrag end */
        if (vindex > 0)
        {
            /* render */
            if (RwIm3DTransform
                (brush->vertex, vindex, ctm,
                 (brush->texture ? rwIM3D_VERTEXUV : 0) |
                 Rt2dGlobals.TransformFlags))
            {
                RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRISTRIP,
                                             Rt2dGlobals.topo, vindex);
                RwIm3DEnd();

#ifdef AMB_SPECIFIC
#ifdef RWDEBUG
                WireRender2d(brush->vertex, vindex, Rt2dGlobals.topo,
                             vindex);
#endif
#endif
            }
        }

        DefaultPathStroke2d(path->next, brush);
    }

    RWRETURN(path);
}

/**
 * \ingroup rt2d
 * \ref Rt2dPathStroke
 * is used to paint the specified path using the given
 * brush.
 * The include file rt2d.h and the library file rt2d.lib are required to
 * use this function.
 * \param path  Pointer to the path.
 * \param brush  Pointer to the brush.
 * \return a pointer to the path if successful or NULL if there is an
 * error.
 * \see Rt2dBrushSetRGBA
 * \see Rt2dBrushSetUV
 * \see Rt2dBrushSetTexture
 * \see Rt2dBrushSetWidth
 */
Rt2dPath           *
Rt2dPathStroke(Rt2dPath * path, Rt2dBrush * brush)
{
    RWAPIFUNCTION(RWSTRING("Rt2dPathStroke"));

#ifdef SKY2_DRVMODEL_H

    RWRETURN(_rt2dPS2PathStroke(path, brush));

#else /* SKY2_DRVMODEL_H */

    RWRETURN(DefaultPathStroke2d(path, brush));

#endif /* SKY2_DRVMODEL_H */
}
