//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 1993-2008 Raven Software
// Copyright(C) 2005-2014 Simon Howard
//
// 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.
//
// P_sight.c

#include <stdlib.h>

#include "doomdef.h"
#include "p_local.h"

/*
==============================================================================

							P_CheckSight

This uses specialized forms of the maputils routines for optimized performance

==============================================================================
*/

fixed_t sightzstart;            // eye z of looker
fixed_t topslope, bottomslope;  // slopes to top and bottom of target

int sightcounts[3];

/*
==============
=
= PTR_SightTraverse
=
==============
*/

boolean PTR_SightTraverse(intercept_t * in)
{
    line_t *li;
    fixed_t slope;

    li = in->d.line;

//
// crosses a two sided line
//
    P_LineOpening(li);

    if (openbottom >= opentop)  // quick test for totally closed doors
        return false;           // stop

    if (li->frontsector->floorheight != li->backsector->floorheight)
    {
        slope = FixedDiv(openbottom - sightzstart, in->frac);
        if (slope > bottomslope)
            bottomslope = slope;
    }

    if (li->frontsector->ceilingheight != li->backsector->ceilingheight)
    {
        slope = FixedDiv(opentop - sightzstart, in->frac);
        if (slope < topslope)
            topslope = slope;
    }

    if (topslope <= bottomslope)
        return false;           // stop

    return true;                // keep going
}



/*
==================
=
= P_SightBlockLinesIterator
=
===================
*/

boolean P_SightBlockLinesIterator(int x, int y)
{
    int offset;
    short *list;
    line_t *ld;
    int s1, s2;
    divline_t dl;

    offset = y * bmapwidth + x;

    offset = *(blockmap + offset);

    for (list = blockmaplump + offset; *list != -1; list++)
    {
        ld = &lines[*list];
        if (ld->validcount == validcount)
            continue;           // line has already been checked
        ld->validcount = validcount;

        s1 = P_PointOnDivlineSide(ld->v1->x, ld->v1->y, &trace);
        s2 = P_PointOnDivlineSide(ld->v2->x, ld->v2->y, &trace);
        if (s1 == s2)
            continue;           // line isn't crossed
        P_MakeDivline(ld, &dl);
        s1 = P_PointOnDivlineSide(trace.x, trace.y, &dl);
        s2 = P_PointOnDivlineSide(trace.x + trace.dx, trace.y + trace.dy,
                                  &dl);
        if (s1 == s2)
            continue;           // line isn't crossed

        // try to early out the check
        if (!ld->backsector)
            return false;       // stop checking

        // store the line for later intersection testing
        intercept_p->d.line = ld;
        intercept_p++;

    }

    return true;                // everything was checked
}

/*
====================
=
= P_SightTraverseIntercepts
=
= Returns true if the traverser function returns true for all lines
====================
*/

boolean P_SightTraverseIntercepts(void)
{
    int count;
    fixed_t dist;
    intercept_t *scan, *in;
    divline_t dl;

    count = intercept_p - intercepts;
//
// calculate intercept distance
//
    for (scan = intercepts; scan < intercept_p; scan++)
    {
        P_MakeDivline(scan->d.line, &dl);
        scan->frac = P_InterceptVector(&trace, &dl);
    }

//
// go through in order
//      
    in = 0;                     // shut up compiler warning

    while (count--)
    {
        dist = INT_MAX;
        for (scan = intercepts; scan < intercept_p; scan++)
            if (scan->frac < dist)
            {
                dist = scan->frac;
                in = scan;
            }

        if (!PTR_SightTraverse(in))
            return false;       // don't bother going farther
        in->frac = INT_MAX;
    }

    return true;                // everything was traversed
}



/*
==================
=
= P_SightPathTraverse
=
= Traces a line from x1,y1 to x2,y2, calling the traverser function for each
= Returns true if the traverser function returns true for all lines
==================
*/

