los.cc: basic raycasting algorithm; losight(), see_grid() etc. ray.cc: ray_def implementation. mon-los.cc: monster_los
This includes adding a bunch of #includes; there's probably some obsolete includes of view.h now.
ACZYEIX7WMPIIODKCATBCUE626AJ4ZGGBOMVC6BGXM27EQU2RECAC AJZ7S56FRB76V3R4NRLECP55MCJG2HU7MREG6OP5GVT3BH2Z2QFQC FBVU7IUFVPEVV5ON6PWJWJ7GW27F2ANGN5O6BKVDBYTJLGAB3IZQC 75SFJQTH5RWBB7T4R4FZW4DSZJAJWA7WNXP73ZSTX4DFDTWXEVQAC R4V76NHTUCQ25LBZKMS34INQEXVTJ5TT3QZSPGG6S2WR2FYKK5DQC 3WFU4DTXWJ56KQZ4SRJESF42HB6YKOTLZ2ROGKEHO4RWRPABVFQAC T72EIKU4IJF3G3FBUDWMLMAFFUEF3ZNMKJFB5G3PZLFWH7W3H44AC MWVTCJK5QG3PA7QCTNZHNKVG7CVTBI4VV5IAIYMSRI65WKD4UMNQC C7MS2OSFVKCD7M7H5WKFAX3WXAWK4O3JULA56WPSXFXE22XB4QSQC YDQAWQ3JOVG5Y5VJH6UL35LXFCH55XT5WWETU63RSYYYIA4K3IWAC 3GH3RH5QFHWAELRYSRB2BAS5TEN6SYRXLNZHN65HPQCJWILENGYQC WUT3GMXCKAOUKQMOODQBZR7SWEVCSMX2UDNRM6DC37MD6VTBPRSQC KFULGQQOHWUTXOM3BXCCYPGGVGGY4Z6265XUFRCBPNLTZAEHJZSQC XPCGZBHHSL6MB3ORMUJI64BAERU6AZTIY6RK56BBW7SNB3IK24IAC K2CS6TCX2NDVL2ASEHGP4J4K4IJ6FP3ANNKTSIWVG43HPYSBX6ZQC SDLKLUNFGVKDS55DDJZCBAVIB7NL3RRYPTACAY65SCUQKV6APFSAC HVSBRVFZODK4R7P6EJ7Y7AHZDVRSNSFDHIH3M3DDGG5E26Z4KD5AC ZGZVOMRXLVC42FV67RBTLBOZWFYRL4UHL54A365BR76OUIYRWQXAC ZNMT5CZHP2FC4HTLNA7KYEDGFBXSCUE5QHJOALVPE6RDPHSEDXRQC AUXVWXWIFSTWFA6VZXN2FMG7FQEKRZVV6MD32VQQ7J2RKCXHAVGAC HVEU33HIUHCYFINJMVSSOIRUPZGGVC7NMWUN2EADG2W373JP3WOAC TGJZXTUIAKCFZQJ54ZQEBGFBVZSJCAX6AWDRSH3TP7UJRLGUM5SAC ILOED4VB4I6VPAUTR75ZWX6MXDYXB5DO2EDK2UH67O3HNKWV23RQC RPOZZWKG5GLPHVZZ7ZKMKS64ZMV2LDCQSARBJFJ6FZOTOKCQO7FAC LTX72QGIPNUGWQN5ULPOMFCOPZTK7472DQY4AYX5WM3WHSUVXI5QC ZFGIHLY6UMKMJOU62DUHJWHLFU76ML226WNDRIKOYHMG2BEL7PNQC GQL5SIGBHLU3FMCE54XVGLRY5AZHRM6DUEB722REA2DPLGJSN6EQC RWH6IAVXQD3Q7ITNN5WOOUHBDBYURLXFEURA7QFHS4ZED2CG7MOQC ANOEQTM6IGCBTESKKQ5PCBSDTZ7VGRCMDIOAFEH4R7DJHKWKDFAAC IH5TVAZGKN7IQOTSOJ56WG5C3CMTG3AI63XT3XVNHC7DI4N2TSUAC DGPZZUWTMWADFTFLZ37DHWXXDXMBZHROCFTYL472HI6ETGEK6CBAC Q3B3UVMYEVC4YJUPYVSNTR4DJH4E6J4JJDHZNT5LNOCHCPPMEMXAC SVY2PTCLXR3KNPQAWXVXTTGCC5DR334HOAKHYO3VDDRWM2BWMALAC GVCGKTH5IJ4VSQEIN4CRC7ZFVZW26JPIYNCPTO7GY66CSZZEW3ZQC 542UIZKI65UDRNEMGFFDBWYD5XC7AYLTZ3JZQRR2GHYJALD3YY6QC KG3D4GGXEYUSFORCP4WVOABKHSGNPS6YV4GEO27AGQVPWGQDP76AC UCEAWJ4I6AEFR64SSUFQSX6Q62JGLMJQ5BOO5MQSV3XIE7KAS6CQC YKVVFNKT2M5WG2KBURRTOJG23BJVI6WUBP5JOGYPHQBS4RNGFGWQC 22ORIULMB2NHLFZNJFK2AF275Q6XBKOBE4ZRMOADY64VK5FAUUSQC UZ6N6HOUPGVSPC5NQROEEDWMEGJA5XUWUY2AKH5QG65AZ25PVXDAC AGKLT4LWLCN3JE4K72NLWJ2KWVH7QQYUT22B2YTKEJOIPVR3XITAC C5U3HSOOQ7BKXKXIDS7MLVXUKDTHAWJ5NXNX6YDXTM3GWY5UWX4QC 4PKN27E55QIIHAAGJ3G7XSIYYELDO3HQUPVLVWCFGCUBYDSKL3DQC JJULXW764V5C2HJKZNWQAEWB6QM5YZADD7ZCE35LYTBFEM6PMYCAC 77H4BWWPPGLM3PLZH4QTAJRXIZTSDVNCOKZE223I437FN2UJ34RQC QKGDOYIYKE6B36ION5O2DRW65DWWPZMYNWJVH7LJJ7FPGGM2MYAQC KF62TOB3VZ3HRC2OROCBMK5ULPQILZR4BVJJZWQTJR4XU4F26HIQC WC3PNJUPYSMCYWOETNBJVOFXH5S6ZED4XXJBSF54V7P3HZYVO5KAC LVP5I5DHJGY3DG3DLIYBDVNULBCM7CD3HF4E5DW5QHCM6P2UZEJQC ID373JATLMWAY526Q6Q5FXHRNFWMEOFXPHGPAUUY5OAMPFDN5SJAC 75M6AVUSS3G5EJECJJRB67V5UYDOIV26FZNB2FFCMBZ33EK7FVIQC Q52ILIIFMJ6HBSIUF7V7XQ6DQJGGYMHUZJ6CWVQKDX2JKBHEP2HAC OHJE4HQ2B3HVV4BOPBRFKO6HBRTLZEI74P5PLJF5GLSRD2MFWXHQC S34LKQDIQJLIWVIPASOJBBZ6ZCXDHP5KPS7TRBZJSCDRVNCLK6UAC BHG4L3FEW3UDCNBQ4GLEONTAKFIXTRFCLQ2BQ4YNPF3I6NRZXBGQC VD4KDTGHVKCN35AWREYB4TEOUMCTW7SAUPAMTMF5ABC7VBHVKP4AC 3SQQ7NFTRSYDTYI4A6NWKUMOD65JJ5YPSJJIME6JDAAAN7IF6KGQC BNTPYSO6ECZ76CHHLDP3ASSPDSEUH4Y3E3LETKNXVWAWJRFL3YEQC DMG73XDQHY2X2PHKWIY56XKD3O4NPGZKKIO6GX3IV2LLRVXPGKYQC TZ643KHSE5CUPXFSQ7VYVOCM5MTQ7F4SENEYQX2RNFHGHLQVS3RQC FSJKED4U2SOUP64DTHF2NEGAYY7EUMSIDKC2SATEXAXEVOCNL3CAC PKXXBHS3LWLPZI2QVRX22MSQ4R2626IXRSNHFFYHXYTLJJQU54LQC UDDZ7BNFAK3BDG5UIOMEDWGH4XRJOWV4AERTZOJ7VT6IOKBP4EIQC PEZFWKRHDHV4UJTPK5XJZ3CGTZ3LPTDYSPTYBENLQ7VRSP7YFSIQC OOFJ4IAVGB6DAUN2AWJBKMFKR7NCE5PCFF5IURV6L5TJYK6CHTHAC HFEFKHVV2ULXS6ZEFNX6ZXXUJKME5L2PITQ3VRTKMRUOU3DHXXWQC 6GJYM7D3VKAKCIHAOEZW5XPE7KIJZXJCYZYMHVJAECV5QZWGQAUAC ASH5CK6CPBKMLGGIRJ5GKTWMS5W3OBVHTL66RTYZIPFM6KFBYA3QC H7AOW4T4Q7AKOXREMK6ZXN3GK6A4I24ICE6VTROMJ5Y3LX47TSDAC WT66JDIRTLLP37SHTV4GI3V64JFJ4D25LNRLGCHFG6CLEFKJ3QGQC K2RBO245UPBDAGMUUMB5EPUNKBDMQ2FM4HLCTTF6Y7ILWW2XVSDQC 5KTPCJG42MF2B34CEH6VXAJIOZR6QOS2BWYNW7DXM3WA7N3GH4HQC 33NP4VXH6MMMH4JFK73G4ENZ2VYFKW2AWXIRITZLVIENKDOSJO2AC 7NDXS36TE7QVXTXJWMYSVG5UHCCLPIO4VL6NXFGTDK3ZNKE3A2IAC 547JREUJXTZNYVGHNNAET5F5O5JYYGNTDQB6ABZNT7YX5EY64OHAC CK7CT5TUFUL2AQY7FUHB5JI3FC2KSPWUWHXC6VEUJJ7G4OWUQFTAC QEEJFAETO6B2J4IWDIDCJ5UNIFNNHHG22IWF2CUJRTJJBNE47CWQC BXXOYFMWNQY4TLLJBFYROSH43KO5F4JQTATU3ZEJNKGIJGOIQN4AC JM7UAK777RAVDAVLQLEOBRTGNW2B47S5G55XITJXO243IUNZHVYQC DTO3EUKWHZ5RJNGNCFYXSOVTIPVXPP637F2W7WFGYKJ7JK7VNKNQC FVT2J6IVMSQZYKQGUHQVGT4ADYM7AWUQ4U7766GBRRFMSR2WBMLAC KJO5N6UIPKQ6TZNNOWZEHUAWZNUW7CAHLIW2ARX47K4SIE3N5LYAC CG77HA7A5KTY4POJCZ4PSWFZL6VFYQ7MZEMP2HHMRW4QNBQ7LMRAC UL7XFKMUX3WIU4O2LZANK4ECJ654UZPDBFGNXUEYZYOLKBYBCG6AC MQ62OAMLGJVRW2QIL4PAZRAU6PC52ZVGY2FCOBIY6IWGQIHMU5CAC W52PCSHX72WAMWKG6L4BPUBVMO6E72KYYBNKAA7554KNOTY6V7WQC COLMJH3UIQFF4R5AV642OJK4HHGUIIPLNP5WGKLWWYNJV7ZGPI7AC ED62QWGKBPORWVKDFOQRKJXEIWZVNGR3O4KWQBDSRNPT36AYOQYAC AUXHSGS4EFOPZ6TVZYWNVOUDO7NYKUKE3HBKGQQWTALSVFOE3HAAC HKQTMQVLLOBG2VO47TUGSTQALA3D2YLMEVADXXYNR4RGGKD3F2ZAC H2TREER2QXNII3D6GTVASY5RVE7S7KATVKWRKOOISR5ZGOTLPFZQC void clear_rays_on_exit();void losight(env_show_grid &sh, feature_grid &gr,const coord_def& center, bool clear_walls_block = false,bool ignore_clouds = false);
bool see_grid( const env_show_grid &show,const coord_def &c,const coord_def &pos );bool see_grid(const coord_def &p);bool see_grid_no_trans( const coord_def &p );bool trans_wall_blocking( const coord_def &p );bool grid_see_grid(const coord_def& p1, const coord_def& p2,dungeon_feature_type allowed = DNGN_UNSEEN);
inline bool see_grid( int grx, int gry ){return see_grid(coord_def(grx, gry));}inline bool see_grid_no_trans(int x, int y){return see_grid_no_trans(coord_def(x, y));}inline bool trans_wall_blocking(int x, int y){return trans_wall_blocking(coord_def(x, y));}
struct ray_def;bool find_ray( const coord_def& source, const coord_def& target,bool allow_fallback, ray_def& ray, int cycle_dir = 0,bool find_shortest = false, bool ignore_solid = false );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 = true,bool just_check = false);
#define _monster_los_LSIZE (2 * LOS_RADIUS + 1)// This class can be used to fill the entire surroundings (los_range)// of a monster or other position with seen/unseen values, so as to be able// to compare several positions within this range.class monster_los{public:monster_los();virtual ~monster_los();// public methodsvoid set_monster(monsters *mon);void set_los_centre(int x, int y);void set_los_centre(const coord_def& p) { this->set_los_centre(p.x, p.y); }void set_los_range(int r);void fill_los_field(void);bool in_sight(int x, int y);bool in_sight(const coord_def& p) { return this->in_sight(p.x, p.y); }protected:// protected methodscoord_def pos_to_index(coord_def &p);coord_def index_to_pos(coord_def &i);void set_los_value(int x, int y, bool blocked, bool override = false);int get_los_value(int x, int y);bool is_blocked(int x, int y);bool is_unknown(int x, int y);void check_los_beam(int dx, int dy);// The (fixed) size of the array.static const int LSIZE;static const int L_VISIBLE;static const int L_UNKNOWN;static const int L_BLOCKED;// The centre of our los field.int gridx, gridy;// Habitat checks etc. should be done for this monster.// Usually the monster whose LOS we're trying to calculate// (if mon->x == gridx, mon->y == gridy).// Else, any monster trying to move around within this los field.monsters *mons;// Range may never be greater than LOS_RADIUS!int range;// The array to store the LOS values.int los_field[_monster_los_LSIZE][_monster_los_LSIZE];};
// Static class members must be initialised outside of the class// declaration, or gcc won't define them in view.o and we'll get a// linking error.const int monster_los::LSIZE = _monster_los_LSIZE;const int monster_los::L_VISIBLE = 1;const int monster_los::L_UNKNOWN = 0;const int monster_los::L_BLOCKED = -1;
}}}}}}// The LOS code now uses raycasting -- haranp#define LONGSIZE (sizeof(unsigned long)*8)#define LOS_MAX_RANGE_X 9#define LOS_MAX_RANGE_Y 9#define LOS_MAX_RANGE 9// the following two constants represent the 'middle' of the sh array.// since the current shown area is 19x19, centering the view at (9,9)// means it will be exactly centered.// This is done to accomodate possible future changes in viewable screen// area - simply change sh_xo and sh_yo to the new view center.const int sh_xo = 9; // X and Y origins for the sh arrayconst int sh_yo = 9;// Data used for the LOS algorithmint los_radius_squared = 8*8 + 1;unsigned long* los_blockrays = NULL;unsigned long* dead_rays = NULL;unsigned long* smoke_rays = NULL;std::vector<short> ray_coord_x;std::vector<short> ray_coord_y;std::vector<short> compressed_ray_x;std::vector<short> compressed_ray_y;std::vector<int> raylengths;std::vector<ray_def> fullrays;void clear_rays_on_exit(){delete[] dead_rays;delete[] smoke_rays;delete[] los_blockrays;}void setLOSRadius(int newLR){los_radius_squared = newLR * newLR + 1*1;}bool get_bit_in_long_array( const unsigned long* data, int where ){int wordloc = where / LONGSIZE;int bitloc = where % LONGSIZE;return ((data[wordloc] & (1UL << bitloc)) != 0);}static void _set_bit_in_long_array( unsigned long* data, int where ){int wordloc = where / LONGSIZE;int bitloc = where % LONGSIZE;data[wordloc] |= (1UL << bitloc);}#define EPSILON_VALUE 0.00001bool double_is_zero( const double x ){return (x > -EPSILON_VALUE) && (x < EPSILON_VALUE);}// note that slope must be nonnegative!// returns 0 if the advance was in x, 1 if it was in y, 2 if it was// the diagonalstatic int _find_next_intercept(double* accx, double* accy, const double slope){// handle perpendicularsif ( double_is_zero(slope) ){*accx += 1.0;return 0;}if ( slope > 100.0 ){*accy += 1.0;return 1;}const double xtarget = (static_cast<int>(*accx) + 1);const double ytarget = (static_cast<int>(*accy) + 1);const double xdistance = xtarget - *accx;const double ydistance = ytarget - *accy;double distdiff = (xdistance * slope - ydistance);// exact cornerif ( double_is_zero( distdiff ) ){// move somewhat away from the cornerif ( slope > 1.0 ){*accx = xtarget + EPSILON_VALUE * 2;*accy = ytarget + EPSILON_VALUE * 2 * slope;}else{*accx = xtarget + EPSILON_VALUE * 2 / slope;*accy = ytarget + EPSILON_VALUE * 2;}return 2;}// move to the boundarydouble traveldist;int rc = -1;if ( distdiff > 0.0 ){traveldist = ydistance / slope;rc = 1;}else{traveldist = xdistance;rc = 0;}// and a little into the next cell, taking care// not to go too farif ( distdiff < 0.0 )distdiff = -distdiff;traveldist += std::min(EPSILON_VALUE * 10.0, 0.5 * distdiff / slope);*accx += traveldist;*accy += traveldist * slope;return rc;}ray_def::ray_def() : accx(0.0), accy(0.0), slope(0.0), quadrant(0),fullray_idx(0){}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? fabs(slope) > 1.0 : fabs(slope) < 1.0)return (reflect(oldx, floor(oldx) + 0.5));const double flnew = floor(newx);const double flold = floor(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-rightint bouncequad[4][3] ={{ 1, 3, 2 }, { 0, 2, 3 }, { 3, 1, 0 }, { 2, 0, 1 }};int oldx = x(), oldy = y();const double oldaccx = accx, oldaccy = accy;int rc = advance(false);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 )quadrant = bouncequad[quadrant][rc];else{ASSERT( (oldx != newx) && (oldy != newy) );if ( blocked_x && blocked_y )quadrant = bouncequad[quadrant][rc];else if ( blocked_x )quadrant = bouncequad[quadrant][1];elsequadrant = bouncequad[quadrant][0];}set_reflect_point(oldaccx, oldaccy, &accx, &accy, blocked_x, blocked_y);}double ray_def::get_degrees() const{if (slope > 100.0){if (quadrant == 3 || quadrant == 2)return (90.0);elsereturn (270.0);}else if (double_is_zero(slope)){if (quadrant == 0 || quadrant == 3)return (0.0);elsereturn (180.0);}double deg = atan(slope) * 180.0 / M_PI;switch (quadrant){case 0:return (360.0 - deg);case 1:return (180.0 + deg);case 2:return (180.0 - deg);case 3:return (deg);}ASSERT(!"ray has illegal quadrant");return (0.0);}void ray_def::set_degrees(double deg){while (deg < 0.0)deg += 360.0;while (deg >= 360.0)deg -= 360.0;double _slope = tan(deg / 180.0 * M_PI);if (double_is_zero(_slope)){slope = 0.0;if (deg < 90.0 || deg > 270.0)quadrant = 0; // right/eastelsequadrant = 1; // left/west}else if (_slope > 0){slope = _slope;if (deg >= 180.0 && deg <= 270.0)quadrant = 1;elsequadrant = 3;}else{slope = -_slope;if (deg >= 90 && deg <= 180)quadrant = 2;elsequadrant = 0;}if (slope > 1000.0)slope = 1000.0;}void ray_def::regress(){int opp_quadrant[4] = { 2, 3, 0, 1 };quadrant = opp_quadrant[quadrant];advance(false);quadrant = opp_quadrant[quadrant];}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 minimise the number of moves on the ray, look one// step ahead and see if we can get a diagonal.const coord_def old(static_cast<int>(accx), static_cast<int>(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(static_cast<int>(accx), static_cast<int>(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 ){case 0:// going down-rightrc = _find_next_intercept( &accx, &accy, slope );return rc;case 1:// going down-leftaccx = 100.0 - EPSILON_VALUE/10.0 - accx;rc = _find_next_intercept( &accx, &accy, slope );accx = 100.0 - EPSILON_VALUE/10.0 - accx;return rc;case 2:// going up-leftaccx = 100.0 - EPSILON_VALUE/10.0 - accx;accy = 100.0 - EPSILON_VALUE/10.0 - accy;rc = _find_next_intercept( &accx, &accy, slope );accx = 100.0 - EPSILON_VALUE/10.0 - accx;accy = 100.0 - EPSILON_VALUE/10.0 - accy;return rc;case 3:// going up-rightaccy = 100.0 - EPSILON_VALUE/10.0 - accy;rc = _find_next_intercept( &accx, &accy, slope );accy = 100.0 - EPSILON_VALUE/10.0 - accy;return rc;default:return -1;}}// Shoot a ray from the given start point (accx, accy) with the given// slope, with a maximum distance (in either x or y coordinate) of// maxrange. Store the visited cells in xpos[] and ypos[], and// return the number of cells visited.static int _shoot_ray( double accx, double accy, const double slope,int maxrange, int xpos[], int ypos[] ){int curx, cury;int cellnum;for (cellnum = 0; true; ++cellnum){_find_next_intercept( &accx, &accy, slope );curx = static_cast<int>(accx);cury = static_cast<int>(accy);if (curx*curx + cury*cury > los_radius_squared)break;// Work with the new square.xpos[cellnum] = curx;ypos[cellnum] = cury;}return cellnum;}// Check if the passed ray has already been created.static bool _is_duplicate_ray( int len, int xpos[], int ypos[] ){int cur_offset = 0;for (unsigned int i = 0; i < raylengths.size(); ++i){// Only compare equal-length rays.if (raylengths[i] != len){cur_offset += raylengths[i];continue;}int j;for (j = 0; j < len; ++j){if (ray_coord_x[j + cur_offset] != xpos[j]|| ray_coord_y[j + cur_offset] != ypos[j]){break;}}// Exact duplicate?if (j == len)return (true);// Move to beginning of next ray.cur_offset += raylengths[i];}return (false);}// Is starta...lengtha a subset of startb...lengthb?static bool _is_subset( int starta, int startb, int lengtha, int lengthb ){int cura = starta, curb = startb;int enda = starta + lengtha, endb = startb + lengthb;while (cura < enda && curb < endb){if (ray_coord_x[curb] > ray_coord_x[cura])return (false);if (ray_coord_y[curb] > ray_coord_y[cura])return (false);if (ray_coord_x[cura] == ray_coord_x[curb]&& ray_coord_y[cura] == ray_coord_y[curb]){++cura;}++curb;}return (cura == enda);}// Returns a vector which lists all the nonduped cellrays (by index).static std::vector<int> _find_nonduped_cellrays(){// A cellray c in a fullray f is duped if there is a fullray g// such that g contains c and g[:c] is a subset of f[:c].int raynum, cellnum, curidx, testidx, testray, testcell;bool is_duplicate;std::vector<int> result;for (curidx = 0, raynum = 0;raynum < static_cast<int>(raylengths.size());curidx += raylengths[raynum++]){for (cellnum = 0; cellnum < raylengths[raynum]; ++cellnum){// Is the cellray raynum[cellnum] duplicated?is_duplicate = false;// XXX: We should really check everything up to now// completely, and all further rays to see if they're// proper subsets.const int curx = ray_coord_x[curidx + cellnum];const int cury = ray_coord_y[curidx + cellnum];for (testidx = 0, testray = 0; testray < raynum;testidx += raylengths[testray++]){// Scan ahead to see if there's an intersect.for (testcell = 0; testcell < raylengths[raynum]; ++testcell){const int testx = ray_coord_x[testidx + testcell];const int testy = ray_coord_y[testidx + testcell];// We can short-circuit sometimes.if (testx > curx || testy > cury)break;// Bingo!if (testx == curx && testy == cury){is_duplicate = _is_subset(testidx, curidx,testcell, cellnum);break;}}if (is_duplicate)break; // No point in checking further rays.}if (!is_duplicate)result.push_back(curidx + cellnum);}}return result;}// Create and register the ray defined by the arguments.// Return true if the ray was actually registered (i.e., not a duplicate.)static bool _register_ray( double accx, double accy, double slope ){int xpos[LOS_MAX_RANGE * 2 + 1], ypos[LOS_MAX_RANGE * 2 + 1];int raylen = _shoot_ray( accx, accy, slope, LOS_MAX_RANGE, xpos, ypos );// Early out if ray already exists.if (_is_duplicate_ray(raylen, xpos, ypos))return (false);// Not duplicate, register.for (int i = 0; i < raylen; ++i){// Create the cellrays.ray_coord_x.push_back(xpos[i]);ray_coord_y.push_back(ypos[i]);}// Register the fullray.raylengths.push_back(raylen);ray_def ray;ray.accx = accx;ray.accy = accy;ray.slope = slope;ray.quadrant = 0;fullrays.push_back(ray);return (true);}static void _create_blockrays(){// determine nonduplicated raysstd::vector<int> nondupe_cellrays = _find_nonduped_cellrays();const unsigned int num_nondupe_rays = nondupe_cellrays.size();const unsigned int num_nondupe_words =(num_nondupe_rays + LONGSIZE - 1) / LONGSIZE;const unsigned int num_cellrays = ray_coord_x.size();const unsigned int num_words = (num_cellrays + LONGSIZE - 1) / LONGSIZE;// first build all the rays: easier to do blocking calculations thereunsigned long* full_los_blockrays;full_los_blockrays = new unsigned long[num_words * (LOS_MAX_RANGE_X+1) *(LOS_MAX_RANGE_Y+1)];memset((void*)full_los_blockrays, 0, sizeof(unsigned long) * num_words *(LOS_MAX_RANGE_X+1) * (LOS_MAX_RANGE_Y+1));int cur_offset = 0;for (unsigned int ray = 0; ray < raylengths.size(); ++ray){for (int i = 0; i < raylengths[ray]; ++i){// every cell blocks...unsigned long* const inptr = full_los_blockrays +(ray_coord_x[i + cur_offset] * (LOS_MAX_RANGE_Y + 1) +ray_coord_y[i + cur_offset]) * num_words;// ...all following cellraysfor (int j = i+1; j < raylengths[ray]; ++j)_set_bit_in_long_array( inptr, j + cur_offset );}cur_offset += raylengths[ray];}// we've built the basic blockray array; now compress it, keeping// only the nonduplicated cellrays.// allocate and clear memorylos_blockrays = new unsigned long[num_nondupe_words * (LOS_MAX_RANGE_X+1) * (LOS_MAX_RANGE_Y + 1)];memset((void*)los_blockrays, 0, sizeof(unsigned long) * num_nondupe_words *(LOS_MAX_RANGE_X+1) * (LOS_MAX_RANGE_Y+1));// we want to only keep the cellrays from nondupe_cellrays.compressed_ray_x.resize(num_nondupe_rays);compressed_ray_y.resize(num_nondupe_rays);for (unsigned int i = 0; i < num_nondupe_rays; ++i){compressed_ray_x[i] = ray_coord_x[nondupe_cellrays[i]];compressed_ray_y[i] = ray_coord_y[nondupe_cellrays[i]];}unsigned long* oldptr = full_los_blockrays;unsigned long* newptr = los_blockrays;for (int x = 0; x <= LOS_MAX_RANGE_X; ++x)for (int y = 0; y <= LOS_MAX_RANGE_Y; ++y){for (unsigned int i = 0; i < num_nondupe_rays; ++i)if (get_bit_in_long_array(oldptr, nondupe_cellrays[i]))_set_bit_in_long_array(newptr, i);oldptr += num_words;newptr += num_nondupe_words;}// we can throw away full_los_blockrays nowdelete[] full_los_blockrays;dead_rays = new unsigned long[num_nondupe_words];smoke_rays = new unsigned long[num_nondupe_words];#ifdef DEBUG_DIAGNOSTICSmprf( MSGCH_DIAGNOSTICS, "Cellrays: %d Fullrays: %u Compressed: %u",num_cellrays, raylengths.size(), num_nondupe_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;}// Cast all raysvoid raycast(){static bool done_raycast = false;if (done_raycast)return;// Creating all rays for first quadrant// We have a considerable amount of overkill.done_raycast = true;// register perpendiculars FIRST, to make them top choice// when selecting beams_register_ray( 0.5, 0.5, 1000.0 );_register_ray( 0.5, 0.5, 0.0 );// For a slope of M = y/x, every x we move on the X axis means// that we move y on the y axis. We want to look at the resolution// of x/y: in that case, every step on the X axis means an increase// of 1 in the Y axis at the intercept point. We can assume gcd(x,y)=1,// so we look at steps of 1/y.// Changing the order a bit. We want to order by the complexity// of the beam, which is log(x) + log(y) ~ xy.std::vector<std::pair<int,int> > xyangles;for ( int xangle = 1; xangle <= 2*LOS_MAX_RANGE; ++xangle )for ( int yangle = 1; yangle <= 2*LOS_MAX_RANGE; ++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 <= 2*yangle; ++intercept ){double xstart = ((double)(intercept)) / (2*yangle);double ystart = 1;// now move back just inside the cell// y should be "about to change"xstart -= EPSILON_VALUE * xangle;ystart -= EPSILON_VALUE * yangle;_register_ray( xstart, ystart, slope );// also draw the identical ray in octant 2_register_ray( ystart, xstart, rslope );}}// Now create the appropriate blockrays array_create_blockrays();}static void _set_ray_quadrant( ray_def& ray, int sx, int sy, int tx, int ty ){if ( tx >= sx && ty >= sy )ray.quadrant = 0;else if ( tx < sx && ty >= sy )ray.quadrant = 1;else if ( tx < sx && ty < sy )ray.quadrant = 2;else if ( tx >= sx && ty < sy )ray.quadrant = 3;elsempr("Bad ray quadrant!", MSGCH_DIAGNOSTICS);}static int _cyclic_offset( unsigned int ui, int cycle_dir, int startpoint,int maxvalue ){const int i = (int)ui;if ( startpoint < 0 )return i;switch ( cycle_dir ){case 1:return (i + startpoint + 1) % maxvalue;case -1:return (i - 1 - startpoint + maxvalue) % maxvalue;case 0:default:return i;}}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 bool _superior_ray(int shortest, int imbalance,int raylen, int rayimbalance,double slope_diff, double ray_slope_diff){if (shortest != raylen)return (shortest > raylen);if (imbalance != rayimbalance)return (imbalance > rayimbalance);return (slope_diff > ray_slope_diff);}// Find a nonblocked ray from source to target. Return false if no// such ray could be found, otherwise return true and fill ray// appropriately.// If allow_fallback is true, fall back to a center-to-center ray// if range is too great or all rays are blocked.// 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( const coord_def& source, const coord_def& target,bool allow_fallback, ray_def& ray, int cycle_dir,bool find_shortest, bool ignore_solid ){int cellray, inray;const int sourcex = source.x;const int sourcey = source.y;const int targetx = target.x;const int targety = target.y;const int signx = ((targetx - sourcex >= 0) ? 1 : -1);const int signy = ((targety - sourcey >= 0) ? 1 : -1);const int absx = signx * (targetx - sourcex);const int absy = signy * (targety - sourcey);int cur_offset = 0;int shortest = INFINITE_DISTANCE;int imbalance = INFINITE_DISTANCE;const double want_slope = _calc_slope(absx, absy);double slope_diff = VERTICAL_SLOPE * 10.0;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,fullrays.size() );// Yeah, yeah, this is O(n^2). I know.cur_offset = 0;for (int i = 0; i < fullray; ++i)cur_offset += raylengths[i];for (cellray = 0; cellray < raylengths[fullray]; ++cellray){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){const int xi = signx * ray_coord_x[inray + cur_offset];const int yi = signy * ray_coord_y[inray + cur_offset];if (inray < cellray && !ignore_solid&& grid_is_solid(grd[sourcex + xi][sourcey + yi])){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 offunaliased_ray.pop_back();}}else++real_length;unaliased_ray.push_back(c3);c1 = unaliased_ray[real_length - 1];}}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;}}}const double ray_slope_diff = find_shortest ?fabs(_slope_factor(fullrays[fullray]) - want_slope) : 0.0;if (!blocked&& (!find_shortest|| _superior_ray(shortest, imbalance,real_length, cimbalance,slope_diff, ray_slope_diff))){// Success!ray = fullrays[fullray];ray.fullray_idx = fullray;shortest = real_length;imbalance = cimbalance;slope_diff = ray_slope_diff;if (sourcex > targetx)ray.accx = 1.0 - ray.accx;if (sourcey > targety)ray.accy = 1.0 - ray.accy;ray.accx += sourcex;ray.accy += sourcey;_set_ray_quadrant(ray, sourcex, sourcey, targetx, targety);if (!find_shortest)return (true);}}}}if (find_shortest && shortest != INFINITE_DISTANCE)return (true);if (allow_fallback){ray.accx = sourcex + 0.5;ray.accy = sourcey + 0.5;if (targetx == sourcex)ray.slope = VERTICAL_SLOPE;else{ray.slope = targety - sourcey;ray.slope /= targetx - sourcex;if (ray.slope < 0)ray.slope = -ray.slope;}_set_ray_quadrant(ray, sourcex, sourcey, targetx, targety);ray.fullray_idx = -1;return (true);}return (false);}// Count the number of matching features between two points along// a beam-like path; the path will pass through solid features.// By default, it excludes end points from the count.// If just_check is true, the function will return early once one// such feature is encountered.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);ray.fullray_idx = -1; // to quiet valgrind// We don't need to find the shortest beam, any beam will suffice.find_ray( source, target, true, ray, 0, false, true );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) // Only needs to be > 0.return (count);}if (reached_target)break;ray.advance(true);}return (count);}// The rule behind LOS is:// Two cells can see each other if there is any line from some point// of the first to some point of the second ("generous" LOS.)//// We use raycasting. The algorithm:// PRECOMPUTATION:// Create a large bundle of rays and cast them.// Mark, for each one, which cells kill it (and where.)// Also, for each one, note which cells it passes.// ACTUAL LOS:// Unite the ray-killers for the given map; this tells you which rays// are dead.// Look up which cells the surviving rays have, and that's your LOS!// OPTIMIZATIONS:// WLOG, we can assume that we're in a specific quadrant - say the// first quadrant - and just mirror everything after that. We can// likely get away with a single octant, but we don't do that. (To// do...)// Rays are actually split by each cell they pass. So each "ray" only// identifies a single cell, and we can do logical ORs. Once a cell// kills a cellray, it will kill all remaining cellrays of that ray.// Also, rays are checked to see if they are duplicates of each// other. If they are, they're eliminated.// Some cellrays can also be eliminated. In general, a cellray is// unnecessary if there is another cellray with the same coordinates,// and whose path (up to those coordinates) is a subset, not necessarily// proper, of the original path. We still store the original cellrays// fully for beam detection and such.// PERFORMANCE:// With reasonable values we have around 6000 cellrays, meaning// around 600Kb (75 KB) of data. This gets cut down to 700 cellrays// after removing duplicates. That means that we need to do// around 22*100*4 ~ 9,000 memory reads + writes per LOS call on a// 32-bit system. Not too bad.// IMPROVEMENTS:// Smoke will now only block LOS after two cells of smoke. This is// done by updating with a second array.void losight(env_show_grid &sh,feature_grid &gr, const coord_def& center,bool clear_walls_block, bool ignore_clouds){raycast();const int x_p = center.x;const int y_p = center.y;// go quadrant by quadrantconst int quadrant_x[4] = { 1, -1, -1, 1 };const int quadrant_y[4] = { 1, 1, -1, -1 };// clear out shsh.init(0);if (crawl_state.arena || crawl_state.arena_suspended){for (int y = -ENV_SHOW_OFFSET; y <= ENV_SHOW_OFFSET; ++y)for (int x = -ENV_SHOW_OFFSET; x <= ENV_SHOW_OFFSET; ++x){const coord_def pos = center + coord_def(x, y);if (map_bounds(pos))sh[x + sh_xo][y + sh_yo] = gr(pos);}return;}const unsigned int num_cellrays = compressed_ray_x.size();const unsigned int num_words = (num_cellrays + LONGSIZE - 1) / LONGSIZE;for (int quadrant = 0; quadrant < 4; ++quadrant){const int xmult = quadrant_x[quadrant];const int ymult = quadrant_y[quadrant];// clear out the dead rays arraymemset( (void*)dead_rays, 0, sizeof(unsigned long) * num_words);memset( (void*)smoke_rays, 0, sizeof(unsigned long) * num_words);// kill all blocked raysconst unsigned long* inptr = los_blockrays;for (int xdiff = 0; xdiff <= LOS_MAX_RANGE_X; ++xdiff)for (int ydiff = 0; ydiff <= LOS_MAX_RANGE_Y;++ydiff, inptr += num_words){const int realx = x_p + xdiff * xmult;const int realy = y_p + ydiff * ymult;if (!map_bounds(realx, realy))continue;coord_def real(realx, realy);dungeon_feature_type dfeat = grid_appearance(gr, real);// if this cell is opaque...// ... or something you can see but not walk through ...if (grid_is_opaque(dfeat)|| clear_walls_block && dfeat < DNGN_MINMOVE){// then block the appropriate raysfor (unsigned int i = 0; i < num_words; ++i)dead_rays[i] |= inptr[i];}else if (!ignore_clouds&& is_opaque_cloud(env.cgrid[realx][realy])){// block rays which have already seen a cloudfor (unsigned int i = 0; i < num_words; ++i){dead_rays[i] |= (smoke_rays[i] & inptr[i]);smoke_rays[i] |= inptr[i];}}}// ray calculation done, now work out which cells in this// quadrant are visibleunsigned int rayidx = 0;for (unsigned int wordloc = 0; wordloc < num_words; ++wordloc){const unsigned long curword = dead_rays[wordloc];// Note: the last word may be incompletefor (unsigned int bitloc = 0; bitloc < LONGSIZE; ++bitloc){// make the cells seen by this ray at this point visibleif ( ((curword >> bitloc) & 1UL) == 0 ){// this ray is alive!const int realx = xmult * compressed_ray_x[rayidx];const int realy = ymult * compressed_ray_y[rayidx];// update shadow mapif (x_p + realx >= 0 && x_p + realx < GXM&& y_p + realy >= 0 && y_p + realy < GYM&& realx * realx + realy * realy <= los_radius_squared){sh[sh_xo+realx][sh_yo+realy] = gr[x_p+realx][y_p+realy];
}}bool see_grid( 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);}// Answers the question: "Is a grid within character's line of sight?"bool see_grid( const coord_def &p ){return ((crawl_state.arena || crawl_state.arena_suspended)&& crawl_view.in_grid_los(p))|| see_grid(env.show, you.pos(), p);}// Answers the question: "Would a grid be within character's line of sight,// even if all translucent/clear walls were made opaque?"bool see_grid_no_trans( const coord_def &p ){return see_grid(env.no_trans_show, you.pos(), p);}// Is the grid visible, but a translucent wall is in the way?bool trans_wall_blocking( const coord_def &p ){return see_grid(p) && !see_grid_no_trans(p);
// Usually calculates whether from one grid someone could see the other.// Depending on the viewer's habitat, 'allowed' can be set to DNGN_FLOOR,// DNGN_SHALLOW_WATER or DNGN_DEEP_WATER.// Yes, this ignores lava-loving monsters.// XXX: It turns out the beams are not symmetrical, i.e. switching// pos1 and pos2 may result in small variations.bool grid_see_grid(const coord_def& p1, const coord_def& p2,dungeon_feature_type allowed){if (distance(p1, p2) > los_radius_squared)return (false);dungeon_feature_type max_disallowed = DNGN_MAXOPAQUE;if (allowed != DNGN_UNSEEN)max_disallowed = static_cast<dungeon_feature_type>(allowed - 1);// XXX: Ignoring clouds for now.return (!num_feats_between(p1, p2, DNGN_UNSEEN, max_disallowed,true, true));}
void calc_show_los(){if (!crawl_state.arena && !crawl_state.arena_suspended){// Must be done first.losight(env.show, grd, you.pos());// What would be visible, if all of the translucent walls were// made opaque.losight(env.no_trans_show, grd, you.pos(), true);}else{losight(env.show, grd, crawl_view.glosc());}}
/////////////////////////////////////////////////////////////////////////////// monster_losmonster_los::monster_los(): gridx(0), gridy(0), mons(), range(LOS_RADIUS), los_field(){}monster_los::~monster_los(){}void monster_los::set_monster(monsters *mon){mons = mon;set_los_centre(mon->pos());}void monster_los::set_los_centre(int x, int y){if (!in_bounds(x, y))return;gridx = x;gridy = y;}void monster_los::set_los_range(int r){ASSERT (r >= 1 && r <= LOS_RADIUS);range = r;}coord_def monster_los::pos_to_index(coord_def &p){int ix = LOS_RADIUS + p.x - gridx;int iy = LOS_RADIUS + p.y - gridy;ASSERT(ix >= 0 && ix < LSIZE);ASSERT(iy >= 0 && iy < LSIZE);return (coord_def(ix, iy));}coord_def monster_los::index_to_pos(coord_def &i){int px = i.x + gridx - LOS_RADIUS;int py = i.y + gridy - LOS_RADIUS;ASSERT(in_bounds(px, py));return (coord_def(px, py));}void monster_los::set_los_value(int x, int y, bool blocked, bool override){if (!override && !is_unknown(x,y))return;coord_def c(x,y);coord_def lpos = pos_to_index(c);int value = (blocked ? L_BLOCKED : L_VISIBLE);if (value != los_field[lpos.x][lpos.y])los_field[lpos.x][lpos.y] = value;}int monster_los::get_los_value(int x, int y){// Too far away -> definitely out of sight!if (distance(x, y, gridx, gridy) > los_radius_squared)return (L_BLOCKED);coord_def c(x,y);coord_def lpos = pos_to_index(c);return (los_field[lpos.x][lpos.y]);}bool monster_los::in_sight(int x, int y){// Is the path to (x,y) clear?return (get_los_value(x,y) == L_VISIBLE);}bool monster_los::is_blocked(int x, int y){// Is the path to (x,y) blocked?return (get_los_value(x, y) == L_BLOCKED);}bool monster_los::is_unknown(int x, int y){return (get_los_value(x, y) == L_UNKNOWN);}static bool _blocks_movement_sight(monsters *mon, dungeon_feature_type feat){if (feat < DNGN_MINMOVE)return (true);if (!mon) // No monster defined?return (false);if (!mon->can_pass_through_feat(feat))return (true);return (false);}void monster_los::fill_los_field(){int pos_x, pos_y;for (int k = 1; k <= range; k++)for (int i = -1; i <= 1; i++)for (int j = -1; j <= 1; j++){if (i == 0 && j == 0) // Ignore centre grid.continue;pos_x = gridx + k*i;pos_y = gridy + k*j;if (!in_bounds(pos_x, pos_y))continue;if (!_blocks_movement_sight(mons, grd[pos_x][pos_y]))set_los_value(pos_x, pos_y, false);else{set_los_value(pos_x, pos_y, true);// Check all beam potentially going through a blocked grid.check_los_beam(pos_x, pos_y);}}}// (cx, cy) is the centre point// (dx, dy) is the target we're aiming *through*// target1 and target2 are targets we'll be aiming *at* to fire through (dx,dy)static bool _set_beam_target(int cx, int cy, int dx, int dy,int &target1_x, int &target1_y,int &target2_x, int &target2_y,int range){const int xdist = dx - cx;const int ydist = dy - cy;if (xdist == 0 && ydist == 0)return (false); // Nothing to be done.if (xdist <= -range || xdist >= range|| ydist <= -range || ydist >= range){// Grids on the edge of a monster's LOS don't block sight any further.return (false);}/** The following code divides the field into eights of different directions.** \ NW | NE /* \ | /* WN \ | / EN* ----------------* WS / | \ ES* / | \* / SW | SE \** target1_x and target1_y mark the base line target, so the base beam ends* on the diagonal line closest to the target (or on one of the straight* lines if cx == dx or dx == dy).** target2_x and target2_y then mark the second target our beam finder should* cycle through. It'll always be target2_x = dx or target2_y = dy, the other* being on the edge of LOS, which one depending on the quadrant.** The beam finder can then cycle from the nearest corner (target1) to the* second edge target closest to (dx,dy).*/if (xdist == 0){target1_x = cx;target1_y = (ydist > 0 ? cy + range: cy - range);target2_x = target1_x;target2_y = target1_y;}else if (ydist == 0){target1_x = (xdist > 0 ? cx + range: cx - range);target1_y = cy;target2_x = target1_x;target2_y = target1_y;}else if (xdist < 0 && ydist < 0 || xdist > 0 && ydist > 0){if (xdist < 0){target1_x = cx - range;target1_y = cy - range;}else{target1_x = cx + range;target1_y = cy + range;}if (xdist == ydist){target2_x = target1_x;target2_y = target1_y;}else{if (xdist < 0) // both are negative (upper left corner){if (dx > dy){target2_x = dx;target2_y = cy - range;}else{target2_x = cx - range;target2_y = dy;}}else // both are positive (lower right corner){if (dx > dy){target2_x = cx + range;target2_y = dy;}else{target2_x = dx;target2_y = cy + range;}}}}else if (xdist < 0 && ydist > 0 || xdist > 0 && ydist < 0){if (xdist < 0) // lower left corner{target1_x = cx - range;target1_y = cy + range;}else // upper right corner{target1_x = cx + range;target1_y = cy - range;}if (xdist == -ydist){target2_x = target1_x;target2_y = target1_y;}else{if (xdist < 0) // ydist > 0{if (-xdist > ydist){target2_x = cx - range;target2_y = dy;}else{target2_x = dx;target2_y = cy + range;}}else // xdist > 0, ydist < 0{if (-xdist > ydist){target2_x = dx;target2_y = cy - range;}else{target2_x = cx + range;target2_y = dy;}}}}else{// Everything should have been handled above.ASSERT(false);}return (true);}void monster_los::check_los_beam(int dx, int dy){ray_def ray;int target1_x = 0, target1_y = 0, target2_x = 0, target2_y = 0;if (!_set_beam_target(gridx, gridy, dx, dy, target1_x, target1_y,target2_x, target2_y, range)){// Nothing to be done.return;}if (target1_x > target2_x || target1_y > target2_y){// Swap the two targets so our loop will work correctly.int help = target1_x;target1_x = target2_x;target2_x = help;help = target1_y;target1_y = target2_y;target2_y = help;}const int max_dist = range;int dist;bool blocked = false;for (int tx = target1_x; tx <= target2_x; tx++)for (int ty = target1_y; ty <= target2_y; ty++){// If (tx, ty) lies outside the level boundaries, there's nothing// that shooting a ray into that direction could bring us, esp.// as earlier grids in the ray will already have been handled, and// out of bounds grids are simply skipped in any LoS check.if (!map_bounds(tx, ty))continue;// Already calculated a beam to (tx, ty), don't do so again.if (!is_unknown(tx, ty))continue;dist = 0;ray.fullray_idx = -1; // to quiet valgrindfind_ray( coord_def(gridx, gridy), coord_def(tx, ty),true, ray, 0, true, true );if (ray.x() == gridx && ray.y() == gridy)ray.advance(true);while (dist++ <= max_dist){// The ray brings us out of bounds of the level map.// Since we're always shooting outwards there's nothing more// to look at in that direction, and we can break the loop.if (!map_bounds(ray.x(), ray.y()))break;if (blocked){// Earlier grid blocks this beam, set to blocked if// unknown, but don't overwrite visible grids.set_los_value(ray.x(), ray.y(), true);}else if (_blocks_movement_sight(mons, grd[ray.x()][ray.y()])){set_los_value(ray.x(), ray.y(), true);// The rest of the beam will now be blocked.blocked = true;}else{// Allow overriding in case another beam has marked this// field as blocked, because we've found a solution where// that isn't the case.set_los_value(ray.x(), ray.y(), false, true);}if (ray.x() == tx && ray.y() == ty)break;ray.advance(true);}}}
/** File: ray.cc* Summary: Ray definition*/#include "AppHdr.h"REVISION("$Rev$");#include "ray.h"#include <cmath>#include "los.h"#include "terrain.h"// note that slope must be nonnegative!// returns 0 if the advance was in x, 1 if it was in y, 2 if it was// the diagonalstatic int _find_next_intercept(double* accx, double* accy, const double slope){// handle perpendicularsif ( double_is_zero(slope) ){*accx += 1.0;return 0;}if ( slope > 100.0 ){*accy += 1.0;return 1;}const double xtarget = (static_cast<int>(*accx) + 1);const double ytarget = (static_cast<int>(*accy) + 1);const double xdistance = xtarget - *accx;const double ydistance = ytarget - *accy;double distdiff = (xdistance * slope - ydistance);// exact cornerif ( double_is_zero( distdiff ) ){// move somewhat away from the cornerif ( slope > 1.0 ){*accx = xtarget + EPSILON_VALUE * 2;*accy = ytarget + EPSILON_VALUE * 2 * slope;}else{*accx = xtarget + EPSILON_VALUE * 2 / slope;*accy = ytarget + EPSILON_VALUE * 2;}return 2;}// move to the boundarydouble traveldist;int rc = -1;if ( distdiff > 0.0 ){traveldist = ydistance / slope;rc = 1;}else{traveldist = xdistance;rc = 0;}// and a little into the next cell, taking care// not to go too farif ( distdiff < 0.0 )distdiff = -distdiff;traveldist += std::min(EPSILON_VALUE * 10.0, 0.5 * distdiff / slope);*accx += traveldist;*accy += traveldist * slope;return rc;}// Shoot a ray from the given start point (accx, accy) with the given// slope, with a maximum distance (in either x or y coordinate) of// maxrange. Store the visited cells in xpos[] and ypos[], and// return the number of cells visited.int shoot_ray( double accx, double accy, const double slope,int maxrange, int xpos[], int ypos[] ){int curx, cury;int cellnum;for (cellnum = 0; true; ++cellnum){_find_next_intercept( &accx, &accy, slope );curx = static_cast<int>(accx);cury = static_cast<int>(accy);if (curx*curx + cury*cury > get_los_radius_squared())break;// Work with the new square.xpos[cellnum] = curx;ypos[cellnum] = cury;}return cellnum;}ray_def::ray_def() : accx(0.0), accy(0.0), slope(0.0), quadrant(0),fullray_idx(0){}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? fabs(slope) > 1.0 : fabs(slope) < 1.0)return (reflect(oldx, floor(oldx) + 0.5));const double flnew = floor(newx);const double flold = floor(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-rightint bouncequad[4][3] ={{ 1, 3, 2 }, { 0, 2, 3 }, { 3, 1, 0 }, { 2, 0, 1 }};int oldx = x(), oldy = y();const double oldaccx = accx, oldaccy = accy;int rc = advance(false);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 )quadrant = bouncequad[quadrant][rc];else{ASSERT( (oldx != newx) && (oldy != newy) );if ( blocked_x && blocked_y )quadrant = bouncequad[quadrant][rc];else if ( blocked_x )quadrant = bouncequad[quadrant][1];elsequadrant = bouncequad[quadrant][0];}set_reflect_point(oldaccx, oldaccy, &accx, &accy, blocked_x, blocked_y);}double ray_def::get_degrees() const{if (slope > 100.0){if (quadrant == 3 || quadrant == 2)return (90.0);elsereturn (270.0);}else if (double_is_zero(slope)){if (quadrant == 0 || quadrant == 3)return (0.0);elsereturn (180.0);}double deg = atan(slope) * 180.0 / M_PI;switch (quadrant){case 0:return (360.0 - deg);case 1:return (180.0 + deg);case 2:return (180.0 - deg);case 3:return (deg);}ASSERT(!"ray has illegal quadrant");return (0.0);}void ray_def::set_degrees(double deg){while (deg < 0.0)deg += 360.0;while (deg >= 360.0)deg -= 360.0;double _slope = tan(deg / 180.0 * M_PI);if (double_is_zero(_slope)){slope = 0.0;if (deg < 90.0 || deg > 270.0)quadrant = 0; // right/eastelsequadrant = 1; // left/west}else if (_slope > 0){slope = _slope;if (deg >= 180.0 && deg <= 270.0)quadrant = 1;elsequadrant = 3;}else{slope = -_slope;if (deg >= 90 && deg <= 180)quadrant = 2;elsequadrant = 0;}if (slope > 1000.0)slope = 1000.0;}void ray_def::regress(){int opp_quadrant[4] = { 2, 3, 0, 1 };quadrant = opp_quadrant[quadrant];advance(false);quadrant = opp_quadrant[quadrant];}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 minimise the number of moves on the ray, look one// step ahead and see if we can get a diagonal.const coord_def old(static_cast<int>(accx), static_cast<int>(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(static_cast<int>(accx), static_cast<int>(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 ){case 0:// going down-rightrc = _find_next_intercept( &accx, &accy, slope );return rc;case 1:// going down-leftaccx = 100.0 - EPSILON_VALUE/10.0 - accx;rc = _find_next_intercept( &accx, &accy, slope );accx = 100.0 - EPSILON_VALUE/10.0 - accx;return rc;case 2:// going up-leftaccx = 100.0 - EPSILON_VALUE/10.0 - accx;accy = 100.0 - EPSILON_VALUE/10.0 - accy;rc = _find_next_intercept( &accx, &accy, slope );accx = 100.0 - EPSILON_VALUE/10.0 - accx;accy = 100.0 - EPSILON_VALUE/10.0 - accy;return rc;case 3:// going up-rightaccy = 100.0 - EPSILON_VALUE/10.0 - accy;rc = _find_next_intercept( &accx, &accy, slope );accy = 100.0 - EPSILON_VALUE/10.0 - accy;return rc;default:return -1;}}
#ifndef MON_LOS_H/** File: mon-los.cc* Summary: Monster line-of-sight.*/#define MON_LOS_H#define _monster_los_LSIZE (2 * LOS_RADIUS + 1)// This class can be used to fill the entire surroundings (los_range)// of a monster or other position with seen/unseen values, so as to be able// to compare several positions within this range.class monster_los{public:monster_los();virtual ~monster_los();// public methodsvoid set_monster(monsters *mon);void set_los_centre(int x, int y);void set_los_centre(const coord_def& p) { this->set_los_centre(p.x, p.y); }void set_los_range(int r);void fill_los_field(void);bool in_sight(int x, int y);bool in_sight(const coord_def& p) { return this->in_sight(p.x, p.y); }protected:// protected methodscoord_def pos_to_index(coord_def &p);coord_def index_to_pos(coord_def &i);void set_los_value(int x, int y, bool blocked, bool override = false);int get_los_value(int x, int y);bool is_blocked(int x, int y);bool is_unknown(int x, int y);void check_los_beam(int dx, int dy);// The (fixed) size of the array.static const int LSIZE;static const int L_VISIBLE;static const int L_UNKNOWN;static const int L_BLOCKED;// The centre of our los field.int gridx, gridy;// Habitat checks etc. should be done for this monster.// Usually the monster whose LOS we're trying to calculate// (if mon->x == gridx, mon->y == gridy).// Else, any monster trying to move around within this los field.monsters *mons;// Range may never be greater than LOS_RADIUS!int range;// The array to store the LOS values.int los_field[_monster_los_LSIZE][_monster_los_LSIZE];};#endif
/** File: mon-los.cc* Summary: Monster line-of-sight.*/#include "AppHdr.h"REVISION("$Rev$");#include "mon-los.h"#include "los.h"#include "ray.h"#include "stuff.h"// Static class members must be initialised outside of the class// declaration, or gcc won't define them in view.o and we'll get a// linking error.const int monster_los::LSIZE = _monster_los_LSIZE;const int monster_los::L_VISIBLE = 1;const int monster_los::L_UNKNOWN = 0;const int monster_los::L_BLOCKED = -1;/////////////////////////////////////////////////////////////////////////////// monster_losmonster_los::monster_los(): gridx(0), gridy(0), mons(), range(LOS_RADIUS), los_field(){}monster_los::~monster_los(){}void monster_los::set_monster(monsters *mon){mons = mon;set_los_centre(mon->pos());}void monster_los::set_los_centre(int x, int y){if (!in_bounds(x, y))return;gridx = x;gridy = y;}void monster_los::set_los_range(int r){ASSERT (r >= 1 && r <= LOS_RADIUS);range = r;}coord_def monster_los::pos_to_index(coord_def &p){int ix = LOS_RADIUS + p.x - gridx;int iy = LOS_RADIUS + p.y - gridy;ASSERT(ix >= 0 && ix < LSIZE);ASSERT(iy >= 0 && iy < LSIZE);return (coord_def(ix, iy));}coord_def monster_los::index_to_pos(coord_def &i){int px = i.x + gridx - LOS_RADIUS;int py = i.y + gridy - LOS_RADIUS;ASSERT(in_bounds(px, py));return (coord_def(px, py));}void monster_los::set_los_value(int x, int y, bool blocked, bool override){if (!override && !is_unknown(x,y))return;coord_def c(x,y);coord_def lpos = pos_to_index(c);int value = (blocked ? L_BLOCKED : L_VISIBLE);if (value != los_field[lpos.x][lpos.y])los_field[lpos.x][lpos.y] = value;}int monster_los::get_los_value(int x, int y){// Too far away -> definitely out of sight!if (distance(x, y, gridx, gridy) > get_los_radius_squared())return (L_BLOCKED);coord_def c(x,y);coord_def lpos = pos_to_index(c);return (los_field[lpos.x][lpos.y]);}bool monster_los::in_sight(int x, int y){// Is the path to (x,y) clear?return (get_los_value(x,y) == L_VISIBLE);}bool monster_los::is_blocked(int x, int y){// Is the path to (x,y) blocked?return (get_los_value(x, y) == L_BLOCKED);}bool monster_los::is_unknown(int x, int y){return (get_los_value(x, y) == L_UNKNOWN);}static bool _blocks_movement_sight(monsters *mon, dungeon_feature_type feat){if (feat < DNGN_MINMOVE)return (true);if (!mon) // No monster defined?return (false);if (!mon->can_pass_through_feat(feat))return (true);return (false);}void monster_los::fill_los_field(){int pos_x, pos_y;for (int k = 1; k <= range; k++)for (int i = -1; i <= 1; i++)for (int j = -1; j <= 1; j++){if (i == 0 && j == 0) // Ignore centre grid.continue;pos_x = gridx + k*i;pos_y = gridy + k*j;if (!in_bounds(pos_x, pos_y))continue;if (!_blocks_movement_sight(mons, grd[pos_x][pos_y]))set_los_value(pos_x, pos_y, false);else{set_los_value(pos_x, pos_y, true);// Check all beam potentially going through a blocked grid.check_los_beam(pos_x, pos_y);}}}// (cx, cy) is the centre point// (dx, dy) is the target we're aiming *through*// target1 and target2 are targets we'll be aiming *at* to fire through (dx,dy)static bool _set_beam_target(int cx, int cy, int dx, int dy,int &target1_x, int &target1_y,int &target2_x, int &target2_y,int range){const int xdist = dx - cx;const int ydist = dy - cy;if (xdist == 0 && ydist == 0)return (false); // Nothing to be done.if (xdist <= -range || xdist >= range|| ydist <= -range || ydist >= range){// Grids on the edge of a monster's LOS don't block sight any further.return (false);}/** The following code divides the field into eights of different directions.** \ NW | NE /* \ | /* WN \ | / EN* ----------------* WS / | \ ES* / | \* / SW | SE \** target1_x and target1_y mark the base line target, so the base beam ends* on the diagonal line closest to the target (or on one of the straight* lines if cx == dx or dx == dy).** target2_x and target2_y then mark the second target our beam finder should* cycle through. It'll always be target2_x = dx or target2_y = dy, the other* being on the edge of LOS, which one depending on the quadrant.** The beam finder can then cycle from the nearest corner (target1) to the* second edge target closest to (dx,dy).*/if (xdist == 0){target1_x = cx;target1_y = (ydist > 0 ? cy + range: cy - range);target2_x = target1_x;target2_y = target1_y;}else if (ydist == 0){target1_x = (xdist > 0 ? cx + range: cx - range);target1_y = cy;target2_x = target1_x;target2_y = target1_y;}else if (xdist < 0 && ydist < 0 || xdist > 0 && ydist > 0){if (xdist < 0){target1_x = cx - range;target1_y = cy - range;}else{target1_x = cx + range;target1_y = cy + range;}if (xdist == ydist){target2_x = target1_x;target2_y = target1_y;}else{if (xdist < 0) // both are negative (upper left corner){if (dx > dy){target2_x = dx;target2_y = cy - range;}else{target2_x = cx - range;target2_y = dy;}}else // both are positive (lower right corner){if (dx > dy){target2_x = cx + range;target2_y = dy;}else{target2_x = dx;target2_y = cy + range;}}}}else if (xdist < 0 && ydist > 0 || xdist > 0 && ydist < 0){if (xdist < 0) // lower left corner{target1_x = cx - range;target1_y = cy + range;}else // upper right corner{target1_x = cx + range;target1_y = cy - range;}if (xdist == -ydist){target2_x = target1_x;target2_y = target1_y;}else{if (xdist < 0) // ydist > 0{if (-xdist > ydist){target2_x = cx - range;target2_y = dy;}else{target2_x = dx;target2_y = cy + range;}}else // xdist > 0, ydist < 0{if (-xdist > ydist){target2_x = dx;target2_y = cy - range;}else{target2_x = cx + range;target2_y = dy;}}}}else{// Everything should have been handled above.ASSERT(false);}return (true);}void monster_los::check_los_beam(int dx, int dy){ray_def ray;int target1_x = 0, target1_y = 0, target2_x = 0, target2_y = 0;if (!_set_beam_target(gridx, gridy, dx, dy, target1_x, target1_y,target2_x, target2_y, range)){// Nothing to be done.return;}if (target1_x > target2_x || target1_y > target2_y){// Swap the two targets so our loop will work correctly.int help = target1_x;target1_x = target2_x;target2_x = help;help = target1_y;target1_y = target2_y;target2_y = help;}const int max_dist = range;int dist;bool blocked = false;for (int tx = target1_x; tx <= target2_x; tx++)for (int ty = target1_y; ty <= target2_y; ty++){// If (tx, ty) lies outside the level boundaries, there's nothing// that shooting a ray into that direction could bring us, esp.// as earlier grids in the ray will already have been handled, and// out of bounds grids are simply skipped in any LoS check.if (!map_bounds(tx, ty))continue;// Already calculated a beam to (tx, ty), don't do so again.if (!is_unknown(tx, ty))continue;dist = 0;ray.fullray_idx = -1; // to quiet valgrindfind_ray( coord_def(gridx, gridy), coord_def(tx, ty),true, ray, 0, true, true );if (ray.x() == gridx && ray.y() == gridy)ray.advance(true);while (dist++ <= max_dist){// The ray brings us out of bounds of the level map.// Since we're always shooting outwards there's nothing more// to look at in that direction, and we can break the loop.if (!map_bounds(ray.x(), ray.y()))break;if (blocked){// Earlier grid blocks this beam, set to blocked if// unknown, but don't overwrite visible grids.set_los_value(ray.x(), ray.y(), true);}else if (_blocks_movement_sight(mons, grd[ray.x()][ray.y()])){set_los_value(ray.x(), ray.y(), true);// The rest of the beam will now be blocked.blocked = true;}else{// Allow overriding in case another beam has marked this// field as blocked, because we've found a solution where// that isn't the case.set_los_value(ray.x(), ray.y(), false, true);}if (ray.x() == tx && ray.y() == ty)break;ray.advance(true);}}}
/** File: los.h* Summary: Line-of-sight algorithm.*/#ifndef LOS_H#define LOS_H#include "externs.h"#define EPSILON_VALUE 0.00001bool double_is_zero(const double x);void setLOSRadius(int newLR);int get_los_radius_squared(); // XXXstruct ray_def;bool find_ray( const coord_def& source, const coord_def& target,bool allow_fallback, ray_def& ray, int cycle_dir = 0,bool find_shortest = false, bool ignore_solid = false );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 = true,bool just_check = false);bool grid_see_grid(const coord_def& p1, const coord_def& p2,dungeon_feature_type allowed = DNGN_UNSEEN);void clear_rays_on_exit();void losight(env_show_grid &sh, feature_grid &gr,const coord_def& center, bool clear_walls_block = false,bool ignore_clouds = false);void calc_show_los();bool see_grid( const env_show_grid &show,const coord_def &c,const coord_def &pos );bool see_grid(const coord_def &p);bool see_grid_no_trans( const coord_def &p );bool trans_wall_blocking( const coord_def &p );inline bool see_grid( int grx, int gry ){return see_grid(coord_def(grx, gry));}inline bool see_grid_no_trans(int x, int y){return see_grid_no_trans(coord_def(x, y));}inline bool trans_wall_blocking(int x, int y){return trans_wall_blocking(coord_def(x, y));}#endif
/** File: los.cc* Summary: Line-of-sight algorithm.*/#include "AppHdr.h"REVISION("$Rev$");#include "los.h"#include <cmath>#include "cloud.h"#include "debug.h"#include "directn.h"#include "externs.h"#include "ray.h"#include "state.h"#include "stuff.h"#include "terrain.h"// The LOS code now uses raycasting -- haranp#define LONGSIZE (sizeof(unsigned long)*8)#define LOS_MAX_RANGE_X 9#define LOS_MAX_RANGE_Y 9#define LOS_MAX_RANGE 9// the following two constants represent the 'middle' of the sh array.// since the current shown area is 19x19, centering the view at (9,9)// means it will be exactly centered.// This is done to accomodate possible future changes in viewable screen// area - simply change sh_xo and sh_yo to the new view center.const int sh_xo = 9; // X and Y origins for the sh arrayconst int sh_yo = 9;unsigned long* los_blockrays = NULL;unsigned long* dead_rays = NULL;unsigned long* smoke_rays = NULL;std::vector<short> ray_coord_x;std::vector<short> ray_coord_y;std::vector<short> compressed_ray_x;std::vector<short> compressed_ray_y;std::vector<int> raylengths;std::vector<ray_def> fullrays;void clear_rays_on_exit(){delete[] dead_rays;delete[] smoke_rays;delete[] los_blockrays;}int _los_radius_squared = LOS_RADIUS * LOS_RADIUS + 1;void setLOSRadius(int newLR){_los_radius_squared = newLR * newLR + 1*1;}// XXX: just for monster_losint get_los_radius_squared(){return _los_radius_squared;}bool _get_bit_in_long_array( const unsigned long* data, int where ){int wordloc = where / LONGSIZE;int bitloc = where % LONGSIZE;return ((data[wordloc] & (1UL << bitloc)) != 0);}static void _set_bit_in_long_array( unsigned long* data, int where ){int wordloc = where / LONGSIZE;int bitloc = where % LONGSIZE;data[wordloc] |= (1UL << bitloc);}bool double_is_zero( const double x ){return (x > -EPSILON_VALUE) && (x < EPSILON_VALUE);}// Check if the passed ray has already been created.static bool _is_duplicate_ray( int len, int xpos[], int ypos[] ){int cur_offset = 0;for (unsigned int i = 0; i < raylengths.size(); ++i){// Only compare equal-length rays.if (raylengths[i] != len){cur_offset += raylengths[i];continue;}int j;for (j = 0; j < len; ++j){if (ray_coord_x[j + cur_offset] != xpos[j]|| ray_coord_y[j + cur_offset] != ypos[j]){break;}}// Exact duplicate?if (j == len)return (true);// Move to beginning of next ray.cur_offset += raylengths[i];}return (false);}// Is starta...lengtha a subset of startb...lengthb?static bool _is_subset( int starta, int startb, int lengtha, int lengthb ){int cura = starta, curb = startb;int enda = starta + lengtha, endb = startb + lengthb;while (cura < enda && curb < endb){if (ray_coord_x[curb] > ray_coord_x[cura])return (false);if (ray_coord_y[curb] > ray_coord_y[cura])return (false);if (ray_coord_x[cura] == ray_coord_x[curb]&& ray_coord_y[cura] == ray_coord_y[curb]){++cura;}++curb;}return (cura == enda);}// Returns a vector which lists all the nonduped cellrays (by index).static std::vector<int> _find_nonduped_cellrays(){// A cellray c in a fullray f is duped if there is a fullray g// such that g contains c and g[:c] is a subset of f[:c].int raynum, cellnum, curidx, testidx, testray, testcell;bool is_duplicate;std::vector<int> result;for (curidx = 0, raynum = 0;raynum < static_cast<int>(raylengths.size());curidx += raylengths[raynum++]){for (cellnum = 0; cellnum < raylengths[raynum]; ++cellnum){// Is the cellray raynum[cellnum] duplicated?is_duplicate = false;// XXX: We should really check everything up to now// completely, and all further rays to see if they're// proper subsets.const int curx = ray_coord_x[curidx + cellnum];const int cury = ray_coord_y[curidx + cellnum];for (testidx = 0, testray = 0; testray < raynum;testidx += raylengths[testray++]){// Scan ahead to see if there's an intersect.for (testcell = 0; testcell < raylengths[raynum]; ++testcell){const int testx = ray_coord_x[testidx + testcell];const int testy = ray_coord_y[testidx + testcell];// We can short-circuit sometimes.if (testx > curx || testy > cury)break;// Bingo!if (testx == curx && testy == cury){is_duplicate = _is_subset(testidx, curidx,testcell, cellnum);break;}}if (is_duplicate)break; // No point in checking further rays.}if (!is_duplicate)result.push_back(curidx + cellnum);}}return result;}// Create and register the ray defined by the arguments.// Return true if the ray was actually registered (i.e., not a duplicate.)static bool _register_ray( double accx, double accy, double slope ){int xpos[LOS_MAX_RANGE * 2 + 1], ypos[LOS_MAX_RANGE * 2 + 1];int raylen = shoot_ray( accx, accy, slope, LOS_MAX_RANGE, xpos, ypos );// Early out if ray already exists.if (_is_duplicate_ray(raylen, xpos, ypos))return (false);// Not duplicate, register.for (int i = 0; i < raylen; ++i){// Create the cellrays.ray_coord_x.push_back(xpos[i]);ray_coord_y.push_back(ypos[i]);}// Register the fullray.raylengths.push_back(raylen);ray_def ray;ray.accx = accx;ray.accy = accy;ray.slope = slope;ray.quadrant = 0;fullrays.push_back(ray);return (true);}static void _create_blockrays(){// determine nonduplicated raysstd::vector<int> nondupe_cellrays = _find_nonduped_cellrays();const unsigned int num_nondupe_rays = nondupe_cellrays.size();const unsigned int num_nondupe_words =(num_nondupe_rays + LONGSIZE - 1) / LONGSIZE;const unsigned int num_cellrays = ray_coord_x.size();const unsigned int num_words = (num_cellrays + LONGSIZE - 1) / LONGSIZE;// first build all the rays: easier to do blocking calculations thereunsigned long* full_los_blockrays;full_los_blockrays = new unsigned long[num_words * (LOS_MAX_RANGE_X+1) *(LOS_MAX_RANGE_Y+1)];memset((void*)full_los_blockrays, 0, sizeof(unsigned long) * num_words *(LOS_MAX_RANGE_X+1) * (LOS_MAX_RANGE_Y+1));int cur_offset = 0;for (unsigned int ray = 0; ray < raylengths.size(); ++ray){for (int i = 0; i < raylengths[ray]; ++i){// every cell blocks...unsigned long* const inptr = full_los_blockrays +(ray_coord_x[i + cur_offset] * (LOS_MAX_RANGE_Y + 1) +ray_coord_y[i + cur_offset]) * num_words;// ...all following cellraysfor (int j = i+1; j < raylengths[ray]; ++j)_set_bit_in_long_array( inptr, j + cur_offset );}cur_offset += raylengths[ray];}// we've built the basic blockray array; now compress it, keeping// only the nonduplicated cellrays.// allocate and clear memorylos_blockrays = new unsigned long[num_nondupe_words * (LOS_MAX_RANGE_X+1) * (LOS_MAX_RANGE_Y + 1)];memset((void*)los_blockrays, 0, sizeof(unsigned long) * num_nondupe_words *(LOS_MAX_RANGE_X+1) * (LOS_MAX_RANGE_Y+1));// we want to only keep the cellrays from nondupe_cellrays.compressed_ray_x.resize(num_nondupe_rays);compressed_ray_y.resize(num_nondupe_rays);for (unsigned int i = 0; i < num_nondupe_rays; ++i){compressed_ray_x[i] = ray_coord_x[nondupe_cellrays[i]];compressed_ray_y[i] = ray_coord_y[nondupe_cellrays[i]];}unsigned long* oldptr = full_los_blockrays;unsigned long* newptr = los_blockrays;for (int x = 0; x <= LOS_MAX_RANGE_X; ++x)for (int y = 0; y <= LOS_MAX_RANGE_Y; ++y){for (unsigned int i = 0; i < num_nondupe_rays; ++i)if (_get_bit_in_long_array(oldptr, nondupe_cellrays[i]))_set_bit_in_long_array(newptr, i);oldptr += num_words;newptr += num_nondupe_words;}// we can throw away full_los_blockrays nowdelete[] full_los_blockrays;dead_rays = new unsigned long[num_nondupe_words];smoke_rays = new unsigned long[num_nondupe_words];#ifdef DEBUG_DIAGNOSTICSmprf( MSGCH_DIAGNOSTICS, "Cellrays: %d Fullrays: %u Compressed: %u",num_cellrays, raylengths.size(), num_nondupe_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;}// Cast all raysvoid raycast(){static bool done_raycast = false;if (done_raycast)return;// Creating all rays for first quadrant// We have a considerable amount of overkill.done_raycast = true;// register perpendiculars FIRST, to make them top choice// when selecting beams_register_ray( 0.5, 0.5, 1000.0 );_register_ray( 0.5, 0.5, 0.0 );// For a slope of M = y/x, every x we move on the X axis means// that we move y on the y axis. We want to look at the resolution// of x/y: in that case, every step on the X axis means an increase// of 1 in the Y axis at the intercept point. We can assume gcd(x,y)=1,// so we look at steps of 1/y.// Changing the order a bit. We want to order by the complexity// of the beam, which is log(x) + log(y) ~ xy.std::vector<std::pair<int,int> > xyangles;for ( int xangle = 1; xangle <= 2*LOS_MAX_RANGE; ++xangle )for ( int yangle = 1; yangle <= 2*LOS_MAX_RANGE; ++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 <= 2*yangle; ++intercept ){double xstart = ((double)(intercept)) / (2*yangle);double ystart = 1;// now move back just inside the cell// y should be "about to change"xstart -= EPSILON_VALUE * xangle;ystart -= EPSILON_VALUE * yangle;_register_ray( xstart, ystart, slope );// also draw the identical ray in octant 2_register_ray( ystart, xstart, rslope );}}// Now create the appropriate blockrays array_create_blockrays();}static void _set_ray_quadrant( ray_def& ray, int sx, int sy, int tx, int ty ){if ( tx >= sx && ty >= sy )ray.quadrant = 0;else if ( tx < sx && ty >= sy )ray.quadrant = 1;else if ( tx < sx && ty < sy )ray.quadrant = 2;else if ( tx >= sx && ty < sy )ray.quadrant = 3;elsempr("Bad ray quadrant!", MSGCH_DIAGNOSTICS);}static int _cyclic_offset( unsigned int ui, int cycle_dir, int startpoint,int maxvalue ){const int i = (int)ui;if ( startpoint < 0 )return i;switch ( cycle_dir ){case 1:return (i + startpoint + 1) % maxvalue;case -1:return (i - 1 - startpoint + maxvalue) % maxvalue;case 0:default:return i;}}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 bool _superior_ray(int shortest, int imbalance,int raylen, int rayimbalance,double slope_diff, double ray_slope_diff){if (shortest != raylen)return (shortest > raylen);if (imbalance != rayimbalance)return (imbalance > rayimbalance);return (slope_diff > ray_slope_diff);}// Find a nonblocked ray from source to target. Return false if no// such ray could be found, otherwise return true and fill ray// appropriately.// If allow_fallback is true, fall back to a center-to-center ray// if range is too great or all rays are blocked.// 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( const coord_def& source, const coord_def& target,bool allow_fallback, ray_def& ray, int cycle_dir,bool find_shortest, bool ignore_solid ){int cellray, inray;const int sourcex = source.x;const int sourcey = source.y;const int targetx = target.x;const int targety = target.y;const int signx = ((targetx - sourcex >= 0) ? 1 : -1);const int signy = ((targety - sourcey >= 0) ? 1 : -1);const int absx = signx * (targetx - sourcex);const int absy = signy * (targety - sourcey);int cur_offset = 0;int shortest = INFINITE_DISTANCE;int imbalance = INFINITE_DISTANCE;const double want_slope = _calc_slope(absx, absy);double slope_diff = VERTICAL_SLOPE * 10.0;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,fullrays.size() );// Yeah, yeah, this is O(n^2). I know.cur_offset = 0;for (int i = 0; i < fullray; ++i)cur_offset += raylengths[i];for (cellray = 0; cellray < raylengths[fullray]; ++cellray){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){const int xi = signx * ray_coord_x[inray + cur_offset];const int yi = signy * ray_coord_y[inray + cur_offset];if (inray < cellray && !ignore_solid&& grid_is_solid(grd[sourcex + xi][sourcey + yi])){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 offunaliased_ray.pop_back();}}else++real_length;unaliased_ray.push_back(c3);c1 = unaliased_ray[real_length - 1];}}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;}}}const double ray_slope_diff = find_shortest ?fabs(_slope_factor(fullrays[fullray]) - want_slope) : 0.0;if (!blocked&& (!find_shortest|| _superior_ray(shortest, imbalance,real_length, cimbalance,slope_diff, ray_slope_diff))){// Success!ray = fullrays[fullray];ray.fullray_idx = fullray;shortest = real_length;imbalance = cimbalance;slope_diff = ray_slope_diff;if (sourcex > targetx)ray.accx = 1.0 - ray.accx;if (sourcey > targety)ray.accy = 1.0 - ray.accy;ray.accx += sourcex;ray.accy += sourcey;_set_ray_quadrant(ray, sourcex, sourcey, targetx, targety);if (!find_shortest)return (true);}}}}if (find_shortest && shortest != INFINITE_DISTANCE)return (true);if (allow_fallback){ray.accx = sourcex + 0.5;ray.accy = sourcey + 0.5;if (targetx == sourcex)ray.slope = VERTICAL_SLOPE;else{ray.slope = targety - sourcey;ray.slope /= targetx - sourcex;if (ray.slope < 0)ray.slope = -ray.slope;}_set_ray_quadrant(ray, sourcex, sourcey, targetx, targety);ray.fullray_idx = -1;return (true);}return (false);}// Count the number of matching features between two points along// a beam-like path; the path will pass through solid features.// By default, it excludes end points from the count.// If just_check is true, the function will return early once one// such feature is encountered.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);ray.fullray_idx = -1; // to quiet valgrind// We don't need to find the shortest beam, any beam will suffice.find_ray( source, target, true, ray, 0, false, true );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) // Only needs to be > 0.return (count);}if (reached_target)break;ray.advance(true);}return (count);}// Usually calculates whether from one grid someone could see the other.// Depending on the viewer's habitat, 'allowed' can be set to DNGN_FLOOR,// DNGN_SHALLOW_WATER or DNGN_DEEP_WATER.// Yes, this ignores lava-loving monsters.// XXX: It turns out the beams are not symmetrical, i.e. switching// pos1 and pos2 may result in small variations.bool grid_see_grid(const coord_def& p1, const coord_def& p2,dungeon_feature_type allowed){if (distance(p1, p2) > _los_radius_squared)return (false);dungeon_feature_type max_disallowed = DNGN_MAXOPAQUE;if (allowed != DNGN_UNSEEN)max_disallowed = static_cast<dungeon_feature_type>(allowed - 1);// XXX: Ignoring clouds for now.return (!num_feats_between(p1, p2, DNGN_UNSEEN, max_disallowed,true, true));}// The rule behind LOS is:// Two cells can see each other if there is any line from some point// of the first to some point of the second ("generous" LOS.)//// We use raycasting. The algorithm:// PRECOMPUTATION:// Create a large bundle of rays and cast them.// Mark, for each one, which cells kill it (and where.)// Also, for each one, note which cells it passes.// ACTUAL LOS:// Unite the ray-killers for the given map; this tells you which rays// are dead.// Look up which cells the surviving rays have, and that's your LOS!// OPTIMIZATIONS:// WLOG, we can assume that we're in a specific quadrant - say the// first quadrant - and just mirror everything after that. We can// likely get away with a single octant, but we don't do that. (To// do...)// Rays are actually split by each cell they pass. So each "ray" only// identifies a single cell, and we can do logical ORs. Once a cell// kills a cellray, it will kill all remaining cellrays of that ray.// Also, rays are checked to see if they are duplicates of each// other. If they are, they're eliminated.// Some cellrays can also be eliminated. In general, a cellray is// unnecessary if there is another cellray with the same coordinates,// and whose path (up to those coordinates) is a subset, not necessarily// proper, of the original path. We still store the original cellrays// fully for beam detection and such.// PERFORMANCE:// With reasonable values we have around 6000 cellrays, meaning// around 600Kb (75 KB) of data. This gets cut down to 700 cellrays// after removing duplicates. That means that we need to do// around 22*100*4 ~ 9,000 memory reads + writes per LOS call on a// 32-bit system. Not too bad.// IMPROVEMENTS:// Smoke will now only block LOS after two cells of smoke. This is// done by updating with a second array.void losight(env_show_grid &sh,feature_grid &gr, const coord_def& center,bool clear_walls_block, bool ignore_clouds){raycast();const int x_p = center.x;const int y_p = center.y;// go quadrant by quadrantconst int quadrant_x[4] = { 1, -1, -1, 1 };const int quadrant_y[4] = { 1, 1, -1, -1 };// clear out shsh.init(0);if (crawl_state.arena || crawl_state.arena_suspended){for (int y = -ENV_SHOW_OFFSET; y <= ENV_SHOW_OFFSET; ++y)for (int x = -ENV_SHOW_OFFSET; x <= ENV_SHOW_OFFSET; ++x){const coord_def pos = center + coord_def(x, y);if (map_bounds(pos))sh[x + sh_xo][y + sh_yo] = gr(pos);}return;}const unsigned int num_cellrays = compressed_ray_x.size();const unsigned int num_words = (num_cellrays + LONGSIZE - 1) / LONGSIZE;for (int quadrant = 0; quadrant < 4; ++quadrant){const int xmult = quadrant_x[quadrant];const int ymult = quadrant_y[quadrant];// clear out the dead rays arraymemset( (void*)dead_rays, 0, sizeof(unsigned long) * num_words);memset( (void*)smoke_rays, 0, sizeof(unsigned long) * num_words);// kill all blocked raysconst unsigned long* inptr = los_blockrays;for (int xdiff = 0; xdiff <= LOS_MAX_RANGE_X; ++xdiff)for (int ydiff = 0; ydiff <= LOS_MAX_RANGE_Y;++ydiff, inptr += num_words){const int realx = x_p + xdiff * xmult;const int realy = y_p + ydiff * ymult;if (!map_bounds(realx, realy))continue;coord_def real(realx, realy);dungeon_feature_type dfeat = grid_appearance(gr, real);// if this cell is opaque...// ... or something you can see but not walk through ...if (grid_is_opaque(dfeat)|| clear_walls_block && dfeat < DNGN_MINMOVE){// then block the appropriate raysfor (unsigned int i = 0; i < num_words; ++i)dead_rays[i] |= inptr[i];}else if (!ignore_clouds&& is_opaque_cloud(env.cgrid[realx][realy])){// block rays which have already seen a cloudfor (unsigned int i = 0; i < num_words; ++i){dead_rays[i] |= (smoke_rays[i] & inptr[i]);smoke_rays[i] |= inptr[i];}}}// ray calculation done, now work out which cells in this// quadrant are visibleunsigned int rayidx = 0;for (unsigned int wordloc = 0; wordloc < num_words; ++wordloc){const unsigned long curword = dead_rays[wordloc];// Note: the last word may be incompletefor (unsigned int bitloc = 0; bitloc < LONGSIZE; ++bitloc){// make the cells seen by this ray at this point visibleif ( ((curword >> bitloc) & 1UL) == 0 ){// this ray is alive!const int realx = xmult * compressed_ray_x[rayidx];const int realy = ymult * compressed_ray_y[rayidx];// update shadow mapif (x_p + realx >= 0 && x_p + realx < GXM&& y_p + realy >= 0 && y_p + realy < GYM&& realx * realx + realy * realy <= _los_radius_squared){sh[sh_xo+realx][sh_yo+realy] = gr[x_p+realx][y_p+realy];}}++rayidx;if (rayidx == num_cellrays)break;}}}// [dshaligram] The player's current position is always visible.sh[sh_xo][sh_yo] = gr[x_p][y_p];*dead_rays = NULL;*smoke_rays = NULL;*los_blockrays = NULL;}void calc_show_los(){if (!crawl_state.arena && !crawl_state.arena_suspended){// Must be done first.losight(env.show, grd, you.pos());// What would be visible, if all of the translucent walls were// made opaque.losight(env.no_trans_show, grd, you.pos(), true);}else{losight(env.show, grd, crawl_view.glosc());}}bool see_grid( 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);}// Answers the question: "Is a grid within character's line of sight?"bool see_grid( const coord_def &p ){return ((crawl_state.arena || crawl_state.arena_suspended)&& crawl_view.in_grid_los(p))|| see_grid(env.show, you.pos(), p);}// Answers the question: "Would a grid be within character's line of sight,// even if all translucent/clear walls were made opaque?"bool see_grid_no_trans( const coord_def &p ){return see_grid(env.no_trans_show, you.pos(), p);}// Is the grid visible, but a translucent wall is in the way?bool trans_wall_blocking( const coord_def &p ){return see_grid(p) && !see_grid_no_trans(p);}