//
//  Copyright (C) 1999 by
//  id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman
//
//  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.
//
// DESCRIPTION:
//      LineOfSight/Visibility checks, uses REJECT Lookup Table.
//
//-----------------------------------------------------------------------------

#include "doomdata.h"
#include "doomstat.h"
#include "doomtype.h"
#include "i_system.h"
#include "m_bbox.h"
#include "m_fixed.h"
#include "p_maputl.h"
#include "p_mobj.h"
#include "p_setup.h"
#include "r_defs.h"
#include "r_main.h"
#include "r_state.h"
#include "tables.h"

//
// P_CheckSight
//
// killough 4/19/98:
// Convert LOS info to struct for reentrancy and efficiency of data locality

typedef struct {
  fixed_t sightzstart, t2x, t2y;   // eye z of looker
  divline_t strace;                // from t1 to t2
  fixed_t topslope, bottomslope;   // slopes to top and bottom of target
  fixed_t bbox[4];
} los_t;

//
// P_DivlineSide
// Returns side 0 (front), 1 (back), or 2 (on).
//
// killough 4/19/98: made static, cleaned up
// killough 12/98: made external

static int DivlineSide(fixed_t x, fixed_t y, const divline_t *node)
{
  fixed_t left = 0, right = 0; // [FG] initialize
  return
    !node->dx ? x == node->x ? 2 : x <= node->x ? node->dy > 0 : node->dy < 0 :
    !node->dy ? (mbf21 ? y : x) == node->y ? 2 : y <= node->y ? node->dx < 0 : node->dx > 0 :
    (right = ((y - node->y) >> FRACBITS) * (node->dx >> FRACBITS)) <
    (left  = ((x - node->x) >> FRACBITS) * (node->dy >> FRACBITS)) ? 0 :
    right == left ? 2 : 1;
}

//
// P_InterceptVector2
// Returns the fractional intercept point
// along the first divline.
//
// killough 4/19/98: made static, cleaned up

static fixed_t P_InterceptVector2(const divline_t *v2, const divline_t *v1)
{
  fixed_t den;
  return (den = FixedMul(v1->dy>>8, v2->dx) - FixedMul(v1->dx>>8, v2->dy)) ?
    FixedDiv(FixedMul((v1->x - v2->x)>>8, v1->dy) +
             FixedMul((v2->y - v1->y)>>8, v1->dx), den) : 0;
}

//
// P_CrossSubsector
// Returns true
//  if strace crosses the given subsector successfully.
//
// killough 4/19/98: made static and cleaned up

static boolean P_CrossSubsector(int num, register los_t *los)
{
  ssline_t *ssline = &sslines[sslines_indexes[num]];
  const ssline_t *ssline_last = &sslines[sslines_indexes[num + 1]];

#ifdef RANGECHECK
  if (num >= numsubsectors)
    I_Error("ss %i with numss = %i", num, numsubsectors);
#endif

  for (; ssline < ssline_last; ssline++)
    {
      divline_t divl;
      fixed_t opentop, openbottom;
      const sector_t *front, *back;
      fixed_t frac;

      // OPTIMIZE: killough 4/20/98: Added quick bounding-box rejection test

      // [FG] Compatibility bug in P_CrossSubsector
      // http://prboom.sourceforge.net/mbf-bugs.html
      if (!demo_compatibility)
      {
      if (ssline->bbox[BOXLEFT  ] > los->bbox[BOXRIGHT ] ||
          ssline->bbox[BOXRIGHT ] < los->bbox[BOXLEFT  ] ||
          ssline->bbox[BOXBOTTOM] > los->bbox[BOXTOP   ] ||
          ssline->bbox[BOXTOP]    < los->bbox[BOXBOTTOM])
      {
          ssline->linedef->validcount = validcount;
          continue;
      }
      }

      // line isn't crossed?
      if (DivlineSide(ssline->x1, ssline->y1, &los->strace)
          == DivlineSide(ssline->x2, ssline->y2, &los->strace))
      {
        ssline->linedef->validcount = validcount;
        continue;
      }

      divl.dx = ssline->x2 - (divl.x = ssline->x1);
      divl.dy = ssline->y2 - (divl.y = ssline->y1);

      // line isn't crossed?
      if (DivlineSide(los->strace.x, los->strace.y, &divl)
          == DivlineSide(los->t2x, los->t2y, &divl))
      {
        ssline->linedef->validcount = validcount;
        continue;
      }

      // allready checked other side?
      if (ssline->linedef->validcount == validcount)
        continue;

      ssline->linedef->validcount = validcount;

      // stop because it is not two sided anyway
      if (!(ssline->linedef->flags & ML_TWOSIDED))
        return false;

      // crosses a two sided line
      front = ssline->seg->frontsector;
      back = ssline->seg->backsector;

      // missed back side on two-sided lines.
      if (demo_compatibility && !back)
      {
        back = GetSectorAtNullAddress();
      }

      // no wall to block sight with?
      if (front->floorheight == back->floorheight   &&
          front->ceilingheight == back->ceilingheight)
        continue;

      // possible occluder
      // because of ceiling height differences
      opentop = MIN(front->ceilingheight, back->ceilingheight);

      // because of floor height differences
      openbottom = MAX(front->floorheight, back->floorheight);

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

      frac = P_InterceptVector2(&los->strace, &divl);

      if (front->floorheight != back->floorheight)
        {
          fixed_t slope = FixedDiv(openbottom - los->sightzstart , frac);
          if (slope > los->bottomslope)
            los->bottomslope = slope;
        }

      if (front->ceilingheight != back->ceilingheight)
        {
          fixed_t slope = FixedDiv(opentop - los->sightzstart , frac);
          if (slope < los->topslope)
            los->topslope = slope;
        }

      if (los->topslope <= los->bottomslope)
        return false;               // stop
    }
  // passed the subsector ok
  return true;
}

