// Hyperbolic Rogue -- basic graphics // Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details /** \file basegraph.cpp * \brief This file implements the basic graphical routines */ #include "hyper.h" namespace hr { EX int fontscale = 100; #if HDR /** configuration of the current view */ struct display_data { /** This specifies the heptagon the view is currently centered on. * Unused in masterless tilings -- precise_center is used there. */ heptspin view_center; /** The current rotation, relative to viewctr. */ transmatrix view_matrix; /** The view relative to the player character. */ transmatrix player_matrix; /** The cell which is precisely in the center. */ cellwalker precise_center; /** On-screen coordinates for all the visible cells. */ unordered_map cellmatrices, old_cellmatrices; /** Position of the current map view, relative to the screen (0 to 1). */ ld xmin, ymin, xmax, ymax; /** Position of the current map view, in pixels. */ ld xtop, ytop, xsize, ysize; display_data() { xmin = ymin = 0; xmax = ymax = 1; } /** Center of the current map view, in pixels. */ int xcenter, ycenter; ld radius; int scrsize; bool sidescreen; ld tanfov; GLfloat scrdist, scrdist_text; ld eyewidth(); bool stereo_active(); bool in_anaglyph(); void set_viewport(int ed); void set_projection(int ed); void set_mask(int ed); void set_all(int ed); }; #define View (current_display->view_matrix) #define cwtV (current_display->player_matrix) #define viewctr (current_display->view_center) #define centerover (current_display->precise_center) #define gmatrix (current_display->cellmatrices) #define gmatrix0 (current_display->old_cellmatrices) #endif EX display_data default_display; EX display_data *current_display = &default_display; /** Color of the background. */ EX unsigned backcolor = 0; EX unsigned bordcolor = 0; EX unsigned forecolor = 0xFFFFFF; int utfsize(char c) { unsigned char cu = c; if(cu < 128) return 1; if(cu < 224) return 2; if(cu < 0xF0) return 3; return 4; } EX int get_sightrange() { return getDistLimit() + sightrange_bonus; } EX int get_sightrange_ambush() { return max(get_sightrange(), ambush_distance); } namespace stereo { eStereo mode; ld ipd; ld lr_eyewidth, anaglyph_eyewidth; ld fov, tanfov; GLfloat scrdist; } bool display_data::in_anaglyph() { return vid.stereo_mode == sAnaglyph; } bool display_data::stereo_active() { return vid.stereo_mode != sOFF; } ld display_data::eyewidth() { switch(vid.stereo_mode) { case sAnaglyph: return vid.anaglyph_eyewidth; case sLR: return vid.lr_eyewidth; default: return 0; } } bool eqs(const char* x, const char* y) { return *y? *x==*y?eqs(x+1,y+1):false:true; } EX int getnext(const char* s, int& i) { int siz = utfsize(s[i]); // if(fontdeb) printf("s=%s i=%d siz=%d\n", s, i, siz); if(siz == 1) return s[i++]; for(int k=0; k= surf->w || y >= surf->h) return qpixel_pixel_outside; char *p = (char*) surf->pixels; p += y * surf->pitch; color_t *pi = (color_t*) (p); return pi[x]; } #endif #if CAP_SDLTTF EX string fontpath = ISWEB ? "sans-serif" : HYPERPATH "DejaVuSans-Bold.ttf"; void loadfont(int siz) { if(!font[siz]) { font[siz] = TTF_OpenFont(fontpath.c_str(), siz); // Destination set by ./configure (in the GitHub repository) #ifdef FONTDESTDIR if (font[siz] == NULL) { font[siz] = TTF_OpenFont(FONTDESTDIR, siz); } #endif if (font[siz] == NULL) { printf("error: Font file not found: %s\n", fontpath.c_str()); exit(1); } } } #endif #if !ISFAKEMOBILE && !ISANDROID & !ISIOS int textwidth(int siz, const string &str) { if(isize(str) == 0) return 0; #if CAP_SDLTTF loadfont(siz); int w, h; TTF_SizeUTF8(font[siz], str.c_str(), &w, &h); // printf("width = %d [%d]\n", w, isize(str)); return w; #elif CAP_GL return gl_width(siz, str.c_str()); #else return 0; #endif } #endif #if ISIOS int textwidth(int siz, const string &str) { return mainfont->getSize(str, siz / 36.0).width; } #endif EX int darkenedby(int c, int lev) { for(int i=0; i> 1); return c; } bool fading = false; ld fadeout = 1; EX color_t darkened(color_t c) { if(inmirrorcount&1) c = gradient(c, winf[waMirror].color, 0, 0.5, 1); else if(inmirrorcount) c = gradient(c, winf[waCloud].color, 0, 0.5, 1); if(fading) c = gradient(backcolor, c, 0, fadeout, 1); if(vid.desaturate) { ld luminance = 0.2125 * part(c,2) + 0.7154 * part(c,1) + 0.0721 * part(c, 0); c = gradient(c, int(luminance+.5) * 0x10101, 0, vid.desaturate, 100); } for(int i=0; i> 1) + ((backcolor & 0xFEFEFE) >> 1); return c; } EX color_t darkena3(color_t c, int lev, int a) { return (darkenedby(c, lev) << 8) + a; } EX color_t darkena(color_t c, int lev, int a) { return darkena3(c, lev, GDIM == 3 ? 255 : a); } #if !CAP_GL void setcameraangle(bool b) { } #endif #if CAP_GL EX bool shaderside_projection; EX void start_projection(int ed, bool perspective) { glhr::use_projection(); glhr::new_projection(); shaderside_projection = perspective; auto cd = current_display; if(ed && vid.stereo_mode == sLR) { glhr::projection_multiply(glhr::translate(ed, 0, 0)); glhr::projection_multiply(glhr::scale(2, 1, 1)); } ld tx = (cd->xcenter-cd->xtop)*2./cd->xsize - 1; ld ty = (cd->ycenter-cd->ytop)*2./cd->ysize - 1; glhr::projection_multiply(glhr::translate(tx, -ty, 0)); } #if CAP_VR EX hookset *hooks_vr_eye_view, *hooks_vr_eye_projection; #endif EX void eyewidth_translate(int ed) { glhr::using_eyeshift = false; #if CAP_VR if(callhandlers(false, hooks_vr_eye_view)) ; else #endif if(ed) glhr::projection_multiply(glhr::translate(-ed * current_display->eyewidth(), 0, 0)); } glhr::glmatrix model_orientation_gl() { glhr::glmatrix s = glhr::id; for(int a=0; a last_projection; EX bool new_projection_needed; #if HDR inline void reset_projection() { new_projection_needed = true; } #endif void display_data::set_all(int ed) { auto t = this; auto current_projection = tie(ed, pmodel, t, current_rbuffer); if(new_projection_needed || last_projection != current_projection) { last_projection = current_projection; set_projection(ed); set_mask(ed); set_viewport(ed); new_projection_needed = false; } } void display_data::set_projection(int ed) { DEBBI(DF_GRAPH, ("current_display->set_projection")); bool pers3 = false; bool apply_models = !among(pmodel, mdUnchanged, mdFlatten, mdRug); shaderside_projection = false; glhr::new_shader_projection = glhr::shader_projection::standard; if(vid.consider_shader_projection && pmodel == mdDisk && !spherespecial && !(hyperbolic && vid.alpha <= -1) && MDIM == 3) shaderside_projection = true; else if(vid.consider_shader_projection && !glhr::noshaders) { if(pmodel == mdDisk && !spherespecial && !(hyperbolic && vid.alpha <= -1) && GDIM == 3 && apply_models) shaderside_projection = true, glhr::new_shader_projection = glhr::shader_projection::ball; if(pmodel == mdBand && hyperbolic && apply_models) shaderside_projection = true, glhr::new_shader_projection = (GDIM == 2 ? glhr::shader_projection::band : glhr::shader_projection::band3); if(pmodel == mdHalfplane && hyperbolic && apply_models && GDIM == 2) shaderside_projection = true, glhr::new_shader_projection = glhr::shader_projection::halfplane; if(pmodel == mdHalfplane && hyperbolic && apply_models && GDIM == 3 && vid.alpha == 1) shaderside_projection = true, glhr::new_shader_projection = glhr::shader_projection::halfplane3; if(GDIM == 3 && hyperbolic && apply_models && pmodel == mdPerspective) shaderside_projection = true, glhr::new_shader_projection = glhr::shader_projection::standardH3, pers3 = true; if(GDIM == 3 && translatable && apply_models && pmodel == mdPerspective) shaderside_projection = true, glhr::new_shader_projection = glhr::shader_projection::standardR3, pers3 = true; if(GDIM == 3 && apply_models && pmodel == mdPerspective && in_s2xe()) shaderside_projection = true, glhr::new_shader_projection = glhr::shader_projection::standardR3, pers3 = true; if(GDIM == 3 && apply_models && pmodel == mdPerspective && in_h2xe()) shaderside_projection = true, glhr::new_shader_projection = glhr::shader_projection::standardEH2, pers3 = true; if(GDIM == 3 && apply_models && pmodel == mdGeodesic && sol) shaderside_projection = true, glhr::new_shader_projection = glhr::shader_projection::standardSolv, pers3 = true; if(GDIM == 3 && apply_models && pmodel == mdGeodesic && nih) shaderside_projection = true, glhr::new_shader_projection = glhr::shader_projection::standardNIH, pers3 = true; if(GDIM == 3 && apply_models && pmodel == mdGeodesic && sl2) shaderside_projection = true, glhr::new_shader_projection = glhr::shader_projection::standardSL2, pers3 = true; if(GDIM == 3 && apply_models && pmodel == mdGeodesic && nil) shaderside_projection = true, glhr::new_shader_projection = glhr::shader_projection::standardNil, pers3 = true; if(GDIM == 3 && sphere && apply_models && pmodel == mdPerspective) { shaderside_projection = true; pers3 = true; int sp = spherephase & 3; if(sp == 0) glhr::new_shader_projection = glhr::shader_projection::standardS30; if(sp == 1) glhr::new_shader_projection = glhr::shader_projection::standardS31; if(sp == 2) glhr::new_shader_projection = glhr::shader_projection::standardS32; if(sp == 3) glhr::new_shader_projection = glhr::shader_projection::standardS33; } } if(pmodel == mdFlatten) shaderside_projection = true, glhr::new_shader_projection = glhr::shader_projection::flatten; start_projection(ed, shaderside_projection); if(pmodel == mdRug) return; #if CAP_SOLV if(among(glhr::new_shader_projection, glhr::shader_projection::standardSolv, glhr::shader_projection::standardNIH)) { auto &tab = solnihv::get_tabled(); GLuint invexpid = tab.get_texture_id(); glActiveTexture(GL_TEXTURE0 + glhr::INVERSE_EXP_BINDING); glBindTexture(GL_TEXTURE_3D, invexpid); glActiveTexture(GL_TEXTURE0 + 0); glhr::set_solv_prec(tab.PRECX, tab.PRECY, tab.PRECZ); } #endif if(glhr::new_shader_projection == glhr::shader_projection::standardSL2) { glhr::set_index_sl(0); glhr::set_sl_iterations(slr::steps); } auto cd = current_display; if(!shaderside_projection || glhr::new_shader_projection == glhr::shader_projection::flatten) { if(GDIM == 3 && apply_models) { glhr::projection_multiply(glhr::ortho(cd->xsize/2, -cd->ysize/2, 1)); glhr::id_modelview(); } else { glhr::projection_multiply(glhr::ortho(cd->xsize/2, -cd->ysize/2, abs(current_display->scrdist) + 30000)); if(ed) { glhr::glmatrix m = glhr::id; m[2][0] -= ed; glhr::projection_multiply(m); } glhr::id_modelview(); } } else { if(hyperbolic && vid.alpha > -1 && GDIM == 2) { // Because of the transformation from H3 to the Minkowski hyperboloid, // points with negative Z can be generated in some 3D settings. // This happens for points below the camera, but above the plane. // These points should still be viewed, though, so we disable the // depth clipping glhr::projection_multiply(glhr::scale(1,1,0)); } eyewidth_translate(ed); #if CAP_VR if(callhandlers(false, hooks_vr_eye_projection)) ; else #endif if(pers3) { glhr::projection_multiply(glhr::frustum(current_display->tanfov, current_display->tanfov * cd->ysize / cd->xsize)); glhr::projection_multiply(glhr::scale(1, -1, -1)); if(nisot::local_perspective_used()) { if(prod) { for(int i=0; i<3; i++) nisot::local_perspective[3][i] = nisot::local_perspective[i][3] = 0; nisot::local_perspective[3][3] = 1; } glhr::projection_multiply(glhr::tmtogl_transpose(nisot::local_perspective)); } } else if(MDIM == 4) { glhr::glmatrix M = glhr::ortho(cd->xsize/current_display->radius/2, -cd->ysize/current_display->radius/2, 1); using models::clip_max; using models::clip_min; M[2][2] = 2 / (clip_max - clip_min); M[3][2] = (clip_min + clip_max) / (clip_max - clip_min); glhr::projection_multiply(M); if(nisot::local_perspective_used()) glhr::projection_multiply(glhr::tmtogl_transpose(nisot::local_perspective)); } else { glhr::projection_multiply(glhr::frustum(cd->xsize / cd->ysize, 1)); GLfloat sc = current_display->radius / (cd->ysize/2.); glhr::projection_multiply(glhr::scale(sc, -sc, -1)); } if(ed) { if(pers3) { if(anyshiftclick) glhr::projection_multiply(glhr::tmtogl(xpush(vid.ipd * ed/2))); else { glhr::using_eyeshift = true; glhr::eyeshift = glhr::tmtogl(xpush(vid.ipd * ed/2)); } } else glhr::projection_multiply(glhr::translate(vid.ipd * ed/2, 0, 0)); } if(pers3) { glhr::fog_max(1/sightranges[geometry], darkena(backcolor, 0, 0xFF)); } if(glhr::new_shader_projection == glhr::shader_projection::ball) glhr::set_ualpha(vid.alpha); if(among(glhr::new_shader_projection, glhr::shader_projection::band, glhr::shader_projection::band3)) { glhr::projection_multiply(model_orientation_gl()); glhr::projection_multiply(glhr::scale(2 / M_PI, 2 / M_PI, GDIM == 3 ? 2/M_PI : 1)); } if(among(glhr::new_shader_projection, glhr::shader_projection::halfplane, glhr::shader_projection::halfplane3)) { glhr::projection_multiply(model_orientation_gl()); glhr::projection_multiply(glhr::translate(0, 1, 0)); glhr::projection_multiply(glhr::scale(-1, 1, 1)); glhr::projection_multiply(glhr::scale(models::halfplane_scale, models::halfplane_scale, GDIM == 3 ? models::halfplane_scale : 1)); glhr::projection_multiply(glhr::translate(0, 0.5, 0)); } } if(vid.camera_angle && !among(pmodel, mdUnchanged, mdFlatten, mdRug)) { ld cam = vid.camera_angle * degree; GLfloat cc = cos(cam); GLfloat ss = sin(cam); GLfloat yzspin[16] = { 1, 0, 0, 0, 0, cc, ss, 0, 0, -ss, cc, 0, 0, 0, 0, 1 }; glhr::projection_multiply(glhr::as_glmatrix(yzspin)); } } void display_data::set_mask(int ed) { if(ed == 0 || vid.stereo_mode != sAnaglyph) { glColorMask( GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE ); } else if(ed == 1) { glColorMask( GL_TRUE,GL_FALSE,GL_FALSE,GL_TRUE ); } else if(ed == -1) { glColorMask( GL_FALSE,GL_TRUE,GL_TRUE,GL_TRUE ); } } void display_data::set_viewport(int ed) { ld xtop = current_display->xtop; ld ytop = current_display->ytop; ld xsize = current_display->xsize; ld ysize = current_display->ysize; if(ed == 0 || vid.stereo_mode != sLR) ; else if(ed == 1) xsize /= 2; else if(ed == -1) xsize /= 2, xtop += xsize; glViewport(xtop, ytop, xsize, ysize); } EX bool model_needs_depth() { return GDIM == 3 || pmodel == mdBall; } EX void setGLProjection(color_t col IS(backcolor)) { DEBBI(DF_GRAPH, ("setGLProjection")); GLERR("pre_setGLProjection"); glClearColor(part(col, 2) / 255.0, part(col, 1) / 255.0, part(col, 0) / 255.0, 1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); GLERR("setGLProjection #1"); glEnable(GL_BLEND); #ifndef GLES_ONLY if(vid.antialias & AA_LINES) { glEnable(GL_LINE_SMOOTH); glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); } else glDisable(GL_LINE_SMOOTH); #endif glLineWidth(vid.linewidth); GLERR("setGLProjection #2"); #ifndef GLES_ONLY if(vid.antialias & AA_POLY) { glEnable(GL_POLYGON_SMOOTH); glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST); } else glDisable(GL_POLYGON_SMOOTH); #endif GLERR("setGLProjection #3"); //glLineWidth(1.0f); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); #ifdef GL_ES glClearDepthf(1.0f); #else glClearDepth(1.0f); #endif glDepthFunc(GL_LEQUAL); GLERR("setGLProjection"); reset_projection(); glhr::set_depthwrite(true); glClear(GL_DEPTH_BUFFER_BIT); } EX int next_p2 (int a ) { int rval=1; // rval<<=1 Is A Prettier Way Of Writing rval*=2; while(rvalw; int otheight = txt->h; #endif if(otwidth+curx > FONTTEXTURESIZE) curx = 0, cury += theight, theight = 0; theight = max(theight, otheight); for(int j=0; j=otwidth || j>=otheight) ? 0 : (tpix[tpixindex++] * 0x100) | 0xFF; #else ((i>=txt->w || j>=txt->h) ? 0 : ((qpixel(txt, i, j)>>24)&0xFF) * 0x100) | 0x00FF; #endif } f.widths[ch] = otwidth; f.heights[ch] = otheight; f.tx0[ch] = (float) curx / (float) FONTTEXTURESIZE; f.tx1[ch] = (float) (curx+otwidth) / (float) FONTTEXTURESIZE; f.ty0[ch] = (float) cury; f.ty1[ch] = (float) (cury+otheight); curx += otwidth; } void init_glfont(int size) { if(glfont[size]) return; DEBBI(DF_GRAPH, ("init GL font: ", size)); #if !CAP_TABFONT loadfont(size); if(!font[size]) return; #endif glfont[size] = new glfont_t; glfont_t& f(*(glfont[size])); //f.list_base = glGenLists(128); glGenTextures(1, &f.texture ); #if !CAP_TABFONT char str[2]; str[1] = 0; SDL_Color white; white.r = white.g = white.b = 255; #endif #if CAP_TABFONT resetTabFont(); #endif // glListBase(0); curx = 0, cury = 0, theight = 0; for(int ch=1;ch vid.fsize || size > 72) gsiz = 72; #if CAP_FIXEDSIZE gsiz = CAP_FIXEDSIZE; #endif init_glfont(gsiz); if(!glfont[gsiz]) return 0; glfont_t& f(*glfont[gsiz]); int x = 0; for(int i=0; s[i];) { int tabid = getnext(s,i); x += f.widths[tabid] * size/gsiz; } return x; } vector tver; glhr::textured_vertex charvertex(int x1, int y1, ld tx, ld ty) { glhr::textured_vertex res; res.coords[0] = x1; res.coords[1] = y1; res.coords[2] = 0; res.coords[3] = 1; res.texture[0] = tx; res.texture[1] = ty; return res; } bool gl_print(int x, int y, int shift, int size, const char *s, color_t color, int align) { int gsiz = size; if(size > vid.fsize || size > 72) gsiz = 72; #if CAP_FIXEDSIZE gsiz = CAP_FIXEDSIZE; #endif init_glfont(gsiz); if(!glfont[gsiz]) return false; glfont_t& f(*glfont[gsiz]); int tsize = 0; for(int i=0; s[i];) { tsize += f.widths[getnext(s,i)] * size/gsiz; } x -= tsize * align / 16; y += f.heights[32] * size / (gsiz*2); int ysiz = f.heights[32] * size / gsiz; bool clicked = (mousex >= x && mousey <= y && mousex <= x+tsize && mousey >= y-ysiz); color_t icolor = (color << 8) | 0xFF; if(icolor != text_color || f.texture != text_texture || shift != text_shift || shapes_merged) { glflush(); text_color = icolor; text_texture = f.texture; text_shift = shift; } texts_merged++; auto& tver = text_vertices; glBindTexture(GL_TEXTURE_2D, f.texture); for(int i=0; s[i];) { int tabid = getnext(s,i); int wi = f.widths[tabid] * size/gsiz; int hi = f.heights[tabid] * size/gsiz; GLERR("pre-print"); glhr::textured_vertex t00 = charvertex(x, y-hi, f.tx0[tabid], f.ty0[tabid]); glhr::textured_vertex t01 = charvertex(x, y, f.tx0[tabid], f.ty1[tabid]); glhr::textured_vertex t11 = charvertex(x+wi, y, f.tx1[tabid], f.ty1[tabid]); glhr::textured_vertex t10 = charvertex(x+wi, y-hi, f.tx1[tabid], f.ty0[tabid]); tver.push_back(t00); tver.push_back(t01); tver.push_back(t10); tver.push_back(t10); tver.push_back(t01); tver.push_back(t11); x += wi; } return clicked; } #endif EX purehookset hooks_resetGL; EX void resetGL() { DEBBI(DF_INIT | DF_GRAPH, ("reset GL")) callhooks(hooks_resetGL); #if CAP_GLFONT for(int i=0; i<128; i++) if(glfont[i]) { delete glfont[i]; glfont[i] = NULL; } #endif #if MAXMDIM >= 4 if(floor_textures) { delete floor_textures; floor_textures = NULL; } #endif check_cgi(); cgi.require_shapes(); #if MAXMDIM >= 4 if(GDIM == 3 && !floor_textures) make_floor_textures(); #endif cgi.initPolyForGL(); } #endif #if CAP_XGD vector graphdata; void gdpush(int t) { graphdata.push_back(t); } bool displaychr(int x, int y, int shift, int size, char chr, color_t col) { gdpush(2); gdpush(x); gdpush(y); gdpush(8); gdpush(col); gdpush(size); gdpush(0); gdpush(1); gdpush(chr); return false; } void gdpush_utf8(const string& s) { int g = (int) graphdata.size(), q = 0; gdpush((int) s.size()); for(int i=0; i= 192 && uch < 224) { int u = ((s[i] - 192)&31) << 6; i++; u += (s[i] - 128) & 63; gdpush(u); q++; } else if(uch >= 224 && uch < 240) { int u = ((s[i] - 224)&15) << 12; i++; u += (s[i] & 63) << 6; i++; u += (s[i] & 63) << 0; gdpush(u); q++; } else #endif { gdpush(s[i]); q++; } } graphdata[g] = q; } EX bool displayfr(int x, int y, int b, int size, const string &s, color_t color, int align) { gdpush(2); gdpush(x); gdpush(y); gdpush(align); gdpush(color); gdpush(size); gdpush(b); gdpush_utf8(s); int mx = mousex - x; int my = mousey - y; int len = textwidth(size, s); return mx >= -len*align/32 && mx <= +len*(16-align)/32 && my >= -size*3/4 && my <= +size*3/4; } EX bool displaystr(int x, int y, int shift, int size, const string &s, color_t color, int align) { return displayfr(x,y,0,size,s,color,align); } EX bool displaystr(int x, int y, int shift, int size, char const *s, color_t color, int align) { return displayfr(x,y,0,size,s,color,align); } #endif #if !CAP_XGD EX bool displaystr(int x, int y, int shift, int size, const char *str, color_t color, int align) { if(strlen(str) == 0) return false; if(size < 4 || size > 255) { return false; } #if CAP_GLFONT if(vid.usingGL) return gl_print(x, y, shift, size, str, color, align); #endif #if !CAP_SDLTTF static bool towarn = true; if(towarn) towarn = false, printf("WARNING: NOTTF works only with OpenGL!\n"); return false; #else SDL_Color col; col.r = (color >> 16) & 255; col.g = (color >> 8 ) & 255; col.b = (color >> 0 ) & 255; col.r >>= darken; col.g >>= darken; col.b >>= darken; loadfont(size); SDL_Surface *txt = ((vid.antialias & AA_FONT)?TTF_RenderUTF8_Blended:TTF_RenderUTF8_Solid)(font[size], str, col); if(txt == NULL) return false; SDL_Rect rect; rect.w = txt->w; rect.h = txt->h; rect.x = x - rect.w * align / 16; rect.y = y - rect.h/2; bool clicked = (mousex >= rect.x && mousey >= rect.y && mousex <= rect.x+rect.w && mousey <= rect.y+rect.h); if(shift) { SDL_Surface* txt2 = SDL_DisplayFormat(txt); SDL_LockSurface(txt2); SDL_LockSurface(s); color_t c0 = qpixel(txt2, 0, 0); for(int yy=0; yy= 2) { int b1 = b-1; displaystr(x-b1, y-b1, 0, size, s, p, align); displaystr(x-b1, y+b1, 0, size, s, p, align); displaystr(x+b1, y-b1, 0, size, s, p, align); displaystr(x+b1, y+b1, 0, size, s, p, align); } return displaystr(x, y, 0, size, s, color, align); } EX bool displayfr(int x, int y, int b, int size, const string &s, color_t color, int align) { return displayfrSP(x, y, 0, b, size, s, color, align, poly_outline>>8); } EX bool displaychr(int x, int y, int shift, int size, char chr, color_t col) { char buf[2]; buf[0] = chr; buf[1] = 0; return displaystr(x, y, shift, size, buf, col, 8); } #endif #if HDR struct msginfo { int stamp; time_t rtstamp; int gtstamp; int turnstamp; char flashout; char spamtype; int quantity; string msg; }; #endif EX vector msgs; EX vector gamelog; EX void flashMessages() { for(int i=0; i 1) s += " (x" + its(m.quantity) + ")"; return s; } void addMessageToLog(msginfo& m, vector& log) { if(isize(log) != 0) { msginfo& last = log[isize(log)-1]; if(last.msg == m.msg) { int q = m.quantity + last.quantity; last = m; last.quantity = q; return; } } if(isize(log) < 1000) log.push_back(m); else { for(int i=0; iradius; if(sphere) { if(sphereflipped()) vradius /= sqrt(vid.alpha*vid.alpha - 1); else vradius = 1e12; // use the following } if(euclid) vradius = current_display->radius * get_sightrange() / (1 + vid.alpha) / 2.5; vradius = min(vradius, min(vid.xres, vid.yres) / 2); return vradius; } EX void drawmessage(const string& s, int& y, color_t col) { int rrad = (int) realradius(); int space; if(dual::state) space = vid.xres; else if(y > current_display->ycenter + rrad * vid.stretch) space = vid.xres; else if(y > current_display->ycenter) space = current_display->xcenter - rhypot(rrad, (y-current_display->ycenter) / vid.stretch); else if(y > current_display->ycenter - vid.fsize) space = current_display->xcenter - rrad; else if(y > current_display->ycenter - vid.fsize - rrad * vid.stretch) space = current_display->xcenter - rhypot(rrad, (current_display->ycenter-vid.fsize-y) / vid.stretch); else space = vid.xres; if(textwidth(vid.fsize, s) <= space) { displayfr(0, y, 1, vid.fsize, s, col, 0); y -= vid.fsize; return; } for(int i=1; i=0; j--) { int age = msgs[j].flashout * (t - msgs[j].stamp); poly_outline = gradient(bordcolor, backcolor, 0, age, 256*vid.flashtime) << 8; color_t col = gradient(forecolor, backcolor, 0, age, 256*vid.flashtime); drawmessage(fullmsg(msgs[j]), y, col); } } else { for(int j=0; j> 8; } return c; } EX void drawCircle(int x, int y, int size, color_t color, color_t fillcolor IS(0)) { if(size < 0) size = -size; #if CAP_GL && CAP_POLY if(vid.usingGL) { glflush(); glhr::be_nontextured(); glhr::id_modelview(); dynamicval em(pmodel, mdUnchanged); glcoords.clear(); x -= current_display->xcenter; y -= current_display->ycenter; int pts = size * 4; if(pts > 1500) pts = 1500; if(ISMOBILE && pts > 72) pts = 72; for(int r=0; rscrdist)); } current_display->set_all(0); glhr::vertices(glcoords); glhr::set_depthtest(false); if(fillcolor) { glhr::color2(fillcolor); glDrawArrays(GL_TRIANGLE_FAN, 0, pts); } if(color) { glhr::color2(color); glDrawArrays(GL_LINE_LOOP, 0, pts); } return; } #endif #if CAP_XGD gdpush(4); gdpush(color); gdpush(x); gdpush(y); gdpush(size); #elif CAP_SDLGFX if(vid.stretch == 1) { if(fillcolor) filledCircleColor(s, x, y, size, fillcolor); if(color) ((vid.antialias && AA_NOGL)?aacircleColor:circleColor) (s, x, y, size, color); } else { if(fillcolor) filledEllipseColor(s, x, y, size, size * vid.stretch, fillcolor); if(color) ((vid.antialias && AA_NOGL)?aaellipseColor:ellipseColor) (s, x, y, size, size * vid.stretch, color); } #elif CAP_SDL int pts = size * 4; if(pts > 1500) pts = 1500; for(int r=0; rradius * cgi.crossf) * (1+vid.alpha) * 2; } EX bool setfsize = true; EX bool vsync_off; EX void do_setfsize() { dual::split_or_do([&] { vid.fsize = min(vid.yres * fontscale/ 3200, vid.xres * fontscale/ 4800), setfsize = false; }); } EX void disable_vsync() { #if !ISMOBWEB SDL_GL_SetAttribute( SDL_GL_SWAP_CONTROL, 0 ); #endif } #if CAP_SDL EX void setvideomode() { DEBBI(DF_INIT | DF_GRAPH, ("setvideomode")); if(!vid.full) { if(vid.xres > vid.xscr) vid.xres = vid.xscr * 9/10, setfsize = true; if(vid.yres > vid.yscr) vid.yres = vid.yscr * 9/10, setfsize = true; } if(setfsize) do_setfsize(); int flags = 0; #if CAP_GL if(vid.usingGL) { flags = SDL_OPENGL | SDL_HWSURFACE | SDL_GL_DOUBLEBUFFER; SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 1); if(vsync_off) disable_vsync(); if(vid.antialias & AA_MULTI) { SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, (vid.antialias & AA_MULTI16) ? 16 : 4); } } #endif int sizeflag = (vid.full ? SDL_FULLSCREEN : SDL_RESIZABLE); s = s_screen = SDL_SetVideoMode(vid.xres, vid.yres, 32, flags | sizeflag); if(vid.full && !s) { vid.xres = vid.xscr; vid.yres = vid.yscr; do_setfsize(); s = s_screen = SDL_SetVideoMode(vid.xres, vid.yres, 32, flags | SDL_FULLSCREEN); } if(!s) { addMessage("Failed to set the graphical mode: "+its(vid.xres)+"x"+its(vid.yres)+(vid.full ? " fullscreen" : " windowed")); vid.xres = 640; vid.yres = 480; SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); vid.antialias &= ~AA_MULTI; s = s_screen = SDL_SetVideoMode(vid.xres, vid.yres, 32, flags | SDL_RESIZABLE); } #if CAP_GL if(vid.usingGL) { if(vid.antialias & AA_MULTI) { glEnable(GL_MULTISAMPLE); glEnable(GL_MULTISAMPLE_ARB); } else { glDisable(GL_MULTISAMPLE); glDisable(GL_MULTISAMPLE_ARB); } glViewport(0, 0, vid.xres, vid.yres); glhr::init(); resetGL(); } #endif } #endif EX bool noGUI = false; EX void initgraph() { DEBBI(DF_INIT | DF_GRAPH, ("initgraph")); initConfig(); #if CAP_SDLJOY joyx = joyy = 0; joydir.d = -1; #endif restartGraph(); if(noGUI) { #if CAP_COMMANDLINE arg::read(2); #endif return; } #if CAP_SDL if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) == -1) { printf("Failed to initialize video.\n"); exit(2); } #if ISWEB vid.xscr = vid.xres = 1200; vid.yscr = vid.yres = 900; #else const SDL_VideoInfo *inf = SDL_GetVideoInfo(); vid.xscr = vid.xres = inf->current_w; vid.yscr = vid.yres = inf->current_h; #endif #ifdef CUSTOM_CAPTION SDL_WM_SetCaption(CUSTOM_CAPTION, CUSTOM_CAPTION); #else SDL_WM_SetCaption("HyperRogue " VER, "HyperRogue " VER); #endif #endif preparesort(); #if CAP_CONFIG loadConfig(); #endif arcm::current.parse(); if(hybri) geometry = hybrid::underlying; #if CAP_COMMANDLINE arg::read(2); #endif check_cgi(); cgi.require_basics(); #if CAP_SDL setvideomode(); if(!s) { printf("Failed to initialize graphics.\n"); exit(2); } SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); SDL_EnableUNICODE(1); #endif #if CAP_SDLTTF if(TTF_Init() != 0) { printf("Failed to initialize TTF.\n"); exit(2); } #endif #if CAP_SDLJOY initJoysticks(); #endif #if CAP_SDLAUDIO initAudio(); #endif } EX void cleargraph() { DEBBI(DF_INIT, ("clear graph")); #if CAP_SDLTTF for(int i=0; i<256; i++) if(font[i]) TTF_CloseFont(font[i]); #endif #if CAL_GLFONT for(int i=0; i<128; i++) if(glfont[i]) delete glfont[i]; #endif #if CAP_SDLJOY closeJoysticks(); #endif #if CAP_SDL SDL_Quit(); #endif } EX int calcfps() { #define CFPS 30 static int last[CFPS], lidx = 0; int ct = ticks; int ret = ct - last[lidx]; last[lidx] = ct; lidx++; lidx %= CFPS; if(ret == 0) return 0; return (1000 * CFPS) / ret; } EX namespace subscreens { EX vector player_displays; EX bool in; EX int current_player; EX bool is_current_player(int id) { if(!in) return true; return id == current_player; } EX void prepare() { int N = multi::players; if(N > 1) { player_displays.resize(N, *current_display); int qrows[10] = {1, 1, 1, 1, 2, 2, 2, 3, 3, 3}; int rows = qrows[N]; int cols = (N + rows - 1) / rows; for(int i=0; i c(current_display, &player_displays[p]); what(); } in = false; return true; } return false; } } }