/*
 * Copyright (C) 1996-1997 Id Software, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 */
// d_sprite.c: software top-level rasterization driver module for drawing
// sprites

#include "quakedef.h"
#include "d_local.h"

static int    sprite_height;
static int    minindex, maxindex;
static sspan_t *sprite_spans;

#if !id386

/*
 * D_SpriteDrawSpans
 */
void D_SpriteDrawSpans (sspan_t * pspan) {
    int           count, spancount, izistep;
    int           izi;
    byte         *pbase, *pdest;
    fixed16_t     s, t, snext, tnext, sstep, tstep;
    float         sdivz, tdivz, zi, z, du, dv, spancountminus1;
    float         sdivz8stepu, tdivz8stepu, zi8stepu;
    byte          btemp;
    short        *pz;

    sstep = 0;                         // keep compiler happy
    tstep = 0;                         // ditto

    pbase = cacheblock;

    sdivz8stepu = d_sdivzstepu * 8;
    tdivz8stepu = d_tdivzstepu * 8;
    zi8stepu = d_zistepu * 8;

    // we count on FP exceptions being turned off to avoid range problems
    izistep = (int) (d_zistepu * 0x8000 * 0x10000);

    do {
        pdest = (byte *) d_viewbuffer + (screenwidth * pspan->v) + pspan->u;
        pz = d_pzbuffer + (d_zwidth * pspan->v) + pspan->u;

        count = pspan->count;

        if (count <= 0)
            goto NextSpan;

        // calculate the initial s/z, t/z, 1/z, s, and t and clamp
        du = (float) pspan->u;
        dv = (float) pspan->v;

        sdivz = d_sdivzorigin + dv * d_sdivzstepv + du * d_sdivzstepu;
        tdivz = d_tdivzorigin + dv * d_tdivzstepv + du * d_tdivzstepu;
        zi = d_ziorigin + dv * d_zistepv + du * d_zistepu;
        z = (float) 0x10000 / zi;      // prescale to 16.16 fixed-point
        // we count on FP exceptions being turned off to avoid range problems
        izi = (int) (zi * 0x8000 * 0x10000);

        s = (int) (sdivz * z) + sadjust;
        if (s > bbextents)
            s = bbextents;
        else if (s < 0)
            s = 0;

        t = (int) (tdivz * z) + tadjust;
        if (t > bbextentt)
            t = bbextentt;
        else if (t < 0)
            t = 0;

        do {
            // calculate s and t at the far end of the span
            if (count >= 8)
                spancount = 8;
            else
                spancount = count;

            count -= spancount;

            if (count) {
                // calculate s/z, t/z, zi->fixed s and t at far end of span,
                // calculate s and t steps across span by shifting
                sdivz += sdivz8stepu;
                tdivz += tdivz8stepu;
                zi += zi8stepu;
                z = (float) 0x10000 / zi;       // prescale to 16.16
                // fixed-point

                snext = (int) (sdivz * z) + sadjust;
                if (snext > bbextents)
                    snext = bbextents;
                else if (snext < 8)
                    snext = 8;         // prevent round-off error on <0 steps
                // from
                // from causing overstepping & running off the
                // edge of the texture

                tnext = (int) (tdivz * z) + tadjust;
                if (tnext > bbextentt)
                    tnext = bbextentt;
                else if (tnext < 8)
                    tnext = 8;         // guard against round-off error on <0
                // steps

                sstep = (snext - s) >> 3;
                tstep = (tnext - t) >> 3;
            } else {
                // calculate s/z, t/z, zi->fixed s and t at last pixel in span
                // (so
                // can't step off polygon), clamp, calculate s and t steps
                // across
                // span by division, biasing steps low so we don't run off the
                // texture
                spancountminus1 = (float) (spancount - 1);
                sdivz += d_sdivzstepu * spancountminus1;
                tdivz += d_tdivzstepu * spancountminus1;
                zi += d_zistepu * spancountminus1;
                z = (float) 0x10000 / zi;       // prescale to 16.16
                // fixed-point
                snext = (int) (sdivz * z) + sadjust;
                if (snext > bbextents)
                    snext = bbextents;
                else if (snext < 8)
                    snext = 8;         // prevent round-off error on <0 steps
                // from
                // from causing overstepping & running off the
                // edge of the texture

                tnext = (int) (tdivz * z) + tadjust;
                if (tnext > bbextentt)
                    tnext = bbextentt;
                else if (tnext < 8)
                    tnext = 8;         // guard against round-off error on <0
                // steps

                if (spancount > 1) {
                    sstep = (snext - s) / (spancount - 1);
                    tstep = (tnext - t) / (spancount - 1);
                }
            }

            do {
                btemp = *(pbase + (s >> 16) + (t >> 16) * cachewidth);
                if (btemp != 255) {
                    if (*pz <= (izi >> 16)) {
                        *pz = izi >> 16;
                        *pdest = btemp;
                    }
                }

                izi += izistep;
                pdest++;
                pz++;
                s += sstep;
                t += tstep;
            }
            while (--spancount > 0);

            s = snext;
            t = tnext;

        }
        while (count > 0);

      NextSpan:
        pspan++;

    }
    while (pspan->count != DS_SPAN_LIST_END);
}

