summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/view.cc
diff options
context:
space:
mode:
Diffstat (limited to 'crawl-ref/source/view.cc')
-rw-r--r--crawl-ref/source/view.cc197
1 files changed, 181 insertions, 16 deletions
diff --git a/crawl-ref/source/view.cc b/crawl-ref/source/view.cc
index 20ca4468ea..3858f89aba 100644
--- a/crawl-ref/source/view.cc
+++ b/crawl-ref/source/view.cc
@@ -27,6 +27,7 @@
#include "view.h"
#include <string.h>
+#include <cmath>
#ifdef DOS
#include <conio.h>
@@ -1144,6 +1145,50 @@ static int find_next_intercept(double* accx, double* accy, const double slope)
return rc;
}
+double ray_def::reflect(double p, double c) const
+{
+ return (c + c - p);
+}
+
+double ray_def::reflect(bool rx, double oldx, double newx) const
+{
+ if (rx? abs(slope) > 1.0 : abs(slope) < 1.0)
+ return (reflect(oldx, floor(oldx) + 0.5));
+
+ const int flnew = newx;
+ const int flold = oldx;
+ return (reflect(oldx,
+ flnew > flold? flnew :
+ flold > flnew? flold :
+ (newx + oldx) / 2));
+}
+
+void ray_def::set_reflect_point(const double oldx, const double oldy,
+ double *newx, double *newy,
+ bool blocked_x, bool blocked_y)
+{
+ if (blocked_x == blocked_y)
+ {
+ // What to do?
+ *newx = oldx;
+ *newy = oldy;
+ return;
+ }
+
+ if (blocked_x)
+ {
+ ASSERT(int(oldy) != int(*newy));
+ *newy = oldy;
+ *newx = reflect(true, oldx, *newx);
+ }
+ else
+ {
+ ASSERT(int(oldx) != int(*newx));
+ *newx = oldx;
+ *newy = reflect(false, oldy, *newy);
+ }
+}
+
void ray_def::advance_and_bounce()
{
// 0 = down-right, 1 = down-left, 2 = up-left, 3 = up-right
@@ -1152,9 +1197,14 @@ void ray_def::advance_and_bounce()
{ 1, 3, 2 }, { 0, 2, 3 }, { 3, 1, 0 }, { 2, 0, 1 }
};
int oldx = x(), oldy = y();
- int rc = advance();
+ const double oldaccx = accx, oldaccy = accy;
+ int rc = advance(true);
int newx = x(), newy = y();
ASSERT( grid_is_solid(grd[newx][newy]) );
+
+ const bool blocked_x = grid_is_solid(grd[oldx][newy]);
+ const bool blocked_y = grid_is_solid(grd[newx][oldy]);
+
if ( double_is_zero(slope) || slope > 100.0 )
quadrant = bouncequad[quadrant][2];
else if ( rc != 2 )
@@ -1162,8 +1212,6 @@ void ray_def::advance_and_bounce()
else
{
ASSERT( (oldx != newx) && (oldy != newy) );
- bool blocked_x = grid_is_solid(grd[oldx][newy]);
- bool blocked_y = grid_is_solid(grd[newx][oldy]);
if ( blocked_x && blocked_y )
quadrant = bouncequad[quadrant][rc];
else if ( blocked_x )
@@ -1171,18 +1219,54 @@ void ray_def::advance_and_bounce()
else
quadrant = bouncequad[quadrant][0];
}
- advance();
+
+ set_reflect_point(oldaccx, oldaccy, &accx, &accy, blocked_x, blocked_y);
}
-void ray_def::regress()
+void ray_def::regress(const coord_def &point)
{
int opp_quadrant[4] = { 2, 3, 0, 1 };
quadrant = opp_quadrant[quadrant];
- advance();
+ advance(true, &point);
quadrant = opp_quadrant[quadrant];
}
-int ray_def::advance()
+int ray_def::advance_through(const coord_def &target)
+{
+ return (advance(true, &target));
+}
+
+int ray_def::advance(bool shortest_possible, const coord_def *target)
+{
+ if (!shortest_possible)
+ return (raw_advance());
+
+ // If we want to minimize the number of moves on the ray, look one
+ // step ahead and see if we can get a diagonal.
+
+ const coord_def old(accx, accy);
+ const int ret = raw_advance();
+
+ if (ret == 2 || (target && pos() == *target))
+ return (ret);
+
+ const double maccx = accx, maccy = accy;
+ if (raw_advance() != 2)
+ {
+ const coord_def second(accx, accy);
+ // If we can convert to a diagonal, do so.
+ if ((second - old).abs() == 2)
+ return (2);
+ }
+
+ // No diagonal, so roll back.
+ accx = maccx;
+ accy = maccy;
+
+ return (ret);
+}
+
+int ray_def::raw_advance()
{
int rc;
switch ( quadrant )
@@ -1566,9 +1650,12 @@ static int cyclic_offset( unsigned int ui, int cycle_dir, int startpoint,
// If cycle_dir is 0, find the first fitting ray. If it is 1 or -1,
// assume that ray is appropriately filled in, and look for the next
// ray in that cycle direction.
+// If find_shortest is true, examine all rays that hit the target and
+// take the shortest (starting at ray.fullray_idx).
bool find_ray( int sourcex, int sourcey, int targetx, int targety,
- bool allow_fallback, ray_def& ray, int cycle_dir )
+ bool allow_fallback, ray_def& ray, int cycle_dir,
+ bool find_shortest )
{
int cellray, inray;
@@ -1577,6 +1664,10 @@ bool find_ray( int sourcex, int sourcey, int targetx, int targety,
const int absx = signx * (targetx - sourcex);
const int absy = signy * (targety - sourcey);
int cur_offset = 0;
+ int shortest = INFINITE_DISTANCE;
+ int imbalance = INFINITE_DISTANCE;
+ std::vector<coord_def> unaliased_ray;
+
for ( unsigned int fray = 0; fray < fullrays.size(); ++fray )
{
const int fullray = cyclic_offset( fray, cycle_dir, ray.fullray_idx,
@@ -1591,25 +1682,94 @@ bool find_ray( int sourcex, int sourcey, int targetx, int targety,
if ( ray_coord_x[cellray + cur_offset] == absx &&
ray_coord_y[cellray + cur_offset] == absy )
{
+ if (find_shortest)
+ {
+ unaliased_ray.clear();
+ unaliased_ray.push_back(coord_def(0, 0));
+ }
// check if we're blocked so far
bool blocked = false;
+ coord_def c1, c3;
+ int real_length = 0;
for ( inray = 0; inray < cellray; ++inray )
{
- if (grid_is_solid(
- grd[sourcex
- + signx * ray_coord_x[inray + cur_offset]]
- [sourcey
- + signy * ray_coord_y[inray + cur_offset]]))
+ const int xi = signx * ray_coord_x[inray + cur_offset];
+ const int yi = signy * ray_coord_y[inray + cur_offset];
+ if (grid_is_solid(grd[sourcex + xi][sourcey + yi]))
{
- blocked = true;
+ blocked = true;
break;
}
+
+ if (find_shortest)
+ {
+ c3 = coord_def(xi, yi);
+
+ // We've moved at least two steps if inray > 0.
+ if (inray)
+ {
+ // Check for a perpendicular corner on the ray and
+ // pretend that it's a diagonal.
+ if ((c3 - c1).abs() != 2)
+ ++real_length;
+ else
+ {
+ // c2 was a dud move, pop it off
+ unaliased_ray.pop_back();
+ }
+ }
+ else
+ ++real_length;
+
+ unaliased_ray.push_back(c3);
+ c1 = unaliased_ray[real_length - 1];
+ }
+ }
+
+ if (find_shortest)
+ unaliased_ray.push_back(
+ coord_def(signx * absx, signy * absy));
+
+ int cimbalance = 0;
+ // If this ray is a candidate for shortest, calculate
+ // the imbalance. I'm defining 'imbalance' as the
+ // number of consecutive diagonal or orthogonal moves
+ // in the ray. This is a reasonable measure of deviation from
+ // the Bresenham line between our selected source and
+ // destination.
+ if (!blocked && find_shortest && shortest >= real_length)
+ {
+ int diags = 0, straights = 0;
+ for (int i = 1, size = unaliased_ray.size(); i < size;
+ ++i)
+ {
+ const int dist =
+ (unaliased_ray[i] - unaliased_ray[i - 1]).abs();
+ if (dist == 2)
+ {
+ straights = 0;
+ if (++diags > cimbalance)
+ cimbalance = diags;
+ }
+ else
+ {
+ diags = 0;
+ if (++straights > cimbalance)
+ cimbalance = straights;
+ }
+ }
}
- if ( !blocked )
+ if ( !blocked
+ && (!find_shortest
+ || shortest > real_length
+ || (shortest == real_length
+ && imbalance > cimbalance)) )
{
// success!
+ shortest = real_length;
+ imbalance = cimbalance;
ray = fullrays[fullray];
ray.fullray_idx = fullray;
if ( sourcex > targetx )
@@ -1619,11 +1779,16 @@ bool find_ray( int sourcex, int sourcey, int targetx, int targety,
ray.accx += sourcex;
ray.accy += sourcey;
set_ray_quadrant(ray, sourcex, sourcey, targetx, targety);
- return true;
+ if (!find_shortest)
+ return true;
}
}
}
}
+
+ if (find_shortest && shortest != INFINITE_DISTANCE)
+ return (true);
+
if ( allow_fallback )
{
ray.accx = sourcex + 0.5;