boolean P_SightPathTraverse(fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2)
{
    fixed_t xt1, yt1, xt2, yt2;
    fixed_t xstep, ystep;
    fixed_t partial;
    fixed_t xintercept, yintercept;
    int mapx, mapy, mapxstep, mapystep;
    int count;

    validcount++;
    intercept_p = intercepts;

    if (((x1 - bmaporgx) & (MAPBLOCKSIZE - 1)) == 0)
        x1 += FRACUNIT;         // don't side exactly on a line
    if (((y1 - bmaporgy) & (MAPBLOCKSIZE - 1)) == 0)
        y1 += FRACUNIT;         // don't side exactly on a line
    trace.x = x1;
    trace.y = y1;
    trace.dx = x2 - x1;
    trace.dy = y2 - y1;

    x1 -= bmaporgx;
    y1 -= bmaporgy;
    xt1 = x1 >> MAPBLOCKSHIFT;
    yt1 = y1 >> MAPBLOCKSHIFT;

    x2 -= bmaporgx;
    y2 -= bmaporgy;
    xt2 = x2 >> MAPBLOCKSHIFT;
    yt2 = y2 >> MAPBLOCKSHIFT;

// points should never be out of bounds, but check once instead of
// each block
    if (xt1 < 0 || yt1 < 0 || xt1 >= bmapwidth || yt1 >= bmapheight
        || xt2 < 0 || yt2 < 0 || xt2 >= bmapwidth || yt2 >= bmapheight)
        return false;

    if (xt2 > xt1)
    {
        mapxstep = 1;
        partial = FRACUNIT - ((x1 >> MAPBTOFRAC) & (FRACUNIT - 1));
        ystep = FixedDiv(y2 - y1, abs(x2 - x1));
    }
    else if (xt2 < xt1)
    {
        mapxstep = -1;
        partial = (x1 >> MAPBTOFRAC) & (FRACUNIT - 1);
        ystep = FixedDiv(y2 - y1, abs(x2 - x1));
    }
    else
    {
        mapxstep = 0;
        partial = FRACUNIT;
        ystep = 256 * FRACUNIT;
    }
    yintercept = (y1 >> MAPBTOFRAC) + FixedMul(partial, ystep);


    if (yt2 > yt1)
    {
        mapystep = 1;
        partial = FRACUNIT - ((y1 >> MAPBTOFRAC) & (FRACUNIT - 1));
        xstep = FixedDiv(x2 - x1, abs(y2 - y1));
    }
    else if (yt2 < yt1)
    {
        mapystep = -1;
        partial = (y1 >> MAPBTOFRAC) & (FRACUNIT - 1);
        xstep = FixedDiv(x2 - x1, abs(y2 - y1));
    }
    else
    {
        mapystep = 0;
        partial = FRACUNIT;
        xstep = 256 * FRACUNIT;
    }
    xintercept = (x1 >> MAPBTOFRAC) + FixedMul(partial, xstep);


//
// step through map blocks
// Count is present to prevent a round off error from skipping the break
    mapx = xt1;
    mapy = yt1;


    for (count = 0; count < 64; count++)
    {
        if (!P_SightBlockLinesIterator(mapx, mapy))
        {
            sightcounts[1]++;
            return false;       // early out
        }

        if (mapx == xt2 && mapy == yt2)
            break;

        if ((yintercept >> FRACBITS) == mapy)
        {
            yintercept += ystep;
            mapx += mapxstep;
        }
        else if ((xintercept >> FRACBITS) == mapx)
        {
            xintercept += xstep;
            mapy += mapystep;
        }

    }


//
// couldn't early out, so go through the sorted list
//
    sightcounts[2]++;

    return P_SightTraverseIntercepts();
}



/*
=====================
=
= P_CheckSight
=
= Returns true if a straight line between t1 and t2 is unobstructed
= look from eyes of t1 to any part of t2
=
=====================
*/

boolean P_CheckSight(mobj_t * t1, mobj_t * t2)
{
    int s1, s2;
    int pnum, bytenum, bitnum;

//
// check for trivial rejection
//
    s1 = (t1->subsector->sector - sectors);
    s2 = (t2->subsector->sector - sectors);
    pnum = s1 * numsectors + s2;
    bytenum = pnum >> 3;
    bitnum = 1 << (pnum & 7);

    if (rejectmatrix[bytenum] & bitnum)
    {
        sightcounts[0]++;
        return false;           // can't possibly be connected
    }

//
// check precisely
//              
    sightzstart = t1->z + t1->height - (t1->height >> 2);
    topslope = (t2->z + t2->height) - sightzstart;
    bottomslope = (t2->z) - sightzstart;

    return P_SightPathTraverse(t1->x, t1->y, t2->x, t2->y);
}
