#include "AppHdr.h"
#include "los.h"
#include <cmath>
#include <algorithm>
#include "bitary.h"
#include "coord.h"
#include "coordit.h"
#include "debug.h"
#include "directn.h"
#include "externs.h"
#include "losparam.h"
#include "player.h"
#include "ray.h"
#include "state.h"
#include "stuff.h"
#include "terrain.h"
const bounds_func& bds_precalc = bds_maxlos;
#define LOS_MAX_ANGLE (2*LOS_MAX_RANGE-2)
#define LOS_INTERCEPT_MULT (2)
const int sh_xo = ENV_SHOW_OFFSET; const int sh_yo = ENV_SHOW_OFFSET;
const coord_def sh_o = coord_def(sh_xo, sh_yo);
struct los_ray;
std::vector<los_ray> fullrays;
std::vector<coord_def> ray_coords;
std::vector<coord_def> cellray_ends;
typedef FixedArray<bit_array*, LOS_MAX_RANGE+1, LOS_MAX_RANGE+1> blockrays_t;
blockrays_t blockrays;
struct cellray;
FixedArray<std::vector<cellray>, LOS_MAX_RANGE+1, LOS_MAX_RANGE+1> min_cellrays;
bit_array *dead_rays = NULL;
bit_array *smoke_rays = NULL;
class quadrant_iterator : public rectangle_iterator
{
public:
quadrant_iterator()
: rectangle_iterator(coord_def(0,0),
coord_def(LOS_MAX_RANGE, LOS_MAX_RANGE))
{
}
};
void clear_rays_on_exit()
{
delete dead_rays;
delete smoke_rays;
for (quadrant_iterator qi; qi; qi++)
delete blockrays(*qi);
}
int _los_radius_sq = LOS_RADIUS_SQ;
void set_los_radius(int r)
{
ASSERT(r <= LOS_MAX_RADIUS);
_los_radius_sq = r * r + 1;
}
int get_los_radius_sq()
{
return _los_radius_sq;
}
bool double_is_zero(const double x)
{
return (x > -EPSILON_VALUE) && (x < EPSILON_VALUE);
}
struct los_ray : public ray_def
{
unsigned int start;
unsigned int length;
los_ray(double ax, double ay, double s)
: ray_def(ax, ay, s), length(0)
{
}
std::vector<coord_def> footprint()
{
std::vector<coord_def> cs;
los_ray copy = *this;
coord_def c;
int cellnum;
for (cellnum = 0; true; ++cellnum)
{
copy.raw_advance_pos();
c = copy.pos();
if (!bds_precalc(c))
break;
cs.push_back(c);
}
return cs;
}
coord_def operator[](unsigned int i)
{
ASSERT(0 <= i && i < length);
return ray_coords[start+i];
}
};
static bool _is_same_ray(los_ray ray, std::vector<coord_def> newray)
{
if (ray.length != newray.size())
return false;
for (unsigned int i = 0; i < ray.length; i++)
if (ray[i] != newray[i])
return false;
return true;
}
static bool _is_duplicate_ray(std::vector<coord_def> newray)
{
for (unsigned int i = 0; i < fullrays.size(); ++i)
if (_is_same_ray(fullrays[i], newray))
return true;
return false;
}
struct cellray
{
los_ray ray;
unsigned int end;
cellray(const los_ray& r, unsigned int e)
: ray(r), end(e), imbalance(-1), slope_diff(-1)
{
}
int index() const { return (ray.start + end); }
coord_def target() const { return ray_coords[index()]; }
coord_def operator[](unsigned int i)
{
ASSERT(0 <= i && i <= end);
return ray_coords[ray.start+i];
}
int imbalance;
double slope_diff;
void calc_params();
};
bool _is_better(const cellray& a, const cellray& b)
{
ASSERT(a.target() == b.target());
ASSERT(a.imbalance >= 0 && b.imbalance >= 0);
if (a.imbalance < b.imbalance)
return (true);
else if (a.imbalance > b.imbalance)
return (false);
else
return (a.slope_diff < b.slope_diff);
}
enum compare_type
{
C_SUBRAY,
C_SUPERRAY,
C_NEITHER
};
compare_type _compare_cellrays(const cellray& a, const cellray& b)
{
if (a.target() != b.target())
return C_NEITHER;
int cura = a.ray.start;
int curb = b.ray.start;
int enda = cura + a.end;
int endb = curb + b.end;
bool maybe_sub = true;
bool maybe_super = true;
while (cura < enda && curb < endb && (maybe_sub || maybe_super))
{
coord_def pa = ray_coords[cura];
coord_def pb = ray_coords[curb];
if (pa.x > pb.x || pa.y > pb.y)
{
maybe_super = false;
curb++;
}
if (pa.x < pb.x || pa.y < pb.y)
{
maybe_sub = false;
cura++;
}
if (pa == pb)
{
cura++;
curb++;
}
}
maybe_sub = maybe_sub && (cura == enda);
maybe_super = maybe_super && (curb == endb);
if (maybe_sub)
return C_SUBRAY; else if (maybe_super)
return C_SUPERRAY;
else
return C_NEITHER;
}
static std::vector<int> _find_minimal_cellrays()
{
FixedArray<std::list<cellray>, LOS_MAX_RANGE+1, LOS_MAX_RANGE+1> minima;
std::list<cellray>::iterator min_it;
for (unsigned int r = 0; r < fullrays.size(); ++r)
{
los_ray ray = fullrays[r];
for (unsigned int i = 0; i < ray.length; ++i)
{
bool dup = false;
cellray c(ray, i);
std::list<cellray>& min = minima(c.target());
bool erased = false;
for (min_it = min.begin();
min_it != min.end() && !dup; )
{
switch(_compare_cellrays(*min_it, c))
{
case C_SUBRAY:
dup = true;
break;
case C_SUPERRAY:
min_it = min.erase(min_it);
erased = true;
break;
case C_NEITHER:
default:
break;
}
if (!erased)
min_it++;
else
erased = false;
}
if (!dup)
min.push_back(c);
}
}
std::vector<int> result;
for (quadrant_iterator qi; qi; qi++)
{
std::list<cellray>& min = minima(*qi);
for (min_it = min.begin(); min_it != min.end(); min_it++)
{
min_it->calc_params();
result.push_back(min_it->index());
}
min.sort(_is_better);
min_cellrays(*qi) = std::vector<cellray>(min.begin(), min.end());
}
return result;
}
static void _register_ray(double accx, double accy, double slope)
{
los_ray ray = los_ray(accx, accy, slope);
std::vector<coord_def> coords = ray.footprint();
if (_is_duplicate_ray(coords))
return;
ray.start = ray_coords.size();
ray.length = coords.size();
for (unsigned int i = 0; i < coords.size(); i++)
ray_coords.push_back(coords[i]);
fullrays.push_back(ray);
}
static void _create_blockrays()
{
const int n_cellrays = ray_coords.size();
blockrays_t all_blockrays;
for (quadrant_iterator qi; qi; qi++)
all_blockrays(*qi) = new bit_array(n_cellrays);
for (unsigned int r = 0; r < fullrays.size(); ++r)
{
los_ray ray = fullrays[r];
for (unsigned int i = 0; i < ray.length; ++i)
{
for (unsigned int j = i + 1; j < ray.length; ++j)
all_blockrays(ray[i])->set(ray.start + j);
}
}
std::vector<int> min_indices = _find_minimal_cellrays();
const int n_min_rays = min_indices.size();
cellray_ends.resize(n_min_rays);
for (int i = 0; i < n_min_rays; ++i)
cellray_ends[i] = ray_coords[min_indices[i]];
for (quadrant_iterator qi; qi; qi++)
{
blockrays(*qi) = new bit_array(n_min_rays);
for (int i = 0; i < n_min_rays; ++i)
blockrays(*qi)->set(i, all_blockrays(*qi)
->get(min_indices[i]));
}
for (quadrant_iterator qi; qi; qi++)
delete all_blockrays(*qi);
dead_rays = new bit_array(n_min_rays);
smoke_rays = new bit_array(n_min_rays);
#ifdef DEBUG_DIAGNOSTICS
mprf( MSGCH_DIAGNOSTICS, "Cellrays: %d Fullrays: %u Minimal cellrays: %u",
n_cellrays, fullrays.size(), n_min_rays );
#endif
}
static int _gcd( int x, int y )
{
int tmp;
while (y != 0)
{
x %= y;
tmp = x;
x = y;
y = tmp;
}
return x;
}
bool complexity_lt( const std::pair<int,int>& lhs,
const std::pair<int,int>& rhs )
{
return lhs.first * lhs.second < rhs.first * rhs.second;
}
void raycast()
{
static bool done_raycast = false;
if (done_raycast)
return;
done_raycast = true;
_register_ray(0.5, 0.5, 1000.0);
_register_ray(0.5, 0.5, 0.0);
std::vector<std::pair<int,int> > xyangles;
for (int xangle = 1; xangle <= LOS_MAX_ANGLE; ++xangle)
for (int yangle = 1; yangle <= LOS_MAX_ANGLE; ++yangle)
{
if (_gcd(xangle, yangle) == 1)
xyangles.push_back(std::pair<int,int>(xangle, yangle));
}
std::sort(xyangles.begin(), xyangles.end(), complexity_lt);
for (unsigned int i = 0; i < xyangles.size(); ++i)
{
const int xangle = xyangles[i].first;
const int yangle = xyangles[i].second;
const double slope = ((double)(yangle)) / xangle;
const double rslope = ((double)(xangle)) / yangle;
for (int intercept = 1; intercept <= LOS_INTERCEPT_MULT*yangle; ++intercept )
{
double xstart = ((double)intercept) / (LOS_INTERCEPT_MULT*yangle);
double ystart = 1;
xstart -= EPSILON_VALUE * xangle;
ystart -= EPSILON_VALUE * yangle;
_register_ray(xstart, ystart, slope);
_register_ray(ystart, xstart, rslope);
}
}
_create_blockrays();
}
static void _set_ray_quadrant(ray_def& ray, int sx, int sy, int tx, int ty)
{
ray.quadx = tx >= sx ? 1 : -1;
ray.quady = ty >= sy ? 1 : -1;
}
static const double VERTICAL_SLOPE = 10000.0;
static double _calc_slope(double x, double y)
{
if (double_is_zero(x))
return (VERTICAL_SLOPE);
const double slope = y / x;
return (slope > VERTICAL_SLOPE ? VERTICAL_SLOPE : slope);
}
static double _slope_factor(const ray_def &ray)
{
double xdiff = fabs(ray.accx - 0.5), ydiff = fabs(ray.accy - 0.5);
if (double_is_zero(xdiff) && double_is_zero(ydiff))
return ray.slope;
const double slope = _calc_slope(ydiff, xdiff);
return (slope + ray.slope) / 2.0;
}
static int _imbalance(ray_def ray, const coord_def& target)
{
int imb = 0;
int diags = 0, straights = 0;
while (ray.pos() != target)
{
adv_type adv = ray.advance_through(target);
if (adv == ADV_XY)
{
straights = 0;
if (++diags > imb)
imb = diags;
diags++;
}
else
{
diags = 0;
if (++straights > imb)
imb = straights;
}
}
return imb;
}
void cellray::calc_params()
{
coord_def trg = target();
imbalance = _imbalance(ray, trg);
slope_diff = _slope_factor(ray) - _calc_slope(trg.x, trg.y);
}
bool _find_ray_se(const coord_def& target, ray_def& ray,
const opacity_func& opc, const bounds_func& bds,
bool cycle)
{
ASSERT(target.x >= 0 && target.y >= 0 && !target.origin());
if (!bds(target))
return false;
ASSERT(bds_precalc(target));
raycast();
const std::vector<cellray> &min = min_cellrays(target);
ASSERT(min.size() > 0);
cellray c = min[0]; unsigned int index = 0;
#ifdef DEBUG_DIAGNOSTICS
if (cycle)
mprf(MSGCH_DIAGNOSTICS, "cycling from %d (total %d)",
ray.cycle_idx, min.size());
#endif
unsigned int start = cycle ? ray.cycle_idx + 1 : 0;
ASSERT(0 <= start && start <= min.size());
int blocked = OPC_OPAQUE;
for (unsigned int i = start;
(blocked >= OPC_OPAQUE) && (i < start + min.size()); i++)
{
index = i % min.size();
c = min[index];
blocked = OPC_CLEAR;
for (unsigned int j = 0; j < c.end && blocked < OPC_OPAQUE; j++)
blocked += opc(c[j]);
}
if (blocked >= OPC_OPAQUE)
return (false);
ray = c.ray;
ray.cycle_idx = index;
return (true);
}
struct opacity_trans : opacity_func
{
const coord_def& source;
int signx, signy;
const opacity_func& orig;
opacity_trans(const opacity_func& opc, const coord_def& s, int sx, int sy)
: source(s), signx(sx), signy(sy), orig(opc)
{
}
CLONE(opacity_trans)
opacity_type operator()(const coord_def &l) const
{
return orig(transform(l));
}
coord_def transform(const coord_def &l) const
{
return coord_def(source.x + signx*l.x, source.y + signy*l.y);
}
};
bool find_ray(const coord_def& source, const coord_def& target,
ray_def& ray, const opacity_func& opc, const bounds_func &bds,
bool cycle)
{
if (target == source || !map_bounds(source) || !map_bounds(target))
return false;
const int signx = ((target.x - source.x >= 0) ? 1 : -1);
const int signy = ((target.y - source.y >= 0) ? 1 : -1);
const int absx = signx * (target.x - source.x);
const int absy = signy * (target.y - source.y);
const coord_def abs = coord_def(absx, absy);
opacity_trans opc_trans = opacity_trans(opc, source, signx, signy);
if (!_find_ray_se(abs, ray, opc_trans, bds, cycle))
return (false);
if (signx < 0)
ray.accx = 1.0 - ray.accx;
if (signy < 0)
ray.accy = 1.0 - ray.accy;
ray.accx += source.x;
ray.accy += source.y;
_set_ray_quadrant(ray, source.x, source.y, target.x, target.y);
return (true);
}
bool exists_ray(const coord_def& source, const coord_def& target,
const opacity_func& opc, const bounds_func &bds)
{
ray_def ray;
return (find_ray(source, target, ray, opc, bds));
}
dungeon_feature_type ray_blocker(const coord_def& source,
const coord_def& target)
{
ray_def ray;
if (!find_ray(source, target, ray, opc_default))
{
ASSERT (false);
return (NUM_REAL_FEATURES);
}
ray.advance(false); int blocked = 0;
while (ray.pos() != target)
{
blocked += opc_solid(ray.pos());
if (blocked >= OPC_OPAQUE)
return (env.grid(ray.pos()));
ray.advance(false);
}
ASSERT (false);
return (NUM_REAL_FEATURES);
}
void fallback_ray(const coord_def& source, const coord_def& target,
ray_def& ray)
{
ray.accx = source.x + 0.5;
ray.accy = source.y + 0.5;
coord_def diff = target - source;
ray.slope = _calc_slope(std::abs(diff.x), std::abs(diff.y));
_set_ray_quadrant(ray, source.x, source.y, target.x, target.y);
}
int num_feats_between(const coord_def& source, const coord_def& target,
dungeon_feature_type min_feat,
dungeon_feature_type max_feat,
bool exclude_endpoints, bool just_check)
{
ray_def ray;
int count = 0;
int max_dist = grid_distance(source, target);
fallback_ray(source, target, ray);
if (exclude_endpoints && ray.pos() == source)
{
ray.advance(true);
max_dist--;
}
int dist = 0;
bool reached_target = false;
while (dist++ <= max_dist)
{
const dungeon_feature_type feat = grd(ray.pos());
if (ray.pos() == target)
reached_target = true;
if (feat >= min_feat && feat <= max_feat
&& (!exclude_endpoints || !reached_target))
{
count++;
if (just_check) return (count);
}
if (reached_target)
break;
ray.advance(true);
}
return (count);
}
bool cell_see_cell(const coord_def& p1, const coord_def& p2)
{
return exists_ray(p1, p2, opc_fullyopaque);
}
void _losight_quadrant(env_show_grid& sh, const los_param& dat, int sx, int sy)
{
const unsigned int num_cellrays = cellray_ends.size();
dead_rays->reset();
smoke_rays->reset();
for (quadrant_iterator qi; qi; qi++)
{
coord_def p = coord_def(sx*(qi->x), sy*(qi->y));
if (!dat.los_bounds(p))
continue;
switch (dat.opacity(p))
{
case OPC_OPAQUE:
*dead_rays |= *blockrays(*qi);
break;
case OPC_HALF:
*dead_rays |= (*smoke_rays & *blockrays(*qi));
*smoke_rays |= *blockrays(*qi);
break;
default:
break;
}
}
for (unsigned int rayidx = 0; rayidx < num_cellrays; ++rayidx)
{
if (!dead_rays->get(rayidx))
{
const coord_def p = coord_def(sx * cellray_ends[rayidx].x,
sy * cellray_ends[rayidx].y);
if (dat.los_bounds(p))
sh(p+sh_o) = dat.appearance(p);
}
}
}
void losight(env_show_grid& sh, const los_param& dat)
{
sh.init(0);
raycast();
const int quadrant_x[4] = { 1, -1, -1, 1 };
const int quadrant_y[4] = { 1, 1, -1, -1 };
for (int q = 0; q < 4; ++q)
_losight_quadrant(sh, dat, quadrant_x[q], quadrant_y[q]);
const coord_def o = coord_def(0,0);
sh(o+sh_o) = dat.appearance(o);
}
struct los_param_funcs : public los_param
{
coord_def center;
const opacity_func& opc;
const bounds_func& bounds;
los_param_funcs(const coord_def& c,
const opacity_func& o, const bounds_func& b)
: center(c), opc(o), bounds(b)
{
}
bool los_bounds(const coord_def& p) const
{
return (map_bounds(p + center) && bounds(p));
}
unsigned appearance(const coord_def& p) const
{
return (grid_appearance(p + center));
}
opacity_type opacity(const coord_def& p) const
{
return (opc(p + center));
}
};
void losight(env_show_grid& sh, const coord_def& center,
const opacity_func& opc, const bounds_func& bounds)
{
losight(sh, los_param_funcs(center, opc, bounds));
}
los_def::los_def(const coord_def& c, const opacity_func &o,
const bounds_func &b)
: center(c), opc(o.clone()), bds(b.clone())
{
}
los_def::los_def(const los_def& los)
: show(los.show), center(los.center),
opc(los.opc->clone()), bds(los.bds->clone())
{
}
los_def& los_def::operator=(const los_def& los)
{
init(los.center, *los.opc, *los.bds);
show = los.show;
return (*this);
}
void los_def::init(const coord_def &c, const opacity_func &o,
const bounds_func &b)
{
set_center(c);
set_opacity(o);
set_bounds(b);
}
los_def::~los_def()
{
delete opc;
delete bds;
}
void los_def::update()
{
losight(show, center, *opc, *bds);
}
void los_def::set_center(const coord_def& c)
{
center = c;
}
void los_def::set_opacity(const opacity_func &o)
{
delete opc;
opc = o.clone();
}
void los_def::set_bounds(const bounds_func &b)
{
delete bds;
bds = b.clone();
}
bool los_def::in_bounds(const coord_def& p) const
{
return ((*bds)(p));
}
bool los_def::see_cell(const coord_def& p) const
{
return (::see_cell(show, center, p));
}
void losight_permissive(env_show_grid &sh, const coord_def& center)
{
for (int x = -ENV_SHOW_OFFSET; x <= ENV_SHOW_OFFSET; ++x)
for (int y = -ENV_SHOW_OFFSET; y <= ENV_SHOW_OFFSET; ++y)
{
const coord_def pos = center + coord_def(x, y);
if (map_bounds(pos))
sh[x + sh_xo][y + sh_yo] = env.grid(pos);
}
}
void calc_show_los()
{
if (!crawl_state.arena && !crawl_state.arena_suspended)
{
losight(env.show, you.pos());
losight(env.no_trans_show, you.pos(), opc_solid);
}
else
{
losight_permissive(env.show, crawl_view.glosc());
}
}
bool see_cell(const env_show_grid &show,
const coord_def &c,
const coord_def &pos)
{
if (c == pos)
return (true);
const coord_def ip = pos - c;
if (ip.rdist() <= ENV_SHOW_OFFSET)
{
const coord_def sp(ip + coord_def(ENV_SHOW_OFFSET, ENV_SHOW_OFFSET));
if (show(sp))
return (true);
}
return (false);
}
bool see_cell(const coord_def &p)
{
return (((crawl_state.arena || crawl_state.arena_suspended)
&& crawl_view.in_grid_los(p))
|| see_cell(env.show, you.pos(), p));
}
bool see_cell_no_trans(const coord_def &p)
{
return see_cell(env.no_trans_show, you.pos(), p);
}
bool trans_wall_blocking(const coord_def &p)
{
return see_cell(p) && !see_cell_no_trans(p);
}