#endif

/*
 * D_SpriteScanLeftEdge
 */
void D_SpriteScanLeftEdge (void) {
    int           i, v, itop, ibottom, lmaxindex;
    emitpoint_t  *pvert, *pnext;
    sspan_t      *pspan;
    float         du, dv, vtop, vbottom, slope;
    fixed16_t     u, u_step;

    pspan = sprite_spans;
    i = minindex;
    if (i == 0)
        i = r_spritedesc.nump;

    lmaxindex = maxindex;
    if (lmaxindex == 0)
        lmaxindex = r_spritedesc.nump;

    vtop = ceil (r_spritedesc.pverts[i].v);

    do {
        pvert = &r_spritedesc.pverts[i];
        pnext = pvert - 1;

        vbottom = ceil (pnext->v);

        if (vtop < vbottom) {
            du = pnext->u - pvert->u;
            dv = pnext->v - pvert->v;
            slope = du / dv;
            u_step = (int) (slope * 0x10000);
            // adjust u to ceil the integer portion
            u = (int) ((pvert->u + (slope * (vtop - pvert->v))) * 0x10000) + (0x10000 - 1);
            itop = (int) vtop;
            ibottom = (int) vbottom;

            for (v = itop; v < ibottom; v++) {
                pspan->u = u >> 16;
                pspan->v = v;
                u += u_step;
                pspan++;
            }
        }

        vtop = vbottom;

        i--;
        if (i == 0)
            i = r_spritedesc.nump;

    }
    while (i != lmaxindex);
}

/*
 * D_SpriteScanRightEdge
 */
void D_SpriteScanRightEdge (void) {
    int           i, v, itop, ibottom;
    emitpoint_t  *pvert, *pnext;
    sspan_t      *pspan;
    float         du, dv, vtop, vbottom, slope, uvert, unext, vvert, vnext;
    fixed16_t     u, u_step;

    pspan = sprite_spans;
    i = minindex;

    vvert = r_spritedesc.pverts[i].v;
    if (vvert < r_refdef.fvrecty_adj)
        vvert = r_refdef.fvrecty_adj;
    if (vvert > r_refdef.fvrectbottom_adj)
        vvert = r_refdef.fvrectbottom_adj;

    vtop = ceil (vvert);

    do {
        pvert = &r_spritedesc.pverts[i];
        pnext = pvert + 1;

        vnext = pnext->v;
        if (vnext < r_refdef.fvrecty_adj)
            vnext = r_refdef.fvrecty_adj;
        if (vnext > r_refdef.fvrectbottom_adj)
            vnext = r_refdef.fvrectbottom_adj;

        vbottom = ceil (vnext);

        if (vtop < vbottom) {
            uvert = pvert->u;
            if (uvert < r_refdef.fvrectx_adj)
                uvert = r_refdef.fvrectx_adj;
            if (uvert > r_refdef.fvrectright_adj)
                uvert = r_refdef.fvrectright_adj;

            unext = pnext->u;
            if (unext < r_refdef.fvrectx_adj)
                unext = r_refdef.fvrectx_adj;
            if (unext > r_refdef.fvrectright_adj)
                unext = r_refdef.fvrectright_adj;

            du = unext - uvert;
            dv = vnext - vvert;
            slope = du / dv;
            u_step = (int) (slope * 0x10000);
            // adjust u to ceil the integer portion
            u = (int) ((uvert + (slope * (vtop - vvert))) * 0x10000) + (0x10000 - 1);
            itop = (int) vtop;
            ibottom = (int) vbottom;

            for (v = itop; v < ibottom; v++) {
                pspan->count = (u >> 16) - pspan->u;
                u += u_step;
                pspan++;
            }
        }

        vtop = vbottom;
        vvert = vnext;

        i++;
        if (i == r_spritedesc.nump)
            i = 0;

    }
    while (i != maxindex);

    pspan->count = DS_SPAN_LIST_END;   // mark the end of the span list
}

