// Hyperbolic Rogue -- rendering // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details /** \file drawing.cpp * \brief Rendering shapes (dqi_draw), queue of shapes to render (ptds), etc. */ #include "hyper.h" namespace hr { #if HDR static const int POLY_DRAWLINES = 1; // draw the lines static const int POLY_DRAWAREA = 2; // draw the area static const int POLY_INVERSE = 4; // draw the inverse -- useful in stereographic projection static const int POLY_ISSIDE = 8; // never draw in inverse static const int POLY_BEHIND = 16; // there are points behind the camera static const int POLY_TOOLARGE = 32; // some coordinates are too large -- best not to draw to avoid glitches static const int POLY_INFRONT = 64; // on the sphere (orthogonal projection), do not draw without any points in front static const int POLY_HASWALLS = 128; // floor shapes which have their sidewalls static const int POLY_PLAIN = 256; // plain floors static const int POLY_FULL = 512; // full floors static const int POLY_HASSHADOW = 1024; // floor shapes which have their shadows, or can use shFloorShadow static const int POLY_GP = 2048; // Goldberg shapes static const int POLY_VCONVEX = 4096; // Convex shape (vertex) static const int POLY_CCONVEX = 8192; // Convex shape (central) static const int POLY_CENTERIN = 16384; // new system of side checking static const int POLY_FORCEWIDE = (1<<15); // force wide lines static const int POLY_NOTINFRONT = (1<<16); // points not in front static const int POLY_NIF_ERROR = (1<<17); // points moved to the outline cross the image, disable static const int POLY_BADCENTERIN = (1<<18); // new system of side checking static const int POLY_PRECISE_WIDE = (1<<19); // precise width calculation static const int POLY_FORCE_INVERTED = (1<<20); // force inverted static const int POLY_ALWAYS_IN = (1<<21); // always draw this static const int POLY_TRIANGLES = (1<<22); // made of TRIANGLES, not TRIANGLE_FAN static const int POLY_INTENSE = (1<<23); // extra intense colors static const int POLY_DEBUG = (1<<24); // debug this shape static const int POLY_PRINTABLE = (1<<25); // these walls are printable static const int POLY_FAT = (1<<26); // fatten this model in WRL export (used for Rug) static const int POLY_SHADE_TEXTURE = (1<<27); // texture has 'z' coordinate for shading static const int POLY_ONE_LEVEL = (1<<28); // only one level of the universal cover in SL(2,R) /** \brief A graphical element that can be drawn. Objects are not drawn immediately but rather queued. * * HyperRogue map rendering functions do not draw its data immediately; instead, they call the 'queue' functions * which store the data to draw in hr::ptds. This approach lets us draw the elements in the correct order. */ struct drawqueueitem { /** \brief The higher the priority, the earlier we should draw this object. */ PPR prio; /** \brief Color of this object. */ color_t color; /** \brief Some priorities need extra sorting inside the given class. This attribute is used to specify the inner sorting priority. */ int subprio; /** \brief Draw the object. */ virtual void draw() = 0; /** \brief Draw the object as background. */ virtual void draw_back() {} virtual ~drawqueueitem() {} /** \brief When minimizing OpenGL calls, we need to group items of the same color, etc. together. This value is used as an extra sorting key. */ virtual color_t outline_group() = 0; }; /** \brief Drawqueueitem used to draw polygons. The majority of drawqueueitems fall here. */ struct dqi_poly : drawqueueitem { /** \brief matrix used to transform the model */ shiftmatrix V; /** \brief a vector of GL vertices where the model is stored */ const vector *tab; /** \brief the where does the model start */ int offset; /** \brief how many vertices in the model */ int cnt; /** \brief the offset in the texture vertices */ int offset_texture; /** \brief outline color */ color_t outline; /** \brief width of boundary lines */ double linewidth; /** \brief various flags */ int flags; /** \brief Texture data for textured polygons. Requires POLY_TRIANGLES flag */ struct basic_textureinfo *tinf; /** \brief used to find the correct side to draw in spherical geometries */ hyperpoint intester; /** \brief temporarily cached data */ float cache; void draw(); void gldraw(); void draw_back(); virtual color_t outline_group() { return outline; } }; /** \brief Drawqueueitem used to draw lines */ struct dqi_line : drawqueueitem { /** \brief starting and ending point */ shiftpoint H1, H2; /** \brief how accurately to render the line */ int prf; /** \brief width of this line */ double width; void draw(); void draw_back(); virtual color_t outline_group() { return color; } }; /** \brief Drawqueueitem used to draw strings, using sccreen coodinates */ struct dqi_string : drawqueueitem { /** \brief text */ string str; /** onscreen position */ int x, y; /** shift in anaglyph mode */ int shift; /** font size */ int size; /** frame color */ int frame; /** alignment (0-8-16) */ int align; void draw(); virtual color_t outline_group() { return 1; } }; /** Drawqueueitem used to draw circles, using screen coordinates */ struct dqi_circle : drawqueueitem { /** \brief onscreen position */ int x, y; /** \brief circle size */ int size; /** \brief which color should it be filled with */ color_t fillcolor; /** \brief width of the circle */ double linewidth; void draw(); virtual color_t outline_group() { return 2; } }; /** \brief Perform an arbitrary action. May temporarily change the model, etc. */ struct dqi_action : drawqueueitem { reaction_t action; dqi_action(const reaction_t& a) : action(a) {} void draw() { action(); } virtual color_t outline_group() { return 2; } }; #endif /** \brief Return a reference to i-th component of col. * \arg i For colors with alpha, A=0, R=1, G=2, B=3. For colors without alpha, R=0, G=1, B=2. */ EX unsigned char& part(color_t& col, int i) { unsigned char* c = (unsigned char*) &col; #if ISMOBILE return c[i]; #else #if SDL_BYTEORDER == SDL_BIG_ENDIAN return c[sizeof(col) - 1 - i]; #else return c[i]; #endif #endif } bool fatborder; EX color_t poly_outline; EX vector> ptds; #if CAP_GL EX color_t text_color; EX int text_shift; EX GLuint text_texture; EX int texts_merged; EX int shapes_merged; #if MINIMIZE_GL_CALLS color_t triangle_color, line_color; vector triangle_vertices; vector line_vertices; #endif EX void glflush() { DEBBI(DF_GRAPH, ("glflush")); #if MINIMIZE_GL_CALLS if(isize(triangle_vertices)) { // printf("%08X %08X | %d shapes, %d/%d vertices\n", triangle_color, line_color, shapes_merged, isize(triangle_vertices), isize(line_vertices)); if(triangle_color) { glhr::be_nontextured(); glapplymatrix(Id); glhr::current_vertices = NULL; glhr::vertices(triangle_vertices); glhr::color2(triangle_color); glDrawArrays(GL_TRIANGLES, 0, isize(triangle_vertices)); } triangle_vertices.clear(); } if(isize(line_vertices)) { if(line_color) { glhr::be_nontextured(); glapplymatrix(Id); glhr::current_vertices = NULL; glhr::vertices(line_vertices); glhr::color2(line_color); glDrawArrays(GL_LINES, 0, isize(line_vertices)); } line_vertices.clear(); } shapes_merged = 0; #endif if(isize(text_vertices)) { // printf("%08X | %d texts, %d vertices\n", text_color, texts_merged, isize(text_vertices)); current_display->next_shader_flags = GF_TEXTURE; dynamicval m(pmodel, mdPixel); if(!svg::in) current_display->set_all(0,0); glBindTexture(GL_TEXTURE_2D, text_texture); glhr::color2(text_color); glhr::set_depthtest(false); for(int ed = (current_display->stereo_active() && text_shift)?-1:0; ed<2; ed+=2) { glhr::set_modelview(glhr::translate(-ed*text_shift-current_display->xcenter,-current_display->ycenter, 0)); current_display->set_mask(ed); glhr::current_vertices = NULL; glhr::prepare(text_vertices); glDrawArrays(GL_TRIANGLES, 0, isize(text_vertices)); GLERR("print"); } if(current_display->stereo_active() && text_shift && !svg::in) current_display->set_mask(0); texts_merged = 0; text_vertices.clear(); } } #endif #if CAP_SDL && !ISMOBILE SDL_Surface *aux; #endif #if CAP_POLY #if HDR #define POLYMAX 60000 #endif EX vector glcoords; #endif EX int spherespecial, spherephase; #if CAP_POLY int polyi; EX int polyx[POLYMAX], polyxr[POLYMAX], polyy[POLYMAX]; int poly_flags; void add1(const hyperpoint& H) { glcoords.push_back(glhr::pointtogl(H)); } bool is_behind(const hyperpoint& H) { return pmodel == mdDisk && (hyperbolic ? H[2] >= 0 : true) && (nonisotropic ? false : pconf.alpha + H[2] <= BEHIND_LIMIT); } hyperpoint be_just_on_view(const hyperpoint& H1, const hyperpoint &H2) { // H1[2] * t + H2[2] * (1-t) == BEHIND_LIMIT - pconf.alpha // H2[2]- BEHIND_LIMIT + pconf.alpha = t * (H2[2] - H1[2]) ld t = (H2[2] - BEHIND_LIMIT + pconf.alpha) / (H2[2] - H1[2]); return H1 * t + H2 * (1-t); } bool last_infront; bool nif_error_in(ld x1, ld y1, ld x2, ld y2) { return pow(x1 * x2 + y2 * y2, 2) < (x1*x1+y1*y1)*(x2*x2+y2*y2)*.5; } bool knowgood; hyperpoint goodpoint; vector> tofix; EX bool two_sided_model() { if(GDIM == 3) return false; if(pmodel == mdHyperboloid) return !euclid; // if(pmodel == mdHemisphere) return true; if(pmodel == mdDisk) return sphere; if(pmodel == mdHemisphere) return true; if(pmodel == mdRotatedHyperboles) return true; if(pmodel == mdSpiral && pconf.spiral_cone < 360) return true; return false; } EX int get_side(const hyperpoint& H) { if(pmodel == mdDisk && sphere) { double curnorm = H[0]*H[0]+H[1]*H[1]+H[2]*H[2]; double horizon = curnorm / pconf.alpha; return (H[2] <= -horizon) ? -1 : 1; } if(pmodel == mdRotatedHyperboles) return H[1] > 0 ? -1 : 1; if(pmodel == mdHyperboloid && hyperbolic) return (models::sin_ball * H[2] > -models::cos_ball * H[1]) ? -1 : 1; if(pmodel == mdHyperboloid && sphere) return (models::sin_ball * H[2] > models::cos_ball * H[1]) ? -1 : 1; if(pmodel == mdHemisphere) { hyperpoint res; applymodel(shiftless(H), res); return res[2] < 0 ? -1 : 1; } if(pmodel == mdSpiral && pconf.spiral_cone < 360) { return cone_side(shiftless(H)); } return 0; } EX bool correct_side(const hyperpoint& H) { return get_side(H) == spherespecial; } hyperpoint Hlast; void fixpoint(glvertex& hscr, hyperpoint H) { hyperpoint bad = H, good = goodpoint; for(int i=0; i<10; i++) { hyperpoint mid = midz(bad, good); if(correct_side(mid)) good = mid; else bad = mid; } hyperpoint Hscr; applymodel(shiftless(good), Hscr); hscr = glhr::makevertex(Hscr[0]*current_display->radius, Hscr[1]*current_display->radius*pconf.stretch, Hscr[2]*current_display->radius); } void addpoint(const shiftpoint& H) { if(true) { ld z = current_display->radius; // if(pconf.alpha + H[2] <= BEHIND_LIMIT && pmodel == mdDisk) poly_flags |= POLY_BEHIND; if(spherespecial) { auto H0 = H.h; if(correct_side(H0)) { poly_flags |= POLY_INFRONT, last_infront = false; if(!knowgood || (spherespecial > 0 ? H[2]>goodpoint[2] : H[2]stereo_active()) glcoords[i][2] = 0; polyx[i] = current_display->xcenter + glcoords[i][0] - glcoords[i][2]; polyxr[i] = current_display->xcenter + glcoords[i][0] + glcoords[i][2]; polyy[i] = current_display->ycenter + glcoords[i][1]; } } bool behind3(shiftpoint h) { if(pmodel == mdGeodesic) return lp_apply(inverse_exp(h))[2] < 0; return h[2] < 0; } void addpoly(const shiftmatrix& V, const vector &tab, int ofs, int cnt) { if(pmodel == mdPixel) { for(int i=ofs; i(Hscr[0]*current_display->radius+10, Hscr[1]*current_display->radius*pconf.stretch, Hscr[2]*vid.radius)); glcoords.push_back(make_array(Hscr[0]*current_display->radius, Hscr[1]*current_display->radius*pconf.stretch+10, Hscr[2]*vid.radius)); glcoords.push_back(make_array(Hscr[0]*current_display->radius-10, Hscr[1]*current_display->radius*pconf.stretch, Hscr[2]*vid.radius)); glcoords.push_back(make_array(Hscr[0]*current_display->radius, Hscr[1]*current_display->radius*pconf.stretch-10, Hscr[2]*vid.radius)); glcoords.push_back(make_array(Hscr[0]*current_display->radius+10, Hscr[1]*current_display->radius*pconf.stretch, Hscr[2]*vid.radius)); */ } } #if CAP_SDLGFX void aapolylineColor(SDL_Surface *s, int*x, int *y, int polyi, color_t col) { for(int i=1; i spx(px, px + polyi); std::vector spy(py, py + polyi); filledPolygonColor(s, spx.data(), spy.data(), polyi, col); } #endif #if CAP_TEXTURE void drawTexturedTriangle(SDL_Surface *s, int *px, int *py, glvertex *tv, color_t col) { transmatrix source = matrix3( px[0], px[1], px[2], py[0], py[1], py[2], 1, 1, 1); transmatrix target = matrix3( tv[0][0], tv[1][0], tv[2][0], tv[0][1], tv[1][1], tv[2][1], 1, 1, 1 ); transmatrix isource = inverse(source); int minx = px[0], maxx = px[0]; int miny = py[0], maxy = py[0]; for(int i=1; i<3; i++) minx = min(minx, px[i]), maxx = max(maxx, px[i]), miny = min(miny, py[i]), maxy = max(maxy, py[i]); for(int mx=minx; mx= -1e-7 && h[1] >= -1e-7 && h[2] >= -1e-7) { hyperpoint ht = target * h; int tw = texture::config.data.twidth; int x = int(ht[0] * tw) & (tw-1); int y = int(ht[1] * tw) & (tw-1); color_t c; if(texture::config.data.texture_pixels.size() == 0) c = 0xFFFFFFFF; else c = texture::config.data.texture_pixels[y * tw + x]; auto& pix = qpixel(s, mx, my); for(int p=0; p<3; p++) { int alpha = part(c, 3) * part(col, 0); auto& v = part(pix, p); v = ((255*255 - alpha) * 255 * v + alpha * part(col, p+1) * part(c, p) + 255 * 255 * 255/2 + 1) / (255 * 255 * 255); } } } } #endif #if CAP_GL EX int global_projection; int min_slr, max_slr = 0; #if MAXMDIM >= 4 extern renderbuffer *floor_textures; #endif void dqi_poly::gldraw() { auto& v = *tab; int ioffset = offset; #if MINIMIZE_GL_CALLS if(current_display->stereo_active() == 0 && !tinf && (color == 0 || ((flags & (POLY_VCONVEX | POLY_CCONVEX)) && !(flags & (POLY_INVERSE | POLY_FORCE_INVERTED))))) { if(color != triangle_color || outline != line_color || texts_merged) { glflush(); triangle_color = color; line_color = outline; } shapes_merged++; if((flags & POLY_CCONVEX) && !(flags & POLY_VCONVEX)) { vector v2(cnt+1); for(int i=0; i v2(cnt); for(int i=0; inext_shader_flags |= GF_TEXTURE_SHADED; glBindTexture(GL_TEXTURE_2D, tinf->texture_id); glhr::vertices_texture(v, tinf->tvertices, offset, offset_texture); ioffset = 0; } else { glhr::be_nontextured(); glhr::vertices(v); } next_slr: for(int ed = current_display->stereo_active() ? -1 : 0; ed<2; ed+=2) { if(global_projection && global_projection != ed) continue; if(min_slr < max_slr) { current_display->set_all(ed, sl2 ? 0 : V.shift); glhr::set_index_sl(V.shift + M_PI * min_slr * hybrid::csteps / cgi.psl_steps); } else { current_display->set_all(ed, V.shift); } bool draw = color; flagtype sp = get_shader_flags(); if(sp & SF_DIRECT) { if((sp & SF_BAND) && V[2][2] > 1e8) continue; glapplymatrix(V.T); } if(draw) { if(flags & POLY_TRIANGLES) { glhr::color2(color, (flags & POLY_INTENSE) ? 2 : 1); glhr::set_depthtest(model_needs_depth() && prio < PPR::SUPERLINE); glhr::set_depthwrite(model_needs_depth() && prio != PPR::TRANSPARENT_SHADOW && prio != PPR::EUCLIDEAN_SKY); glhr::set_fogbase(prio == PPR::SKY ? 1.0 + (euclid ? 20 : 5 / sightranges[geometry]) : 1.0); glDrawArrays(GL_TRIANGLES, ioffset, cnt); } else { glEnable(GL_STENCIL_TEST); glColorMask( GL_FALSE,GL_FALSE,GL_FALSE,GL_FALSE ); glhr::set_depthtest(false); glStencilOp( GL_INVERT, GL_INVERT, GL_INVERT); glStencilFunc( GL_ALWAYS, 0x1, 0x1 ); glhr::color2(0xFFFFFFFF); glDrawArrays(tinf ? GL_TRIANGLES : GL_TRIANGLE_FAN, offset, cnt); current_display->set_mask(ed); glhr::color2(color); glhr::set_depthtest(model_needs_depth() && prio < PPR::SUPERLINE); glhr::set_depthwrite(model_needs_depth() && prio != PPR::TRANSPARENT_SHADOW && prio != PPR::EUCLIDEAN_SKY); glhr::set_fogbase(prio == PPR::SKY ? 1.0 + (euclid ? 20 : 5 / sightranges[geometry]) : 1.0); if(flags & (POLY_INVERSE | POLY_FORCE_INVERTED)) { glStencilOp( GL_ZERO, GL_ZERO, GL_ZERO); glStencilFunc( GL_NOTEQUAL, 1, 1); GLfloat xx = vid.xres; GLfloat yy = vid.yres; vector scr = { glhr::makevertex(-xx, -yy, 0), glhr::makevertex(+xx, -yy, 0), glhr::makevertex(+xx, +yy, 0), glhr::makevertex(-xx, +yy, 0) }; glhr::vertices(scr); glhr::id_modelview(); glDrawArrays(tinf ? GL_TRIANGLES : GL_TRIANGLE_FAN, 0, 4); glhr::vertices(v); if(sp & SF_DIRECT) glapplymatrix(V.T); } else { glStencilOp( GL_ZERO, GL_ZERO, GL_ZERO); glStencilFunc( GL_EQUAL, 1, 1); glDrawArrays(tinf ? GL_TRIANGLES : GL_TRIANGLE_FAN, offset, cnt); } glDisable(GL_STENCIL_TEST); } } if(outline && !tinf) { glhr::color2(outline); glhr::set_depthtest(model_needs_depth() && prio < PPR::SUPERLINE); glhr::set_depthwrite(model_needs_depth() && prio != PPR::TRANSPARENT_SHADOW && prio != PPR::EUCLIDEAN_SKY); glhr::set_fogbase(prio == PPR::SKY ? 1.0 + (euclid ? 20 : 5 / sightranges[geometry]) : 1.0); if(flags & POLY_TRIANGLES) { vector v1; v1.reserve(cnt * 2); for(int i=0; i= (inHighQual ? 1 : 2)) { hyperpoint h0 = h.h / zlevel(h.h); shiftmatrix T = shiftless(rgpushxto0(h0), h.shift); return scale_at(T); } return 1; } EX void set_width(ld w) { #if MINIMIZE_GL_CALLS if(w != glhr::current_linewidth) glflush(); #endif #if CAP_GL glhr::set_linewidth(w); #endif } // this part makes cylindrical projections on the sphere work namespace cyl { int loop_min = 0, loop_max = 0; vector periods; ld period_at(ld y) { ld m = current_display->radius; y /= (m * pconf.stretch); switch(pmodel) { case mdBand: return m * 4; case mdSinusoidal: return m * 2 * cos(y * M_PI); case mdMollweide: return m * 2 * sqrt(1 - y*y*4); case mdCollignon: { if(pconf.collignon_reflected && y > 0) y = -y; y += signed_sqrt(pconf.collignon_parameter); return abs(m*y*2/1.2); } default: return m * 2; } } void adjust(bool tinf) { periods.resize(isize(glcoords)); if(!models::model_straight) for(auto& g: glcoords) models::apply_orientation(g[0], g[1]); for(int i = 0; ixcenter), dist(vid.yres, current_display->ycenter)); ld cmin = -chypot/2, cmax = chypot/2, dmin = -chypot, dmax = chypot; ld z = pconf.stretch * current_display->radius; switch(pmodel) { case mdSinusoidal: case mdBandEquidistant: case mdMollweide: dmax = z/2, dmin = -dmax; break; case mdBandEquiarea: dmax = z/M_PI, dmin = -dmax; break; case mdCollignon: dmin = z * (signed_sqrt(pconf.collignon_parameter - 1) - signed_sqrt(pconf.collignon_parameter)); if(pconf.collignon_reflected) dmax = -dmin; else dmax = z * (signed_sqrt(pconf.collignon_parameter + 1) - signed_sqrt(pconf.collignon_parameter)); break; default: ; } bool had = false; ld first, next; for(int i = 0; i 1) { if(!had) { next = first = glcoords[i][0] / periods[i]; had = true; } else { glcoords[i][0] /= periods[i]; glcoords[i][0] -= round_nearest(glcoords[i][0]-next); next = glcoords[i][0]; glcoords[i][0] *= periods[i]; } loop_min = min(loop_min, floor((cmin - glcoords[i][0]) / periods[i])); loop_max = max(loop_max, ceil((cmax - glcoords[i][0]) / periods[i])); } if(!had) return; ld last = first - round_nearest(first-next); if(loop_max > 100) loop_max = 100; if(loop_min < -100) loop_min = -100; if(abs(first - last) < 1e-6) { if(!models::model_straight) for(auto& g: glcoords) models::apply_orientation(g[1], g[0]); } else { if(tinf) { // this cannot work after cycled loop_min = 1; loop_max = 0; return; } if(last < first) { reverse(glcoords.begin(), glcoords.end()); reverse(periods.begin(), periods.end()); swap(first, last); } for(int i=0; iV * p->intester; if(is_behind(h1.h)) { if(sphere) { for(int i=0; i<3; i++) h1[i] = -h1[i]; poly_flags &= ~POLY_CENTERIN; } else nofill = true; } applymodel(h1, hscr); hscr[0] *= current_display->radius; hscr[1] *= current_display->radius * pconf.stretch; for(int i=0; iV.T)) - 1) > 1e-6) nofill = true; /* nofill = true; outline = (flags & POLY_CENTERIN) ? 0x00FF00FF : 0xFF0000FF; addpoint(hscr); */ } /* if(poly_flags & POLY_BADCENTERIN) { glcoords.push_back(glhr::makevertex(hscr[0]+10, hscr[1]*pconf.stretch, hscr[2])); glcoords.push_back(glhr::makevertex(hscr[0], hscr[1]*pconf.stretch+10, hscr[2])); glcoords.push_back(glhr::makevertex(hscr[0]-10, hscr[1]*pconf.stretch, hscr[2])); glcoords.push_back(glhr::makevertex(hscr[0], hscr[1]*pconf.stretch-10, hscr[2])); glcoords.push_back(glhr::makevertex(hscr[0]+10, hscr[1]*pconf.stretch, hscr[2])); } */ } void compute_side_by_area() { double rarea = 0; for(int i=0; i0) poly_flags ^= POLY_INVERSE; } ld get_width(dqi_poly* p) { if((p->flags & POLY_FORCEWIDE) || pmodel == mdPixel) return p->linewidth; else if(p->flags & POLY_PRECISE_WIDE) { ld maxwidth = 0; for(int i=0; icnt; i++) { shiftpoint h1 = p->V * glhr::gltopoint((*p->tab)[p->offset+i]); maxwidth = max(maxwidth, linewidthat(h1)); } return maxwidth * p->linewidth; } else return linewidthat(tC0(p->V)) * p->linewidth; } void debug_this() { } glvertex junk = glhr::makevertex(0,0,1); EX namespace s2xe { int maxgen; bool with_zero; ld minz, maxy, miny; typedef array pt; basic_textureinfo stinf; pt lerp(const pt& h0, const pt& h1, ld x) { pt s; for(int i=0; i<5; i++) s[i] = h0[i] + (h1[i]-h0[i]) * x; return s; } void add2(pt h, int gen) { glcoords.push_back(glhr::pointtogl(point31(sin(h[0]) * (h[1] + 2 * M_PI * gen), cos(h[0]) * (h[1] + 2 * M_PI * gen), h[2]))); stinf.tvertices.push_back(glhr::makevertex(h[3], h[4], 0)); } void addall(pt h0, pt h1, pt h2) { for(int gen=-maxgen; gen <= maxgen; gen++) if(gen || with_zero) { add2(h0, gen); add2(h1, gen); add2(h2, gen); } } void draw_s2xe0(dqi_poly *p); bool to_right(const pt& h2, const pt& h1) { ld x2 = h2[0]; ld x1 = h1[0]; if(x2 < x1) x2 += 2 * M_PI; return x2 >= x2 && x2 <= x1 + M_PI; } EX int qrings = 32; ld seg() { return 2 * M_PI / qrings; } void add_ortho_triangle(pt bl, pt tl, pt br, pt tr) { auto sg = seg(); int s0 = ceil(bl[0] / sg); int s1 = floor(br[0] / sg); pt bat[1000], tat[1000]; bat[0] = bl; tat[0] = tl; int s = 1; for(int i = s0; i <= s1; i++) { ld f = (i*sg-bl[0]) / (br[0]-bl[0]); bat[s] = lerp(bl, br, f); tat[s] = lerp(tl, tr, f); s++; } bat[s] = br; tat[s] = tr; while(s--) { addall(bat[s], bat[s+1], tat[s+1]); addall(bat[s], tat[s+1], tat[s]); } } void add_ordered_triangle(array v) { if(v[1][0] < v[0][0]) v[1][0] += 2 * M_PI; if(v[2][0] < v[1][0]) v[2][0] += 2 * M_PI; if(v[2][0] - v[0][0] < 1e-6) return; ld x = (v[1][0] - v[0][0]) / (v[2][0] - v[0][0]); if(v[2][0] < v[0][0] + M_PI / 4 && maxy < M_PI - M_PI/4 && sightranges[geometry] <= 5) { addall(v[0], v[1], v[2]); return; } auto mv = lerp(v[0], v[2], x); add_ortho_triangle(v[0], v[0], mv, v[1]); add_ortho_triangle(mv, v[1], v[2], v[2]); /* int zl = floor(v[1][0] / seg()); int zr = ceil(v[1][0] / seg()); if(zl < zr && zl * seg > v[0][0] && zr * seg < v[2][0]) { ld fl = (zl*seg-v[0][0]) / (v[2][0]-v[0][0]); ld fr = (zr*seg-v[0][0]) / (v[2][0]-v[0][0]); addall(lerp(v[0], v[2], fl), v[1], lerp(v[0], v[2], fr)); } */ // add_ortho_triangle(v[0], tv[0], v[1], tv[1], v[2], tv[2], v[2], tv[2]); } void add_triangle_around(array v) { ld baseheight = (v[0][1] > M_PI/2) ? M_PI : 0; ld tu = (v[0][3] + v[1][3] + v[2][3]) / 3; ld tv = (v[0][4] + v[1][4] + v[2][4]) / 3; array vhigh; for(int i=0; i<3; i++) { vhigh[i] = v[i]; vhigh[i][1] = baseheight; vhigh[i][3] = tu; vhigh[i][4] = tv; } if(v[1][0] < v[0][0]) v[1][0] = v[1][0] + 2 * M_PI, vhigh[1][0] = vhigh[1][0] + 2 * M_PI; add_ortho_triangle(v[0], vhigh[0], v[1], vhigh[1]); if(v[2][0] < v[1][0]) v[2][0] = v[2][0] + 2 * M_PI, vhigh[2][0] = vhigh[2][0] + 2 * M_PI; add_ortho_triangle(v[1], vhigh[1], v[2], vhigh[2]); if(v[0][0] < v[2][0]) v[0][0] = v[0][0] + 2 * M_PI, vhigh[0][0] = vhigh[0][0] + 2 * M_PI; add_ortho_triangle(v[2], vhigh[2], v[0], vhigh[0]); } void add_s2xe_triangle(array v) { bool r0 = to_right(v[1], v[0]); bool r1 = to_right(v[2], v[1]); bool r2 = to_right(v[0], v[2]); minz = min(abs(v[0][2]), max(abs(v[1][2]), abs(v[2][2]))); auto& s = sightranges[geometry]; maxgen = sqrt(s * s - minz * minz) / (2 * M_PI) + 1; maxy = max(v[0][1], max(v[1][1], v[2][1])); miny = min(v[0][1], min(v[1][1], v[2][1])); with_zero = true; if(maxy < M_PI / 4) { add2(v[0], 0); add2(v[1], 0); add2(v[2], 0); with_zero = false; } rotated: if(r0 && r1 && r2) { add_triangle_around(v); } else if(r0 && r1) { add_ordered_triangle(v); } else if(r2 && !r0 && !r1) { add_ordered_triangle(make_array(v[2], v[1], v[0])); } else if(!r0 && !r1 && !r2) { add_triangle_around(make_array(v[2], v[1], v[0])); } else { tie(r0, r1, r2) = make_tuple(r1, r2, r0); tie(v[0], v[1], v[2]) = make_tuple(v[1], v[2], v[0]); goto rotated; } } #if CAP_GL void draw_s2xe(dqi_poly *p) { if(!p->cnt) return; if(p->flags & POLY_TRIANGLES) { dqi_poly npoly = *p; npoly.offset = 0; npoly.tab = &glcoords; if(p->tinf) { npoly.tinf = p->tinf ? &stinf : NULL; npoly.offset_texture = 0; stinf.texture_id = p->tinf->texture_id; } else { npoly.tinf = NULL; } npoly.V = shiftless(Id); auto& pV = p->V.T; set_width(1); glcoords.clear(); stinf.tvertices.clear(); for(int i=0; icnt; i+=3) { array v; for(int k=0; k<3; k++) { hyperpoint h = pV * glhr::gltopoint( (*p->tab)[p->offset+i+k]); v[k][2] = hypot_d(3, h); auto dp = product_decompose(h); v[k][2] = dp.first; v[k][0] = atan2(h[0], h[1]); v[k][1] = acos_auto_clamp(dp.second[2]); if(p->tinf) { auto& tv = p->tinf->tvertices[p->offset_texture+i+k]; v[k][3] = tv[0]; v[k][4] = tv[1]; } } add_s2xe_triangle(v); } npoly.cnt = isize(glcoords); npoly.gldraw(); } else draw_s2xe0(p); } #endif struct point_data { hyperpoint direction; ld distance; ld z; int bad; }; #if CAP_GL void draw_s2xe0(dqi_poly *p) { if(!p->cnt) return; dqi_poly npoly = *p; npoly.offset = 0; npoly.tab = &glcoords; npoly.V = shiftless(Id); npoly.flags &= ~ (POLY_INVERSE | POLY_FORCE_INVERTED); set_width(1); glcoords.clear(); int maxgen = sightranges[geometry] / (2 * M_PI) + 1; auto crossdot = [&] (const hyperpoint h1, const hyperpoint h2) { return make_pair(h1[0] * h2[1] - h1[1] * h2[0], h1[0] * h2[0] + h1[1] * h2[1]); }; vector pd; for(int i=0; icnt; i++) { hyperpoint h = p->V.T * glhr::gltopoint( (*p->tab)[p->offset+i]); pd.emplace_back(); auto& next = pd.back(); auto dp = product_decompose(h); next.direction = dp.second; next.z = dp.first; // next.tpoint = p->tinf ? p->tinf->tvertices[p->offset+i] : glvertex(); ld hyp = hypot_d(2, next.direction); next.distance = acos_auto_clamp(next.direction[2]); if(hyp == 0) { next.direction = point2(1, 0); } else { next.direction[0] /= hyp; next.direction[1] /= hyp; } if(next.distance < 1e-3) next.bad = 1; else if(next.distance > M_PI - 1e-3) next.bad = 2; else next.bad = 0; } glcoords.resize(p->cnt); for(auto c: pd) if(c.bad == 2) return; bool no_gens = false; for(int i=0; icnt; i++) { auto &c1 = pd[i]; auto &c0 = pd[i==0?p->cnt-1 : i-1]; if(c1.distance > M_PI/2 && c0.distance > M_PI/2 && crossdot(c0.direction, c1.direction).second < 0) return; if(c1.bad == 2) return; if(c1.bad == 1) no_gens = true; } if(!no_gens) { vector angles(p->cnt); for(int i=0; icnt; i++) { angles[i] = atan2(pd[i].direction[1], pd[i].direction[0]); } sort(angles.begin(), angles.end()); angles.push_back(angles[0] + 2 * M_PI); bool ok = false; for(int i=1; i= angles[i-1] + M_PI) ok = true; if(!ok) { for(auto &c: pd) if(c.distance > M_PI/2) return; no_gens = true; } } int g = no_gens ? 0 : maxgen; for(int gen=-g; gen<=g; gen++) { for(int i=0; icnt; i++) { auto& cur = pd[i]; ld d = cur.distance + 2 * M_PI * gen; hyperpoint h; h[0] = cur.direction[0] * d; h[1] = cur.direction[1] * d; h[2] = cur.z; glcoords[i] = glhr::pointtogl(h); } npoly.gldraw(); } } #endif EX } EX int berger_limit = 2; void draw_stretch(dqi_poly *p) { dqi_poly npoly = *p; npoly.offset = 0; npoly.tab = &glcoords; npoly.V = shiftless(Id); npoly.flags &= ~(POLY_INVERSE | POLY_FORCE_INVERTED); transmatrix T2 = stretch::translate( tC0(inverse(View)) ); transmatrix U = View * T2; transmatrix iUV = inverse(U) * p->V.T; vector hs; vector ths; hs.resize(p->cnt); ths.resize(p->cnt); for(int i=0; icnt; i++) hs[i] = iUV * glhr::gltopoint( (*p->tab)[p->offset+i] ); vector > results; results.resize(p->cnt); auto& stinf = s2xe::stinf; if(p->tinf) { npoly.tinf = &stinf; npoly.offset_texture = 0; stinf.texture_id = p->tinf->texture_id; stinf.tvertices.clear(); } else { npoly.tinf = NULL; } npoly.V = shiftless(Id); set_width(1); glcoords.clear(); for(int i=0; icnt; i++) results[i] = stretch::inverse_exp_all(hs[i], berger_limit); auto test = [] (hyperpoint a, hyperpoint b) -> bool { return sqhypot_d(3, a-b) < 2; }; if(p->flags & POLY_TRIANGLES) { for(int i=0; icnt; i+=3) { auto &la = results[i]; auto &lb = results[i+1]; auto &lc = results[i+2]; int ia = 0, ib = 0, ic = 0; for(auto& ha: la) for(auto& hb: lb) if(test(ha, hb)) for(auto& hc: lc) if(test(ha, hc) && test(hb, hc)) { glcoords.push_back(glhr::pointtogl(U * ha)); glcoords.push_back(glhr::pointtogl(U * hb)); glcoords.push_back(glhr::pointtogl(U * hc)); if(p->tinf) for(int j=0; j<3; j++) stinf.tvertices.push_back(p->tinf->tvertices[p->offset_texture+i+j]); ia++; ib++; ic++; } } npoly.cnt = isize(glcoords); npoly.gldraw(); } else if(p->cnt) { for(auto& ha: results[0]) { vector has; has.push_back(ha); glcoords.push_back(glhr::pointtogl(U * ha)); for(int i=1; icnt; i++) { hyperpoint best = C0; ld dist = 10; for(auto& hb: results[i]) { ld d = sqhypot_d(3, hb-has.back()); if(d < dist) dist = d, best = hb; } if(dist < 2) has.push_back(best); } if(isize(has) < 3) continue; glcoords.clear(); for(auto& h: has) glcoords.push_back(glhr::pointtogl(U * h)); npoly.cnt = isize(glcoords); npoly.gldraw(); } } } EX namespace ods { #if CAP_ODS EX bool project(hyperpoint h, hyperpoint& h1, hyperpoint& h2, bool eye) { ld tanalpha = tan_auto(vid.ipd/2); if(eye) tanalpha = -tanalpha; if(!sphere) tanalpha = -tanalpha; ld& x = h[0]; ld z = -h[1]; ld y = -h[2]; ld& t = h[3]; ld y02 = (x*x + y*y - tanalpha*tanalpha*t*t); if(y02 < 0) return false; ld y0 = sqrt(y02); ld theta = atan(z / y0); for(int i=0; i<2; i++) { hyperpoint& h = (i ? h1 : h2); if(i == 1) theta = -theta, y0 = -y0; ld x0 = t * tanalpha; ld phi = atan2(y, x) - atan2(y0, x0) + M_PI; ld delta; if(euclid) delta = hypot(y0, z); else if(sphere) delta = atan2_auto(z / sin(theta), t / cos_auto(vid.ipd/2)); else { // ld delta = euclid ? hypot(y0,z) : atan2_auto(z / sin(theta), t / cos_auto(vid.ipd/2)); ld p = z / sin(theta) / t * cos_auto(vid.ipd / 2); delta = (p > 1) ? 13 : (p < -1) ? -13 : atanh(p); } if(euclid || hyperbolic) phi -= M_PI; if(hyperbolic) delta = -delta; h[0] = phi; h[1] = theta; h[2] = delta; if(euclid || hyperbolic) h[1] = -theta; } return true; } void draw_ods(dqi_poly *p) { auto& stinf = s2xe::stinf; if(!p->cnt) return; if(!(p->flags & POLY_TRIANGLES)) return; dqi_poly npoly = *p; npoly.offset = 0; npoly.tab = &glcoords; npoly.V = Id; npoly.tinf = p->tinf ? &stinf : NULL; if(npoly.tinf) { npoly.offset_texture = 0; stinf.texture_id = p->tinf->texture_id; stinf.tvertices.clear(); } npoly.V = Id; glcoords.clear(); array h; if(0) for(int i=0; icnt; i+=3) { for(int j=0; j<3; j++) h[j] = p->V * glhr::gltopoint((*p->tab)[p->offset+i+j]); for(int j=0; j<3; j++) { glcoords.push_back(glhr::makevertex(h[j][0], h[j][1], h[j][2])); if(npoly.tinf) stinf.tvertices.push_back(p->tinf->tvertices[i+j]); } } if(1) for(int i=0; icnt; i+=3) { for(int j=0; j<3; j++) { hyperpoint o = p->V * glhr::gltopoint((*p->tab)[p->offset+i+j]); if(nonisotropic || prod) { o = lp_apply(inverse_exp(o, iTable, false)); o[3] = 1; dynamicval g(geometry, gEuclid); if(!project(o, h[j], h[j+3], global_projection == -1)) goto next_i; } else if(!project(o, h[j], h[j+3], global_projection == -1)) goto next_i; } for(int j=0; j<6; j++) { // let Delta be from 0 to 2PI if(h[j][2]<0) h[j][2] += 2 * M_PI; // Theta is from -PI/2 to PI/2. Let it be from 0 to PI h[j][1] += global_projection * M_PI/2; h[j][3] = 1; } /* natsph here */ if(h[0][2] < 0) swap(h[0], h[3]); if(h[1][2] < 0) swap(h[1], h[4]); if(h[2][2] < 0) swap(h[2], h[5]); cyclefix(h[0][0], 0); cyclefix(h[1][0], h[0][0]); cyclefix(h[2][0], h[0][0]); cyclefix(h[3][0], 0); cyclefix(h[4][0], h[3][0]); cyclefix(h[5][0], h[3][0]); if(abs(h[1][1] - h[0][1]) > M_PI/2) goto next_i; if(abs(h[2][1] - h[0][1]) > M_PI/2) goto next_i; if(h[0][0] < -M_PI || h[0][0] > M_PI) println(hlog, h[0][0]); if(1) { int fst = 0, lst = 0; if(h[1][0] < -M_PI || h[2][0] < -M_PI) lst++; if(h[1][0] > +M_PI || h[2][0] > +M_PI) fst--; for(int x=fst; x<=lst; x++) for(int j=0; j<3; j++) { glcoords.push_back(glhr::makevertex(h[j][0] + 2 * M_PI * x, h[j][1], h[j][2])); if(npoly.tinf) stinf.tvertices.push_back(p->tinf->tvertices[p->offset_texture+i+j]); } } /* natsph here */ next_i: ; } npoly.cnt = isize(glcoords); // npoly.color = 0xFFFFFFFF; npoly.gldraw(); } #endif EX } void dqi_poly::draw() { if(flags & POLY_DEBUG) debug_this(); if(debugflags & DF_VERTEX) { println(hlog, tie(V, offset, cnt, offset_texture, outline, linewidth, flags, intester, cache), (cell*) tinf); for(int i=0; iset_all(global_projection, 0), (get_shader_flags() & SF_DIRECT))) { s2xe::draw_s2xe(this); return; } #endif if(!hyperbolic && among(pmodel, mdPolygonal, mdPolynomial)) { bool any = false; for(int i=0; i 0) any = true; } if(!any) return; } if(sphere && tinf && GDIM == 2 && cnt > 3) { int i = cnt; cnt = 3; for(int j=0; j phases[MAX_PHASE]; extern int twopoint_sphere_flips; extern bool twopoint_do_flips; int pha; if(twopoint_do_flips) { for(int i=0; iradius); h[1] *= pconf.stretch; if(i == 0) phases[j].push_back(h); else { int best = -1; ld bhypot = 1e60; for(int j0=0; j0radius)); // check if the i-th edge intersects the boundary of the ellipse // (which corresponds to the segment between the antipodes of foci) // if yes, switch cpha to the opposite shiftpoint h2 = V * glhr::gltopoint((*tab)[offset+(i+1)%cnt]); hyperpoint ah1 = h1.h, ah2 = h2.h; models::apply_orientation(ah1[0], ah1[1]); models::apply_orientation(ah2[0], ah2[1]); if(ah1[1] * ah2[1] > 0) continue; ld c1 = ah1[1], c2 = -ah2[1]; if(c1 < 0) c1 = -c1, c2 = -c2; hyperpoint h = ah1 * c1 + ah2 * c2; h /= hypot_d(3, h); if(h[2] < 0 && abs(h[0]) < sin(pconf.twopoint_param)) cpha = 1-cpha, pha = 2; } if(cpha == 1) pha = 0; } } dynamicval d1(pmodel, mdPixel); dynamicval d2(V.T, Id); dynamicval d3(offset, 0); dynamicval d4(tab, tab); for(int j=0; j d5(cnt, isize(phases[j])); tab = &phases[j]; draw(); } return; } /* if(spherespecial && prio == PPR::MOBILE_ARROW) { if(spherephase == 0) return; dynamicval ss(spherespecial, 0); draw(); return; } */ if(vid.usingGL && (current_display->set_all(global_projection, V.shift), get_shader_flags() & SF_DIRECT) && sphere && (stretch::factor || ray::in_use)) { draw_stretch(this); return; } #if CAP_GL if(vid.usingGL && (current_display->set_all(global_projection, V.shift), get_shader_flags() & SF_DIRECT)) { if(sl2 && pmodel == mdGeodesic && hybrid::csteps) { ld z = atan2(V.T[2][3], V.T[3][3]) + V.shift; auto zr = sightranges[geometry]; ld ns = stretch::not_squared(); ld db = cgi.psl_steps / M_PI / ns / hybrid::csteps; min_slr = floor((-zr - z) * db); max_slr = ceil((zr - z) * db); if(min_slr > max_slr) return; if(flags & POLY_ONE_LEVEL) min_slr = max_slr = 0; max_slr++; } else min_slr = 0, max_slr = 1; set_width(get_width(this)); flags &= ~POLY_INVERSE; gldraw(); return; } #endif glcoords.clear(); poly_flags = flags; double d = 0, curradius = 0; if(sphere) { d = det(V.T); curradius = pow(abs(d), 1/3.); } /* outline = 0x80808080; color = 0; */ last_infront = false; addpoly(V, *tab, offset, cnt); if(!(sphere && pconf.alpha < .9)) if(pmodel != mdJoukowsky) if(!(flags & POLY_ALWAYS_IN)) for(int i=1; i vid.xres * 2 || dy > vid.yres * 2) return; } if(poly_flags & POLY_BEHIND) return; if(isize(glcoords) <= 1) return; cyl::loop_min = cyl::loop_max = 0; if(sphere && mdBandAny()) cyl::adjust(tinf); int poly_limit = max(vid.xres, vid.yres) * 2; if(0) for(auto& p: glcoords) { if(abs(p[0]) > poly_limit || abs(p[1]) > poly_limit) return; // too large! } bool equi = mdAzimuthalEqui() || pmodel == mdFisheye; bool nofill = false; if(poly_flags & POLY_NIF_ERROR) return; if(spherespecial == 1 && sphere && (poly_flags & POLY_INFRONT) && (poly_flags & POLY_NOTINFRONT) && pconf.alpha <= 1) { bool around_center = false; for(int i=0; i 0 || equi)) can_have_inverse = true; if(pmodel == mdJoukowsky) can_have_inverse = true; if(pmodel == mdJoukowskyInverted && pconf.skiprope) can_have_inverse = true; if(pmodel == mdDisk && hyperbolic && pconf.alpha <= -1) can_have_inverse = true; if(pmodel == mdSpiral && pconf.skiprope) can_have_inverse = true; if(pmodel == mdCentralInversion) can_have_inverse = true; if(can_have_inverse && !(poly_flags & POLY_ISSIDE)) { if(!tinf) compute_side_by_centerin(this, nofill); else { if(d < 0) poly_flags ^= POLY_INVERSE; if(pmodel == mdCentralInversion) poly_flags ^= POLY_INVERSE; compute_side_by_area(); } if(poly_flags & POLY_INVERSE) { if(curradius < pconf.alpha - 1e-6) return; if(!sphere) return; } } else poly_flags &=~ POLY_INVERSE; if(spherespecial) { if(!hiliteclick && !(poly_flags & POLY_INFRONT)) return; } int lastl = 0; for(int l=cyl::loop_min; l <= cyl::loop_max; l++) { if(l || lastl) { for(int i=0; iradius * sin(a), current_display->radius * pconf.stretch * cos(a), 0)); } poly_flags ^= POLY_INVERSE; } else { // If we are on a zlevel, the algorithm above will not work correctly. // It is hard to tell what to do in this case. Just fill neither side nofill = true; } } #if CAP_GL if(vid.usingGL) { poly_flags &= ~(POLY_VCONVEX | POLY_CCONVEX); // if(pmodel == 0) for(int i=0; iscrdist; if(tinf && (poly_flags & POLY_INVERSE)) { return; } set_width(get_width(this)); dqi_poly npoly = (*this); npoly.V = shiftless(Id, V.shift); npoly.tab = &glcoords; npoly.offset = 0; npoly.cnt = isize(glcoords); if(nofill) npoly.color = 0, npoly.tinf = NULL; npoly.flags = poly_flags; npoly.gldraw(); continue; } #endif #if CAP_SVG if(svg::in) { coords_to_poly(); color_t col = color; if(poly_flags & POLY_INVERSE) col = 0; if(poly_flags & POLY_TRIANGLES) { for(int i=0; itvertices[offset_texture + i], color); #endif } else if(poly_flags & POLY_INVERSE) { int i = polyi; if(true) { polyx[i] = 0; polyy[i] = 0; i++; polyx[i] = vid.xres; polyy[i] = 0; i++; polyx[i] = vid.xres; polyy[i] = vid.yres; i++; polyx[i] = 0; polyy[i] = vid.yres; i++; polyx[i] = 0; polyy[i] = 0; i++; } filledPolygonColorI(s, polyx, polyy, polyi+5, color); } else if(poly_flags & POLY_TRIANGLES) { for(int i=0; istereo_active()) filledPolygonColorI(aux, polyxr, polyy, polyi, color); ((vid.antialias & AA_NOGL) ?aapolylineColor:polylineColor)(s, polyx, polyy, polyi, outline); if(current_display->stereo_active()) aapolylineColor(aux, polyxr, polyy, polyi, outline); if(vid.xres >= 2000 || fatborder) { int xmi = 3000, xma = -3000; for(int t=0; t xmi + 20) for(int x=-1; x<2; x++) for(int y=-1; y<=2; y++) if(x*x+y*y == 1) { for(int t=0; t prettylinepoints; EX void prettypoint(const hyperpoint& h) { prettylinepoints.push_back(glhr::pointtogl(h)); } EX void prettylinesub(const hyperpoint& h1, const hyperpoint& h2, int lev) { if(lev >= 0 && pmodel != mdPixel) { hyperpoint h3 = midz(h1, h2); prettylinesub(h1, h3, lev-1); prettylinesub(h3, h2, lev-1); } else prettypoint(h2); } EX void prettyline(hyperpoint h1, hyperpoint h2, ld shift, color_t col, int lev, int flags, PPR prio) { prettylinepoints.clear(); prettypoint(h1); prettylinesub(h1, h2, lev); dqi_poly ptd; ptd.V = shiftless(Id, shift); ptd.tab = &prettylinepoints; ptd.offset = 0; ptd.cnt = isize(prettylinepoints); ptd.linewidth = vid.linewidth; ptd.color = 0; ptd.outline = col; ptd.flags = POLY_ISSIDE | POLY_PRECISE_WIDE | flags; ptd.tinf = NULL; ptd.intester = C0; ptd.prio = prio; ptd.draw(); } EX void prettypoly(const vector& t, color_t fillcol, color_t linecol, int lev) { prettylinepoints.clear(); prettypoint(t[0]); for(int i=0; i curvedata; int curvestart = 0; bool keep_curvedata = false; EX void queuereset(eModel m, PPR prio) { queueaction(prio, [m] () { glflush(); pmodel = m; }); } void dqi_line::draw() { dynamicval d(vid.linewidth, width); prettyline(H1.h, unshift(H2, H1.shift), H1.shift, color, prf, 0, prio); } void dqi_string::draw() { #if CAP_SVG if(svg::in) { svg::text(x, y, size, str, frame, color, align); return; } #endif #if !ISMOBILE int fr = frame & 255; displayfrSP(x, y, shift, fr, size, str, color, align, frame >> 8); #else displayfr(x, y, frame, size, str, color, align); #endif } void dqi_circle::draw() { #if CAP_SVG if(svg::in) { svg::circle(x, y, size, color, fillcolor, linewidth); } else #endif drawCircle(x, y, size, color, fillcolor); } EX void initquickqueue() { ptds.clear(); poly_outline = OUTLINE_NONE; } EX void sortquickqueue() { for(int i=1; iprio < ptds[i-1]->prio) { swap(ptds[i], ptds[i-1]); i--; } else i++; } EX void quickqueue() { current_display->next_shader_flags = 0; spherespecial = 0; reset_projection(); current_display->set_all(0, 0); int siz = isize(ptds); for(int i=0; idraw(); ptds.clear(); if(!keep_curvedata) { curvedata.clear(); curvestart = 0; } } /* todo */ ld xintval(const shiftpoint& h) { if(sphereflipped()) return -h.h[2]; if(hyperbolic) return -h.h[2]; return -intval(h.h, C0); } EX ld backbrightness = .25; EX purehookset hooks_drawqueue; constexpr int PMAX = int(PPR::MAX); int qp[PMAX], qp0[PMAX]; color_t darken_color(color_t& color, bool outline) { int alpha = color & 255; if(sphere && pmodel == mdDisk && pconf.alpha <= 1) return 0; else { if(outline && alpha < 255) return color - alpha + int(backbrightness * alpha); else return (gradient(modelcolor>>8, color>>8, 0, backbrightness, 1)<<8) | 0xFF; } } void dqi_poly::draw_back() { dynamicval dvo(outline, darken_color(outline, true)); dynamicval dvc(color, darken_color(color, false)); draw(); } void dqi_line::draw_back() { dynamicval dvc(color, darken_color(color, true)); draw(); } EX void sort_drawqueue() { DEBBI(DF_GRAPH, ("sort_drawqueue")); for(int a=0; a>> subqueue; for(auto& p: ptds) subqueue[(p->prio == PPR::CIRCLE || p->prio == PPR::OUTCIRCLE) ? 0 : p->outline_group()].push_back(move(p)); ptds.clear(); for(auto& p: subqueue) for(auto& r: p.second) ptds.push_back(move(r)); subqueue.clear(); for(auto& p: ptds) subqueue[(p->prio == PPR::CIRCLE || p->prio == PPR::OUTCIRCLE) ? 0 : p->color].push_back(move(p)); ptds.clear(); for(auto& p: subqueue) for(auto& r: p.second) ptds.push_back(move(r)); #endif for(auto& p: ptds) { int pd = p->prio - PPR::ZERO; if(pd < 0 || pd >= PMAX) { printf("Illegal priority %d\n", pd); p->prio = PPR(rand() % int(PPR::MAX)); } qp[pd]++; } int total = 0; for(int a=0; a> ptds2; ptds2.resize(siz); for(int i = 0; iprio)]++] = move(ptds[i]); swap(ptds, ptds2); } EX void reverse_priority(PPR p) { reverse(ptds.begin()+qp0[int(p)], ptds.begin()+qp[int(p)]); } EX void reverse_side_priorities() { for(PPR p: {PPR::REDWALLs, PPR::REDWALLs2, PPR::REDWALLs3, PPR::WALL3s, PPR::LAKEWALL, PPR::INLAKEWALL, PPR::BELOWBOTTOM, PPR::BSHALLOW, PPR::ASHALLOW}) reverse_priority(p); } // on the sphere, parts on the back are drawn first EX void draw_backside() { DEBBI(DF_GRAPH, ("draw_backside")); if(pmodel == mdHyperboloid && hyperbolic) { dynamicval dv (pmodel, mdHyperboloidFlat); for(auto& ptd: ptds) if(!among(ptd->prio, PPR::MOBILE_ARROW, PPR::OUTCIRCLE, PPR::CIRCLE)) ptd->draw(); } spherespecial = sphereflipped() ? 1 : -1; reset_projection(); if(pmodel == mdRotatedHyperboles) { for(auto& ptd: ptds) if(!among(ptd->prio, PPR::MOBILE_ARROW, PPR::OUTCIRCLE, PPR::CIRCLE)) ptd->draw(); glflush(); } else { reverse_side_priorities(); for(int i=isize(ptds)-1; i>=0; i--) if(!among(ptds[i]->prio, PPR::MOBILE_ARROW, PPR::OUTCIRCLE, PPR::CIRCLE)) ptds[i]->draw_back(); glflush(); reverse_side_priorities(); } spherespecial *= -1; spherephase = 1; reset_projection(); } extern bool lshiftclick, lctrlclick; EX void reverse_transparent_walls() { int pt = int(PPR::TRANSPARENT_WALL); reverse(&ptds[qp0[pt]], &ptds[qp[pt]]); } EX void draw_main() { DEBBI(DF_GRAPH, ("draw_main")); if(sphere && GDIM == 3 && pmodel == mdPerspective && !stretch::in() && !ray::in_use) { if(ray::in_use && !ray::comparison_mode) { ray::cast(); reset_projection(); } #if CAP_GL for(int p: {1, 0, 2, 3}) { if(elliptic && p < 2) continue; glhr::set_depthwrite(true); if(p == 0 || p == 3) { #ifdef GL_ES glClearDepthf(1.0f); #else glClearDepth(1.0f); #endif glDepthFunc(GL_LEQUAL); } else { #ifdef GL_ES glClearDepthf(0.0f); #else glClearDepth(0.0f); #endif glDepthFunc(GL_GEQUAL); } glClear(GL_DEPTH_BUFFER_BIT); glhr::be_nontextured(); spherephase = p; reset_projection(); for(auto& ptd: ptds) ptd->draw(); if(elliptic) { spherephase = p | 4; reset_projection(); for(auto& ptd: ptds) ptd->draw(); } // glflush(); } #endif } else { DEBB(DF_GRAPH, ("draw_main1")); if(ray::in_use && !ray::comparison_mode) { ray::cast(); reset_projection(); } DEBB(DF_GRAPH, ("outcircle")); for(auto& ptd: ptds) if(ptd->prio == PPR::OUTCIRCLE) ptd->draw(); if(two_sided_model()) draw_backside(); for(auto& ptd: ptds) if(ptd->prio != PPR::OUTCIRCLE) { DEBBI(DF_VERTEX, ("prio: ", int(ptd->prio), " color ", ptd->color)); dynamicval ss(spherespecial, among(ptd->prio, PPR::MOBILE_ARROW, PPR::OUTCIRCLE, PPR::CIRCLE) ? 0 : spherespecial); ptd->draw(); } glflush(); #if CAP_RAY if(ray::in_use && ray::comparison_mode) { glDepthFunc(GL_LEQUAL); #ifdef GLES_ONLY glClearDepthf(1.0f); #else glClearDepth(1.0f); #endif glClear(GL_DEPTH_BUFFER_BIT); ray::cast(); } #endif } } #if CAP_VR EX hookset hooks_vr_draw_all; #endif EX void drawqueue() { DEBBI(DF_GRAPH, ("drawqueue")); #if CAP_WRL if(wrl::in) { wrl::render(); return; } #endif callhooks(hooks_drawqueue); current_display->next_shader_flags = 0; reset_projection(); // reset_projection() is not sufficient here, because we need to know shaderside_projection #if CAP_GL if(vid.usingGL) glClear(GL_STENCIL_BUFFER_BIT); #endif profile_start(3); sort_drawqueue(); DEBB(DF_GRAPH, ("sort walls")); if(GDIM == 2) for(PPR p: {PPR::REDWALLs, PPR::REDWALLs2, PPR::REDWALLs3, PPR::WALL3s, PPR::LAKEWALL, PPR::INLAKEWALL, PPR::BELOWBOTTOM, PPR::ASHALLOW, PPR::BSHALLOW}) { int pp = int(p); if(qp0[pp] == qp[pp]) continue; for(int i=qp0[pp]; i& p1, const unique_ptr& p2) { auto ap1 = (dqi_poly&) *p1; auto ap2 = (dqi_poly&) *p2; return ap1.cache < ap2.cache; }); } for(PPR p: {PPR::TRANSPARENT_WALL}) { int pp = int(p); if(qp0[pp] == qp[pp]) continue; sort(&ptds[qp0[int(p)]], &ptds[qp[int(p)]], [] (const unique_ptr& p1, const unique_ptr& p2) { return p1->subprio > p2->subprio; }); } profile_stop(3); #if CAP_SDL if(current_display->stereo_active() && !vid.usingGL) { if(aux && (aux->w != s->w || aux->h != s->h)) SDL_FreeSurface(aux); if(!aux) { aux = SDL_CreateRGBSurface(SDL_SWSURFACE,s->w,s->h,32,0,0,0,0); } // SDL_LockSurface(aux); // memset(aux->pixels, 0, vid.xres * vid.yres * 4); // SDL_UnlockSurface(aux); SDL_BlitSurface(s, NULL, aux, NULL); } #endif spherespecial = 0; spherephase = 0; reset_projection(); #if CAP_VR if(callhandlers(false, hooks_vr)) {} else #endif #if CAP_GL if(model_needs_depth() && current_display->stereo_active()) { global_projection = -1; draw_main(); #if CAP_GL glClear(GL_DEPTH_BUFFER_BIT); #endif global_projection = +1; draw_main(); global_projection = 0; } else #endif { draw_main(); } #if CAP_SDL if(vid.stereo_mode == sAnaglyph && !vid.usingGL) { int qty = s->w * s->h; int *a = (int*) s->pixels; int *b = (int*) aux->pixels; SDL_LockSurface(aux); while(qty) { *a = ((*a) & 0xFF0000) | ((*b) & 0x00FFFF); a++; b++; qty--; } SDL_UnlockSurface(aux); } if(vid.stereo_mode == sLR && !vid.usingGL) { SDL_LockSurface(aux); for(int y=0; y T& queuea(PPR prio, U... u) { ptds.push_back(unique_ptr(new T (u...))); ptds.back()->prio = prio; return (T&) *ptds.back(); } #endif /** colorblind mode */ EX bool cblind; #if HDR enum class eNeon { none, neon, no_boundary, neon2, illustration}; #endif EX eNeon neon_mode; EX bool neon_nofill; EX void apply_neon(color_t& col, int& r) { switch(neon_mode) { case eNeon::none: case eNeon::illustration: break; case eNeon::neon: poly_outline = col << 8; col = 0; break; case eNeon::no_boundary: r = 0; break; case eNeon::neon2: poly_outline = col << 8; col &= 0xFEFEFE; col >>= 1; break; } } #if CAP_SHAPES /** used when neon_mode is eNeon::illustration */ EX color_t magentize(color_t x) { if(neon_mode != eNeon::illustration) return x; int green = part(x,2); int magenta = (part(x, 1) + part(x, 3)) / 2; int nm = max(magenta, green); int gm = (magenta + green)/2; nm = (nm + 255) / 2; gm = gm / 2; return (nm * 0x1000100) | (gm * 0x10000) | (part(x, 0)); } EX color_t monochromatize(color_t x) { int c = part(x,2) + part(x,1) + part(x, 3); c ++; c /= 3; return c * 0x1010100 | (part(x, 0)); } EX dqi_poly& queuepolyat(const shiftmatrix& V, const hpcshape& h, color_t col, PPR prio) { if(prio == PPR::DEFAULT) prio = h.prio; auto& ptd = queuea (prio); ptd.V = V; ptd.offset = h.s; ptd.cnt = h.e-h.s; ptd.tab = &cgi.ourshape; if(cblind) { // protanopia /* int r = (56 * part(col,3) + 43 * part(col,2)) / 100; int g = (58 * part(col,3) + 42 * part(col,2)) / 100; int b = (24 * part(col,2) + 75 * part(col,1)) / 100; */ // deuteranopia /* int r = (625 * part(col,3) + 375 * part(col,2)) / 1000; int g = (700 * part(col,3) + 300 * part(col,2)) / 1000; int b = (300 * part(col,2) + 700 * part(col,1)) / 1000; part(col,3) = r; part(col,2) = g; part(col,1) = b; */ part(col,2) = part(col,3) = (part(col,2) * 2 + part(col,3) + 1)/3; } if(neon_mode == eNeon::none) { ptd.color = (darkened(col >> 8) << 8) + (col & 0xFF); ptd.outline = poly_outline; if(h.flags & POLY_TRIANGLES) ptd.outline = 0; } else switch(neon_mode) { case eNeon::neon: ptd.color = (poly_outline & 0xFFFFFF00) | (col & 0xFF); ptd.outline = (darkened(col >> 8) << 8) | (col & 0xFF); if(col == 0xFF) ptd.outline = 0xFFFFFFFF; if(neon_nofill && ptd.color == 0xFF) ptd.color = 0; break; case eNeon::no_boundary: ptd.color = (darkened(col >> 8) << 8) + (col & 0xFF); ptd.outline = 0; break; case eNeon::neon2: ptd.color = (darkened(col >> 8) << 8) + (col & 0xFF) + ((col & 0xFF) >> 2); ptd.outline = (darkened(col >> 8) << 8) + (col & 0xFF); if(col == 0xFF) ptd.outline = 0xFFFFFFFF; if(poly_outline != 0xFF) ptd.outline = poly_outline; if(neon_nofill && ptd.color == 0xFF) ptd.color = 0; break; case eNeon::illustration: { if(poly_outline && (poly_outline>>8) != bordcolor) { ptd.color = magentize(col); ptd.outline = 0xFF; } else { ptd.outline = poly_outline; ptd.color = monochromatize(col); } if(ptd.color & 0xFF) ptd.color |= 0xFF; if(ptd.outline & 0xFF) ptd.outline |= 0xFF; break; } case eNeon::none: ; } ptd.linewidth = vid.linewidth; ptd.flags = h.flags; ptd.tinf = h.tinf; if(neon_mode != eNeon::none && (h.flags & POLY_TRIANGLES)) ptd.tinf = nullptr; ptd.offset_texture = h.texture_offset; ptd.intester = h.intester; return ptd; } #endif EX dqi_poly& queuetable(const shiftmatrix& V, const vector& f, int cnt, color_t linecol, color_t fillcol, PPR prio) { auto& ptd = queuea (prio); ptd.V = V; ptd.tab = &f; ptd.offset = 0; ptd.cnt = cnt; ptd.color = fillcol; ptd.outline = linecol; ptd.linewidth = vid.linewidth; ptd.flags = POLY_ISSIDE | POLY_PRECISE_WIDE; ptd.tinf = NULL; ptd.intester = C0; return ptd; } #if CAP_SHAPES EX dqi_poly& queuepoly(const shiftmatrix& V, const hpcshape& h, color_t col) { return queuepolyat(V,h,col,h.prio); } void queuepolyb(const shiftmatrix& V, const hpcshape& h, color_t col, int b) { queuepolyat(V,h,col,h.prio+b); } #endif EX void curvepoint(const hyperpoint& H1) { curvedata.push_back(glhr::pointtogl(H1)); } EX dqi_poly& queuecurve(const shiftmatrix& V, color_t linecol, color_t fillcol, PPR prio) { auto &res = queuetable(V, curvedata, isize(curvedata)-curvestart, linecol, fillcol, prio); res.offset = curvestart; curvestart = isize(curvedata); return res; } EX dqi_action& queueaction(PPR prio, const reaction_t& action) { return queuea (prio, action); } EX dqi_line& queueline(const shiftpoint& H1, const shiftpoint& H2, color_t col, int prf IS(0), PPR prio IS(PPR::LINE)) { auto& ptd = queuea (prio); ptd.H1 = H1; ptd.H2 = H2; ptd.prf = prf; ptd.width = vid.linewidth; ptd.color = (darkened(col >> 8) << 8) + (col & 0xFF); return ptd; } EX void queuestr(int x, int y, int shift, int size, string str, color_t col, int frame IS(0), int align IS(8)) { auto& ptd = queuea (PPR::TEXT); ptd.x = x; ptd.y = y; ptd.str = str; ptd.align = align; ptd.shift = shift; ptd.size = size; ptd.color = darkened(col); ptd.frame = frame ? ((poly_outline & ~ 255)+frame) : 0; } EX void queuecircle(int x, int y, int size, color_t color, PPR prio IS(PPR::CIRCLE), color_t fillcolor IS(0)) { auto& ptd = queuea(prio); ptd.x = x; ptd.y = y; ptd.size = size; ptd.color = color; ptd.fillcolor = fillcolor; ptd.linewidth = vid.linewidth; } EX void getcoord0(const shiftpoint& h, int& xc, int &yc, int &sc) { hyperpoint hscr; applymodel(h, hscr); xc = current_display->xcenter + current_display->radius * hscr[0]; yc = current_display->ycenter + current_display->radius * pconf.stretch * hscr[1]; sc = 0; // EYETODO sc = vid.eye * current_display->radius * hscr[2]; } EX ld scale_in_pixels(const shiftmatrix& V) { return scale_at(V) * cgi.scalefactor * current_display->radius / 2.5; } EX bool getcoord0_checked(const shiftpoint& h, int& xc, int &yc, int &zc) { if(invalid_point(h)) return false; if(point_behind(h)) return false; getcoord0(h, xc, yc, zc); return true; } EX void queuestr(const shiftpoint& h, int size, const string& chr, color_t col, int frame IS(0)) { int xc, yc, sc; if(getcoord0_checked(h, xc, yc, sc)) queuestr(xc, yc, sc, size, chr, col, frame); } EX void queuestr(const shiftmatrix& V, double size, const string& chr, color_t col, int frame IS(0), int align IS(8)) { int xc, yc, sc; if(getcoord0_checked(tC0(V), xc, yc, sc)) queuestr(xc, yc, sc, scale_in_pixels(V) * size, chr, col, frame, align); } EX void queuestrn(const shiftmatrix& V, double size, const string& chr, color_t col, int frame IS(0), int align IS(8)) { switch(neon_mode) { case eNeon::none: queuestr(V, size, chr, col, frame, align); break; case eNeon::neon: { dynamicval c(poly_outline, col << 8); queuestr(V, size, chr, 0, frame, align); break; } case eNeon::no_boundary: { queuestr(V, size, chr, col, 0, align); break; } case eNeon::neon2: { dynamicval c(poly_outline, (col << 8) | 0xFF); queuestr(V, size, chr, (col & 0xFEFEFE) >> 1, frame, align); break; } case eNeon::illustration: { dynamicval c(poly_outline, poly_outline); if(poly_outline && (poly_outline>>8) != bordcolor) { col = magentize(col << 8) >> 8; poly_outline = 0xFF; } else { col = monochromatize(col << 8) >> 8; } queuestr(V, size, chr, col, frame, align); } } } EX void queuecircle(const shiftmatrix& V, double size, color_t col) { int xc, yc, sc; if(!getcoord0_checked(tC0(V), xc, yc, sc)) return; int xs, ys, ss; getcoord0(V * xpush0(.01), xs, ys, ss); queuecircle(xc, yc, scale_in_pixels(V) * size, col); } #endif }