1
0
mirror of https://github.com/zenorogue/hyperrogue.git synced 2025-08-29 00:42:19 +00:00
hyperrogue/graph.cpp

2100 lines
59 KiB
C++

// Hyperbolic Rogue -- main graphics file
// Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
/** \file graph.cpp
* \brief Drawing cells, monsters, items, etc.
*/
#include "hyper.h"
namespace hr {
EX int last_firelimit;
EX int firelimit;
EX int inmirrorcount = 0;
/** wall optimization: do not draw things beyond walls */
EX bool wallopt;
EX bool in_wallopt() { return wallopt || racing::on; }
EX bool spatial_graphics;
EX bool wmspatial, wmescher, wmplain, wmblack, wmascii, wmascii3;
EX bool mmspatial, mmhigh, mmmon, mmitem;
EX int detaillevel = 0;
EX bool first_cell_to_draw = true;
EX bool zh_ascii = false;
EX bool in_perspective() {
return models::is_perspective(pconf.model);
}
EX bool in_perspective_v() {
return models::is_perspective(vpconf.model);
}
EX bool hide_player() {
return GDIM == 3 && playermoved && vid.yshift == 0 && vid.sspeed > -5 && in_perspective() && (first_cell_to_draw || elliptic) && (WDIM == 3 || vid.camera == 0) && !inmirrorcount
#if CAP_RACING
&& !(racing::on && !racing::use_standard_centering() && !racing::player_relative)
#endif
;
}
EX transmatrix ddspin180(cell *c, int dir) { return ddspin(c, dir, M_PI); }
EX transmatrix iddspin180(cell *c, int dir) { return iddspin(c, dir, M_PI); }
EX transmatrix lpispin() {
return spin180();
}
EX hookset<bool(int sym, int uni)> hooks_handleKey;
EX hookset<bool(cell *c, const shiftmatrix& V)> hooks_drawcell;
EX purehookset hooks_frame, hooks_markers;
#define WOLNIEJ 1
#define BTOFF 0x404040
#define BTON 0xC0C000
// #define PANDORA
int colorbar;
EX bool inHighQual; // taking high quality screenshot
EX bool auraNOGL; // aura without GL
//
int axestate;
EX int ticks;
EX int frameid;
EX bool nomap;
EX eItem orbToTarget;
EX eMonster monsterToSummon;
EX int sightrange_bonus = 0;
EX string mouseovers;
EX int darken = 0;
EX bool doHighlight() {
return mmhigh;
}
int dlit;
ld spina(cell *c, int dir) {
return TAU * dir / c->type;
}
/** @brief used to alternate colors depending on distance to something. In chessboard-patterned geometries, automatically a third step.
* In some cases, we want to avoid a number of colors in the table -- set @param subtract to the number of such colors.
*/
EX color_t get_color_auto3(int f, const colortable& ctab, int subtract IS(0)) {
int size = ctab.size() - subtract;
if(size < 1) return 0;
if(geosupport_chessboard() && size == 2) {
f = gmod(f, 3);
if(f == 1)
return gradient(ctab[0], ctab[1], 0, 1, 2);
return ctab[f/2];
}
else
return ctab[gmod(f, size)];
}
EX ld cheilevel(ld v) {
return cgi.FLOOR + (cgi.HEAD - cgi.FLOOR) * v;
}
EX transmatrix chei(const transmatrix V, int a, int b) {
#if MAXMDIM >= 4
if(GDIM == 2) return V;
return V * lzpush(cheilevel((a+.5) / b));
#else
return V;
#endif
}
EX shiftmatrix chei(const shiftmatrix V, int a, int b) {
#if MAXMDIM >= 4
if(GDIM == 2) return V;
return V * lzpush(cheilevel((a+.5) / b));
#else
return V;
#endif
}
EX bool ivoryz;
/** Change the level of V. Takes ivoryz and all geometries into account */
EX transmatrix at_smart_lof(const transmatrix& V, ld lev) {
if(!mmspatial) return V;
if(ivoryz) return mzscale(V, lev);
return orthogonal_move_fol(V, lev);
}
EX shiftmatrix at_smart_lof(const shiftmatrix& V, ld lev) { return shiftless(at_smart_lof(V.T, lev), V.shift); }
EX color_t kind_outline(eItem it) {
int k = itemclass(it);
if(k == IC_TREASURE)
return OUTLINE_TREASURE;
else if(k == IC_ORB)
return OUTLINE_ORB;
else
return OUTLINE_OTHER;
}
/** should objects fly slightly up and down in product/twisted product geometries */
EX bool bobbing = true;
EX void draw_ascii(const shiftmatrix& V, const string& s, color_t col, ld size, ld size2) {
int id = isize(ptds);
if(WDIM == 2 && GDIM == 3)
queuestrn(V * lzpush(cgi.FLOOR - cgi.scalefactor * size / 4), size * mapfontscale / 100, s, darkenedby(col, darken), 0);
else
queuestrn(V, size2 * mapfontscale / 100, s, darkenedby(col, darken), GDIM == 3 ? 0 : 2);
while(id < isize(ptds)) ptds[id++]->prio = PPR::MONSTER_BODY;
}
EX void draw_ascii(const shiftmatrix& V, char glyph, color_t col, ld size) {
draw_ascii(V, s0 + glyph, col, size, 1);
}
EX void draw_ascii_or_zh(const shiftmatrix& V, char glyph, const string& name, color_t col, ld size, ld zh_size) {
#if CAP_TRANS
if(zh_ascii) {
auto p = XLAT1_acc(name, 8);
if(p) {
string chinese = p;
chinese.resize(utfsize(chinese[0]));
dynamicval<fontdata*> df(cfont, cfont_chinese);
draw_ascii(V, chinese, col, size, zh_size);
return;
}
}
#endif
draw_ascii(V, glyph, col, size);
}
// push down the queue after q-th element, `down` absolute units down,
// based on cell c and transmatrix V
// do change the zoom factor? do change the priorities?
EX int cellcolor(cell *c) {
if(isPlayerOn(c) || isFriendly(c)) return OUTLINE_FRIEND;
if(noHighlight(c->monst)) return OUTLINE_NONE;
if(c->monst) return OUTLINE_ENEMY;
if(c->wall == waMirror) return c->land == laMirror ? OUTLINE_TREASURE : OUTLINE_ORB;
if(c->item && !itemHiddenFromSight(c)) {
int k = itemclass(c->item);
if(k == IC_TREASURE)
return OUTLINE_TREASURE;
else if(k == IC_ORB)
return OUTLINE_ORB;
else
return OUTLINE_OTHER;
}
return OUTLINE_NONE;
}
#define AURA 180
array<array<int,4>,AURA+1> aurac;
int haveaura_cached;
/** 0 = no aura, 1 = standard aura, 2 = Joukowsky aura */
EX int haveaura() {
if(!(vid.aurastr>0 && !svg::in && (auraNOGL || vid.usingGL))) return 0;
if(vrhr::active()) return 0;
if(sphere && mdAzimuthalEqui()) return 0;
if(among(pmodel, mdJoukowsky, mdJoukowskyInverted) && hyperbolic && pconf.model_transition < 1)
return 2;
if(among(pmodel, mdFisheye, mdFisheye2)) return 1;
return pmodel == mdDisk && (!sphere || pconf.alpha > 10) && !euclid;
}
vector<pair<int, int> > auraspecials;
int auramemo;
EX void clearaura() {
haveaura_cached = haveaura();
if(!haveaura_cached) return;
for(int a=0; a<AURA; a++) for(int b=0; b<4; b++)
aurac[a][b] = 0;
auraspecials.clear();
auramemo = 128 * 128 / vid.aurastr;
}
void apply_joukowsky_aura(shiftpoint& h) {
if(haveaura_cached == 2) {
hyperpoint ret;
applymodel(h, ret);
h.h = ret;
}
if(nonisotropic) {
h.h = lp_apply(inverse_exp(h, pfNO_DISTANCE));
}
}
EX void addauraspecial(shiftpoint h, color_t col, int dir) {
if(!haveaura_cached) return;
apply_joukowsky_aura(h);
int r = int(2*AURA + dir + atan2(h[1], h[0]) * AURA / TAU) % AURA;
auraspecials.emplace_back(r, col);
}
EX void addaura(shiftpoint h, color_t col, int fd) {
if(!haveaura_cached) return;
apply_joukowsky_aura(h);
int r = gmod(atan2(h[1], h[0]) * AURA / TAU, AURA);
aurac[r][3] += auramemo << fd;
col = darkened(col);
aurac[r][0] += (col>>16)&255;
aurac[r][1] += (col>>8)&255;
aurac[r][2] += (col>>0)&255;
}
void sumaura(int v) {
int auc[AURA];
for(int t=0; t<AURA; t++) auc[t] = aurac[t][v];
int val = 0;
if(vid.aurasmoothen < 1) vid.aurasmoothen = 1;
if(vid.aurasmoothen > AURA) vid.aurasmoothen = AURA;
int SMO = vid.aurasmoothen;
for(int t=0; t<SMO; t++) val += auc[t];
for(int t=0; t<AURA; t++) {
int tt = (t + SMO/2) % AURA;
aurac[tt][v] = val;
val -= auc[t];
val += auc[(t+SMO) % AURA];
}
aurac[AURA][v] = aurac[0][v];
}
#if CAP_GL
vector<glhr::colored_vertex> auravertices;
#endif
EX void drawaura() {
DEBBI(DF_GRAPH, ("draw aura"));
if(!haveaura()) return;
if(vid.stereo_mode) return;
double rad = current_display->radius;
if(sphere && !mdAzimuthalEqui()) rad /= sqrt(pconf.alpha*pconf.alpha - 1);
if(hyperbolic && pmodel == mdFisheye) {
ld h = 1;
h /= pconf.fisheye_param;
ld nrad = h / sqrt(2 + h*h);
rad *= nrad;
}
for(int v=0; v<4; v++) sumaura(v);
for(auto& p: auraspecials) {
int r = p.first;
aurac[r][3] = auramemo;
for(int k=0; k<3; k++) aurac[r][k] = (p.second >> (16-8*k)) & 255;
}
#if CAP_SDL || CAP_GL
ld bak[3];
bak[0] = ((backcolor>>16)&255)/255.;
bak[1] = ((backcolor>>8)&255)/255.;
bak[2] = ((backcolor>>0)&255)/255.;
#endif
#if CAP_SDL
if(!vid.usingGL) {
SDL_LockSurface(s);
for(int y=0; y<vid.yres; y++)
for(int x=0; x<vid.xres; x++) {
ld hx = (x * 1. - current_display->xcenter) / rad;
ld hy = (y * 1. - current_display->ycenter) / rad / pconf.stretch;
if(!models::camera_straight) camrotate(hx, hy);
ld fac = sqrt(hx*hx+hy*hy);
if(fac < 1) continue;
ld dd = log((fac - .99999) / .00001);
ld cmul = 1 - dd/10.;
if(cmul>1) cmul=1;
if(cmul<0) cmul=0;
ld alpha = AURA * atan2(hx,hy) / TAU;
if(alpha<0) alpha += AURA;
if(alpha >= AURA) alpha -= AURA;
int rm = int(alpha);
ld fr = alpha-rm;
if(rm<0 || rm >= AURA) continue;
color_t& p = qpixel(s, x, y);
for(int c=0; c<3; c++) {
ld c1 = aurac[rm][2-c] / (aurac[rm][3]+.1);
ld c2 = aurac[rm+1][2-c] / (aurac[rm+1][3]+.1);
const ld one = 1;
part(p, c) = int(255 * min(one, bak[2-c] + cmul * ((c1 + fr * (c2-c1) - bak[2-c]))));
}
}
SDL_UnlockSurface(s);
return;
}
#endif
#if CAP_GL
float cx[AURA+1][11][5];
double facs[11] = {1, 1.01, 1.02, 1.04, 1.08, 1.70, 1.95, 1.5, 2, 6, 10};
double cmul[11] = {1, .8, .7, .6, .5, .16, .12, .08, .07, .06, 0};
double d2[11] = {0, 2, 4, 6.5, 7, 7.5, 8, 8.5, 9, 9.5, 10};
for(int d=0; d<11; d++) {
double dd = d2[d];
cmul[d] = (1- dd/10.);
facs[d] = .99999 + .00001 * exp(dd);
}
facs[10] = 10;
cmul[1] = cmul[0];
bool inversion = pconf.alpha <= -1 || pmodel == mdJoukowsky;
bool joukowsky = among(pmodel, mdJoukowskyInverted, mdJoukowsky) && hyperbolic && pconf.model_transition < 1;
for(int r=0; r<=AURA; r++) for(int z=0; z<11; z++) {
float rr = (TAU * r) / AURA;
float rad0 = inversion ? rad / facs[z] : rad * facs[z];
int rm = r % AURA;
ld c = cos(rr);
ld s = sin(rr);
if(joukowsky) {
hyperpoint v(c, s, 0, 1);
if(inversion)
models::ori_to_scr(v);
else
models::scr_to_ori(v);
ld c1 = v[0], s1 = v[1];
ld& mt = pconf.model_transition;
ld mt2 = 1 - mt;
ld m = sqrt(c1*c1 + s1*s1 / mt2 / mt2);
m *= 2;
if(inversion) rad0 /= m;
else rad0 *= m;
}
ld x = rad0 * c;
ld y = rad0 * s;
if(!models::camera_straight) {
hyperpoint p = hyperpoint(x, y, rad0, 1);
p = rot_inverse(pconf.cam()) * p;
x = p[0] * rad0 / p[2];
y = p[1] * rad0 / p[2];
}
cx[r][z][0] = x;
cx[r][z][1] = y * pconf.stretch;
for(int u=0; u<3; u++)
cx[r][z][u+2] = bak[u] + (aurac[rm][u] / (aurac[rm][3]+.1) - bak[u]) * cmul[z];
}
auravertices.clear();
for(int r=0; r<AURA; r++) for(int z=0;z<10;z++) {
for(int c=0; c<6; c++) {
int br = (c == 1 || c == 3 || c == 5) ? r+1 : r;
int bz = (c == 2 || c == 4 || c == 5) ? z+1 : z;
auravertices.emplace_back(
cx[br][bz][0], cx[br][bz][1], cx[br][bz][2], cx[br][bz][3], cx[br][bz][4]
);
}
}
glflush();
current_display->next_shader_flags = GF_VARCOLOR;
dynamicval<eModel> m(pmodel, mdPixel);
current_display->set_all(0, 0);
glhr::id_modelview();
glhr::prepare(auravertices);
glhr::set_depthtest(false);
glDrawArrays(GL_TRIANGLES, 0, isize(auravertices));
#endif
}
// int fnt[100][7];
bool bugsNearby(cell *c, int dist = 2) {
if(!(havewhat&HF_BUG)) return false;
if(isBug(c)) return true;
if(dist) for(int t=0; t<c->type; t++) if(c->move(t) && bugsNearby(c->move(t), dist-1)) return true;
return false;
}
EX int celldistAltPlus(cell *c) { return 1000000 + celldistAlt(c); }
EX bool drawstaratvec(double dx, double dy) {
return dx*dx+dy*dy > .05;
}
ld wavefun(ld x) {
return sin(x);
/* x /= TAU;
x -= (int) x;
if(x > .5) return (x-.5) * 2;
else return 0; */
}
// does the current geometry allow nice duals
EX bool has_nice_dual() {
#if CAP_IRR
if(IRREGULAR) return irr::bitruncations_performed > 0;
#endif
#if CAP_ARCM
if(arcm::in()) return geosupport_football() >= 2;
#endif
if(bt::in()) return false;
if(BITRUNCATED) return true;
if(a4) return false;
if((S7 & 1) == 0) return true;
if(PURE) return false;
#if CAP_GP
return (gp::param.first + gp::param.second * 2) % 3 == 0;
#else
return false;
#endif
}
// does the current geometry allow nice duals
EX bool is_nice_dual(cell *c) {
return c->land == laDual && has_nice_dual();
}
EX bool use_swapped_duals() {
return (euclid && !a4) || GOLDBERG;
}
EX bool openorsafe(cell *c) {
#if CAP_COMPLEX2
return c->wall == waMineOpen || mine::marked_safe(c);
#else
return false;
#endif
}
#define Dark(x) darkena(x,0,0xFF)
EX color_t stdgridcolor = 0x202020FF;
EX int gridcolor(cell *c1, cell *c2) {
if(cmode & sm::DRAW && !mapeditor::drawing_tool) return Dark(forecolor);
if(!c2)
return 0x202020 >> darken;
int rd1 = rosedist(c1), rd2 = rosedist(c2);
if(rd1 != rd2) {
int r = rd1+rd2;
if(r == 1) return Dark(0x802020);
if(r == 3) return Dark(0xC02020);
if(r == 2) return Dark(0xF02020);
}
if((get_spatial_info(c1).deep<SIDE::SHALLOW) != (get_spatial_info(c2).deep<SIDE::SHALLOW) && c1->land != laAsteroids && c2->land != laAsteroids)
return Dark(0x808080);
if(c1->land == laAlchemist && c2->land == laAlchemist && c1->wall != c2->wall && !c1->item && !c2->item)
return Dark(0xC020C0);
if((c1->land == laWhirlpool || c2->land == laWhirlpool) && (celldistAlt(c1) != celldistAlt(c2)))
return Dark(0x2020A0);
if(c1->land == laMinefield && c2->land == laMinefield && (openorsafe(c1) != openorsafe(c2)))
return Dark(0xA0A0A0);
if(!darken) return stdgridcolor;
return Dark(0x202020);
}
#if CAP_SHAPES
EX void pushdown(cell *c, int& q, const shiftmatrix &V, double down, bool rezoom, bool repriority) {
#if MAXMDIM >= 4
if(GDIM == 3) {
for(int i=q; i<isize(ptds); i++) {
auto pp = ptds[q++]->as_poly();
if(!pp) continue;
auto& ptd = *pp;
ptd.V = ptd.V * lzpush(+down);
}
return;
}
#endif
// since we might be changing priorities, we have to make sure that we are sorting correctly
if(down > 0 && repriority) {
int qq = q+1;
while(qq < isize(ptds))
if(qq > q && ptds[qq]->prio < ptds[qq-1]->prio) {
swap(ptds[qq], ptds[qq-1]);
qq--;
}
else qq++;
}
while(q < isize(ptds)) {
auto pp = ptds[q++]->as_poly();
if(!pp) continue;
auto& ptd = *pp;
double z2;
double z = zlevel(tC0(ptd.V.T));
double lev = geom3::factor_to_lev(z);
double nlev = lev - down;
double xyscale = rezoom ? geom3::scale_at_lev(lev) / geom3::scale_at_lev(nlev) : 1;
z2 = geom3::lev_to_factor(nlev);
double zscale = z2 / z;
// xyscale = xyscale + (zscale-xyscale) * (1+sin(ticks / 1000.0)) / 2;
ptd.V.T = xyzscale( V.T, xyscale*zscale, zscale)
* z_inverse(V.T) * unshift(ptd.V, V.shift);
if(!repriority) ;
else if(nlev < -vid.lake_bottom-1e-3) {
ptd.prio = PPR::DEEP_FALLANIM;
if(c->wall != waChasm)
ptd.color = 0; // disappear!
}
else if(nlev < -vid.lake_top-1e-3)
ptd.prio = PPR::SHALLOW_FALLANIM;
else if(nlev < 0)
ptd.prio = PPR::FLOOR_FALLANIM;
}
}
#endif
bool allemptynear(cell *c) {
if(c->wall) return false;
forCellEx(c2, c) if(c2->wall) return false;
return true;
}
EX bool bright;
EX int canvasdark;
// how much to darken
EX int getfd(cell *c) {
if(bright) return 0;
if(among(c->land, laAlchemist, laHell, laVariant, laEclectic) && WDIM == 2 && GDIM == 3) return 0;
switch(c->land) {
case laCanvas:
return min(2,max(0,canvasdark));
case laRedRock:
case laReptile:
return 0;
case laSnakeNest:
return realred(c->wall) ? 0 : 1;
case laTerracotta:
case laMercuryRiver:
return (c->wall == waMercury && wmspatial) ? 0 : 1;
case laKraken:
case laDocks:
case laBurial:
case laIvoryTower:
case laDungeon:
case laMountain:
case laEndorian:
case laCaribbean:
case laWhirlwind:
case laRose:
case laWarpSea:
case laTortoise:
case laDragon:
case laHalloween:
case laHunting:
case laOcean:
case laLivefjord:
case laWhirlpool:
case laAlchemist:
case laIce:
case laGraveyard:
case laBlizzard:
case laRlyeh:
case laTemple:
case laWineyard:
case laDeadCaves:
case laPalace:
case laCA:
case laDual:
case laBrownian:
return 1;
case laVariant:
if(isWateryOrBoat(c)) return 1;
return 2;
case laTrollheim:
default:
return 2;
}
}
EX bool just_gmatrix;
EX int colorhash(color_t i) {
return (i * 0x471211 + i*i*0x124159 + i*i*i*0x982165) & 0xFFFFFF;
}
EX ld mousedist(shiftmatrix T) {
if(GDIM == 2) return hdist(mouseh, tC0(T));
shiftpoint T1 = orthogonal_move_fol(T, cgi.FLOOR) * tile_center();
hyperpoint h1;
applymodel(T1, h1);
if(mouseaim_sensitivity) return sqhypot_d(2, h1) + (point_behind(T1) ? 1e10 : 0);
h1 = h1 - point2((mousex - current_display->xcenter) / current_display->radius, (mousey - current_display->ycenter) / current_display->radius);
return sqhypot_d(2, h1) + (point_behind(T1) ? 1e10 : 0);
}
EX vector<vector<hyperpoint>> clipping_plane_sets;
EX int noclipped;
EX bool frustum_culling = true;
EX ld threshold, xyz_threshold;
EX bool clip_checked = false;
EX bool other_stereo_mode() {
return vid.stereo_mode != sOFF;
}
EX void make_clipping_planes() {
#if MAXMDIM >= 4
clip_checked = false;
if(!frustum_culling || PIU(sphere) || experimental || other_stereo_mode() || gproduct || embedded_plane) return;
if(WDIM == 3 && pmodel == mdPerspective && !nonisotropic && !in_s2xe())
threshold = sin_auto(cgi.corner_bonus), xyz_threshold = 0, clip_checked = true;
else if(pmodel == mdGeodesic && sn::in())
threshold = .6, xyz_threshold = 3, clip_checked = true;
else if(pmodel == mdGeodesic && nil)
threshold = 2, xyz_threshold = 3, clip_checked = true;
else return;
clipping_plane_sets.clear();
auto add_clipping_plane_txy = [] (transmatrix T, const transmatrix& nlp, ld x1, ld y1, ld x2, ld y2) {
ld z1 = 1, z2 = 1;
hyperpoint sx = point3(y1 * z2 - y2 * z1, z1 * x2 - z2 * x1, x1 * y2 - x2 * y1);
sx /= hypot_d(3, sx);
sx[3] = 0;
sx = T * sx;
if(nisot::local_perspective_used) sx = ortho_inverse(nlp) * sx;
clipping_plane_sets.back().push_back(sx);
};
#if CAP_VR
auto add_clipping_plane_proj = [&] (transmatrix T, const transmatrix& nlp, const transmatrix& iproj, ld x1, ld y1, ld x2, ld y2) {
hyperpoint h1 = iproj * point31(x1, y1, .5);
hyperpoint h2 = iproj * point31(x2, y2, .5);
h1 /= h1[2]; h2 /= h2[2];
add_clipping_plane_txy(T, nlp, h1[0], h1[1], h2[0], h2[1]);
};
#endif
auto clipping_planes_screen = [&] (const transmatrix& T, const transmatrix& nlp) {
ld tx = current_display->tanfov;
ld ty = tx * current_display->ysize / current_display->xsize;
clipping_plane_sets.push_back({});
add_clipping_plane_txy(T, nlp, +tx, +ty, -tx, +ty);
add_clipping_plane_txy(T, nlp, -tx, +ty, -tx, -ty);
add_clipping_plane_txy(T, nlp, -tx, -ty, +tx, -ty);
add_clipping_plane_txy(T, nlp, +tx, -ty, +tx, +ty);
};
bool stdview = true;
#if CAP_VR
if(vrhr::active()) {
for(auto p: vrhr::frusta) {
if(p.screen)
clipping_planes_screen(inverse(p.pre), p.nlp);
else {
auto iproj = inverse(p.proj);
auto ipre = inverse(p.pre);
clipping_plane_sets.push_back({});
add_clipping_plane_proj(ipre, p.nlp, iproj, 1, 1, 0, 1);
add_clipping_plane_proj(ipre, p.nlp, iproj, 0, 1, 0, 0);
add_clipping_plane_proj(ipre, p.nlp, iproj, 0, 0, 1, 0);
add_clipping_plane_proj(ipre, p.nlp, iproj, 1, 0, 1, 1);
}
stdview = false;
}
}
#endif
if(stdview) clipping_planes_screen(Id, NLP);
#endif
}
EX bool clipped_by(const hyperpoint& H, const vector<hyperpoint>& v) {
for(auto& cpoint: v) if((H|cpoint) < -threshold) return true;
return false;
}
EX bool clipped_by(const hyperpoint& H, const vector<vector<hyperpoint>>& vv) {
for(auto& cps: vv) if(!clipped_by(H, cps)) return false;
return true;
}
bool celldrawer::cell_clipped() {
if(!clip_checked) return false;
hyperpoint H = unshift(tC0(V));
if(xyz_threshold && abs(H[0]) <= xyz_threshold && abs(H[1]) <= xyz_threshold && abs(H[2]) <= xyz_threshold) {
noclipped++;
return false;
}
if(clipped_by(H, clipping_plane_sets)) {
drawcell_in_radar();
return true;
}
noclipped++;
return false;
}
EX ld precise_width = .5;
int grid_depth = 0;
EX bool fat_edges = false;
EX bool gridbelow;
EX void gridline(const shiftmatrix& V1, const hyperpoint h1, const shiftmatrix& V2, const hyperpoint h2, color_t col, int prec) {
transmatrix U2 = unshift(V2, V1.shift);
int c1 = safe_classify_ideals(h1);
int c2 = safe_classify_ideals(h2);
ld d = (c1 <= 0 || c2 <= 0) ? 99 : hdist(V1.T*h1, U2*h2);
#if MAXMDIM >= 4
if(GDIM == 3 && fat_edges) {
if(nonisotropic) {
auto nV1 = V1 * rgpushxto0(h1);
hyperpoint U2 = inverse_shift(nV1, V2*rgpushxto0(h2)) * C0;
auto& p = cgi.get_pipe_noniso(U2, vid.linewidth, ePipeEnd::ball);
queuepoly(nV1, p, col);
return;
}
shiftmatrix T = V1 * rgpushxto0(h1);
transmatrix S = rspintox(inverse_shift(T, V2) * h2);
transmatrix U = rspintoc(inverse_shift(T*S, shiftless(C0)), 2, 1);
auto& p = queuepoly(T * S * U, cgi.get_pipe_iso(d, vid.linewidth, ePipeEnd::ball), col);
p.intester = xpush0(d/2);
return;
}
#endif
while(d > precise_width && d < 100 && grid_depth < 10) {
if(V1.shift != V2.shift || !eqmatrix(V1.T, V2.T, 1e-6)) { gridline(V1, h1, V1, inverse_shift(V1, V2) * h2, col, prec); return; }
hyperpoint h;
if(c1 <= 0 && c2 <= 0) {
h = closest_to_zero(h1, h2);
if(safe_classify_ideals(h) <= 0) return;
h = normalize(h);
}
else if(c2 <= 0) {
dynamicval<int> dw(grid_depth, 99);
for(ld a=0; a<ideal_limit; a+=precise_width)
gridline(V1, towards_inf(h1, h2, a), V1, towards_inf(h1, h2, a+precise_width), col, prec);
return;
}
else if(c1 <= 0) {
dynamicval<int> dw(grid_depth, 99);
for(ld a=0; a<ideal_limit; a+=precise_width)
gridline(V1, towards_inf(h2, h1, a), V1, towards_inf(h2, h1, a+precise_width), col, prec);
return;
}
else h = midz(h1, h2);
grid_depth++;
gridline(V1, h1, V1, h, col, prec);
gridline(V1, h, V1, h2, col, prec);
grid_depth--;
return;
}
#if MAXMDIM >= 4
if(WDIM == 2 && GDIM == 3) {
ld eps = cgi.human_height/100;
queueline(V1*orthogonal_move(h1,cgi.FLOOR+eps), V2*orthogonal_move(h2,cgi.FLOOR+eps), col, prec);
queueline(V1*orthogonal_move(h1,cgi.WALL-eps), V2*orthogonal_move(h2,cgi.WALL-eps), col, prec);
}
else
#endif
queueline(V1*h1, V2*h2, col, prec, gridbelow ? PPR::FLOORd : PPR::LINE);
}
EX void gridline(const shiftmatrix& V, const hyperpoint h1, const hyperpoint h2, color_t col, int prec) {
gridline(V, h1, V, h2, col, prec);
}
EX void set_detail_level(const shiftmatrix& V) {
ld dist0 = hdist0(tC0(V)) - 1e-6;
if(vid.use_smart_range) detaillevel = 2;
else if(dist0 < vid.highdetail) detaillevel = 2;
else if(dist0 < vid.middetail) detaillevel = 1;
else detaillevel = 0;
if((cmode & sm::NUMBER) && (dialog::editingDetail())) {
color_t col =
dist0 < vid.highdetail ? 0xFF80FF80 :
dist0 >= vid.middetail ? 0xFFFF8080 :
0XFFFFFF80;
queuepoly(V, cgi.shHeptaMarker, darkena(col & 0xFFFFFF, 0, 0xFF));
}
}
#if CAP_QUEUE
EX void queuecircleat1(cell *c, const shiftmatrix& V, double rad, color_t col) {
if(WDIM == 3) {
dynamicval<color_t> p(poly_outline, col);
int ofs = currentmap->wall_offset(c);
for(int i=0; i<c->type; i++) {
queuepolyat(V, cgi.shWireframe3D[ofs + i], 0, PPR::SUPERLINE);
}
return;
}
if(spatial_graphics || GDIM == 3) {
vector<hyperpoint> corners(c->type+1);
for(int i=0; i<c->type; i++) corners[i] = get_corner_position(c, i, 3 / rad);
corners[c->type] = corners[0];
for(int i=0; i<c->type; i++) {
queueline(V * orthogonal_move_fol(corners[i], cgi.FLOOR), V * orthogonal_move_fol(corners[i+1], cgi.FLOOR), col, 2, PPR::SUPERLINE);
queueline(V * orthogonal_move_fol(corners[i], cgi.WALL), V * orthogonal_move_fol(corners[i+1], cgi.WALL), col, 2, PPR::SUPERLINE);
queueline(V * orthogonal_move_fol(corners[i], cgi.FLOOR), V * orthogonal_move_fol(corners[i], cgi.WALL), col, 2, PPR::SUPERLINE);
}
return;
}
#if CAP_SHAPES
if(vid.stereo_mode || sphere) {
dynamicval<color_t> p(poly_outline, col);
queuepolyat(V * spintick(100), cgi.shGem[1], 0, PPR::LINE);
return;
}
#endif
queuecircle(V, rad, col);
if(!wmspatial) return;
auto si = get_spatial_info(c);
if(si.top == SIDE::WALL)
queuecircle(orthogonal_move_fol(V, cgi.WALL), rad, col);
if(si.top == SIDE::RED1)
queuecircle(orthogonal_move_fol(V, cgi.RED[1]), rad, col);
if(si.top == SIDE::RED2)
queuecircle(orthogonal_move_fol(V, cgi.RED[2]), rad, col);
if(si.top == SIDE::RED3)
queuecircle(orthogonal_move_fol(V, cgi.RED[3]), rad, col);
if(si.top <= SIDE::WATERLEVEL)
queuecircle(orthogonal_move_fol(V, cgi.WATERLEVEL), rad, col);
}
EX void queuecircleat(cell *c, double rad, color_t col) {
if(!c) return;
for (const shiftmatrix& V : hr::span_at(current_display->all_drawn_copies, c))
queuecircleat1(c, V, rad, col);
}
#endif
#if ISMOBILE
#define MOBON (clicked)
#else
#define MOBON true
#endif
EX cell *forwardcell() {
#if CAP_VR
if(vrhr::active()) {
return vrhr::forward_cell;
}
#endif
movedir md = vectodir(move_destination_vec(6));
cellwalker xc = cwt + md.d + wstep;
return xc.at;
}
EX bool draw_centerover = true;
EX bool should_draw_mouse_cursor() {
if(!mousing || inHighQual) return false;
if(outofmap(mouseh.h)) return false;
if(rug::rugged && !rug::renderonce) return true;
return false;
}
EX void drawMarkers() {
shmup::draw_collision_debug();
if(!(cmode & sm::NORMAL)) return;
if(should_draw_mouse_cursor()) {
for(int i: player_indices()) {
queueline(ggmatrix(playerpos(i)) * (WDIM == 2 && GDIM == 3 ? zpush0(cgi.WALL) : C0), mouseh, 0xFF00FF, grid_prec() + 1);
}
}
callhooks(hooks_markers);
#if CAP_SHAPES
viewmat();
#endif
#if CAP_QUEUE
for(cell *c1: crush_now)
queuecircleat(c1, .8, darkena(minf[moCrusher].color, 0, 0xFF));
#endif
if(!inHighQual) {
bool ok = !ISPANDORA || mousepressed;
ignore(ok);
#if CAP_QUEUE
if(haveMount())
for (const shiftmatrix& V : hr::span_at(current_display->all_drawn_copies, dragon::target)) {
queuestr(V, mapfontscale/100, "X",
gradient(0, iinf[itOrbDomination].color, -1, sintick(dragon::whichturn == turncount ? 75 : 150), 1));
}
#endif
/* for(int i=0; i<12; i++) if(c->type == 5 && c->master == &dodecahedron[i])
queuestr(xc, yc, sc, 4*vid.fsize, s0+('A'+i), iinf[itOrbDomination].color); */
if(1) {
using namespace yendor;
if(yii < isize(yi) && !yi[yii].found) {
cell *keycell = NULL;
int last_i = 0;
for(int i=0; i<YDIST; i++)
if(yi[yii].path[i]->cpdist <= get_sightrange_ambush()) {
keycell = yi[yii].path[i]; last_i = i;
}
if(keycell) {
for(int i = last_i+1; i<YDIST; i++) {
cell *c = yi[yii].path[i];
if(inscreenrange(c))
keycell = c;
}
shiftpoint H = tC0(ggmatrix(keycell));
#if CAP_QUEUE
queue_goal_text(H, 2, "X", 0x10101 * int(128 + 100 * sintick(150)));
int cd = celldistance(yi[yii].key(), cwt.at);
if(cd == DISTANCE_UNKNOWN) for(int i2 = 0; i2<YDIST; i2++) {
int cd2 = celldistance(cwt.at, yi[yii].path[i2]);
if(cd2 != DISTANCE_UNKNOWN) {
cd = cd2 + (YDIST-1-i2);
}
}
queue_goal_text(H, 1, its(cd), 0x10101 * int(128 - 100 * sintick(150)));
#endif
addauraspecial(H, iinf[itOrbYendor].color, 0);
addradar(ggmatrix(keycell), 'X', iinf[itKey].color, kind_outline(itKey), true);
}
}
}
#if CAP_RACING
racing::markers();
#endif
#if CAP_QUEUE
if(lmouseover && vid.drawmousecircle && ok && DEFAULTCONTROL && MOBON && WDIM == 2) {
cell *at = lmouseover;
#if CAP_VR
if(vrhr::active() && vrhr::forward_cell)
at = vrhr::forward_cell;
#endif
queuecircleat(at, .8, darkena(lmouseover->cpdist > 1 ? 0x00FFFF : 0xFF0000, 0, 0xFF));
}
if(global_pushto && vid.drawmousecircle && ok && DEFAULTCONTROL && MOBON && WDIM == 2) {
queuecircleat(global_pushto, .6, darkena(0xFFD500, 0, 0xFF));
}
#endif
#if CAP_SDLJOY && CAP_QUEUE
if(joydir.d >= 0 && WDIM == 2)
queuecircleat(cwt.at->modmove(joydir.d+cwt.spin), .78 - .02 * sintick(199),
darkena(0x00FF00, 0, 0xFF));
#endif
bool m = true;
ignore(m);
#if CAP_MODEL
m = netgen::mode == 0;
#endif
#if CAP_QUEUE
if(centerover && !playermoved && m && !anims::any_animation() && WDIM == 2 && draw_centerover)
queuecircleat(centerover, .70 - .06 * sintick(200),
darkena(int(175 + 25 * sintick(200)), 0, 0xFF));
if(multi::players > 1 || multi::alwaysuse) for(int i=0; i<numplayers(); i++) {
multi::cpid = i;
if(multi::players == 1) multi::player[i] = cwt;
cell *ctgt = multi::multiPlayerTarget(i);
queuecircleat(ctgt, .40 - .06 * sintick(200, i / numplayers()), getcs().uicolor);
}
#endif
// process mouse
#if CAP_SHAPES
if((vid.axes >= 4 || (vid.axes == 1 && !mousing)) && !shmup::on && GDIM == 2) {
if(multi::players == 1) {
forCellIdAll(c2, d, cwt.at) if(gmatrix.count(cwt.at)) draw_movement_arrows(c2, unshift(gmatrix[cwt.at]) * currentmap->adj(cwt.at, d), d);
}
else if(multi::players > 1) for(int p=0; p<multi::players; p++) {
if(multi::playerActive(p) && (vid.axes >= 4 || !drawstaratvec(multi::mdx[p], multi::mdy[p])))
forCellIdAll(c2, d, multi::player[p].at) if(gmatrix.count(cwt.at)) {
multi::cpid = p;
dynamicval<shiftmatrix> ttm(cwtV, multi::whereis[p]);
dynamicval<cellwalker> tcw(cwt, multi::player[p]);
draw_movement_arrows(c2, unshift(gmatrix[cwt.at]) * currentmap->adj(cwt.at, d), d);
}
}
}
if(GDIM == 3 && !inHighQual && !shmup::on && vid.axes3 && playermoved) {
cell *c = forwardcell();
if(c) queuecircleat(c, .8, getcs().uicolor);
}
#endif
if(mhybrid && !shmup::on) {
using namespace sword;
int& ang = sword::dir[multi::cpid].angle;
ang %= sword_angles;
int adj = 1 - ((sword_angles/cwt.at->type)&1);
if(items[itOrbSword])
for (const shiftmatrix& V : hr::span_at(current_display->all_drawn_copies, cwt.at))
queuestr(V * spin(M_PI+(-adj-2*ang)*M_PI/sword_angles) * xpush0(cgi.sword_size), vid.fsize*2, "+", iinf[itOrbSword].color);
if(items[itOrbSword2])
for (const shiftmatrix& V : hr::span_at(current_display->all_drawn_copies, cwt.at))
queuestr(V * spin((-adj-2*ang)*M_PI/sword_angles) * xpush0(-cgi.sword_size), vid.fsize*2, "+", iinf[itOrbSword2].color);
}
if(SWORDDIM == 3 && !shmup::on) {
if(items[itOrbSword])
for (const shiftmatrix& V : hr::span_at(current_display->all_drawn_copies, cwt.at))
queuestr(V * sword::dir[multi::cpid].T * xpush0(cgi.sword_size), vid.fsize*2, "+", iinf[itOrbSword].color);
if(items[itOrbSword2])
for (const shiftmatrix& V : hr::span_at(current_display->all_drawn_copies, cwt.at))
queuestr(V * sword::dir[multi::cpid].T * xpush0(-cgi.sword_size), vid.fsize*2, "+", iinf[itOrbSword2].color);
}
}
monsterToSummon = moNone;
orbToTarget = itNone;
if(mouseover && targetclick) {
multi::cpid = 0;
orbToTarget = targetRangedOrb(mouseover, roCheck);
#if CAP_QUEUE
if(bow::fire_mode) {
queuestr(mousex, mousey, 0, vid.fsize, "+", getcs().bowcolor >> 8);
orbToTarget = itNone;
}
else if(orbToTarget == itOrbSummon) {
monsterToSummon = summonedAt(mouseover);
queuestr(mousex, mousey, 0, vid.fsize, s0+minf[monsterToSummon].glyph, minf[monsterToSummon].color);
queuecircleat(mouseover, 0.6, darkena(minf[monsterToSummon].color, 0, 0xFF));
}
else if(orbToTarget) {
queuestr(mousex, mousey, 0, vid.fsize, "@", iinf[orbToTarget].color);
queuecircleat(mouseover, 0.6, darkena(iinf[orbToTarget].color, 0, 0xFF));
}
#endif
#if CAP_SHAPES
if(orbToTarget && rand() % 200 < ticks - lastt) {
if(orbToTarget == itOrbDragon)
drawFireParticles(mouseover, 2);
else if(orbToTarget == itOrbSummon) {
drawParticles(mouseover, iinf[orbToTarget].color, 1);
drawParticles(mouseover, minf[monsterToSummon].color, 1);
}
else {
drawParticles(mouseover, iinf[orbToTarget].color, 2);
}
}
if(items[itOrbAir] && mouseover->cpdist > 1) {
cell *c1 = mouseover;
int dir = c1->monst == moVoidBeast ? -1 : 1;
for(int it=0; it<10; it++) {
int di;
auto mib = blowoff_destination_dir(c1, di, dir);
if(!mib.proper()) break;
auto& c2 = mib.t;
shiftmatrix T1 = ggmatrix(c1);
shiftmatrix T2 = ggmatrix(c2);
shiftmatrix T = T1 * lrspintox(inverse_shift(T1,T2*C0)) * xpush(hdist(T1*C0, T2*C0) * fractick(50, 0));
color_t aircol = (orbToTarget == itOrbAir ? 0x8080FF40 : 0x8080FF20);
queuepoly(T, cgi.shDisk, aircol);
c1 = c2;
}
}
#endif
}
}
EX bool allowIncreasedSight() {
if(cheater || autocheat) return true;
if(peace::on) return true;
#if CAP_TOUR
if(tour::on) return true;
#endif
if(randomPatternsMode) return true;
if(racing::on) return true;
if(quotient || !hyperbolic || arcm::in() || arb::in()) return true;
if(WDIM == 3) return true;
if(!canmove) return true;
return false;
}
EX bool allowChangeRange() {
if(cheater || peace::on || randomPatternsMode) return true;
#if CAP_TOUR
if(tour::on) return true;
#endif
if(racing::on) return true;
if(arcm::in() || arb::in()) return true;
if(WDIM == 3) return true;
return false;
}
EX purehookset hooks_drawmap;
EX transmatrix actual_view_transform;
EX ld wall_radar(cell *c, transmatrix T, transmatrix LPe, ld max) {
if(!in_perspective() || !vid.use_wall_radar) return max;
transmatrix ori;
if(gproduct) ori = ortho_inverse(LPe);
ld step = max / 20;
ld fixed_yshift = 0;
for(int i=0; i<20; i++) {
T = shift_object(T, ori, ztangent(-step), shift_method(smaWallRadar));
virtualRebase(c, T);
color_t col;
if(isWall3(c, col) || (WDIM == 2 && GDIM == 3 && tC0(T)[2] > cgi.FLOOR)) {
T = shift_object(T, ori, ztangent(step), shift_method(smaWallRadar));
step /= 2; i = 17;
if(step < 1e-3) break;
}
else fixed_yshift += step;
}
return fixed_yshift;
}
/** if this is set to ON, just transform non-isotropic spaces according to View, and apply NLP to view */
EX bool nonisotropic_weird_transforms;
EX void decide_lpu() {
nisot::local_perspective_used = gproduct;
}
EX void make_actual_view() {
decide_lpu();
if(!nisot::local_perspective_used) NLP = Id;
sphereflip = Id;
sphere_flipped = flip_sphere();
if(sphere_flipped) sphereflip[LDIM][LDIM] = -1;
actual_view_transform = sphereflip;
if(vid.yshift && WDIM == 2) actual_view_transform = ypush(vid.yshift) * actual_view_transform;
#if MAXMDIM >= 4
if(GDIM == 3) {
ld max = WDIM == 2 ? vid.camera : vid.yshift;
if(max) {
transmatrix Start = view_inverse(actual_view_transform * View);
ld d = wall_radar(centerover, Start, NLP, max);
actual_view_transform = get_shift_view_of(ztangent(d), actual_view_transform * View) * view_inverse(View);
}
hyperpoint h = tC0(view_inverse(actual_view_transform * View));
camera_level = cgi.emb->get_logical_z(h);
camera_sign = cgi.FLOOR > cgi.WALL;
}
if((nonisotropic || (hyperbolic && bt::in() && !nisot::geodesic_movement)) && !nonisotropic_weird_transforms) {
transmatrix T = actual_view_transform * View;
transmatrix T2 = eupush( tC0(view_inverse(T)) );
NLP = T * T2;
actual_view_transform = ortho_inverse(NLP) * actual_view_transform;
nisot::local_perspective_used = true;
}
#endif
cgi.emb->set_radar_transform();
Viewbase = View;
}
EX shiftmatrix cview(ld base_shift IS(0)) {
return shiftless(actual_view_transform * View, base_shift);
}
EX int point_direction;
EX int through_wall(cell *c, hyperpoint at) {
ld dist = hdist0(at);
int nei = -1;
for(int i=0; i<c->type; i++) {
ld dist1 = hdist0(currentmap->ray_iadj(c, i) * at);
if(dist1 < dist) nei = i, dist = dist1;
}
return nei;
}
EX void precise_mouseover() {
if(WDIM == 3 && (cmode & (sm::EDIT_INSIDE_WALLS | sm::EDIT_BEFORE_WALLS))) {
transmatrix T = view_inverse(View);
transmatrix ori = Id;
if(gproduct) ori = ortho_inverse(NLP);
ld step = 0.2;
cell *c = centerover;
for(int i=0; i<100; i++) {
apply_shift_object(T, ori, ztangent(step));
int pd = through_wall(c, T * C0);
if(pd != -1) {
color_t col;
cell *c1 = c->cmove(pd);
if(isWall3(c1, col)) {
mouseover = c;
mouseover2 = c1;
point_direction = pd;
if(cmode & sm::EDIT_INSIDE_WALLS) {
swap(mouseover, mouseover2);
}
else {
point_direction =c->c.spin(pd);
}
return;
}
else {
T = currentmap->iadj(c, pd) * T;
c = c1;
}
}
}
}
if(WDIM == 3) {
mouseover2 = mouseover = centerover;
ld best = HUGE_VAL;
shiftpoint h = shiftless(direct_exp(lp_iapply(ztangent(0.01))));
point_direction = -1;
shiftmatrix cov = ggmatrix(mouseover2);
forCellIdEx(c1, i, mouseover2) {
shiftpoint h1 = tC0(cov * currentmap->adj(mouseover2, i));
ld dist = geo_dist(h, h1) - geo_dist(shiftless(C0), h1);
if(dist < best) mouseover = c1, best = dist, point_direction = i;
}
return;
}
if(!mouseover) return;
if(GDIM == 3) return;
cell *omouseover = mouseover;
for(int loop = 0; loop < 10; loop++) {
bool found = false;
if(!gmatrix.count(mouseover)) return;
hyperpoint r_mouseh = inverse_shift(gmatrix[mouseover], mouseh);
for(int i=0; i<mouseover->type; i++) {
hyperpoint h1 = get_corner_position(mouseover, gmod(i-1, mouseover->type));
hyperpoint h2 = get_corner_position(mouseover, i);
if(det3(build_matrix(h1, h2, C0, C0)) * det3(build_matrix(h1, h2, r_mouseh, C0)) < 0) {
mouseover2 = mouseover;
mouseover = mouseover->move(i);
found = true;
break;
}
}
if(!found) return;
}
// probably some error... just return the original
mouseover = omouseover;
}
EX transmatrix Viewbase;
EX bool no_wall_rendering;
EX bool set_multi = false;
EX hyperpoint multi_point;
EX void center_multiplayer_map(const vector<hyperpoint>& hs) {
hyperpoint h = Hypc;
for(auto h1: hs) h += h1;
h /= isize(hs);
h = cgi.emb->normalize_flat(h);
cwtV = shiftless(rgpushxto0(h));
if(isize(hs) == 2) {
set_multi = true;
multi_point = hs[1];
}
}
EX void drawthemap() {
check_cgi();
cgi.require_shapes();
DEBBI(DF_GRAPH, ("draw the map"));
last_firelimit = firelimit;
firelimit = 0;
make_clipping_planes();
current_display->radarpoints.clear();
current_display->radarlines.clear();
callhooks(hooks_drawmap);
frameid++;
cells_drawn = 0;
cells_generated = 0;
noclipped = 0;
first_cell_to_draw = true;
if(sightrange_bonus > 0 && !allowIncreasedSight())
sightrange_bonus = 0;
swap(gmatrix0, gmatrix);
gmatrix.clear();
current_display->all_drawn_copies.clear();
wmspatial = vid.wallmode == 4 || vid.wallmode == 5;
wmescher = vid.wallmode == 3 || vid.wallmode == 5;
wmplain = vid.wallmode == 2 || vid.wallmode == 4;
wmascii = vid.wallmode == 0 || vid.wallmode == 6;
wmascii3 = vid.wallmode == 6;
wmblack = vid.wallmode == 1;
mmitem = vid.monmode >= 1;
mmmon = vid.monmode >= 2;
mmspatial = vid.monmode >= 3;
mmhigh = vid.highlightmode >= 1;
if(hiliteclick) mmhigh = !mmhigh;
spatial_graphics = wmspatial || mmspatial;
spatial_graphics = spatial_graphics && GDIM == 2;
#if CAP_RUG
if(rug::rugged && !rug::spatial_rug) spatial_graphics = false;
#endif
if(non_spatial_model())
spatial_graphics = false;
if(pmodel == mdDisk && abs(pconf.alpha) < 1e-6) spatial_graphics = false;
if(!spatial_graphics) wmspatial = mmspatial = false;
if(GDIM == 3) wmspatial = mmspatial = true;
for(int m=0; m<motypes; m++) if(isPrincess(eMonster(m)))
minf[m].name = princessgender() ? "Princess" : "Prince";
#if CAP_RAY
ray::in_use = ray::requested();
#endif
no_wall_rendering = ray::in_use;
// ray::comparison_mode = true;
if(ray::comparison_mode) no_wall_rendering = false;
iinf[itSavedPrincess].name = minf[moPrincess].name;
for(int i=0; i<NUM_GS; i++) {
genderswitch_t& g = genderswitch[i];
if(g.gender != princessgender()) continue;
minf[g.m].help = g.desc;
minf[g.m].name = g.name;
}
if(mapeditor::autochoose) mapeditor::ew = mapeditor::ewsearch;
mapeditor::ewsearch.dist = 1e30;
modist = 1e20; mouseover = NULL;
modist2 = 1e20; mouseover2 = NULL;
compute_graphical_distance();
for(int i=0; i<multi::players; i++) {
multi::ccdist[i] = 1e20; multi::ccat[i] = NULL;
}
downseek.reset();
#if ISMOBILE
mouseovers = XLAT("No info about this...");
#endif
if(mouseout() && !mousepan)
modist = -5;
playerfound = false;
// playerfoundL = false;
// playerfoundR = false;
arrowtraps.clear();
make_actual_view();
currentmap->draw_all();
drawWormSegments();
drawBlizzards();
drawArrowTraps();
precise_mouseover();
ivoryz = false;
linepatterns::drawAll();
callhooks(hooks_frame);
drawMarkers();
drawFlashes();
mapeditor::draw_dtshapes();
set_multi = false;
if(multi::players > 1 && !shmup::on) {
if(multi::split_screen)
cwtV = multi::whereis[subscreens::current_player];
else if(multi::centerplayer != -1)
cwtV = multi::whereis[multi::centerplayer];
else {
vector<hyperpoint> pts;
for(int p=0; p<multi::players; p++) if(multi::playerActive(p))
pts.push_back(unshift(multi::whereis[p] * tile_center()));
center_multiplayer_map(pts);
}
}
if(shmup::on) {
if(multi::split_screen)
cwtV = shmup::pc[subscreens::current_player]->pat;
else if(multi::players == 1)
cwtV = shmup::pc[0]->pat;
else if(multi::centerplayer != -1)
cwtV = shmup::pc[multi::centerplayer]->pat;
else {
vector<hyperpoint> pts;
for(int p=0; p<multi::players; p++)
pts.push_back(unshift(shmup::pc[p]->pat * tile_center()));
center_multiplayer_map(pts);
}
}
#if CAP_SDL
const sdl_keystate_type *keystate = SDL12_GetKeyState(NULL);
lmouseover = mouseover;
lmouseover_distant = lmouseover;
bool useRangedOrb = (!(vid.shifttarget & 1) && haveRangedOrb() && lmouseover && lmouseover->cpdist > 1) || (keystate[SDL12(SDLK_RSHIFT, SDL_SCANCODE_RSHIFT)] | keystate[SDL12(SDLK_LSHIFT, SDL_SCANCODE_LSHIFT)]);
if(!useRangedOrb && !(cmode & sm::MAP) && !(cmode & sm::DRAW) && DEFAULTCONTROL && !mouseout() && !dual::state) {
dynamicval<eGravity> gs(gravity_state, gravity_state);
calcMousedest();
cellwalker cw = cwt; bool f = flipplayer;
items[itWarning]+=2;
movepcto(mousedest.d, mousedest.subdir, true);
items[itWarning] -= 2;
if(cw.spin != cwt.spin) mirror::act(-mousedest.d, mirror::SPINSINGLE);
cwt = cw; flipplayer = f;
lmouseover = mousedest.d >= 0 ? cwt.at->modmove(cwt.spin + mousedest.d) : cwt.at;
}
#endif
}
// old style joystick control
EX bool dronemode;
purehookset hooks_calcparam;
EX int corner_centering;
EX bool permaside;
EX bool old_center;
EX ld min_scale = 1e-6;
EX int forced_center_down = ISANDROID ? 2 : ISIOS ? 40 : 40;
EX ld get_stereo_param() {
if(among(vid.stereo_mode, sPanini, sStereographic)) return vid.stereo_param;
return 0;
}
EX void calcparam() {
DEBBI(DF_GRAPH, ("calc param"));
auto cd = current_display;
cd->xtop = vid.xres * cd->xmin;
cd->ytop = vid.yres * cd->ymin;
cd->xsize = vid.xres * (cd->xmax - cd->xmin);
cd->ysize = vid.yres * (cd->ymax - cd->ymin);
cd->xcenter = cd->xtop + cd->xsize / 2;
cd->ycenter = cd->ytop + cd->ysize / 2;
if(abs(pconf.scale) < min_scale) pconf.scale = 1;
ld realradius = min(cd->xsize / 2, cd->ysize / 2);
cd->scrsize = realradius;
if(!inHighQual) cd->scrsize -= forced_center_down;
current_display->sidescreen = permaside;
if(vid.xres < vid.yres - 2 * vid.fsize && !inHighQual && (old_center || !in_perspective())) {
cd->ycenter = lerp(vid.fsize + cd->scrsize, vid.yres - cd->scrsize - vid.fsize, .8);
}
else {
bool ok = !vrhr::active();
if(vid.xres > vid.yres * 4/3+16 && (cmode & sm::SIDE) && ok && !((cmode & sm::MAYDARK) && centered_menus))
current_display->sidescreen = true;
#if CAP_TOUR
if(tour::on && (tour::slides[tour::currentslide].flags & tour::SIDESCREEN) && ok)
current_display->sidescreen = true;
#endif
if((cmode & sm::DIALOG_OFFMAP) && !centered_menus && vid.xres > vid.yres * 11/10)
current_display->sidescreen = true;
if(current_display->sidescreen) cd->xcenter = vid.yres/2;
}
cd->radius = pconf.scale * cd->scrsize;
if(GDIM == 3 && in_perspective()) cd->radius = cd->scrsize;
realradius = min(realradius, cd->radius);
ld aradius = sphere ? cd->radius / (pconf.alpha - 1) : cd->radius;
#if MAXMDIM >= 4
if(euclid && hybrid::drawing_underlying) aradius *= 2.5;
#endif
if(dronemode) { cd->ycenter -= cd->radius; cd->ycenter += vid.fsize/2; cd->ycenter += vid.fsize/2; cd->radius *= 2; }
if(corner_centering) {
cd->ycenter = cd->ytop + cd->ysize - vid.fsize - aradius;
if(corner_centering == 1)
cd->xcenter = cd->xtop + vid.fsize + aradius;
if(corner_centering == 2)
cd->xcenter = cd->xtop + cd->xsize - vid.fsize - aradius;
}
cd->xcenter += cd->scrsize * pconf.xposition;
cd->ycenter += cd->scrsize * pconf.yposition;
ld fov = vid.fov * degree / 2;
cd->tanfov = sin(fov) / (cos(fov) + get_stereo_param());
#if CAP_SDLTTF
set_cfont();
#endif
callhooks(hooks_calcparam);
reset_projection();
}
EX function<void()> wrap_drawfullmap = drawfullmap;
bool force_sphere_outline = false;
EX void drawfullmap() {
DEBBI(DF_GRAPH, ("draw full map"));
check_cgi();
cgi.require_shapes();
ptds.clear();
/*
if(models::on) {
char ch = 'A';
for(auto& v: history::v) {
queuepoly(ggmatrix(v->base) * v->at, cgi.shTriangle, 0x306090C0);
queuestr(ggmatrix(v->base) * v->at * C0, 10, s0+(ch++), 0xFF0000);
}
}
*/
#if CAP_QUEUE
draw_boundary(0);
draw_boundary(1);
draw_model_elements();
#if MAXMDIM >= 4 && CAP_GL
prepare_sky();
#endif
#endif
/* if(vid.wallmode < 2 && !euclid && !patterns::whichShape) {
int ls = isize(lines);
if(ISMOBILE) ls /= 10;
for(int t=0; t<ls; t++) queueline(View * lines[t].P1, View * lines[t].P2, lines[t].col >> (darken+1));
} */
clearaura();
if(!nomap) drawthemap();
else callhooks(hooks_frame);
if(!inHighQual) {
if((cmode & sm::NORMAL) && !rug::rugged) {
if(multi::players > 1) {
auto bcwtV = cwtV;
for(int i=0; i<multi::players; i++) if(multi::playerActive(i))
cwtV = multi::whereis[i], multi::cpid = i, drawmovestar(multi::mdx[i], multi::mdy[i]);
cwtV = bcwtV;
}
else if(multi::alwaysuse)
drawmovestar(multi::mdx[0], multi::mdy[0]);
else
drawmovestar(0, 0);
}
#if CAP_EDIT
if(cmode & sm::DRAW) mapeditor::drawGrid();
#endif
}
drawaura();
#if CAP_QUEUE
drawqueue();
#endif
}
#if ISMOBILE
extern bool wclick;
#endif
EX bool just_refreshing;
EX int menu_darkening = 2;
EX bool centered_menus = false;
EX string menu_format = "";
EX void gamescreen() {
if(cmode & sm::NOSCR) {
stillscreen = true;
emptyscreen();
return;
}
if(just_refreshing) return;
if(subscreens::split([=] () {
calcparam();
compute_graphical_distance();
gamescreen();
})) {
if(racing::on) return;
// create the gmatrix
View = subscreens::player_displays[0].view_matrix;
centerover = subscreens::player_displays[0].precise_center;
just_gmatrix = true;
currentmap->draw_all();
just_gmatrix = false;
return;
}
stillscreen = false;
auto gx = vid.xres;
auto gy = vid.yres;
if(dual::split([=] () {
vid.xres = gx;
vid.yres = gy;
dual::in_subscreen([=] () { gamescreen(); });
})) {
calcparam();
return;
}
calcparam();
darken = 0;
if(!inHighQual && !vrhr::active()) {
if((cmode & sm::MAYDARK) && !current_display->sidescreen)
darken += menu_darkening;
else if(cmode & sm::DARKEN)
darken += menu_darkening;
}
if(vid.highlightmode == (hiliteclick ? 0 : 2))
darken++;
if(darken >= 8) {
emptyscreen();
return;
}
if(history::includeHistory) history::restore();
festive = festive_date && festive_option;
old_shines = std::move(shines); shines.clear();
anims::apply();
#if CAP_RUG
if(rug::rugged) {
if(!nomap) rug::actDraw();
} else
#endif
wrap_drawfullmap();
anims::rollback();
if(history::includeHistory) history::restoreBack();
poly_outline = OUTLINE_DEFAULT;
#if ISMOBILE
buttonclicked = false;
if((cmode & sm::NORMAL) && vid.stereo_mode != sLR && !inHighQual) {
if(andmode == 0 && shmup::on) {
using namespace shmupballs;
calc();
drawCircle(xmove, yb, rad, OUTLINE_FORE);
drawCircle(xmove, yb, rad/2, OUTLINE_FORE);
drawCircle(xfire, yb, rad, 0xFF0000FF);
drawCircle(xfire, yb, rad/2, 0xFF0000FF);
}
else {
if(!haveMobileCompass()) displayabutton(-1, +1, andmode == 0 && useRangedOrb ? XLAT("FIRE") : andmode == 0 && WDIM == 3 && wclick ? XLAT("WAIT") : XLAT("MOVE"), andmode == 0 ? BTON : BTOFF);
displayabutton(+1, +1, rug::rugged ? XLAT("RUG") :andmode == 1 ? XLAT("BACK") : GDIM == 3 ? XLAT("CAM") : XLAT("DRAG"), andmode == 1 ? BTON : BTOFF);
}
displayabutton(-1, -1, XLAT("INFO"), andmode == 12 ? BTON : BTOFF);
displayabutton(+1, -1, XLAT("MENU"), andmode == 3 ? BTON : BTOFF);
}
#endif
darken = 0;
#if CAP_TEXTURE
if(texture::config.tstate == texture::tsAdjusting)
texture::config.drawRawTexture();
#endif
#if CAP_VR
vrhr::size_and_draw_ui_box();
#endif
}
EX void emptyscreen() {
check_cgi();
cgi.require_shapes();
make_actual_view();
ptds.clear();
ray::in_use = false;
drawqueue();
}
EX int nohelp;
EX bool no_find_player;
EX void show_menu_button() {
if(menu_format != "")
displayButton(vid.xres-8, vid.yres-vid.fsize, eval_programmable_string(menu_format), 'v', 16);
else if(nomenukey || ISMOBILE)
;
#if CAP_TOUR
else if(tour::on)
displayButton(vid.xres-8, vid.yres-vid.fsize, XLAT("(ESC) tour menu"), SDLK_ESCAPE, 16);
#endif
else
displayButton(vid.xres-8, vid.yres-vid.fsize, XLAT("(v) menu"), 'v', 16);
}
EX void normalscreen() {
help = "@";
mouseovers = standard_help();
#if CAP_TOUR
if(tour::on) mouseovers = (tour::slides[tour::currentslide].flags & tour::NOTITLE) ? "" : tour::tourhelp;
#endif
if(GDIM == 3 || !outofmap(mouseh.h)) getcstat = '-';
cmode = sm::NORMAL | sm::DOTOUR | sm::CENTER;
if(viewdists && show_distance_lists) cmode |= sm::SIDE | sm::MAYDARK;
gamescreen(); drawStats();
show_menu_button();
keyhandler = handleKeyNormal;
dialog::key_actions.clear();
if(!playerfound && !anims::any_on() && !sphere && !no_find_player && mapeditor::drawplayer)
displayButton(current_display->xcenter, current_display->ycenter, mousing ? XLAT("find the player") : XLAT("press SPACE to find the player"), ' ', 8);
if(!mapeditor::drawplayer && playermoved && !no_find_player)
displayButton(current_display->xcenter, current_display->ycenter, XLAT("move the camera with arrow keys and Home/End"), PSEUDOKEY_NOHINT, 8);
describeMouseover();
}
EX vector< function<void()> > screens = { normalscreen };
#if HDR
template<class T> void pushScreen(const T& x) { screens.push_back(x); }
inline void popScreen() { if(isize(screens)>1) screens.pop_back(); }
inline void popScreenAll() { while(isize(screens)>1) popScreen(); }
typedef void (*cfunction)();
#endif
EX cfunction current_screen_cfunction() {
auto tgt = screens.back().target<cfunction>();
if(!tgt) return nullptr;
return *tgt;
}
#if HDR
namespace sm {
static constexpr int NORMAL = 1;
static constexpr int MISSION = 2;
static constexpr int HELP = 4;
static constexpr int MAP = 8;
static constexpr int DRAW = 16;
static constexpr int NUMBER = 32;
static constexpr int SHMUPCONFIG = 64;
static constexpr int OVERVIEW = 128;
static constexpr int SIDE = 256;
static constexpr int DOTOUR = 512;
static constexpr int CENTER = 1024;
static constexpr int ZOOMABLE = 4096;
static constexpr int TORUSCONFIG = 8192;
static constexpr int MAYDARK = 16384; // use together with SIDE; if the screen is not wide or centered_menus is set, it will disable SIDE and instead darken the screen
static constexpr int DIALOG_STRICT_X = 32768; // do not interpret dialog clicks outside of the X region
static constexpr int EXPANSION = (1<<16);
static constexpr int HEXEDIT = (1<<17);
static constexpr int VR_MENU = (1<<18); // always show the menu in VR
static constexpr int SHOWCURSOR = (1<<19); // despite MAP/DRAW always show the cursor, no panning
static constexpr int PANNING = (1<<20); // smooth scrolling works
static constexpr int DARKEN = (1<<21); // darken the game background
static constexpr int NOSCR = (1<<22); // do not show the game background
static constexpr int AUTO_VALUES = (1<<23); // automatic place for values
static constexpr int NARROW_LINES = (1<<24); // do make the lines narrower if we needed to reduce width
static constexpr int EDIT_BEFORE_WALLS = (1<<25); // mouseover targets before walls
static constexpr int EDIT_INSIDE_WALLS = (1<<26); // mouseover targets inside walls
static constexpr int DIALOG_WIDE = (1<<27); // make dialogs wide
static constexpr int MOUSEAIM = (1<<28); // mouse aiming active here
static constexpr int DIALOG_OFFMAP = (1<<29); // try hard to keep dialogs off the map
}
#endif
EX int cmode;
EX bool dont_display_minecount = false;
EX color_t titlecolor;
EX void drawscreen() {
DEBBI(DF_GRAPH, ("drawscreen"));
#if CAP_GL
GLWRAP;
#endif
if(vid.xres == 0 || vid.yres == 0) return;
calcparam();
// rug::setVidParam();
#if CAP_GL
if(vid.usingGL) setGLProjection();
#endif
#if CAP_XGD
if(!vid.usingGL) {
gdpush(5); gdpush(backcolor);
}
#endif
#if CAP_VR
vrhr::clear();
#endif
#if CAP_SDL
// SDL_LockSurface(s);
// unsigned char *b = (unsigned char*) s->pixels;
// int n = vid.xres * vid.yres * 4;
// while(n) *b >>= 1, b++, n--;
// memset(s->pixels, 0, vid.xres * vid.yres * 4);
#if CAP_GL
if(!vid.usingGL)
#endif
SDL_FillSurfaceRect(s, NULL, backcolor);
#endif
// displaynum(vx,100, 0, 24, 0xc0c0c0, celldist(cwt.at), ":");
lgetcstat = getcstat;
getcstat = 0; inslider = false;
mouseovers = " ";
cmode = 0;
reset_handlers();
if(!isize(screens)) pushScreen(normalscreen);
screens.back()();
#if !ISMOBILE
color_t col = linf[cwt.at->land].color;
if(cwt.at->land == laRedRock) col = 0xC00000;
if(titlecolor) col = titlecolor;
if(nohelp != 1) {
int size = vid.fsize;
while(size > 3 && textwidth(size, mouseovers) > vid.xres) size--;
poly_outline = (backcolor << 8) | 0xFF;
displayfr(vid.xres/2, vid.fsize, 2, size, mouseovers, col, 8);
}
#endif
drawmessages();
bool normal = cmode & sm::NORMAL;
if((havewhat&HF_BUG) && darken == 0 && normal) if(hive::bugcount[0] || hive::bugcount[1] || hive::bugcount[2]) for(int k=0; k<3; k++)
displayfr(vid.xres/2 + vid.fsize * 5 * (k-1), vid.fsize*2, 2, vid.fsize,
its(hive::bugcount[k]), minf[moBug0+k].color, 8);
bool minefieldNearby = false;
int mines[MAXPLAYER], tmines=0;
for(int p=0; p<numplayers(); p++) {
mines[p] = 0;
cell *c = playerpos(p);
if(!c) continue;
for(cell *c2: adj_minefield_cells(c)) {
if(c2->land == laMinefield)
minefieldNearby = true;
if(c2->wall == waMineMine) {
bool ep = false;
if(!ep) mines[p]++, tmines++;
}
}
}
if((minefieldNearby || tmines) && !items[itOrbAether] && !last_gravity_state && darken == 0 && normal) {
string s;
if(tmines > 9) tmines = 9;
color_t col = minecolors[tmines];
if(tmines == 7) seenSevenMines = true;
if(!dont_display_minecount) for(int p: player_indices())
displayfr(vid.xres * (p+.5) / numplayers(),
current_display->ycenter - current_display->radius * 3/4, 2,
vid.fsize,
mines[p] > 7 ? its(mines[p]) : XLAT(minetexts[mines[p]]), minecolors[mines[p]], 8);
if(minefieldNearby && !shmup::on && cwt.at->land != laMinefield && cwt.peek()->land != laMinefield && !dont_display_minecount) {
displayfr(vid.xres/2, current_display->ycenter - current_display->radius * 3/4 - vid.fsize*3/2, 2,
vid.fsize,
XLAT("WARNING: you are entering a minefield!"),
col, 8);
}
}
// SDL_UnlockSurface(s);
glflush();
DEBB(DF_GRAPH, ("swapbuffers"));
#if CAP_VR
vrhr::submit();
#endif
#if CAP_SDL
present_screen();
#endif
#if CAP_VR
vrhr::handoff();
#endif
//printf("\ec");
}
EX void restartGraph() {
DEBBI(DF_INIT, ("restartGraph"));
if(!autocheat) linepatterns::clearAll();
if(currentmap) resetview();
}
auto graphcm = addHook(hooks_clearmemory, 0, [] () {
DEBBI(DF_MEMORY, ("clear graph memory"));
mouseover = centerover = lmouseover = NULL;
gmatrix.clear(); gmatrix0.clear(); current_display->all_drawn_copies.clear();
clearAnimations();
})
+ addHook(hooks_gamedata, 0, [] (gamedata* gd) {
gd->store(mouseover);
gd->store(lmouseover);
gd->store(current_display->radar_transform);
gd->store(actual_view_transform);
});
EX void drawBug(const cellwalker& cw, color_t col) {
#if CAP_SHAPES
initquickqueue();
shiftmatrix V = ggmatrix(cw.at);
if(cw.spin) V = V * ddspin180(cw.at, cw.spin);
queuepoly(V, cgi.shBugBody, col);
quickqueue();
#endif
}
EX bool inscreenrange_actual(cell *c) {
if(GDIM == 3) return true;
hyperpoint h1; applymodel(ggmatrix(c) * tile_center(), h1);
if(invalid_point(h1)) return false;
auto hscr = toscrcoord(h1);
auto& x = hscr[0], y = hscr[1];
if(x > current_display->xtop + current_display->xsize) return false;
if(x < current_display->xtop) return false;
if(y > current_display->ytop + current_display->ysize) return false;
if(y < current_display->ytop) return false;
return true;
}
EX bool inscreenrange(cell *c) {
if(sphere) return true;
if(euclid) return celldistance(centerover, c) <= get_sightrange_ambush() && inscreenrange_actual(c);
if(nonisotropic) return gmatrix.count(c);
if(geometry == gCrystal344) return gmatrix.count(c) && inscreenrange_actual(c);
auto hd = heptdistance(centerover, c);
if(hd <= 1) return true;
return hd <= 8 && inscreenrange_actual(c);
}
#if MAXMDIM >= 4
auto hooksw = addHook(hooks_swapdim, 100, [] { clearAnimations(); gmatrix.clear(); gmatrix0.clear(); current_display->all_drawn_copies.clear(); });
#endif
}