// 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. */ 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 struct drawqueueitem { PPR prio; color_t color; int subprio; virtual void draw() = 0; virtual void draw_back() {} virtual void draw_pre() {} virtual ~drawqueueitem() {} void draw_darker(); virtual color_t outline_group() = 0; }; struct dqi_poly : drawqueueitem { ld band_shift; transmatrix V; const vector *tab; int offset, cnt, offset_texture; color_t outline; double linewidth; int flags; struct basic_textureinfo *tinf; hyperpoint intester; void draw(); void gldraw(); void draw_back(); virtual color_t outline_group() { return outline; } }; struct dqi_line : drawqueueitem { ld band_shift; hyperpoint H1, H2; int prf; double width; void draw(); void draw_back(); virtual color_t outline_group() { return color; } }; struct dqi_string : drawqueueitem { string str; int x, y, shift, size, frame; int align; void draw(); virtual color_t outline_group() { return 1; } }; struct dqi_circle : drawqueueitem { int x, y, size, fillcolor; double linewidth; void draw(); virtual color_t outline_group() { return 2; } }; 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 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 color_t text_color; int text_shift; GLuint text_texture; int texts_merged; int shapes_merged; vector text_vertices; #if MINIMIZE_GL_CALLS color_t triangle_color, line_color; vector triangle_vertices; vector line_vertices; void glapplymatrix(const transmatrix& V); #endif EX void 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)); glhr::be_textured(); dynamicval pm(pmodel, mdUnchanged); if(!svg::in) current_display->set_all(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, current_display->scrdist)); 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 ISMOBILE==0 SDL_Surface *aux; #endif #if CAP_POLY #define POLYMAX 60000 EX vector glcoords; #endif int spherespecial, spherephase; #if CAP_POLY int polyi; 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) && vid.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 - vid.alpha // H2[2]- BEHIND_LIMIT + vid.alpha = t * (H2[2] - H1[2]) ld t = (H2[2] - BEHIND_LIMIT + vid.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 && models::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 / vid.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(H, res); return res[2] < 0 ? -1 : 1; } if(pmodel == mdSpiral && models::spiral_cone < 360) { return cone_side(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(good, Hscr); hscr = glhr::makevertex(Hscr[0]*current_display->radius, Hscr[1]*current_display->radius*vid.stretch, Hscr[2]*current_display->radius); } void addpoint(const hyperpoint& H) { if(true) { ld z = current_display->radius; // if(vid.alpha + H[2] <= BEHIND_LIMIT && pmodel == mdDisk) poly_flags |= POLY_BEHIND; if(spherespecial) { if(correct_side(H)) { 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]; } } void addpoly(const transmatrix& V, const vector &tab, int ofs, int cnt) { if(pmodel == mdFlatten) { for(int i=ofs; i(Hscr[0]*current_display->radius+10, Hscr[1]*current_display->radius*vid.stretch, Hscr[2]*vid.radius)); glcoords.push_back(make_array(Hscr[0]*current_display->radius, Hscr[1]*current_display->radius*vid.stretch+10, Hscr[2]*vid.radius)); glcoords.push_back(make_array(Hscr[0]*current_display->radius-10, Hscr[1]*current_display->radius*vid.stretch, Hscr[2]*vid.radius)); glcoords.push_back(make_array(Hscr[0]*current_display->radius, Hscr[1]*current_display->radius*vid.stretch-10, Hscr[2]*vid.radius)); glcoords.push_back(make_array(Hscr[0]*current_display->radius+10, Hscr[1]*current_display->radius*vid.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 = 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 void glapplymatrix(const transmatrix& V) { GLfloat mat[16]; int id = 0; if(prod && pmodel == mdPerspective) { for(int y=0; y<3; y++) { for(int x=0; x<3; x++) mat[id++] = V[x][y]; mat[id++] = 0; } mat[12] = 0; mat[13] = 0; mat[14] = 0; mat[15] = 1; glhr::set_modelview(glhr::as_glmatrix(mat)); return; } if(in_perspective() && MDIM == 4) { if(spherephase & 4) { for(int y=0; y<4; y++) { for(int x=0; x<4; x++) mat[id++] = -V[x][y]; } } else { for(int y=0; y<4; y++) { for(int x=0; x<4; x++) mat[id++] = V[x][y]; } } glhr::set_modelview(glhr::as_glmatrix(mat)); return; } if(MDIM == 4) { for(int y=0; y<4; y++) for(int x=0; x<4; x++) mat[id++] = V[x][y]; } else { for(int y=0; y<3; y++) { for(int x=0; x<3; x++) mat[id++] = V[x][y]; mat[id++] = 0; } mat[12] = 0; mat[13] = 0; if(glhr::current_shader_projection != glhr::shader_projection::band) mat[14] = GLfloat(vid.alpha); else mat[14] = 0; mat[15] = 1; } if(vid.stretch != 1) mat[1] *= vid.stretch, mat[5] *= vid.stretch, mat[9] *= vid.stretch, mat[13] *= vid.stretch; if(models::model_has_orientation()) { if(GDIM == 3) for(int a=0; a<4; a++) models::apply_orientation_yz(mat[a*4+1], mat[a*4+2]); for(int a=0; a<4; a++) models::apply_orientation(mat[a*4], mat[a*4+1]); } glhr::set_modelview(glhr::as_glmatrix(mat)); } EX int global_projection; #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; itexture_id); glhr::vertices_texture(v, tinf->tvertices, offset, offset_texture); ioffset = 0; } else { glhr::be_nontextured(); glhr::vertices(v); } for(int ed = current_display->stereo_active() ? -1 : 0; ed<2; ed+=2) { if(global_projection && global_projection != ed) continue; current_display->set_all(ed); bool draw = color; if(shaderside_projection) { if(glhr::current_shader_projection == glhr::shader_projection::band && V[2][2] > 1e8) continue; glapplymatrix(V); } 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; GLfloat dist = shaderside_projection ? current_display->scrdist : 0; vector scr = { glhr::makevertex(-xx, -yy, dist), glhr::makevertex(+xx, -yy, dist), glhr::makevertex(+xx, +yy, dist), glhr::makevertex(-xx, +yy, dist) }; glhr::vertices(scr); glhr::id_modelview(); glDrawArrays(tinf ? GL_TRIANGLES : GL_TRIANGLE_FAN, 0, 4); glhr::vertices(v); if(shaderside_projection) glapplymatrix(V); } 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 && !(flags & POLY_TRIANGLES)) { 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); glDrawArrays(GL_LINE_STRIP, offset, cnt); } } } #endif EX ld scale_at(const transmatrix& T) { if(GDIM == 3 && pmodel == mdPerspective) return 1 / abs((tC0(T))[2]); if(sol) return 1; hyperpoint h1, h2, h3; applymodel(tC0(T), h1); applymodel(T * xpush0(.01), h2); applymodel(T * ypush(.01) * C0, h3); return sqrt(hypot_d(2, h2-h1) * hypot_d(2, h3-h1) / .0001); } EX ld linewidthat(const hyperpoint& h) { if(!(vid.antialias & AA_LINEWIDTH)) return 1; else if(hyperbolic && pmodel == mdDisk && vid.alpha == 1 && !ISWEB) { double dz = h[LDIM]; if(dz < 1 || abs(dz-current_display->scrdist) < 1e-6) return 1; else { double dx = sqrt(dz * dz - 1); double dfc = dx/(dz+1); dfc = 1 - dfc*dfc; return dfc; } } else if(svg::in || inHighQual) { hyperpoint h0 = h / zlevel(h); transmatrix T = rgpushxto0(h0); return scale_at(T); } return 1; } EX void set_width(ld w) { #if MINIMIZE_GL_CALLS if(w != glhr::current_linewidth) glflush(); #endif glhr::set_linewidth(w); } // 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 * vid.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(vid.collignon_reflected && y > 0) y = -y; y += signed_sqrt(vid.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 = vid.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(vid.collignon_parameter - 1) - signed_sqrt(vid.collignon_parameter)); if(vid.collignon_reflected) dmax = -dmin; else dmax = z * (signed_sqrt(vid.collignon_parameter + 1) - signed_sqrt(vid.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)) { 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 * vid.stretch; for(int i=0; iV)) - 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]*vid.stretch, hscr[2])); glcoords.push_back(glhr::makevertex(hscr[0], hscr[1]*vid.stretch+10, hscr[2])); glcoords.push_back(glhr::makevertex(hscr[0]-10, hscr[1]*vid.stretch, hscr[2])); glcoords.push_back(glhr::makevertex(hscr[0], hscr[1]*vid.stretch-10, hscr[2])); glcoords.push_back(glhr::makevertex(hscr[0]+10, hscr[1]*vid.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 == mdUnchanged) return p->linewidth; else if(p->flags & POLY_PRECISE_WIDE) { ld maxwidth = 0; for(int i=0; icnt; i++) { hyperpoint 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); struct point_data { hyperpoint direction; ld distance; ld z; int bad; }; void draw_s2xe(dqi_poly *p, dqi_poly *npoly) { 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 * glhr::gltopoint( (*p->tab)[p->offset+i]); auto& next = pd.emplace_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); if(p->flags & POLY_TRIANGLES) { for(int i=0; icnt; i+=3) { int nbad = 0; for(int j=i; j= 1) nbad = max(nbad, 0); if(pd[j].bad >= 2) nbad = 2; } int g = (pd[i].distance > M_PI/2 || pd[i+1].distance >= M_PI/2 || pd[i+2].distance >= M_PI/2) ? 2 : 1; auto c1 = crossdot(pd[i+0].direction, pd[i+1].direction); auto c2 = crossdot(pd[i+1].direction, pd[i+2].direction); auto c3 = crossdot(pd[i+2].direction, pd[i+0].direction); if(c1.second < 0 || c2.second < 0 || c3.second < 0) nbad = max(nbad, g); if(c1.first > 0 && c2.first > 0 && c3.first > 0) nbad = max(nbad, g); if(c1.first < 0 && c2.first < 0 && c3.first < 0) nbad = max(nbad, g); pd[i].bad = pd[i+1].bad = pd[i+2].bad = nbad; } for(int gen=-maxgen; gen<=maxgen; gen++) { for(int i=0; icnt; i++) { auto& cur = pd[i]; if(cur.bad >= (gen ? 1 : 2)) glcoords[i] = junk; else { hyperpoint h; ld d = cur.distance + 2 * M_PI * gen; h[0] = cur.direction[0] * d; h[1] = cur.direction[1] * d; h[2] = cur.z; glcoords[i] = glhr::pointtogl(h); } } npoly->gldraw(); } } else { 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(); } } } void dqi_poly::draw() { if(flags & POLY_DEBUG) debug_this(); if(prod && vid.usingGL && pmodel == mdPerspective && (current_display->set_all(global_projection), shaderside_projection) && product::product_sphere()) { auto npoly = *this; npoly.offset = 0; npoly.tab = &glcoords; npoly.V = Id; set_width(1); glcoords.clear(); draw_s2xe(this, &npoly); return; } dynamicval bs(hr::band_shift, band_shift); 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] *= vid.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 hyperpoint h2 = V * glhr::gltopoint((*tab)[offset+(i+1)%cnt]); hyperpoint ah1 = h1, ah2 = h2; 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(vid.twopoint_param)) cpha = 1-cpha, pha = 2; } if(cpha == 1) pha = 0; } } dynamicval d1(pmodel, mdUnchanged); dynamicval d2(V, 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 CAP_GL if(vid.usingGL && (current_display->set_all(global_projection), shaderside_projection)) { 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); curradius = pow(abs(d), 1/3.); } /* outline = 0x80808080; color = 0; */ last_infront = false; addpoly(V, *tab, offset, cnt); if(!(sphere && vid.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) && vid.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 && vid.skiprope) can_have_inverse = true; if(pmodel == mdDisk && hyperbolic && vid.alpha <= -1) can_have_inverse = true; if(pmodel == mdSpiral && vid.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 < vid.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 * vid.stretch * cos(a), current_display->scrdist)); } 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 = Id; 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==1 if(svg::in) { coords_to_poly(); color_t col = color; if(poly_flags & POLY_INVERSE) col = 0; svg::polygon(polyx, polyy, polyi, col, outline, get_width(this)); continue; } #endif coords_to_poly(); #if CAP_XGD==1 gdpush(1); gdpush(color); gdpush(outline); gdpush(polyi); 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 filledPolygonColorI(s, polyx, polyy, polyi, color); if(current_display->stereo_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) { 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, color_t col, int lev, int flags, PPR prio) { prettylinepoints.clear(); prettypoint(h1); prettylinesub(h1, h2, lev); dqi_poly ptd; ptd.V = Id; ptd.band_shift = band_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); dynamicval bs(hr::band_shift, band_shift); prettyline(H1, H2, 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==0 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() { spherespecial = 0; reset_projection(); current_display->set_all(0); int siz = isize(ptds); for(int i=0; idraw(); ptds.clear(); if(!keep_curvedata) { curvedata.clear(); curvestart = 0; } } ld xintval(const hyperpoint& h) { if(sphereflipped()) return -h[2]; return -intval(h, C0); } ld backbrightness = .25; purehookset hook_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 && vid.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() { 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}) reverse_priority(p); } // on the sphere, parts on the back are drawn first EX void 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=ptds.size()-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() { if(sphere && GDIM == 3 && pmodel == mdPerspective) { 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(); } } else { 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) { dynamicval ss(spherespecial, among(ptd->prio, PPR::MOBILE_ARROW, PPR::OUTCIRCLE, PPR::CIRCLE) ? 0 : spherespecial); ptd->draw(); } glflush(); } } EX void drawqueue() { callhooks(hook_drawqueue); 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(); for(PPR p: {PPR::REDWALLs, PPR::REDWALLs2, PPR::REDWALLs3, PPR::WALL3s, PPR::LAKEWALL, PPR::INLAKEWALL, PPR::BELOWBOTTOM}) if(GDIM == 2) sort(&ptds[qp0[int(p)]], &ptds[qp[int(p)]], [] (const unique_ptr& p1, const unique_ptr& p2) { auto ap1 = (dqi_poly&) *p1; auto ap2 = (dqi_poly&) *p2; return xintval(ap1.V * xpush0(.1)) < xintval(ap2.V * xpush0(.1)); }); for(PPR p: {PPR::TRANSPARENT_WALL}) 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(model_needs_depth() && current_display->stereo_active()) { global_projection = -1; draw_main(); glClear(GL_DEPTH_BUFFER_BIT); global_projection = +1; draw_main(); global_projection = 0; } else { 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(); } #if CAP_SHAPES EX dqi_poly& queuepolyat(const transmatrix& V, const hpcshape& h, color_t col, PPR prio) { if(prio == PPR::DEFAULT) prio = h.prio; auto& ptd = queuea (prio); ptd.V = V; ptd.band_shift = band_shift; 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; } ptd.color = (darkened(col >> 8) << 8) + (col & 0xFF); ptd.outline = poly_outline; ptd.linewidth = vid.linewidth; ptd.flags = h.flags; ptd.tinf = h.tinf; ptd.offset_texture = h.texture_offset; ptd.intester = h.intester; return ptd; } #endif void addfloats(vector& v, hyperpoint h) { for(int i=0; i<3; i++) v.push_back(h[i]); } EX dqi_poly& queuetable(const transmatrix& V, const vector& f, int cnt, color_t linecol, color_t fillcol, PPR prio) { auto& ptd = queuea (prio); ptd.V = V; ptd.band_shift = band_shift; 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 transmatrix& V, const hpcshape& h, color_t col) { return queuepolyat(V,h,col,h.prio); } void queuepolyb(const transmatrix& 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(color_t linecol, color_t fillcol, PPR prio) { auto &res = queuetable(Id, 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 hyperpoint& H1, const hyperpoint& H2, color_t col, int prf IS(0), PPR prio IS(PPR::LINE)) { auto& ptd = queuea (prio); ptd.H1 = H1; ptd.H2 = H2; ptd.band_shift = band_shift; 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 queuechr(int x, int y, int shift, int size, char chr, color_t col, int frame IS(0), int align IS(8)) { auto& ptd = queuea (PPR::TEXT); ptd.x = x; ptd.y = y; ptd.str = chr; ptd.shift = shift; ptd.size = size; ptd.align = align; ptd.color = col; ptd.frame = frame ? (poly_outline & ~ 255) : 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 hyperpoint& 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 * vid.stretch * hscr[1]; sc = 0; // EYETODO sc = vid.eye * current_display->radius * hscr[2]; } EX ld scale_in_pixels(const transmatrix& V) { return scale_at(V) * cgi.scalefactor * current_display->radius / 2.5; } EX bool getcoord0_checked(const hyperpoint& 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 queuechr(const hyperpoint& h, int size, char chr, color_t col, int frame IS(0)) { int xc, yc, sc; if(getcoord0_checked(h, xc, yc, sc)) queuechr(xc, yc, sc, size, chr, col, frame); } EX void queuechr(const transmatrix& V, double size, char chr, color_t col, int frame IS(0)) { int xc, yc, sc; if(getcoord0_checked(tC0(V), xc, yc, sc)) queuechr(xc, yc, sc, scale_in_pixels(V) * size, chr, col, frame); } EX void queuestr(const hyperpoint& 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 transmatrix& 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 queuecircle(const transmatrix& 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 }