hyperrogue/basegraph.cpp

1637 lines
41 KiB
C++
Raw Permalink Normal View History

// 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"
2021-12-31 22:36:50 +00:00
#ifdef FONTCONFIG
#include <fontconfig/fontconfig.h>
#endif
namespace hr {
2019-08-09 23:56:00 +00:00
#if HDR
struct radarpoint {
hyperpoint h;
char glyph;
color_t color;
color_t line;
};
struct radarline {
hyperpoint h1, h2;
color_t line;
};
/** configuration of the current view */
2019-08-09 23:56:00 +00:00
struct display_data {
/** The cell which is currently in the center. */
cell *precise_center;
/** The current rotation, relative to precise_center. */
transmatrix view_matrix;
/** Camera rotation, used in nonisotropic geometries. */
transmatrix local_perspective;
/** The view relative to the player character. */
shiftmatrix player_matrix;
/** On-screen coordinates for all the visible cells. */
Remove USE_UNORDERED_MAP because it has bit-rotted. Trying to compile with `-DUSE_UNORDERED_MAP` produces lots of compiler errors like these, because of missing `std::hash` specializations. Also, `#define unordered_map map` is just evil! ``` ./nonisotropic.cpp:875:36: note: in instantiation of template class 'std::__1::unordered_map<hr::nilv::mvec, hr::heptagon *, std::__1::hash<hr::nilv::mvec>, std::__1::equal_to<hr::nilv::mvec>, std::__1::allocator<std::__1::pair<const hr::nilv::mvec, hr::heptagon *> > >' requested here unordered_map<mvec, heptagon*> at; ^ ./nonisotropic.cpp:239:58: note: in instantiation of template class 'std::__1::unordered_map<std::__1::pair<hr::heptagon *, hr::heptagon *>, hr::heptagon *, std::__1::hash<std::__1::pair<hr::heptagon *, hr::heptagon *> >, std::__1::equal_to<std::__1::pair<hr::heptagon *, hr::heptagon *> >, std::__1::allocator<std::__1::pair<const std::__1::pair<hr::heptagon *, hr::heptagon *>, hr::heptagon *> > >' requested here unordered_map<pair<heptagon*, heptagon*>, heptagon*> at; ^ ./nonisotropic.cpp:457:49: error: no matching member function for call to 'iadj' while(h1->distance < h2->distance) back = iadj(h2, down) * back, h2 = h2->cmove(down); ^~~~ cell.cpp:42:15: note: candidate function not viable: no known conversion from 'hr::sn::hrmap_solnih' to 'hr::hrmap' for object argument transmatrix iadj(heptagon *h, int d) { ^ cell.cpp:41:22: note: candidate function not viable: no known conversion from 'hr::sn::hrmap_solnih' to 'hr::hrmap' for object argument struct transmatrix iadj(cell *c, int i) { cell *c1 = c->cmove(i); return adj(c1, c->c.spin(i)); } ^ ```
2020-09-25 03:15:19 +00:00
map<cell*, shiftmatrix> 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;
2019-08-09 23:56:00 +00:00
display_data() { xmin = ymin = 0; xmax = ymax = 1; }
/** Center of the current map view, in pixels. */
2019-08-09 23:56:00 +00:00
int xcenter, ycenter;
ld radius;
int scrsize;
bool sidescreen;
ld tanfov;
2019-10-21 20:34:20 +00:00
flagtype next_shader_flags;
2019-08-09 23:56:00 +00:00
vector<radarpoint> radarpoints;
vector<radarline> radarlines;
transmatrix radar_transform;
transmatrix radar_transform_post;
2019-08-09 23:56:00 +00:00
ld eyewidth();
bool separate_eyes();
2019-08-09 23:56:00 +00:00
bool in_anaglyph();
void set_viewport(int ed);
void set_projection(int ed, ld shift);
2019-08-09 23:56:00 +00:00
void set_mask(int ed);
void set_all(int ed, ld shift);
/** Which copy of the player cell? */
transmatrix which_copy;
/** On-screen coordinates for all the visible cells. */
Remove USE_UNORDERED_MAP because it has bit-rotted. Trying to compile with `-DUSE_UNORDERED_MAP` produces lots of compiler errors like these, because of missing `std::hash` specializations. Also, `#define unordered_map map` is just evil! ``` ./nonisotropic.cpp:875:36: note: in instantiation of template class 'std::__1::unordered_map<hr::nilv::mvec, hr::heptagon *, std::__1::hash<hr::nilv::mvec>, std::__1::equal_to<hr::nilv::mvec>, std::__1::allocator<std::__1::pair<const hr::nilv::mvec, hr::heptagon *> > >' requested here unordered_map<mvec, heptagon*> at; ^ ./nonisotropic.cpp:239:58: note: in instantiation of template class 'std::__1::unordered_map<std::__1::pair<hr::heptagon *, hr::heptagon *>, hr::heptagon *, std::__1::hash<std::__1::pair<hr::heptagon *, hr::heptagon *> >, std::__1::equal_to<std::__1::pair<hr::heptagon *, hr::heptagon *> >, std::__1::allocator<std::__1::pair<const std::__1::pair<hr::heptagon *, hr::heptagon *>, hr::heptagon *> > >' requested here unordered_map<pair<heptagon*, heptagon*>, heptagon*> at; ^ ./nonisotropic.cpp:457:49: error: no matching member function for call to 'iadj' while(h1->distance < h2->distance) back = iadj(h2, down) * back, h2 = h2->cmove(down); ^~~~ cell.cpp:42:15: note: candidate function not viable: no known conversion from 'hr::sn::hrmap_solnih' to 'hr::hrmap' for object argument transmatrix iadj(heptagon *h, int d) { ^ cell.cpp:41:22: note: candidate function not viable: no known conversion from 'hr::sn::hrmap_solnih' to 'hr::hrmap' for object argument struct transmatrix iadj(cell *c, int i) { cell *c1 = c->cmove(i); return adj(c1, c->c.spin(i)); } ^ ```
2020-09-25 03:15:19 +00:00
map<cell*, vector<shiftmatrix>> all_drawn_copies;
2019-08-09 23:56:00 +00:00
};
#define View (::hr::current_display->view_matrix)
#define cwtV (::hr::current_display->player_matrix)
#define centerover (::hr::current_display->precise_center)
#define gmatrix (::hr::current_display->cellmatrices)
#define gmatrix0 (::hr::current_display->old_cellmatrices)
#define NLP (::hr::current_display->local_perspective)
2019-08-09 23:56:00 +00:00
#endif
2019-08-09 19:00:52 +00:00
EX display_data default_display;
EX display_data *current_display = &default_display;
/** Color of the background. */
2019-08-09 19:00:52 +00:00
EX unsigned backcolor = 0;
EX unsigned bordcolor = 0;
EX unsigned forecolor = 0xFFFFFF;
2017-07-10 18:47:38 +00:00
2017-07-22 23:33:27 +00:00
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;
}
2017-07-10 18:47:38 +00:00
2019-08-09 19:00:52 +00:00
EX int get_sightrange() { return getDistLimit() + sightrange_bonus; }
2020-10-15 14:33:52 +00:00
EX int get_sightrange_ambush() {
#if CAP_COMPLEX2
return max(get_sightrange(), ambush::distance);
#else
return get_sightrange();
#endif
}
bool display_data::in_anaglyph() { return vid.stereo_mode == sAnaglyph; }
bool display_data::separate_eyes() { return among(vid.stereo_mode, sAnaglyph, sLR); }
2018-02-03 12:41:49 +00:00
ld display_data::eyewidth() {
switch(vid.stereo_mode) {
case sAnaglyph:
return vid.anaglyph_eyewidth;
case sLR:
return vid.lr_eyewidth;
2018-02-03 12:41:49 +00:00
default:
return 0;
}
}
2017-07-22 23:33:27 +00:00
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) {
2017-07-22 23:33:27 +00:00
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<NUMEXTRA; k++)
if(eqs(s+i, natchars[k])) {
i += siz; return 128+k;
}
#ifdef REPLACE_LETTERS
2021-07-04 07:32:59 +00:00
for(int j=0; j<isize(dialog::latin_letters_l); j++)
if(s[i] == dialog::foreign_letters[2*j] && s[i+1] == dialog::foreign_letters[2*j+1]) {
i += 2;
2021-07-04 07:32:59 +00:00
return int(dialog::latin_letters_l[j]);
}
#endif
2018-02-11 20:39:08 +00:00
printf("Unknown character in: '%s' at position %d\n", s, i);
i += siz; return '?';
2017-07-22 23:33:27 +00:00
}
#if CAP_SDLTTF
const int max_font_size = 288;
TTF_Font* font[max_font_size+1];
void fix_font_size(int& size) {
if(size < 1) size = 1;
if(size > max_font_size) size = max_font_size;
if(size > 72) size &=~ 3;
if(size > 144) size &=~ 7;
}
2017-07-10 18:47:38 +00:00
#endif
2017-07-22 23:33:27 +00:00
#if CAP_SDL
#if !CAP_SDL2
#if HDR
2021-03-07 09:42:19 +00:00
typedef SDL_Surface SDL_Renderer;
#define srend s
#endif
#endif
2019-08-09 19:00:52 +00:00
EX SDL_Surface *s;
EX SDL_Surface *s_screen;
#if CAP_SDL2
2021-03-07 13:12:18 +00:00
EX SDL_Renderer *s_renderer, *s_software_renderer;
#if HDR
#define srend s_software_renderer
#endif
EX SDL_Texture *s_texture;
EX SDL_Window *s_window;
2022-06-10 16:45:15 +00:00
EX SDL_GLContext s_context;
EX bool s_have_context;
#endif
2017-07-22 23:33:27 +00:00
EX color_t qpixel_pixel_outside;
2017-07-10 18:47:38 +00:00
2019-08-09 19:00:52 +00:00
EX color_t& qpixel(SDL_Surface *surf, int x, int y) {
2017-12-22 20:24:08 +00:00
if(x<0 || y<0 || x >= surf->w || y >= surf->h) return qpixel_pixel_outside;
2017-07-10 18:47:38 +00:00
char *p = (char*) surf->pixels;
p += y * surf->pitch;
color_t *pi = (color_t*) (p);
2017-07-10 18:47:38 +00:00
return pi[x];
}
EX void present_surface() {
#if CAP_SDL2
2021-03-07 13:12:18 +00:00
SDL_UpdateTexture(s_texture, nullptr, s->pixels, s->w * sizeof (Uint32));
SDL_RenderClear(s_renderer);
SDL_RenderCopy(s_renderer, s_texture, nullptr, nullptr);
SDL_RenderPresent(s_renderer);
#else
SDL_UpdateRect(s, 0, 0, 0, 0);
#endif
}
EX void present_screen() {
#if CAP_GL
if(vid.usingGL) {
#if CAP_SDL2
SDL_GL_SwapWindow(s_window);
#else
SDL_GL_SwapBuffers();
#endif
return;
}
#endif
present_surface();
}
2017-07-10 18:47:38 +00:00
#endif
2017-07-22 23:33:27 +00:00
#if CAP_SDLTTF
2017-12-14 01:50:28 +00:00
2023-05-15 17:33:32 +00:00
#define DEFAULT_FONT "DejaVuSans-Bold.ttf"
2021-12-31 22:36:50 +00:00
2023-05-15 17:33:32 +00:00
#ifdef FONTCONFIG
/** if this is non-empty, find the font using fontconfig */
EX string font_to_find = DEFAULT_FONT;
#endif
/** actual font path */
EX string fontpath = ISWEB ? "sans-serif" : string(HYPERFONTPATH) + DEFAULT_FONT;
const string& findfont() {
2021-12-31 22:36:50 +00:00
#ifdef FONTCONFIG
2023-05-15 17:33:32 +00:00
if(font_to_find == "") return fontpath;
2021-12-31 22:36:50 +00:00
FcPattern *pat;
FcResult result;
if (!FcInit()) {
2023-05-15 17:33:32 +00:00
return fontpath;
2021-12-31 22:36:50 +00:00
}
2023-05-15 17:33:32 +00:00
pat = FcNameParse((FcChar8 *)font_to_find.c_str());
2021-12-31 22:36:50 +00:00
FcConfigSubstitute(0, pat, FcMatchPattern);
FcDefaultSubstitute(pat);
FcPattern *match;
match = FcFontMatch(0, pat, &result);
if (match) {
FcChar8 *file;
if (FcPatternGetString(match, FC_FILE, 0, &file) == FcResultMatch) {
2023-05-15 17:33:32 +00:00
fontpath = (const char *)file;
2021-12-31 22:36:50 +00:00
}
FcPatternDestroy(match);
}
FcPatternDestroy(pat);
FcFini();
2023-05-15 17:33:32 +00:00
font_to_find = "";
2023-05-15 17:36:10 +00:00
if(debugflags & DF_INIT) println(hlog, "fontpath is: ", fontpath);
2021-12-31 22:36:50 +00:00
#endif
2023-05-15 17:33:32 +00:00
return fontpath;
}
2017-12-14 01:50:28 +00:00
2017-07-10 18:47:38 +00:00
void loadfont(int siz) {
fix_font_size(siz);
2017-07-10 18:47:38 +00:00
if(!font[siz]) {
2021-12-31 22:36:50 +00:00
font[siz] = TTF_OpenFont(findfont().c_str(), siz);
2017-07-10 18:47:38 +00:00
// 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) {
2017-12-14 01:50:28 +00:00
printf("error: Font file not found: %s\n", fontpath.c_str());
2017-07-10 18:47:38 +00:00
exit(1);
}
}
}
#endif
2017-07-22 23:33:27 +00:00
#if !ISFAKEMOBILE && !ISANDROID & !ISIOS
2017-07-10 18:47:38 +00:00
int textwidth(int siz, const string &str) {
2018-06-22 12:47:24 +00:00
if(isize(str) == 0) return 0;
2017-07-10 18:47:38 +00:00
2017-07-22 23:33:27 +00:00
#if CAP_SDLTTF
2021-02-07 17:29:49 +00:00
fix_font_size(siz);
2017-07-10 18:47:38 +00:00
loadfont(siz);
int w, h;
TTF_SizeUTF8(font[siz], str.c_str(), &w, &h);
2018-06-22 12:47:24 +00:00
// printf("width = %d [%d]\n", w, isize(str));
2017-07-10 18:47:38 +00:00
return w;
2017-07-22 23:33:27 +00:00
#elif CAP_GL
return gl_width(siz, str.c_str());
#else
return 0;
2017-07-10 18:47:38 +00:00
#endif
}
#endif
2017-07-22 23:33:27 +00:00
#if ISIOS
2017-07-10 18:47:38 +00:00
int textwidth(int siz, const string &str) {
return mainfont->getSize(str, siz / 36.0).width;
}
#endif
2017-07-22 23:33:27 +00:00
#if !CAP_GL
2020-09-11 09:03:53 +00:00
EX void setcameraangle(bool b) { }
2019-08-09 19:00:52 +00:00
#endif
2020-07-03 12:42:33 +00:00
#if !CAP_GL
EX void reset_projection() { }
EX void glflush() { }
EX bool model_needs_depth() { return false; }
void display_data::set_all(int ed, ld lshift) {}
2020-07-03 12:42:33 +00:00
#endif
2019-08-09 19:00:52 +00:00
#if CAP_GL
2017-07-10 18:47:38 +00:00
EX void eyewidth_translate(int ed) {
glhr::using_eyeshift = false;
if(ed) glhr::projection_multiply(glhr::translate(-ed * current_display->eyewidth(), 0, 0));
2018-02-03 12:41:49 +00:00
}
2019-04-23 13:03:17 +00:00
tuple<int, eModel, display_data*, int> last_projection;
2019-08-09 19:00:52 +00:00
EX bool new_projection_needed;
2019-08-09 23:56:00 +00:00
#if HDR
inline void reset_projection() { new_projection_needed = true; }
#endif
2019-04-23 13:03:17 +00:00
EX ld lband_shift;
void display_data::set_all(int ed, ld shift) {
2019-04-23 13:03:17 +00:00
auto t = this;
auto current_projection = tie(ed, pmodel, t, current_rbuffer);
if(new_projection_needed || !glhr::current_glprogram || (next_shader_flags & GF_which) != (glhr::current_glprogram->shader_flags & GF_which) || current_projection != last_projection || shift != lband_shift) {
2019-04-23 13:03:17 +00:00
last_projection = current_projection;
lband_shift = shift;
set_projection(ed, shift);
2019-04-23 13:03:17 +00:00
set_mask(ed);
set_viewport(ed);
new_projection_needed = false;
}
}
void display_data::set_mask(int ed) {
if(ed == 0 || vid.stereo_mode != sAnaglyph) {
2017-07-10 18:47:38 +00:00
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) {
2018-11-20 18:01:10 +00:00
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;
2018-11-20 18:01:10 +00:00
else if(ed == -1) xsize /= 2, xtop += xsize;
2018-11-20 18:01:10 +00:00
glViewport(xtop, ytop, xsize, ysize);
}
2018-02-03 12:41:49 +00:00
2019-08-09 19:00:52 +00:00
EX bool model_needs_depth() {
2019-08-15 13:05:43 +00:00
return GDIM == 3 || pmodel == mdBall;
}
2019-08-09 19:00:52 +00:00
EX void setGLProjection(color_t col IS(backcolor)) {
2019-05-12 23:57:40 +00:00
DEBBI(DF_GRAPH, ("setGLProjection"));
2018-02-11 01:19:49 +00:00
GLERR("pre_setGLProjection");
2017-07-10 18:47:38 +00:00
2019-02-06 17:39:53 +00:00
glClearColor(part(col, 2) / 255.0, part(col, 1) / 255.0, part(col, 0) / 255.0, 1);
2017-07-10 18:47:38 +00:00
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
2020-11-19 17:20:06 +00:00
2018-02-11 01:19:49 +00:00
GLERR("setGLProjection #1");
2017-07-10 18:47:38 +00:00
glEnable(GL_BLEND);
2018-02-11 18:08:17 +00:00
#ifndef GLES_ONLY
2017-07-10 18:47:38 +00:00
if(vid.antialias & AA_LINES) {
glEnable(GL_LINE_SMOOTH);
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
}
else glDisable(GL_LINE_SMOOTH);
2018-02-11 01:19:49 +00:00
#endif
2017-07-10 18:47:38 +00:00
glLineWidth(vid.linewidth);
2017-07-22 23:33:27 +00:00
2018-02-11 01:19:49 +00:00
GLERR("setGLProjection #2");
#ifndef GLES_ONLY
2017-07-10 18:47:38 +00:00
if(vid.antialias & AA_POLY) {
glEnable(GL_POLYGON_SMOOTH);
glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
}
else glDisable(GL_POLYGON_SMOOTH);
2017-07-22 23:33:27 +00:00
#endif
2017-07-10 18:47:38 +00:00
2018-02-11 01:19:49 +00:00
GLERR("setGLProjection #3");
2017-07-10 18:47:38 +00:00
//glLineWidth(1.0f);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
#ifdef GL_ES
glClearDepthf(1.0f);
2017-07-10 18:47:38 +00:00
#else
glClearDepth(1.0f);
2017-07-10 18:47:38 +00:00
#endif
glDepthFunc(GL_LEQUAL);
2017-07-10 18:47:38 +00:00
2018-02-11 01:19:49 +00:00
GLERR("setGLProjection");
2019-04-23 13:03:17 +00:00
reset_projection();
2019-05-15 08:49:54 +00:00
glhr::set_depthwrite(true);
glClear(GL_DEPTH_BUFFER_BIT);
2017-07-10 18:47:38 +00:00
}
EX int next_p2 (int a ) {
int rval=1;
// rval<<=1 Is A Prettier Way Of Writing rval*=2;
while(rval<a) rval<<=1;
return rval;
}
2017-07-22 23:33:27 +00:00
#if CAP_GLFONT
2017-07-10 18:47:38 +00:00
2018-09-04 21:27:51 +00:00
#define CHARS (128+NUMEXTRA)
2021-03-07 20:11:42 +00:00
#if HDR
struct charinfo_t {
int w, h;
float tx0, ty0, tx1, ty1;
};
2017-07-10 18:47:38 +00:00
struct glfont_t {
2018-09-04 21:27:51 +00:00
GLuint texture; // Holds The Texture Id
2017-07-10 18:47:38 +00:00
//GLuint list_base; // Holds The First Display List ID
2021-03-07 20:11:42 +00:00
vector<charinfo_t> chars;
2017-07-10 18:47:38 +00:00
};
const int max_glfont_size = 72;
2021-03-07 20:11:42 +00:00
#endif
EX glfont_t *glfont[max_glfont_size+1];
2017-07-10 18:47:38 +00:00
2018-09-04 21:27:51 +00:00
typedef Uint16 texturepixel;
#define FONTTEXTURESIZE 2048
int curx = 0, cury = 0, theight = 0;
texturepixel fontdata[FONTTEXTURESIZE][FONTTEXTURESIZE];
2017-07-10 18:47:38 +00:00
void sdltogl(SDL_Surface *txt, glfont_t& f, int ch) {
2017-07-22 23:33:27 +00:00
#if CAP_TABFONT
2018-09-04 21:27:51 +00:00
if(ch < 32) return;
int otwidth, otheight, tpixindex = 0;
unsigned char tpix[3000];
2017-07-10 18:47:38 +00:00
loadCompressedChar(otwidth, otheight, tpix);
#else
2018-09-04 21:27:51 +00:00
if(!txt) return;
2017-07-10 18:47:38 +00:00
int otwidth = txt->w;
int otheight = txt->h;
#endif
if(otwidth+curx+1 > FONTTEXTURESIZE) curx = 0, cury += theight+1, theight = 0;
2018-09-04 21:27:51 +00:00
theight = max(theight, otheight);
for(int j=0; j<otheight;j++) for(int i=0; i<otwidth; i++) {
fontdata[j+cury][i+curx] =
2017-07-22 23:33:27 +00:00
#if CAP_TABFONT
(i>=otwidth || j>=otheight) ? 0 : (tpix[tpixindex++] * 0x100) | 0xFF;
2017-07-10 18:47:38 +00:00
#else
2018-09-04 21:27:51 +00:00
((i>=txt->w || j>=txt->h) ? 0 : ((qpixel(txt, i, j)>>24)&0xFF) * 0x100) | 0x00FF;
2017-07-10 18:47:38 +00:00
#endif
}
2021-03-07 20:11:42 +00:00
auto& c = f.chars[ch];
c.w = otwidth;
c.h = otheight;
2017-07-10 18:47:38 +00:00
2021-03-07 20:11:42 +00:00
c.tx0 = (float) curx / (float) FONTTEXTURESIZE;
c.tx1 = (float) (curx+otwidth) / (float) FONTTEXTURESIZE;
c.ty0 = (float) cury;
c.ty1 = (float) (cury+otheight);
curx += otwidth+1;
2017-07-10 18:47:38 +00:00
}
2021-03-07 20:11:42 +00:00
EX void init_glfont(int size) {
2017-07-10 18:47:38 +00:00
if(glfont[size]) return;
2019-05-12 23:57:40 +00:00
DEBBI(DF_GRAPH, ("init GL font: ", size));
2018-09-04 21:27:51 +00:00
2017-07-22 23:33:27 +00:00
#if !CAP_TABFONT
loadfont(size);
if(!font[size]) return;
#endif
2017-07-10 18:47:38 +00:00
glfont[size] = new glfont_t;
glfont_t& f(*(glfont[size]));
2021-03-07 20:11:42 +00:00
f.chars.resize(CHARS);
2017-07-10 18:47:38 +00:00
//f.list_base = glGenLists(128);
2018-09-04 21:27:51 +00:00
glGenTextures(1, &f.texture );
2017-07-10 18:47:38 +00:00
2017-07-22 23:33:27 +00:00
#if !CAP_TABFONT
2017-07-10 18:47:38 +00:00
char str[2]; str[1] = 0;
SDL_Color white;
white.r = white.g = white.b = 255;
#endif
2018-09-04 21:50:50 +00:00
for(int y=0; y<FONTTEXTURESIZE; y++)
for(int x=0; x<FONTTEXTURESIZE; x++)
fontdata[y][x] = 0;
2018-09-04 21:50:50 +00:00
#if CAP_TABFONT
resetTabFont();
#endif
2017-07-10 18:47:38 +00:00
// glListBase(0);
2018-09-04 21:27:51 +00:00
curx = 0, cury = 0, theight = 0;
for(int ch=1;ch<CHARS;ch++) {
2017-07-10 18:47:38 +00:00
if(ch<32) continue;
2017-07-22 23:33:27 +00:00
#if CAP_TABFONT
2017-07-10 18:47:38 +00:00
sdltogl(NULL, f, ch);
#else
SDL_Surface *txt;
int siz = size;
fix_font_size(siz);
2017-07-10 18:47:38 +00:00
if(ch < 128) {
str[0] = ch;
txt = TTF_RenderText_Blended(font[siz], str, white);
2017-07-10 18:47:38 +00:00
}
else {
txt = TTF_RenderUTF8_Blended(font[siz], natchars[ch-128], white);
2017-07-10 18:47:38 +00:00
}
if(txt == NULL) continue;
2017-07-22 23:33:27 +00:00
#if CAP_CREATEFONT
2017-07-10 18:47:38 +00:00
generateFont(ch, txt);
#endif
sdltogl(txt, f, ch);
SDL_FreeSurface(txt);
#endif
}
2018-09-04 21:27:51 +00:00
glBindTexture( GL_TEXTURE_2D, f.texture);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
theight = next_p2(cury + theight);
glTexImage2D( GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, FONTTEXTURESIZE, theight, 0,
2018-09-04 21:27:51 +00:00
GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE,
fontdata);
2021-03-07 20:11:42 +00:00
for(int ch=0; ch<CHARS; ch++) f.chars[ch].ty0 /= theight, f.chars[ch].ty1 /= theight;
2018-09-04 21:27:51 +00:00
#if CAP_CREATEFONT
printf("#define NUMEXTRA %d\n", NUMEXTRA);
#define DEMACRO(x) #x
printf("#define NATCHARS " DEMACRO(NATCHARS) "\n");
#endif
2017-07-10 18:47:38 +00:00
//printf("init size=%d ok\n", size);
GLERR("initfont");
}
int gl_width(int size, const char *s) {
int gsiz = size;
if(size > vid.fsize || size > max_glfont_size) gsiz = max_glfont_size;
2017-07-10 18:47:38 +00:00
2017-07-22 23:33:27 +00:00
#if CAP_FIXEDSIZE
gsiz = CAP_FIXEDSIZE;
2017-07-10 18:47:38 +00:00
#endif
init_glfont(gsiz);
2017-07-22 23:33:27 +00:00
if(!glfont[gsiz]) return 0;
2017-07-10 18:47:38 +00:00
glfont_t& f(*glfont[gsiz]);
int x = 0;
for(int i=0; s[i];) {
int tabid = getnext(s,i);
2021-03-07 20:11:42 +00:00
x += f.chars[tabid].w * size/gsiz;
2017-07-10 18:47:38 +00:00
}
return x;
}
2018-09-04 21:27:51 +00:00
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;
2019-02-22 19:58:40 +00:00
res.coords[3] = 1;
2018-09-04 21:27:51 +00:00
res.texture[0] = tx;
res.texture[1] = ty;
return res;
}
2017-07-10 18:47:38 +00:00
bool gl_print(int x, int y, int shift, int size, const char *s, color_t color, int align) {
2017-07-10 18:47:38 +00:00
int gsiz = size;
if(size > vid.fsize || size > max_glfont_size) gsiz = max_glfont_size;
2017-07-10 18:47:38 +00:00
2017-07-22 23:33:27 +00:00
#if CAP_FIXEDSIZE
gsiz = CAP_FIXEDSIZE;
2017-07-10 18:47:38 +00:00
#endif
init_glfont(gsiz);
2017-07-22 23:33:27 +00:00
if(!glfont[gsiz]) return false;
2017-07-10 18:47:38 +00:00
glfont_t& f(*glfont[gsiz]);
int tsize = 0;
for(int i=0; s[i];) {
2021-03-07 20:11:42 +00:00
tsize += f.chars[getnext(s,i)].w * size/gsiz;
2017-07-10 18:47:38 +00:00
}
x -= tsize * align / 16;
2021-03-07 20:11:42 +00:00
y += f.chars[32].h * size / (gsiz*2);
2017-07-10 18:47:38 +00:00
2021-03-07 20:11:42 +00:00
int ysiz = f.chars[32].h * size / gsiz;
2017-07-10 18:47:38 +00:00
bool clicked = (mousex >= x && mousey <= y && mousex <= x+tsize && mousey >= y-ysiz);
2018-09-04 21:27:51 +00:00
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);
2017-07-10 18:47:38 +00:00
for(int i=0; s[i];) {
int tabid = getnext(s,i);
2021-03-07 20:11:42 +00:00
auto& c = f.chars[tabid];
int wi = c.w * size/gsiz;
int hi = c.h * size/gsiz;
2017-07-10 18:47:38 +00:00
GLERR("pre-print");
2021-03-07 20:11:42 +00:00
glhr::textured_vertex t00 = charvertex(x, y-hi, c.tx0, c.ty0);
glhr::textured_vertex t01 = charvertex(x, y, c.tx0, c.ty1);
glhr::textured_vertex t11 = charvertex(x+wi, y, c.tx1, c.ty1);
glhr::textured_vertex t10 = charvertex(x+wi, y-hi, c.tx1, c.ty0);
2017-07-10 18:47:38 +00:00
2018-09-04 21:27:51 +00:00
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;
2017-07-10 18:47:38 +00:00
}
2018-09-04 21:27:51 +00:00
2017-07-10 18:47:38 +00:00
return clicked;
}
2018-02-11 18:08:17 +00:00
2017-07-10 18:47:38 +00:00
#endif
2019-08-09 19:00:52 +00:00
EX purehookset hooks_resetGL;
2017-07-10 18:47:38 +00:00
2019-08-09 19:00:52 +00:00
EX void resetGL() {
2019-05-12 23:57:40 +00:00
DEBBI(DF_INIT | DF_GRAPH, ("reset GL"))
2017-07-10 18:47:38 +00:00
callhooks(hooks_resetGL);
2017-07-22 23:33:27 +00:00
#if CAP_GLFONT
2021-02-07 13:51:08 +00:00
for(int i=0; i<=max_glfont_size; i++) if(glfont[i]) {
2017-07-10 18:47:38 +00:00
delete glfont[i];
glfont[i] = NULL;
}
#endif
#if MAXMDIM >= 4
if(floor_textures) {
delete floor_textures;
floor_textures = NULL;
}
2017-07-10 18:47:38 +00:00
#endif
2021-03-10 07:40:43 +00:00
#if MAXMDIM >= 4 && CAP_GL
2021-03-09 09:43:50 +00:00
if(airbuf) {
delete airbuf;
airbuf = nullptr;
}
2021-03-10 07:40:43 +00:00
#endif
check_cgi();
if(currentmap) cgi.require_shapes();
2019-06-01 14:59:04 +00:00
#if MAXMDIM >= 4
2019-08-15 13:05:43 +00:00
if(GDIM == 3 && !floor_textures) make_floor_textures();
2019-06-01 14:59:04 +00:00
#endif
cgi.initPolyForGL();
2019-10-26 15:02:20 +00:00
compiled_programs.clear();
matched_programs.clear();
glhr::current_glprogram = nullptr;
ray::reset_raycaster();
2020-10-15 14:33:52 +00:00
#if CAP_RUG
if(rug::glbuf) rug::close_glbuf();
2020-10-15 14:33:52 +00:00
#endif
2017-07-10 18:47:38 +00:00
}
#endif
2017-07-22 23:33:27 +00:00
#if CAP_XGD
vector<int> graphdata;
2020-04-06 06:37:22 +00:00
EX void gdpush(int t) {
2017-07-22 23:33:27 +00:00
graphdata.push_back(t);
}
2020-04-06 06:37:22 +00:00
EX bool displaychr(int x, int y, int shift, int size, char chr, color_t col) {
2017-07-22 23:33:27 +00:00
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;
2018-06-22 12:47:24 +00:00
gdpush((int) s.size()); for(int i=0; i<isize(s); i++) {
2017-07-22 23:33:27 +00:00
#if ISANDROID
unsigned char uch = (unsigned char) s[i];
if(uch >= 192 && uch < 224) {
int u = ((s[i] - 192)&31) << 6;
i++;
u += (s[i] - 128) & 63;
gdpush(u); q++;
}
2019-07-12 21:07:23 +00:00
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++;
}
2017-07-22 23:33:27 +00:00
else
#endif
{
gdpush(s[i]); q++;
}
}
graphdata[g] = q;
}
2019-08-10 08:57:14 +00:00
EX bool displayfr(int x, int y, int b, int size, const string &s, color_t color, int align) {
2017-07-22 23:33:27 +00:00
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;
}
2019-08-10 08:57:14 +00:00
EX bool displaystr(int x, int y, int shift, int size, const string &s, color_t color, int align) {
2017-07-22 23:33:27 +00:00
return displayfr(x,y,0,size,s,color,align);
}
2019-08-10 08:57:14 +00:00
EX bool displaystr(int x, int y, int shift, int size, char const *s, color_t color, int align) {
2017-07-22 23:33:27 +00:00
return displayfr(x,y,0,size,s,color,align);
}
2019-08-10 08:57:14 +00:00
#endif
#if !CAP_XGD
2019-08-09 19:00:52 +00:00
EX bool displaystr(int x, int y, int shift, int size, const char *str, color_t color, int align) {
2017-07-10 18:47:38 +00:00
if(strlen(str) == 0) return false;
2020-08-01 14:41:44 +00:00
if(size < 4 || size > 2000) {
2017-07-10 18:47:38 +00:00
return false;
}
2017-07-22 23:33:27 +00:00
#if CAP_GLFONT
2017-07-10 18:47:38 +00:00
if(vid.usingGL) return gl_print(x, y, shift, size, str, color, align);
#endif
2017-07-22 23:33:27 +00:00
#if !CAP_SDLTTF
2017-07-10 18:47:38 +00:00
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;
fix_font_size(size);
2017-07-10 18:47:38 +00:00
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) {
#if CAP_SDL2
SDL_Surface* txt2 = SDL_ConvertSurfaceFormat(txt, SDL_PIXELFORMAT_RGBA8888, 0);
#else
2017-07-10 18:47:38 +00:00
SDL_Surface* txt2 = SDL_DisplayFormat(txt);
#endif
2017-07-10 18:47:38 +00:00
SDL_LockSurface(txt2);
SDL_LockSurface(s);
color_t c0 = qpixel(txt2, 0, 0);
2017-07-10 18:47:38 +00:00
for(int yy=0; yy<rect.h; yy++)
for(int xx=0; xx<rect.w; xx++) if(qpixel(txt2, xx, yy) != c0)
qpixel(s, rect.x+xx-shift, rect.y+yy) |= color & 0xFF0000,
qpixel(s, rect.x+xx+shift, rect.y+yy) |= color & 0x00FFFF;
SDL_UnlockSurface(s);
SDL_UnlockSurface(txt2);
SDL_FreeSurface(txt2);
}
else {
SDL_BlitSurface(txt, NULL, s,&rect);
}
SDL_FreeSurface(txt);
return clicked;
#endif
}
2019-08-09 19:00:52 +00:00
EX bool displaystr(int x, int y, int shift, int size, const string &s, color_t color, int align) {
2017-07-10 18:47:38 +00:00
return displaystr(x, y, shift, size, s.c_str(), color, align);
}
2019-08-09 19:00:52 +00:00
EX bool displayfrSP(int x, int y, int sh, int b, int size, const string &s, color_t color, int align, int p) {
2017-07-10 18:47:38 +00:00
if(b) {
displaystr(x-b, y, 0, size, s, p, align);
displaystr(x+b, y, 0, size, s, p, align);
displaystr(x, y-b, 0, size, s, p, align);
displaystr(x, y+b, 0, size, s, p, align);
}
if(b >= 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);
}
2019-08-09 19:00:52 +00:00
EX bool displayfr(int x, int y, int b, int size, const string &s, color_t color, int align) {
2017-07-10 18:47:38 +00:00
return displayfrSP(x, y, 0, b, size, s, color, align, poly_outline>>8);
}
2019-08-09 19:00:52 +00:00
EX bool displaychr(int x, int y, int shift, int size, char chr, color_t col) {
2017-07-10 18:47:38 +00:00
char buf[2];
buf[0] = chr; buf[1] = 0;
return displaystr(x, y, shift, size, buf, col, 8);
}
#endif
2019-08-10 00:16:48 +00:00
#if HDR
struct msginfo {
int stamp;
time_t rtstamp;
int gtstamp;
int turnstamp;
char flashout;
char spamtype;
int quantity;
string msg;
};
#endif
EX vector<msginfo> msgs;
2017-07-10 18:47:38 +00:00
EX vector<msginfo> gamelog;
2017-07-10 18:47:38 +00:00
2019-08-09 19:00:52 +00:00
EX void flashMessages() {
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(msgs); i++)
2017-07-10 18:47:38 +00:00
if(msgs[i].stamp < ticks - 1000 && !msgs[i].flashout) {
msgs[i].flashout = true;
msgs[i].stamp = ticks;
}
}
EX string fullmsg(msginfo& m) {
string s = m.msg;
if(m.quantity > 1) s += " (x" + its(m.quantity) + ")";
return s;
}
2017-07-10 18:47:38 +00:00
void addMessageToLog(msginfo& m, vector<msginfo>& log) {
2018-06-22 12:47:24 +00:00
if(isize(log) != 0) {
msginfo& last = log[isize(log)-1];
2017-07-10 18:47:38 +00:00
if(last.msg == m.msg) {
int q = m.quantity + last.quantity;
last = m; last.quantity = q;
return;
}
}
2018-06-22 12:47:24 +00:00
if(isize(log) < 1000)
2017-07-10 18:47:38 +00:00
log.push_back(m);
else {
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(log)-1; i++) swap(log[i], log[i+1]);
log[isize(log)-1] = m;
2017-07-10 18:47:38 +00:00
}
}
2019-08-09 19:00:52 +00:00
EX void clearMessages() { msgs.clear(); }
2017-07-10 18:47:38 +00:00
2019-08-09 19:00:52 +00:00
EX void addMessage(string s, char spamtype) {
2020-02-29 16:58:59 +00:00
LATE( addMessage(s, spamtype); )
2019-05-12 23:57:40 +00:00
DEBB(DF_MSG, ("addMessage: ", s));
2017-07-10 18:47:38 +00:00
msginfo m;
m.msg = s; m.spamtype = spamtype; m.flashout = false; m.stamp = ticks;
m.rtstamp = time(NULL);
m.gtstamp = getgametime();
m.turnstamp = turncount;
2017-07-10 18:47:38 +00:00
m.quantity = 1;
addMessageToLog(m, gamelog);
addMessageToLog(m, msgs);
}
2019-08-09 19:00:52 +00:00
EX color_t colormix(color_t a, color_t b, color_t c) {
2017-07-10 18:47:38 +00:00
for(int p=0; p<3; p++)
part(a, p) = part(a,p) + (part(b,p) - part(a,p)) * part(c,p) / 255;
return a;
}
2022-09-17 10:09:46 +00:00
/* color difference for 24-bit colors, from 0 to 255*3 */
EX int color_diff(color_t a, color_t b) {
int res = 0;
for(int i=0; i<3; i++) res += abs(part(a, i) - part(b, i));
return res;
}
2019-08-09 19:00:52 +00:00
EX int rhypot(int a, int b) { return (int) sqrt(a*a - b*b); }
2017-10-08 12:49:49 +00:00
2019-08-09 19:00:52 +00:00
EX ld realradius() {
ld vradius = current_display->radius;
if(sphere) {
2022-12-04 13:48:12 +00:00
if(flip_sphere())
vradius /= sqrt(pconf.alpha*pconf.alpha - 1);
else
vradius = 1e12; // use the following
}
if(euclid)
vradius = current_display->radius * get_sightrange() / (1 + pconf.alpha) / 2.5;
2017-11-07 12:40:56 +00:00
vradius = min<ld>(vradius, min(vid.xres, vid.yres) / 2);
return vradius;
}
2019-08-09 19:00:52 +00:00
EX void drawmessage(const string& s, int& y, color_t col) {
2021-05-28 22:11:59 +00:00
if(nomsg) return;
2017-11-07 12:40:56 +00:00
int rrad = (int) realradius();
2017-10-08 12:49:49 +00:00
int space;
2019-05-28 23:09:38 +00:00
if(dual::state)
space = vid.xres;
else if(y > current_display->ycenter + rrad * pconf.stretch)
2017-10-08 12:49:49 +00:00
space = vid.xres;
else if(y > current_display->ycenter)
space = current_display->xcenter - rhypot(rrad, (y-current_display->ycenter) / pconf.stretch);
else if(y > current_display->ycenter - vid.fsize)
space = current_display->xcenter - rrad;
else if(y > current_display->ycenter - vid.fsize - rrad * pconf.stretch)
space = current_display->xcenter - rhypot(rrad, (current_display->ycenter-vid.fsize-y) / pconf.stretch);
2017-10-08 12:49:49 +00:00
else
space = vid.xres;
if(textwidth(vid.fsize, s) <= space) {
displayfr(0, y, 1, vid.fsize, s, col, 0);
y -= vid.fsize;
return;
}
2018-06-22 12:47:24 +00:00
for(int i=1; i<isize(s); i++)
2017-10-08 12:49:49 +00:00
if(s[i-1] == ' ' && textwidth(vid.fsize, "..."+s.substr(i)) <= space) {
displayfr(0, y, 1, vid.fsize, "..."+s.substr(i), col, 0);
y -= vid.fsize;
drawmessage(s.substr(0, i-1), y, col);
return;
}
// no chance
displayfr(0, y, 1, vid.fsize, s, col, 0);
y -= vid.fsize;
return;
}
2019-08-09 19:00:52 +00:00
EX void drawmessages() {
2019-05-12 23:57:40 +00:00
DEBBI(DF_GRAPH, ("draw messages"));
2017-07-10 18:47:38 +00:00
int i = 0;
int t = ticks;
2018-06-22 12:47:24 +00:00
for(int j=0; j<isize(msgs); j++) {
if(j < isize(msgs) - vid.msglimit) continue;
2017-07-10 18:47:38 +00:00
int age = msgs[j].flashout * (t - msgs[j].stamp);
if(msgs[j].spamtype) {
2018-06-22 12:47:24 +00:00
for(int i=j+1; i<isize(msgs); i++) if(msgs[i].spamtype == msgs[j].spamtype)
2017-07-10 18:47:38 +00:00
msgs[j].flashout = 2;
}
2017-10-08 12:49:49 +00:00
if(age < 256*vid.flashtime)
msgs[i++] = msgs[j];
}
msgs.resize(i);
if(vid.msgleft == 2) {
2020-04-06 06:46:08 +00:00
int y = vid.yres - vid.fsize - hud_margin(1);
2018-06-22 12:47:24 +00:00
for(int j=isize(msgs)-1; j>=0; j--) {
2017-10-08 12:49:49 +00:00
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);
2017-10-08 12:49:49 +00:00
}
}
else {
2018-06-22 12:47:24 +00:00
for(int j=0; j<isize(msgs); j++) {
2017-10-08 12:49:49 +00:00
int age = msgs[j].flashout * (t - msgs[j].stamp);
int x = vid.msgleft ? 0 : vid.xres / 2;
2018-06-22 12:47:24 +00:00
int y = vid.yres - vid.fsize * (isize(msgs) - j) - (ISIOS ? 4 : 0);
2017-07-10 18:47:38 +00:00
poly_outline = gradient(bordcolor, backcolor, 0, age, 256*vid.flashtime) << 8;
displayfr(x, y, 1, vid.fsize, fullmsg(msgs[j]), gradient(forecolor, backcolor, 0, age, 256*vid.flashtime), vid.msgleft ? 0 : 8);
2017-07-10 18:47:38 +00:00
}
}
}
2019-08-09 19:00:52 +00:00
EX void drawCircle(int x, int y, int size, color_t color, color_t fillcolor IS(0)) {
2017-07-10 18:47:38 +00:00
if(size < 0) size = -size;
2019-02-17 17:41:40 +00:00
#if CAP_GL && CAP_POLY
2017-07-10 18:47:38 +00:00
if(vid.usingGL) {
2018-09-04 21:27:27 +00:00
glflush();
2018-02-08 23:29:20 +00:00
glhr::be_nontextured();
glhr::id_modelview();
2019-10-21 20:34:20 +00:00
dynamicval<eModel> em(pmodel, mdPixel);
2018-02-11 18:08:17 +00:00
glcoords.clear();
x -= current_display->xcenter; y -= current_display->ycenter;
2017-07-10 18:47:38 +00:00
int pts = size * 4;
if(pts > 1500) pts = 1500;
if(ISMOBILE && pts > 72) pts = 72;
for(int r=0; r<pts; r++) {
float rr = (TAU * r) / pts;
glcoords.push_back(glhr::makevertex(x + size * sin(rr), y + size * pconf.stretch * cos(rr), 0));
2017-07-10 18:47:38 +00:00
}
current_display->set_all(0, lband_shift);
2018-02-11 18:08:17 +00:00
glhr::vertices(glcoords);
glhr::set_depthtest(false);
2018-11-08 15:28:17 +00:00
if(fillcolor) {
glhr::color2(fillcolor);
glDrawArrays(GL_TRIANGLE_FAN, 0, pts);
}
if(color) {
glhr::color2(color);
glDrawArrays(GL_LINE_LOOP, 0, pts);
}
2017-07-10 18:47:38 +00:00
return;
}
#endif
2017-07-22 23:33:27 +00:00
#if CAP_XGD
gdpush(4); gdpush(color); gdpush(fillcolor); gdpush(x); gdpush(y); gdpush(size);
2017-07-22 23:33:27 +00:00
#elif CAP_SDLGFX
if(pconf.stretch == 1) {
if(fillcolor) filledCircleColor(srend, x, y, size, fillcolor);
if(color) ((vid.antialias && AA_NOGL)?aacircleColor:circleColor) (srend, x, y, size, align(color));
2018-11-08 15:28:17 +00:00
}
else {
if(fillcolor) filledEllipseColor(srend, x, y, size, size * pconf.stretch, fillcolor);
if(color) ((vid.antialias && AA_NOGL)?aaellipseColor:ellipseColor) (srend, x, y, size, size * pconf.stretch, align(color));
2018-11-08 15:28:17 +00:00
}
2017-07-22 23:33:27 +00:00
#elif CAP_SDL
2017-07-10 18:47:38 +00:00
int pts = size * 4;
if(pts > 1500) pts = 1500;
for(int r=0; r<pts; r++)
qpixel(s, x + int(size * sin(r)), y + int(size * cos(r))) = color;
#endif
}
2019-08-09 19:00:52 +00:00
EX void displayButton(int x, int y, const string& name, int key, int align, int rad IS(0)) {
2017-07-10 18:47:38 +00:00
if(displayfr(x, y, rad, vid.fsize, name, 0x808080, align)) {
displayfr(x, y, rad, vid.fsize, name, 0xFFFF00, align);
getcstat = key;
}
}
2019-08-10 08:57:14 +00:00
#if HDR
#define SETMOUSEKEY 5000
#endif
EX char mousekey = 'n';
EX char newmousekey;
2017-07-10 18:47:38 +00:00
2019-08-09 19:00:52 +00:00
EX void displaymm(char c, int x, int y, int rad, int size, const string& title, int align) {
2017-07-10 18:47:38 +00:00
if(displayfr(x, y, rad, size, title, c == mousekey ? 0xFF8000 : 0xC0C0C0, align)) {
displayfr(x, y, rad, size, title, 0xFFFF00, align);
getcstat = SETMOUSEKEY, newmousekey = c;
}
}
2019-08-09 19:00:52 +00:00
EX bool displayButtonS(int x, int y, const string& name, color_t col, int align, int size) {
2017-07-10 18:47:38 +00:00
if(displaystr(x, y, 0, size, name, col, align)) {
displaystr(x, y, 0, size, name, 0xFFFF00, align);
return true;
}
else return false;
}
2019-08-09 19:00:52 +00:00
EX void displayColorButton(int x, int y, const string& name, int key, int align, int rad, color_t color, color_t color2 IS(0)) {
2017-07-10 18:47:38 +00:00
if(displayfr(x, y, rad, vid.fsize, name, color, align)) {
if(color2) displayfr(x, y, rad, vid.fsize, name, color2, align);
getcstat = key;
}
}
ld textscale() {
return vid.fsize / (current_display->radius * cgi.crossf) * (1+pconf.alpha) * 2;
2017-07-10 18:47:38 +00:00
}
EX void compute_fsize() {
dual::split_or_do([&] {
if(vid.relative_font)
vid.fsize = min(vid.yres * vid.fontscale/ 3200, vid.xres * vid.fontscale/ 4800);
else
vid.fsize = vid.abs_fsize;
if(vid.fsize < 6) vid.fsize = 6;
});
}
EX bool graphics_on;
2022-05-01 09:25:49 +00:00
EX bool request_resolution_change;
EX void do_request_resolution_change() { request_resolution_change = true; }
EX bool want_vsync() {
if(vrhr::active())
return false;
return vid.want_vsync;
}
EX bool need_to_reopen_window() {
if(vid.want_antialias != vid.antialias)
return true;
if(vid.wantGL != vid.usingGL)
return true;
if(want_vsync() != vid.current_vsync)
return true;
2022-05-01 09:25:49 +00:00
if(request_resolution_change)
return true;
return false;
2018-02-20 10:41:55 +00:00
}
EX bool need_to_apply_screen_settings() {
if(need_to_reopen_window())
return true;
if(vid.want_fullscreen != vid.full)
return true;
if(make_pair(vid.xres, vid.yres) != get_requested_resolution())
return true;
return false;
}
2021-03-07 21:31:36 +00:00
EX void close_renderer() {
#if CAP_SDL2
if(s_renderer) SDL_DestroyRenderer(s_renderer), s_renderer = nullptr;
if(s_texture) SDL_DestroyTexture(s_texture), s_texture = nullptr;
if(s) SDL_FreeSurface(s), s = nullptr;
if(s_software_renderer) SDL_DestroyRenderer(s_software_renderer), s_software_renderer = nullptr;
#endif
}
2021-03-09 08:45:33 +00:00
EX void close_window() {
#if CAP_SDL2
close_renderer();
2022-06-10 16:45:15 +00:00
if(s_have_context) {
SDL_GL_DeleteContext(s_context), s_have_context = false;
}
2021-03-09 08:45:33 +00:00
if(s_window) SDL_DestroyWindow(s_window), s_window = nullptr;
#endif
}
EX void apply_screen_settings() {
if(!need_to_apply_screen_settings()) return;
if(!graphics_on) return;
#if ISANDROID
if(vid.full != vid.want_fullscreen)
addMessage(XLAT("Reenter HyperRogue to apply this setting"));
#endif
2021-03-07 21:31:36 +00:00
close_renderer();
#if CAP_VR
if(vrhr::state) vrhr::shutdown_vr();
#endif
2021-03-09 15:04:02 +00:00
#if CAP_SDL
2021-03-09 15:04:02 +00:00
#if !CAP_SDL2
if(need_to_reopen_window())
SDL_QuitSubSystem(SDL_INIT_VIDEO);
2021-03-09 15:04:02 +00:00
#endif
#endif
2021-03-09 15:04:02 +00:00
graphics_on = false;
android_settings_changed();
init_graph();
#if CAP_GL
if(vid.usingGL) {
glhr::be_textured(); glhr::be_nontextured();
}
#endif
}
EX pair<int, int> get_requested_resolution() {
#if ISMOBILE || ISFAKEMOBILE
return { vid.xres, vid.yres };
#endif
if(vid.want_fullscreen && vid.change_fullscr)
return { vid.fullscreen_x, vid.fullscreen_y };
else if(vid.want_fullscreen)
return { vid.xres = vid.xscr, vid.yres = vid.yscr };
else if(vid.relative_window_size)
return { vid.xscr * vid.window_rel_x + .5, vid.yscr * vid.window_rel_y + .5 };
else
return { vid.window_x, vid.window_y };
}
#ifndef CUSTOM_CAPTION
#define CUSTOM_CAPTION ("HyperRogue " VER)
#endif
EX bool resizable = true;
2021-06-01 08:09:44 +00:00
EX void setvideomode_android() {
vid.usingGL = vid.wantGL;
vid.full = vid.want_fullscreen;
vid.antialias = vid.want_antialias;
}
#if CAP_SDL
2021-03-09 08:45:33 +00:00
EX int current_window_flags = -1;
2019-08-09 19:00:52 +00:00
EX void setvideomode() {
2017-07-10 18:47:38 +00:00
2019-05-12 23:57:40 +00:00
DEBBI(DF_INIT | DF_GRAPH, ("setvideomode"));
2017-07-10 18:47:38 +00:00
vid.full = vid.want_fullscreen;
2017-07-10 18:47:38 +00:00
tie(vid.xres, vid.yres) = get_requested_resolution();
compute_fsize();
2017-07-10 18:47:38 +00:00
int flags = 0;
vid.antialias = vid.want_antialias;
2017-07-22 23:33:27 +00:00
#if CAP_GL
vid.usingGL = vid.wantGL;
2017-07-10 18:47:38 +00:00
if(vid.usingGL) {
2021-03-06 13:57:27 +00:00
flags = SDL12(SDL_OPENGL | SDL_HWSURFACE, SDL_WINDOW_OPENGL | SDL_WINDOW_ALLOW_HIGHDPI);
2020-03-26 10:17:27 +00:00
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
2017-07-10 18:47:38 +00:00
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 1);
vid.current_vsync = want_vsync();
#if !ISMOBWEB && !CAP_SDL2
if(vid.current_vsync)
SDL_GL_SetAttribute( SDL_GL_SWAP_CONTROL, 1 );
else
SDL_GL_SetAttribute( SDL_GL_SWAP_CONTROL, 0 );
2021-02-07 17:29:49 +00:00
#endif
2017-07-12 16:03:53 +00:00
if(vid.antialias & AA_MULTI) {
2017-07-10 18:47:38 +00:00
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
2017-08-06 12:50:16 +00:00
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, (vid.antialias & AA_MULTI16) ? 16 : 4);
2017-07-10 18:47:38 +00:00
}
}
#else
vid.usingGL = false;
2017-07-10 18:47:38 +00:00
#endif
int sizeflag = SDL12(vid.full ? SDL_FULLSCREEN : resizable ? SDL_RESIZABLE : 0, vid.full ? SDL_WINDOW_FULLSCREEN : resizable ? SDL_WINDOW_RESIZABLE : 0);
#ifdef WINDOWS
2021-02-07 21:22:07 +00:00
#ifndef OLD_MINGW
static bool set_awareness = true;
if(set_awareness) {
set_awareness = false;
HMODULE user32_dll = LoadLibraryA("User32.dll");
if (user32_dll) {
DPI_AWARENESS_CONTEXT (WINAPI * Loaded_SetProcessDpiAwarenessContext) (DPI_AWARENESS_CONTEXT) =
2021-03-11 09:01:01 +00:00
(DPI_AWARENESS_CONTEXT (WINAPI *) (DPI_AWARENESS_CONTEXT)) (void*)
GetProcAddress(user32_dll, "SetProcessDpiAwarenessContext");
if(Loaded_SetProcessDpiAwarenessContext) {
Loaded_SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
}
FreeLibrary(user32_dll);
}
}
#endif
2021-02-07 21:22:07 +00:00
#endif
2017-07-10 18:47:38 +00:00
2021-03-07 21:31:36 +00:00
#if CAP_SDL2
2021-03-07 13:12:18 +00:00
if(s_renderer) SDL_DestroyRenderer(s_renderer), s_renderer = nullptr;
2021-03-07 21:31:36 +00:00
#endif
2021-03-07 13:12:18 +00:00
auto create_win = [&] {
#if CAP_SDL2
2022-06-10 16:45:15 +00:00
if(s_window && current_window_flags != (flags | sizeflag)) {
if(s_have_context) {
SDL_GL_DeleteContext(s_context), s_have_context = false;
glhr::glew = false;
}
2021-03-09 08:45:33 +00:00
SDL_DestroyWindow(s_window), s_window = nullptr;
2022-06-10 16:45:15 +00:00
}
2021-03-09 08:45:33 +00:00
if(s_window)
SDL_SetWindowSize(s_window, vid.xres, vid.yres);
else
s_window = SDL_CreateWindow(CUSTOM_CAPTION, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
vid.xres, vid.yres,
flags | sizeflag
);
2021-03-09 08:45:33 +00:00
current_window_flags = (flags | sizeflag);
#else
s = SDL_SetVideoMode(vid.xres, vid.yres, 32, flags | sizeflag);
#endif
};
create_win();
2017-07-10 18:47:38 +00:00
2021-03-07 13:12:18 +00:00
auto& sw = SDL12(s, s_window);
if(vid.full && !sw) {
2017-07-10 18:47:38 +00:00
vid.xres = vid.xscr;
vid.yres = vid.yscr;
vid.fsize = 10;
sizeflag = SDL12(SDL_FULLSCREEN, SDL_WINDOW_FULLSCREEN);
create_win();
2017-07-10 18:47:38 +00:00
}
2021-03-07 13:12:18 +00:00
if(!sw) {
2017-07-10 18:47:38 +00:00
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;
sizeflag = SDL12(SDL_RESIZABLE, SDL_WINDOW_RESIZABLE);
create_win();
2017-07-10 18:47:38 +00:00
}
#if CAP_SDL2
2022-06-10 16:45:15 +00:00
if(s_renderer) SDL_DestroyRenderer(s_renderer), s_renderer = nullptr;
2021-03-07 13:12:18 +00:00
s_renderer = SDL_CreateRenderer(s_window, -1, vid.current_vsync ? SDL_RENDERER_PRESENTVSYNC : 0);
2021-03-09 08:45:33 +00:00
SDL_GetRendererOutputSize(s_renderer, &vid.xres, &vid.yres);
2021-03-07 13:12:18 +00:00
if(s_texture) SDL_DestroyTexture(s_texture), s_texture = nullptr;
s_texture = SDL_CreateTexture(s_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, vid.xres, vid.yres);
2021-03-07 13:12:18 +00:00
if(s) SDL_FreeSurface(s), s = nullptr;
s = shot::empty_surface(vid.xres, vid.yres, false);
2021-03-07 13:12:18 +00:00
if(s_software_renderer) SDL_DestroyRenderer(s_software_renderer), s_software_renderer = nullptr;
s_software_renderer = SDL_CreateSoftwareRenderer(s);
#endif
s_screen = s;
2017-07-10 18:47:38 +00:00
2017-07-22 23:33:27 +00:00
#if CAP_GL
2017-07-10 18:47:38 +00:00
if(vid.usingGL) {
2019-05-06 23:10:11 +00:00
if(vid.antialias & AA_MULTI) {
2017-07-22 23:33:27 +00:00
glEnable(GL_MULTISAMPLE);
2019-05-06 23:10:11 +00:00
glEnable(GL_MULTISAMPLE_ARB);
}
else {
2017-07-22 23:33:27 +00:00
glDisable(GL_MULTISAMPLE);
2019-05-06 23:10:11 +00:00
glDisable(GL_MULTISAMPLE_ARB);
}
2017-07-22 23:33:27 +00:00
2022-06-10 16:45:15 +00:00
#if CAP_SDL2
if(s_have_context) SDL_GL_DeleteContext(s_context), s_have_context = false;
if(!s_have_context) s_context = SDL_GL_CreateContext(s_window);
s_have_context = true; glhr::glew = false;
#endif
2017-07-10 18:47:38 +00:00
glViewport(0, 0, vid.xres, vid.yres);
2018-02-08 23:29:20 +00:00
glhr::init();
2017-07-10 18:47:38 +00:00
resetGL();
}
#endif
}
#endif
2019-08-09 19:00:52 +00:00
EX bool noGUI = false;
2017-07-10 18:47:38 +00:00
#if CAP_SDL
EX bool sdl_on = false;
EX int SDL_Init1(Uint32 flags) {
if(!sdl_on) {
sdl_on = true;
return SDL_Init(flags);
}
else
return SDL_InitSubSystem(flags);
}
#endif
2017-07-10 18:47:38 +00:00
EX void init_font() {
#if CAP_SDLTTF
if(TTF_Init() != 0) {
printf("Failed to initialize TTF.\n");
exit(2);
}
#endif
}
2017-07-22 23:33:27 +00:00
EX void close_font() {
#if CAP_SDLTTF
2021-03-07 21:31:36 +00:00
for(int i=0; i<=max_font_size; i++) if(font[i]) {
TTF_CloseFont(font[i]);
font[i] = nullptr;
}
TTF_Quit();
2017-07-22 23:33:27 +00:00
#endif
#if CAL_GLFONT
2021-03-07 21:31:36 +00:00
for(int i=0; i<=max_glfont_size; i++) if(glfont[i]) {
delete glfont[i];
glfont[i] = nullptr;
}
2017-07-10 18:47:38 +00:00
#endif
}
2017-07-10 18:47:38 +00:00
EX void init_graph() {
2017-07-22 23:33:27 +00:00
#if CAP_SDL
if (SDL_Init1(SDL_INIT_VIDEO) == -1)
2017-07-10 18:47:38 +00:00
{
printf("Failed to initialize video.\n");
exit(2);
}
2017-07-22 23:33:27 +00:00
#if ISWEB
2020-10-15 14:35:44 +00:00
get_canvas_size();
2017-07-10 18:47:38 +00:00
#else
if(!vid.xscr) {
#if CAP_SDL2
SDL_DisplayMode dm;
SDL_GetCurrentDisplayMode(0, &dm);
vid.xscr = vid.xres = dm.w;
vid.yscr = vid.yres = dm.h;
#else
const SDL_VideoInfo *inf = SDL_GetVideoInfo();
vid.xscr = vid.xres = inf->current_w;
vid.yscr = vid.yres = inf->current_h;
#endif
}
2017-07-10 18:47:38 +00:00
#endif
#if !CAP_SDL2
SDL_WM_SetCaption(CUSTOM_CAPTION, CUSTOM_CAPTION);
#endif
2017-07-22 23:33:27 +00:00
#endif
2021-06-01 08:09:44 +00:00
graphics_on = true;
2021-06-03 10:25:59 +00:00
#if ISIOS
vid.usingGL = true;
#endif
2021-06-01 08:09:44 +00:00
#if ISANDROID
setvideomode_android();
#endif
#if CAP_SDL
setvideomode();
if(!s) {
printf("Failed to initialize graphics.\n");
exit(2);
}
#if !CAP_SDL2
SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
SDL_EnableUNICODE(1);
#endif
#endif
#if ISANDROID
vid.full = vid.want_fullscreen;
#endif
}
EX void initialize_all() {
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;
}
2017-07-10 18:47:38 +00:00
preparesort();
2017-07-22 23:33:27 +00:00
#if CAP_CONFIG
2017-07-10 18:47:38 +00:00
loadConfig();
#endif
2020-10-15 14:33:52 +00:00
#if CAP_ARCM
arcm::current.parse();
2020-10-15 14:33:52 +00:00
#endif
2022-12-08 18:38:06 +00:00
if(mhybrid) geometry = hybrid::underlying;
2017-07-10 18:47:38 +00:00
2017-07-22 23:33:27 +00:00
#if CAP_COMMANDLINE
2017-07-10 18:47:38 +00:00
arg::read(2);
#endif
init_graph();
check_cgi();
cgi.require_basics();
2017-07-10 18:47:38 +00:00
init_font();
2017-07-22 23:33:27 +00:00
#if CAP_SDLJOY
2017-07-10 18:47:38 +00:00
initJoysticks();
2017-07-22 23:33:27 +00:00
#endif
2017-07-10 18:47:38 +00:00
2017-07-22 23:33:27 +00:00
#if CAP_SDLAUDIO
2017-07-10 18:47:38 +00:00
initAudio();
2017-07-22 23:33:27 +00:00
#endif
2017-07-10 18:47:38 +00:00
}
EX void quit_all() {
2019-05-12 23:57:40 +00:00
DEBBI(DF_INIT, ("clear graph"));
2017-07-22 23:33:27 +00:00
#if CAP_SDLJOY
2017-07-10 18:47:38 +00:00
closeJoysticks();
#endif
2017-07-22 23:33:27 +00:00
#if CAP_SDL
2021-03-09 08:45:33 +00:00
close_window();
2017-07-10 18:47:38 +00:00
SDL_Quit();
sdl_on = false;
2017-07-10 18:47:38 +00:00
#endif
}
2019-08-09 19:00:52 +00:00
EX int calcfps() {
2017-07-10 18:47:38 +00:00
#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;
}
2019-08-09 19:00:52 +00:00
EX namespace subscreens {
2019-03-09 15:20:06 +00:00
EX vector<display_data> player_displays;
/** 'in' is on if we are currently working on a single display */
EX bool in;
2019-08-09 19:00:52 +00:00
EX int current_player;
2019-03-09 15:20:06 +00:00
2019-08-09 19:00:52 +00:00
EX bool is_current_player(int id) {
2019-03-09 15:20:06 +00:00
if(!in) return true;
return id == current_player;
}
2019-08-09 19:00:52 +00:00
EX void prepare() {
2019-03-09 15:20:06 +00:00
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<N; i++) {
auto& pd = player_displays[i];
pd.xmin = (i % cols) * 1. / cols;
pd.xmax = ((i % cols) + 1.) / cols;
pd.ymin = (i / cols) * 1. / rows;
pd.ymax = ((i / cols) + 1.) / rows;
}
}
else {
player_displays.clear();
}
}
2019-08-09 19:00:52 +00:00
EX bool split(reaction_t what) {
2019-03-09 15:20:06 +00:00
using namespace racing;
if(in) return false;
if(!multi::split_screen) return false;
2019-03-09 15:20:06 +00:00
if(!player_displays.empty()) {
in = true;
int& p = current_player;
for(p = 0; p < multi::players; p++) {
dynamicval<display_data*> c(current_display, &player_displays[p]);
what();
}
in = false;
return true;
}
return false;
}
}
}