/*
 * D_SpriteCalculateGradients
 */
void D_SpriteCalculateGradients (void) {
    vec3_t        p_normal, p_saxis, p_taxis, p_temp1;
    float         distinv;

    TransformVector (r_spritedesc.vpn, p_normal);
    TransformVector (r_spritedesc.vright, p_saxis);
    TransformVector (r_spritedesc.vup, p_taxis);
    VectorNegate (p_taxis, p_taxis);

    distinv = 1.0 / (-DotProduct (modelorg, r_spritedesc.vpn));

    d_sdivzstepu = p_saxis[0] * xscaleinv;
    d_tdivzstepu = p_taxis[0] * xscaleinv;

    d_sdivzstepv = -p_saxis[1] * yscaleinv;
    d_tdivzstepv = -p_taxis[1] * yscaleinv;

    d_zistepu = p_normal[0] * xscaleinv * distinv;
    d_zistepv = -p_normal[1] * yscaleinv * distinv;

    d_sdivzorigin = p_saxis[2] - xcenter * d_sdivzstepu - ycenter * d_sdivzstepv;
    d_tdivzorigin = p_taxis[2] - xcenter * d_tdivzstepu - ycenter * d_tdivzstepv;
    d_ziorigin = p_normal[2] * distinv - xcenter * d_zistepu - ycenter * d_zistepv;

    TransformVector (modelorg, p_temp1);

    sadjust = ((fixed16_t) (DotProduct (p_temp1, p_saxis) * 0x10000 + 0.5)) - (-(cachewidth >> 1) << 16);
    tadjust = ((fixed16_t) (DotProduct (p_temp1, p_taxis) * 0x10000 + 0.5)) - (-(sprite_height >> 1) << 16);

    // -1 (-epsilon) so we never wander off the edge of the texture
    bbextents = (cachewidth << 16) - 1;
    bbextentt = (sprite_height << 16) - 1;
}

/*
 * D_DrawSprite
 */
void D_DrawSprite (void) {
    int           i, nump;
    float         ymin, ymax;
    emitpoint_t  *pverts;
    sspan_t       spans[MAXHEIGHT + 1];

    sprite_spans = spans;

    // find the top and bottom vertices, and make sure there's at least one
    // scan to
    // draw
    ymin = 999999.9;
    ymax = -999999.9;
    pverts = r_spritedesc.pverts;

    for (i = 0; i < r_spritedesc.nump; i++) {
        if (pverts->v < ymin) {
            ymin = pverts->v;
            minindex = i;
        }

        if (pverts->v > ymax) {
            ymax = pverts->v;
            maxindex = i;
        }

        pverts++;
    }

    ymin = ceil (ymin);
    ymax = ceil (ymax);

    if (ymin >= ymax)
        return;                        // doesn't cross any scans at all

    cachewidth = r_spritedesc.pspriteframe->width;
    sprite_height = r_spritedesc.pspriteframe->height;
    cacheblock = (byte *) & r_spritedesc.pspriteframe->pixels[0];

    // copy the first vertex to the last vertex, so we don't have to deal with
    // wrapping
    nump = r_spritedesc.nump;
    pverts = r_spritedesc.pverts;
    pverts[nump] = pverts[0];

    D_SpriteCalculateGradients ();
    D_SpriteScanLeftEdge ();
    D_SpriteScanRightEdge ();
    D_SpriteDrawSpans (sprite_spans);
}