//
// P_CrossBSPNode
// Returns true
//  if strace crosses the given node successfully.
//
// killough 4/20/98: rewritten to remove tail recursion, clean up, and optimize

static boolean P_CrossBSPNode(int bspnum, register los_t *los)
{
  while (!(bspnum & NF_SUBSECTOR))
    {
      register const node_t *bsp = nodes + bspnum;
      int side = DivlineSide(los->strace.x,los->strace.y,(divline_t *)bsp)&1;
      if (side == DivlineSide(los->t2x, los->t2y, (divline_t *) bsp))
         bspnum = bsp->children[side]; // doesn't touch the other side
      else         // the partition plane is crossed here
        if (!P_CrossBSPNode(bsp->children[side], los))
          return 0;  // cross the starting side
        else
          bspnum = bsp->children[side^1];  // cross the ending side
    }
  return P_CrossSubsector(bspnum == -1 ? 0 : bspnum & ~NF_SUBSECTOR, los);
}

//
// P_CheckSight
// Returns true
//  if a straight line between t1 and t2 is unobstructed.
// Uses REJECT.
//
// killough 4/20/98: cleaned up, made to use new LOS struct

static boolean P_CheckSight_MBF(mobj_t *t1, mobj_t *t2)
{
  const sector_t *s1 = t1->subsector->sector;
  const sector_t *s2 = t2->subsector->sector;
  int pnum = (s1-sectors)*numsectors + (s2-sectors);
  los_t los;

  // First check for trivial rejection.
  // Determine subsector entries in REJECT table.
  //
  // Check in REJECT table.

  if (rejectmatrix[pnum>>3] & (1 << (pnum&7)))   // can't possibly be connected
    return false;

  // killough 4/19/98: make fake floors and ceilings block monster view
  if ((s1->heightsec != -1 &&
       ((t1->z + t1->height <= sectors[s1->heightsec].floorheight &&
         t2->z >= sectors[s1->heightsec].floorheight) ||
        (t1->z >= sectors[s1->heightsec].ceilingheight &&
         t2->z + t1->height <= sectors[s1->heightsec].ceilingheight)))
      ||
      (s2->heightsec != -1 &&
       ((t2->z + t2->height <= sectors[s2->heightsec].floorheight &&
         t1->z >= sectors[s2->heightsec].floorheight) ||
        (t2->z >= sectors[s2->heightsec].ceilingheight &&
         t1->z + t2->height <= sectors[s2->heightsec].ceilingheight))))
    return false;

  // killough 11/98: shortcut for melee situations
  // [FG] Compatibility bug in P_CheckSight
  // http://prboom.sourceforge.net/mbf-bugs.html
  if (t1->subsector == t2->subsector && demo_version >= DV_MBF)     // same subsector? obviously visible
    return true;

  // An unobstructed LOS is possible.
  // Now look from eyes of t1 to any part of t2.

  validcount++;

  los.topslope = (los.bottomslope = t2->z - (los.sightzstart =
                                             t1->z + t1->height -
                                             (t1->height>>2))) + t2->height;
  los.strace.dx = (los.t2x = t2->x) - (los.strace.x = t1->x);
  los.strace.dy = (los.t2y = t2->y) - (los.strace.y = t1->y);

  if (t1->x > t2->x)
    los.bbox[BOXRIGHT] = t1->x, los.bbox[BOXLEFT] = t2->x;
  else
    los.bbox[BOXRIGHT] = t2->x, los.bbox[BOXLEFT] = t1->x;

  if (t1->y > t2->y)
    los.bbox[BOXTOP] = t1->y, los.bbox[BOXBOTTOM] = t2->y;
  else
    los.bbox[BOXTOP] = t2->y, los.bbox[BOXBOTTOM] = t1->y;

  // the head node is the last node output
  return P_CrossBSPNode(numnodes-1, &los);
}

boolean checksight12;
boolean (*P_CheckSight)(mobj_t *t1, mobj_t *t2) = P_CheckSight_MBF;

void P_UpdateCheckSight(void)
{
  P_CheckSight = CRITICAL(checksight12) ? P_CheckSight_12 : P_CheckSight_MBF;
}

//
// mbf21: P_CheckFov
// Returns true if t2 is within t1's field of view.
// Not directly related to P_CheckSight, but often
// used in tandem.
//
// Adapted from Eternity, so big thanks to Quasar
// 

boolean P_CheckFov(mobj_t *t1, mobj_t *t2, angle_t fov)
{
  angle_t angle, minang, maxang;

  angle = R_PointToAngle2(t1->x, t1->y, t2->x, t2->y);
  minang = t1->angle - fov / 2;
  maxang = t1->angle + fov / 2;

  return((minang > maxang) ? angle >= minang || angle <= maxang
                           : angle >= minang && angle <= maxang);
}

//----------------------------------------------------------------------------
//
// $Log: p_sight.c,v $
// Revision 1.7  1998/05/07  00:55:55  killough
// Make monsters directly tangent to water surface blind
//
// Revision 1.6  1998/05/03  22:34:33  killough
// beautification, header cleanup
//
// Revision 1.5  1998/05/01  14:52:09  killough
// beautification
//
// Revision 1.4  1998/04/24  11:43:08  killough
// minor optimization
//
// Revision 1.3  1998/04/20  11:13:41  killough
// Fix v1.9 demo sync probs, make monsters blind across water
//
// Revision 1.2  1998/01/26  19:24:24  phares
// First rev with no ^Ms
//
// Revision 1.1.1.1  1998/01/19  14:03:00  rand
// Lee's Jan 19 sources
//
//----------------------------------------------------------------------------
