1 // Hyperbolic Rogue -- rendering
2 // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file drawing.cpp
5  *  \brief Rendering shapes (dqi_draw), queue of shapes to render (ptds), etc.
6  */
7 
8 #include "hyper.h"
9 namespace hr {
10 
11 #if HDR
12 static const int POLY_DRAWLINES = 1;            // draw the lines
13 static const int POLY_DRAWAREA = 2;             // draw the area
14 static const int POLY_INVERSE = 4;              // draw the inverse -- useful in stereographic projection
15 static const int POLY_ISSIDE = 8;               // never draw in inverse
16 static const int POLY_BEHIND = 16;              // there are points behind the camera
17 static const int POLY_TOOLARGE = 32;            // some coordinates are too large -- best not to draw to avoid glitches
18 static const int POLY_INFRONT = 64;             // on the sphere (orthogonal projection), do not draw without any points in front
19 static const int POLY_HASWALLS = 128;           // floor shapes which have their sidewalls
20 static const int POLY_PLAIN = 256;              // plain floors
21 static const int POLY_FULL = 512;               // full floors
22 static const int POLY_HASSHADOW = 1024;         // floor shapes which have their shadows, or can use shFloorShadow
23 static const int POLY_GP = 2048;                // Goldberg shapes
24 static const int POLY_VCONVEX = 4096;           // Convex shape (vertex)
25 static const int POLY_CCONVEX = 8192;           // Convex shape (central)
26 static const int POLY_CENTERIN = 16384;         // new system of side checking
27 static const int POLY_FORCEWIDE = (1<<15);      // force wide lines
28 static const int POLY_NOTINFRONT = (1<<16);     // points not in front
29 static const int POLY_NIF_ERROR = (1<<17);      // points moved to the outline cross the image, disable
30 static const int POLY_BADCENTERIN = (1<<18);    // new system of side checking
31 static const int POLY_PRECISE_WIDE = (1<<19);   // precise width calculation
32 static const int POLY_FORCE_INVERTED = (1<<20); // force inverted
33 static const int POLY_ALWAYS_IN = (1<<21);      // always draw this
34 static const int POLY_TRIANGLES = (1<<22);      // made of TRIANGLES, not TRIANGLE_FAN
35 static const int POLY_INTENSE = (1<<23);        // extra intense colors
36 static const int POLY_DEBUG = (1<<24);          // debug this shape
37 static const int POLY_PRINTABLE = (1<<25);      // these walls are printable
38 static const int POLY_FAT = (1<<26);            // fatten this model in WRL export (used for Rug)
39 static const int POLY_SHADE_TEXTURE = (1<<27);  // texture has 'z' coordinate for shading
40 static const int POLY_ONE_LEVEL = (1<<28);      // only one level of the universal cover in SL(2,R)
41 
42 /** \brief A graphical element that can be drawn. Objects are not drawn immediately but rather queued.
43  *
44  *  HyperRogue map rendering functions do not draw its data immediately; instead, they call the 'queue' functions
45  *  which store the data to draw in hr::ptds. This approach lets us draw the elements in the correct order.
46  */
47 
48 struct drawqueueitem {
49   /** \brief The higher the priority, the earlier we should draw this object. */
50   PPR prio;
51   /** \brief Color of this object. */
52   color_t color;
53   /** \brief Some priorities need extra sorting inside the given class. This attribute is used to specify the inner sorting priority. */
54   int subprio;
55   /** \brief Draw the object. */
56   virtual void draw() = 0;
57   /** \brief Draw the object as background. */
draw_backhr::drawqueueitem58   virtual void draw_back() {}
59   virtual ~drawqueueitem() = default;
60   /** \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. */
61   virtual color_t outline_group() = 0;
62   };
63 
64 /** \brief Drawqueueitem used to draw polygons. The majority of drawqueueitems fall here. */
65 struct dqi_poly : drawqueueitem {
66   /** \brief matrix used to transform the model */
67   shiftmatrix V;
68   /** \brief a vector of GL vertices where the model is stored */
69   const vector<glvertex> *tab;
70   /** \brief the where does the model start */
71   int offset;
72   /** \brief how many vertices in the model */
73   int cnt;
74   /** \brief the offset in the texture vertices */
75   int offset_texture;
76   /** \brief outline color */
77   color_t outline;
78   /** \brief width of boundary lines */
79   double linewidth;
80   /** \brief various flags */
81   int flags;
82   /** \brief Texture data for textured polygons. Requires POLY_TRIANGLES flag */
83   struct basic_textureinfo *tinf;
84   /** \brief used to find the correct side to draw in spherical geometries */
85   hyperpoint intester;
86   /** \brief temporarily cached data */
87   float cache;
88   void draw() override;
89   #if CAP_GL
90   void gldraw();
91   #endif
92   void draw_back() override;
outline_grouphr::dqi_poly93   color_t outline_group() override { return outline; }
94   };
95 
96 /** \brief Drawqueueitem used to draw lines */
97 struct dqi_line : drawqueueitem {
98   /** \brief starting and ending point */
99   shiftpoint H1, H2;
100   /** \brief how accurately to render the line */
101   int prf;
102   /** \brief width of this line */
103   double width;
104   void draw() override;
105   void draw_back() override;
outline_grouphr::dqi_line106   color_t outline_group() override { return color; }
107   };
108 
109 /** \brief Drawqueueitem used to draw strings, using sccreen coodinates */
110 struct dqi_string : drawqueueitem {
111   /** \brief text */
112   string str;
113   /** onscreen position */
114   int x, y;
115   /** shift in anaglyph mode */
116   int shift;
117   /** font size */
118   int size;
119   /** frame color */
120   int frame;
121   /** alignment (0-8-16) */
122   int align;
123   void draw() override;
outline_grouphr::dqi_string124   color_t outline_group() override { return 1; }
125   };
126 
127 /** Drawqueueitem used to draw circles, using screen coordinates */
128 struct dqi_circle : drawqueueitem {
129   /** \brief onscreen position */
130   int x, y;
131   /** \brief circle size */
132   int size;
133   /** \brief which color should it be filled with */
134   color_t fillcolor;
135   /** \brief width of the circle */
136   double linewidth;
137   void draw() override;
outline_grouphr::dqi_circle138   color_t outline_group() override { return 2; }
139   };
140 
141 /** \brief Perform an arbitrary action. May temporarily change the model, etc. */
142 struct dqi_action : drawqueueitem {
143   reaction_t action;
dqi_actionhr::dqi_action144   explicit dqi_action(const reaction_t& a) : action(a) {}
drawhr::dqi_action145   void draw() override { action(); }
outline_grouphr::dqi_action146   color_t outline_group() override { return 2; }
147   };
148 #endif
149 
150 EX bool in_vr_sphere;
151 hyperpoint vr_sphere_center;
152 
153 bool fatborder;
154 
155 EX color_t poly_outline;
156 
157 EX vector<unique_ptr<drawqueueitem>> ptds;
158 
159 #if CAP_GL
160 EX color_t text_color;
161 EX int text_shift;
162 EX GLuint text_texture;
163 EX int texts_merged;
164 EX int shapes_merged;
165 
166 #if MINIMIZE_GL_CALLS
167 color_t triangle_color, line_color;
168 ld m_shift;
169 vector<glvertex> triangle_vertices;
170 vector<glvertex> line_vertices;
171 #endif
172 
glflush()173 EX void glflush() {
174   DEBBI(DF_GRAPH, ("glflush"));
175   #if MINIMIZE_GL_CALLS
176   current_display->set_all(0, m_shift);
177   if(isize(triangle_vertices)) {
178     // printf("%08X %08X | %d shapes, %d/%d vertices\n", triangle_color, line_color, shapes_merged, isize(triangle_vertices), isize(line_vertices));
179     if(triangle_color) {
180       glhr::be_nontextured();
181       glapplymatrix(Id);
182       glhr::current_vertices = NULL;
183       glhr::vertices(triangle_vertices);
184       glhr::color2(triangle_color);
185       glDrawArrays(GL_TRIANGLES, 0, isize(triangle_vertices));
186       }
187     triangle_vertices.clear();
188     }
189   if(isize(line_vertices)) {
190     if(line_color) {
191       glhr::be_nontextured();
192       glapplymatrix(Id);
193       glhr::current_vertices = NULL;
194       glhr::vertices(line_vertices);
195       glhr::color2(line_color);
196       glDrawArrays(GL_LINES, 0, isize(line_vertices));
197       }
198     line_vertices.clear();
199     }
200   shapes_merged = 0;
201   #endif
202 
203   if(isize(text_vertices)) {
204     current_display->next_shader_flags = GF_TEXTURE;
205     dynamicval<eModel> m(pmodel, mdPixel);
206     if(!svg::in) current_display->set_all(0,0);
207 
208     auto drawer = [] {
209       glhr::color2(text_color);
210       glBindTexture(GL_TEXTURE_2D, text_texture);
211       glhr::set_depthtest(false);
212       glhr::current_vertices = NULL;
213       glhr::prepare(text_vertices);
214       glDrawArrays(GL_TRIANGLES, 0, isize(text_vertices));
215       };
216 
217     #if CAP_VR
218     if(vrhr::should_render() && vrhr::in_menu())
219       vrhr::in_vr_ui(drawer);
220 
221     else
222     #endif
223     for(int ed = (current_display->stereo_active() && text_shift)?-1:0; ed<2; ed+=2) {
224       glhr::set_modelview(glhr::translate(-ed*text_shift-current_display->xcenter,-current_display->ycenter, 0));
225       current_display->set_mask(ed);
226       drawer();
227 
228       GLERR("print");
229       }
230 
231     if(current_display->stereo_active() && text_shift && !svg::in) current_display->set_mask(0);
232 
233     texts_merged = 0;
234     text_vertices.clear();
235     }
236   }
237 #endif
238 
239 #if CAP_SDL && !ISMOBILE
240 
241 SDL_Surface *aux;
242 #if CAP_SDL2
243 SDL_Renderer *auxrend;
244 #else
245 #define auxrend aux
246 #endif
247 
248 #endif
249 
250 #if CAP_POLY
251 #if HDR
252 #define POLYMAX 60000
253 #endif
254 
255 EX vector<glvertex> glcoords;
256 
257 #endif
258 
259 EX int spherespecial, spherephase;
260 
261 #if CAP_POLY
262 int polyi;
263 
264 EX int polyx[POLYMAX], polyxr[POLYMAX], polyy[POLYMAX];
265 
266 int poly_flags;
267 
add1(const hyperpoint & H)268 void add1(const hyperpoint& H) {
269   glcoords.push_back(glhr::pointtogl(H));
270   }
271 
axial_sign()272 int axial_sign() {
273   return ((axial_x ^ axial_y)&1) ? -1:1;
274   }
275 
is_behind(const hyperpoint & H)276 bool is_behind(const hyperpoint& H) {
277   if(pmodel == mdAxial && sphere) return axial_sign() * H[2] <= BEHIND_LIMIT;
278   if(in_vr_sphere) return false;
279   return pmodel == mdDisk && (hyperbolic ? H[2] >= 0 : true) && (nonisotropic ? false : pconf.alpha + H[2] <= BEHIND_LIMIT);
280   }
281 
be_just_on_view(const hyperpoint & H1,const hyperpoint & H2)282 hyperpoint be_just_on_view(const hyperpoint& H1, const hyperpoint &H2) {
283   // H1[2] * t + H2[2] * (1-t) == BEHIND_LIMIT - pconf.alpha
284   // H2[2]- BEHIND_LIMIT + pconf.alpha = t * (H2[2] - H1[2])
285   ld t = (axial_sign() * H2[2] - BEHIND_LIMIT + (pmodel == mdAxial ? 0 : pconf.alpha)) / (H2[2] - H1[2]) * axial_sign();
286   return H1 * t + H2 * (1-t);
287   }
288 
289 bool last_infront;
290 
nif_error_in(ld x1,ld y1,ld x2,ld y2)291 bool nif_error_in(ld x1, ld y1, ld x2, ld y2) {
292   return pow(x1 * x2 + y2 * y2, 2) < (x1*x1+y1*y1)*(x2*x2+y2*y2)*.5;
293   }
294 
295 bool knowgood;
296 hyperpoint goodpoint;
297 vector<pair<int, hyperpoint>> tofix;
298 
two_sided_model()299 EX bool two_sided_model() {
300   #if CAP_VR
301   bool in_vr = vrhr::rendering();
302   #else
303   constexpr bool in_vr = false;
304   #endif
305   if(GDIM == 3) return false;
306   if(in_vr_sphere) return true;
307   if(pmodel == mdHyperboloid) return !euclid && !in_vr;
308   // if(pmodel == mdHemisphere) return true;
309   if(pmodel == mdDisk) return sphere;
310   if(pmodel == mdRetroLittrow) return sphere;
311   if(pmodel == mdRetroHammer) return sphere;
312   if(pmodel == mdHemisphere) return !in_vr;
313   if(pmodel == mdRotatedHyperboles) return true;
314   if(pmodel == mdSpiral && pconf.spiral_cone < 360) return true;
315   return false;
316   }
317 
get_side(const hyperpoint & H)318 EX int get_side(const hyperpoint& H) {
319   #if CAP_VR
320   if(in_vr_sphere) {
321     hyperpoint Hscr;
322     applymodel(shiftless(H), Hscr);
323     Hscr[3] = 1;
324     E4;
325     hyperpoint actual = vrhr::hmd_mv * Hscr;
326     ld val = 0;
327     for(int i=0; i<3; i++) val += (vr_sphere_center[i] - actual[i]) * actual[i];
328     return val > 0 ? -1 : 1;
329     }
330   #endif
331   if(pmodel == mdDisk && sphere) {
332     double curnorm = H[0]*H[0]+H[1]*H[1]+H[2]*H[2];
333     double horizon = curnorm / pconf.alpha;
334     return (H[2] <= -horizon) ? -1 : 1;
335     }
336   if(pmodel == mdRetroLittrow && sphere) {
337     return H[2] >= 0 ? 1 : -1;
338     }
339   if(pmodel == mdRetroHammer && sphere) {
340     return H[2] >= 0 ? 1 : -1;
341     }
342   if(pmodel == mdRotatedHyperboles)
343     return H[1] > 0 ? -1 : 1;
344   if(pmodel == mdHyperboloid && hyperbolic)
345     return (models::sin_ball * H[2] > -models::cos_ball * H[1]) ? -1 : 1;
346   if(pmodel == mdHyperboloid && sphere)
347     return (models::sin_ball * H[2] > models::cos_ball * H[1]) ? -1 : 1;
348   if(pmodel == mdHemisphere) {
349     hyperpoint res;
350     applymodel(shiftless(H), res);
351     return res[2] < 0 ? -1 : 1;
352     }
353   if(pmodel == mdSpiral && pconf.spiral_cone < 360) {
354     return cone_side(shiftless(H));
355     }
356   return 0;
357   }
358 
correct_side(const hyperpoint & H)359 EX bool correct_side(const hyperpoint& H) {
360   return get_side(H) == spherespecial;
361   }
362 
363 hyperpoint Hlast;
364 
fixpoint(glvertex & hscr,hyperpoint H)365 void fixpoint(glvertex& hscr, hyperpoint H) {
366   hyperpoint bad = H, good = goodpoint;
367 
368   for(int i=0; i<10; i++) {
369     hyperpoint mid = midz(bad, good);
370     if(correct_side(mid))
371       good = mid;
372     else
373       bad = mid;
374     }
375   hyperpoint Hscr;
376   applymodel(shiftless(good), Hscr);
377   #if CAP_VR
378   if(vrhr::rendering())
379     hscr = glhr::makevertex(Hscr[0], Hscr[1]*pconf.stretch, Hscr[2]);
380   else
381   #endif
382     hscr = glhr::makevertex(Hscr[0]*current_display->radius, Hscr[1]*current_display->radius*pconf.stretch, Hscr[2]*current_display->radius);
383   }
384 
addpoint(const shiftpoint & H)385 void addpoint(const shiftpoint& H) {
386   if(true) {
387     ld z = current_display->radius;
388     // if(pconf.alpha + H[2] <= BEHIND_LIMIT && pmodel == mdDisk) poly_flags |= POLY_BEHIND;
389 
390     #if CAP_VR
391     if(vrhr::rendering()) z = 1;
392     #endif
393 
394     if(spherespecial) {
395       auto H0 = H.h;
396       if(correct_side(H0)) {
397         poly_flags |= POLY_INFRONT, last_infront = false;
398         if(!knowgood || (spherespecial > 0 ? H[2]>goodpoint[2] : H[2]<goodpoint[2])) goodpoint = H0, knowgood = true;
399         }
400       else if(sphere && (poly_flags & POLY_ISSIDE)) {
401         double curnorm = H[0]*H[0]+H[1]*H[1]+H[2]*H[2];
402         double horizon = curnorm / pconf.alpha;
403         poly_flags |= POLY_NOTINFRONT;
404         if(last_infront && nif_error_in(glcoords.back()[0], glcoords.back()[1], H[0], H[1]))
405           poly_flags |= POLY_NIF_ERROR;
406 
407         last_infront = true;
408 
409         z *=
410           (sqrt(curnorm - horizon*horizon) / (pconf.alpha - horizon)) /
411           (sqrt(curnorm - H[2]*H[2]) / (pconf.alpha+H[2]));
412         }
413       else {
414         poly_flags |= POLY_NOTINFRONT;
415         tofix.push_back(make_pair(glcoords.size(), H0));
416         add1(H0);
417         return;
418         }
419       }
420     hyperpoint Hscr;
421     applymodel(H, Hscr);
422     if(sphere && pmodel == mdSpiral) {
423       if(isize(glcoords)) {
424         hyperpoint Hscr1;
425         shiftpoint H1 = H; H1.shift += 2 * M_PI;
426         applymodel(H1, Hscr1);
427         if(hypot_d(2, Hlast-Hscr1) < hypot_d(2, Hlast-Hscr)) { Hscr = Hscr1; }
428         H1.shift -= 4 * M_PI;
429         applymodel(H1, Hscr1);
430         if(hypot_d(2, Hlast-Hscr1) < hypot_d(2, Hlast-Hscr)) { Hscr = Hscr1; }
431         }
432       Hlast = Hscr;
433       }
434     #if CAP_VR
435     if(vrhr::rendering()) {
436       for(int i=0; i<3; i++) Hscr[i] *= z;
437       }
438     else
439     #endif
440     if(GDIM == 2) {
441       for(int i=0; i<3; i++) Hscr[i] *= z;
442       Hscr[1] *= pconf.stretch;
443       }
444     else {
445       Hscr[0] *= z;
446       Hscr[1] *= z * pconf.stretch;
447       Hscr[2] = 1 - 2 * (-Hscr[2] - pconf.clip_min) / (pconf.clip_max - pconf.clip_min);
448       }
449     add1(Hscr);
450     }
451   }
452 
coords_to_poly()453 void coords_to_poly() {
454   polyi = isize(glcoords);
455   for(int i=0; i<polyi; i++) {
456     if(!current_display->stereo_active()) glcoords[i][2] = 0;
457 
458     polyx[i]  = current_display->xcenter + glcoords[i][0] - glcoords[i][2];
459     polyxr[i] = current_display->xcenter + glcoords[i][0] + glcoords[i][2];
460     polyy[i]  = current_display->ycenter + glcoords[i][1];
461     }
462   }
463 
behind3(shiftpoint h)464 bool behind3(shiftpoint h) {
465   if(pmodel == mdGeodesic)
466     return lp_apply(inverse_exp(h))[2] < 0;
467   return h[2] < 0;
468   }
469 
addpoly(const shiftmatrix & V,const vector<glvertex> & tab,int ofs,int cnt)470 void addpoly(const shiftmatrix& V, const vector<glvertex> &tab, int ofs, int cnt) {
471   if(pmodel == mdPixel) {
472     for(int i=ofs; i<ofs+cnt; i++) {
473       hyperpoint h = glhr::gltopoint(tab[i]);
474       #if MAXMDIM >= 4
475       h[3] = 1;
476       #endif
477       h = V.T * h;
478       add1(h);
479       }
480     return;
481     }
482   tofix.clear(); knowgood = false;
483   if(among(pmodel, mdPerspective, mdGeodesic)) {
484     if(poly_flags & POLY_TRIANGLES) {
485       for(int i=ofs; i<ofs+cnt; i+=3) {
486         shiftpoint h0 = V * glhr::gltopoint(tab[i]);
487         shiftpoint h1 = V * glhr::gltopoint(tab[i+1]);
488         shiftpoint h2 = V * glhr::gltopoint(tab[i+2]);
489         if(!behind3(h0) && !behind3(h1) && !behind3(h2))
490           addpoint(h0), addpoint(h1), addpoint(h2);
491         }
492       }
493     else {
494       for(int i=ofs; i<ofs+cnt; i++) {
495         shiftpoint h = V * glhr::gltopoint(tab[i]);
496         if(!behind3(h)) addpoint(h);
497         }
498       }
499     return;
500     }
501   shiftpoint last = V * glhr::gltopoint(tab[ofs]);
502   bool last_behind = is_behind(last.h);
503   if(!last_behind) addpoint(last);
504   hyperpoint enter = C0;
505   hyperpoint firstleave;
506   int start_behind = last_behind ? 1 : 0;
507   for(int i=ofs+1; i<ofs+cnt; i++) {
508     shiftpoint curr = V*glhr::gltopoint(tab[i]);
509     if(is_behind(curr.h) != last_behind) {
510       hyperpoint h = be_just_on_view(last.h, curr.h);
511       if(start_behind == 1) start_behind = 2, firstleave = h;
512       if(!last_behind) enter = h;
513       else if(h[0] * enter[0] + h[1] * enter[1] < 0) poly_flags |= POLY_BEHIND;
514       addpoint(shiftless(h));
515       last_behind = !last_behind;
516       }
517     if(!last_behind) addpoint(curr);
518     last = curr;
519     }
520   if(start_behind == 2) {
521     if(firstleave[0] * enter[0] + firstleave[1] * enter[1] < 0) poly_flags |= POLY_BEHIND;
522     else addpoint(shiftless(firstleave));
523     }
524   if(knowgood && isize(tofix)) {
525 
526     if(true) {
527       hyperpoint Hx = V.T * C0, Hy = goodpoint;
528       for(int i=0; i<20; i++) {
529         hyperpoint mid = midz(Hx, Hy);
530         if(correct_side(mid)) Hy = mid;
531         else Hx = mid;
532         }
533       goodpoint = midz(Hy, goodpoint);
534       }
535 
536     for(auto& p: tofix)
537       fixpoint(glcoords[p.first], p.second);
538     /*
539     hyperpoint Hscr;
540     applymodel(goodpoint, Hscr);
541     glcoords.push_back(make_array<GLfloat>(Hscr[0]*current_display->radius+10, Hscr[1]*current_display->radius*pconf.stretch, Hscr[2]*vid.radius));
542     glcoords.push_back(make_array<GLfloat>(Hscr[0]*current_display->radius, Hscr[1]*current_display->radius*pconf.stretch+10, Hscr[2]*vid.radius));
543     glcoords.push_back(make_array<GLfloat>(Hscr[0]*current_display->radius-10, Hscr[1]*current_display->radius*pconf.stretch, Hscr[2]*vid.radius));
544     glcoords.push_back(make_array<GLfloat>(Hscr[0]*current_display->radius, Hscr[1]*current_display->radius*pconf.stretch-10, Hscr[2]*vid.radius));
545     glcoords.push_back(make_array<GLfloat>(Hscr[0]*current_display->radius+10, Hscr[1]*current_display->radius*pconf.stretch, Hscr[2]*vid.radius));  */
546     }
547   }
548 
549 #if CAP_SDLGFX
aapolylineColor(SDL_Renderer * s,int * x,int * y,int polyi,color_t col)550 void aapolylineColor(SDL_Renderer *s, int*x, int *y, int polyi, color_t col) {
551   for(int i=1; i<polyi; i++)
552     aalineColor(s, x[i-1], y[i-1], x[i], y[i], align(col));
553   }
554 
polylineColor(SDL_Renderer * s,int * x,int * y,int polyi,color_t col)555 void polylineColor(SDL_Renderer *s, int *x, int *y, int polyi, color_t col) {
556   for(int i=1; i<polyi; i++)
557     lineColor(s, x[i-1], y[i-1], x[i], y[i], align(col));
558   }
559 
filledPolygonColorI(SDL_Renderer * s,int * px,int * py,int polyi,color_t col)560 EX void filledPolygonColorI(SDL_Renderer *s, int* px, int *py, int polyi, color_t col) {
561   std::vector<Sint16> spx(px, px + polyi);
562   std::vector<Sint16> spy(py, py + polyi);
563   filledPolygonColor(s, spx.data(), spy.data(), polyi, align(col));
564   }
565 #endif
566 
567 #if CAP_TEXTURE
drawTexturedTriangle(SDL_Surface * s,int * px,int * py,glvertex * tv,color_t col)568 void drawTexturedTriangle(SDL_Surface *s, int *px, int *py, glvertex *tv, color_t col) {
569   transmatrix source = matrix3(
570     px[0], px[1], px[2],
571     py[0], py[1], py[2],
572         1,      1,    1);
573 
574 
575   transmatrix target = matrix3(
576     tv[0][0], tv[1][0], tv[2][0],
577     tv[0][1], tv[1][1], tv[2][1],
578            1,        1,        1
579     );
580 
581   transmatrix isource = inverse(source);
582   int minx = px[0], maxx = px[0];
583   int miny = py[0], maxy = py[0];
584   for(int i=1; i<3; i++)
585     minx = min(minx, px[i]), maxx = max(maxx, px[i]),
586     miny = min(miny, py[i]), maxy = max(maxy, py[i]);
587   for(int mx=minx; mx<maxx; mx++)
588   for(int my=miny; my<maxy; my++) {
589     hyperpoint h = isource * point3(mx, my, 1);
590     if(h[0] >= -1e-7 && h[1] >= -1e-7 && h[2] >= -1e-7) {
591       hyperpoint ht = target * h;
592       int tw = texture::config.data.twidth;
593       int x = int(ht[0] * tw) & (tw-1);
594       int y = int(ht[1] * tw) & (tw-1);
595       color_t c;
596       if(texture::config.data.texture_pixels.size() == 0)
597         c = 0xFFFFFFFF;
598       else
599         c = texture::config.data.texture_pixels[y * tw + x];
600       auto& pix = qpixel(s, mx, my);
601       for(int p=0; p<3; p++) {
602         int alpha = part(c, 3) * part(col, 0);
603         auto& v = part(pix, p);
604         v = ((255*255 - alpha) * 255 * v + alpha * part(col, p+1) * part(c, p) + 255 * 255 * 255/2 + 1) / (255 * 255 * 255);
605         }
606       }
607     }
608   }
609 #endif
610 
611 EX int global_projection;
612 
613 #if !CAP_GL
get_shader_flags()614 flagtype get_shader_flags() { return 0; }
615 #endif
616 
617 #if CAP_GL
618 
619 int min_slr, max_slr = 0;
620 
621 #if MAXMDIM >= 4
622 extern renderbuffer *floor_textures;
623 #endif
624 
gldraw()625 void dqi_poly::gldraw() {
626   GLWRAP;
627   auto& v = *tab;
628   int ioffset = offset;
629 
630 #if MINIMIZE_GL_CALLS
631   if(current_display->stereo_active() == 0 && !tinf && (color == 0 || ((flags & (POLY_VCONVEX | POLY_CCONVEX)) && !(flags & (POLY_INVERSE | POLY_FORCE_INVERTED))))) {
632     if(color != triangle_color || outline != line_color || texts_merged || m_shift != V.shift) {
633       glflush();
634       triangle_color = color;
635       line_color = outline;
636       m_shift = V.shift;
637       }
638     shapes_merged++;
639 
640     if((flags & POLY_CCONVEX) && !(flags & POLY_VCONVEX)) {
641       vector<glvertex> v2(cnt+1);
642       for(int i=0; i<cnt+1; i++) v2[i] = glhr::pointtogl( V.T * glhr::gltopoint( v[offset+i-1] ) );
643       if(color) for(int i=0; i<cnt; i++) triangle_vertices.push_back(v2[0]), triangle_vertices.push_back(v2[i]), triangle_vertices.push_back(v2[i+1]);
644       for(int i=1; i<cnt; i++) line_vertices.push_back(v2[i]), line_vertices.push_back(v2[i+1]);
645       }
646     else {
647       vector<glvertex> v2(cnt);
648       for(int i=0; i<cnt; i++) v2[i] = glhr::pointtogl( V.T * glhr::gltopoint( v[offset+i] ) );
649       if(color) for(int i=2; i<cnt-1; i++) triangle_vertices.push_back(v2[0]), triangle_vertices.push_back(v2[i-1]), triangle_vertices.push_back(v2[i]);
650       for(int i=1; i<cnt; i++) line_vertices.push_back(v2[i-1]), line_vertices.push_back(v2[i]);
651       }
652     return;
653     }
654   else glflush();
655 #endif
656 
657   if(tinf) {
658     bool col = isize(tinf->colors);
659     if(col)
660       glhr::be_color_textured();
661     else
662       glhr::be_textured();
663     if(flags & POLY_SHADE_TEXTURE) current_display->next_shader_flags |= GF_TEXTURE_SHADED;
664     glBindTexture(GL_TEXTURE_2D, tinf->texture_id);
665     if(isize(tinf->colors))
666       glhr::vertices_texture_color(v, tinf->tvertices, tinf->colors, offset, offset_texture);
667     else
668       glhr::vertices_texture(v, tinf->tvertices, offset, offset_texture);
669     ioffset = 0;
670     }
671   else {
672     glhr::be_nontextured();
673     glhr::vertices(v);
674     }
675 
676   next_slr:
677 
678   for(int ed = current_display->stereo_active() ? -1 : 0; ed<2; ed+=2) {
679     if(global_projection && global_projection != ed) continue;
680 
681     if(min_slr < max_slr) {
682       current_display->set_all(ed, sl2 ? 0 : V.shift);
683       glhr::set_index_sl(V.shift + M_PI * min_slr * hybrid::csteps / cgi.psl_steps);
684       }
685     else {
686       current_display->set_all(ed, V.shift);
687       }
688     bool draw = color;
689 
690     flagtype sp = get_shader_flags();
691 
692     if(sp & SF_DIRECT) {
693       if((sp & SF_BAND) && V[2][2] > 1e8) continue;
694       glapplymatrix(V.T);
695       }
696 
697     if(draw) {
698       if(flags & POLY_TRIANGLES) {
699         glhr::color2(color, (flags & POLY_INTENSE) ? 2 : 1);
700         glhr::set_depthtest(model_needs_depth() && prio < PPR::SUPERLINE);
701         glhr::set_depthwrite(model_needs_depth() && prio != PPR::TRANSPARENT_SHADOW && prio != PPR::EUCLIDEAN_SKY);
702         glhr::set_fogbase(prio == PPR::SKY ? 1.0 + (euclid ? 20 : 5 / sightranges[geometry]) : 1.0);
703         glDrawArrays(GL_TRIANGLES, ioffset, cnt);
704         }
705       else {
706         glEnable(GL_STENCIL_TEST);
707 
708         glColorMask( GL_FALSE,GL_FALSE,GL_FALSE,GL_FALSE );
709         glhr::set_depthtest(false);
710         glStencilOp( GL_INVERT, GL_INVERT, GL_INVERT);
711         glStencilFunc( GL_ALWAYS, 0x1, 0x1 );
712         glhr::color2(0xFFFFFFFF);
713         glDrawArrays(tinf ? GL_TRIANGLES : GL_TRIANGLE_FAN, offset, cnt);
714 
715         current_display->set_mask(ed);
716         glhr::color2(color);
717         glhr::set_depthtest(model_needs_depth() && prio < PPR::SUPERLINE);
718         glhr::set_depthwrite(model_needs_depth() && prio != PPR::TRANSPARENT_SHADOW && prio != PPR::EUCLIDEAN_SKY);
719         glhr::set_fogbase(prio == PPR::SKY ? 1.0 + (euclid ? 20 : 5 / sightranges[geometry]) : 1.0);
720 
721         if(flags & (POLY_INVERSE | POLY_FORCE_INVERTED)) {
722           glStencilOp( GL_ZERO, GL_ZERO, GL_ZERO);
723           glStencilFunc( GL_NOTEQUAL, 1, 1);
724           GLfloat xx = vid.xres;
725           GLfloat yy = vid.yres;
726           vector<glvertex> scr = {
727             glhr::makevertex(-xx, -yy, 0),
728             glhr::makevertex(+xx, -yy, 0),
729             glhr::makevertex(+xx, +yy, 0),
730             glhr::makevertex(-xx, +yy, 0)
731             };
732           glhr::vertices(scr);
733           glhr::id_modelview();
734           glDrawArrays(tinf ? GL_TRIANGLES : GL_TRIANGLE_FAN, 0, 4);
735           glhr::vertices(v);
736           if(sp & SF_DIRECT) glapplymatrix(V.T);
737           }
738         else {
739           glStencilOp( GL_ZERO, GL_ZERO, GL_ZERO);
740           glStencilFunc( GL_EQUAL, 1, 1);
741           glDrawArrays(tinf ? GL_TRIANGLES : GL_TRIANGLE_FAN, offset, cnt);
742           }
743 
744         glDisable(GL_STENCIL_TEST);
745         }
746       }
747 
748     if(outline && !tinf) {
749       glhr::color2(outline);
750       glhr::set_depthtest(model_needs_depth() && prio < PPR::SUPERLINE);
751       glhr::set_depthwrite(model_needs_depth() && prio != PPR::TRANSPARENT_SHADOW && prio != PPR::EUCLIDEAN_SKY);
752       glhr::set_fogbase(prio == PPR::SKY ? 1.0 + (euclid ? 20 : 5 / sightranges[geometry]) : 1.0);
753 
754       if(flags & POLY_TRIANGLES) {
755         vector<glvertex> v1;
756         v1.reserve(cnt * 2);
757         for(int i=0; i<cnt; i+= 3) {
758           v1.push_back(v[offset+i]);
759           v1.push_back(v[offset+i+1]);
760           v1.push_back(v[offset+i+1]);
761           v1.push_back(v[offset+i+2]);
762           v1.push_back(v[offset+i+2]);
763           v1.push_back(v[offset+i]);
764           }
765         glhr::vertices(v1);
766         glDrawArrays(GL_LINES, 0, cnt*2);
767         // glDrawArrays(GL_LINE_STRIP, ioffset, cnt);
768         }
769 
770       else
771         glDrawArrays(GL_LINE_STRIP, offset, cnt);
772       }
773     }
774 
775   if(min_slr+1 < max_slr) {
776     min_slr++;
777     goto next_slr;
778     }
779   }
780 #endif
781 
scale_at(const shiftmatrix & T)782 EX ld scale_at(const shiftmatrix& T) {
783   if(GDIM == 3 && pmodel == mdPerspective) return 1 / abs((tC0(unshift(T)))[2]);
784   if(sol) return 1;
785   hyperpoint h1, h2, h3;
786   applymodel(tC0(T), h1);
787   applymodel(T * xpush0(.01), h2);
788   applymodel(T * ypush(.01) * C0, h3);
789   return sqrt(hypot_d(2, h2-h1) * hypot_d(2, h3-h1) / .0001);
790   }
791 
792 EX int perfect_linewidth = 1;
793 
linewidthat(const shiftpoint & h)794 EX ld linewidthat(const shiftpoint& h) {
795   if(!vid.fineline) return 1;
796   else if(hyperbolic && pmodel == mdDisk && pconf.alpha == 1 && !ISWEB && !flat_on) {
797     double dz = h[LDIM];
798     if(dz < 1) return 1;
799     else {
800       double dx = sqrt(dz * dz - 1);
801       double dfc = dx/(dz+1);
802       dfc = 1 - dfc*dfc;
803       return dfc;
804       }
805     }
806   else if(perfect_linewidth >= (inHighQual ? 1 : 2)) {
807     hyperpoint h0 = h.h / zlevel(h.h);
808     shiftmatrix T = shiftless(rgpushxto0(h0), h.shift);
809     return scale_at(T);
810     }
811   return 1;
812   }
813 
set_width(ld w)814 EX void set_width(ld w) {
815   #if MINIMIZE_GL_CALLS
816   if(w != glhr::current_linewidth) glflush();
817   #endif
818   #if CAP_GL
819   glhr::set_linewidth(w);
820   #endif
821   }
822 
823 // this part makes cylindrical projections on the sphere work
824 
825 namespace cyl {
826 
827 int loop_min = 0, loop_max = 0;
828 vector<ld> periods;
829 
period_at(ld y)830 ld period_at(ld y) {
831 
832   ld m = current_display->radius;
833   y /= (m * pconf.stretch);
834 
835   switch(pmodel) {
836     case mdBand:
837     case mdMiller:
838       return m * 4;
839     case mdSinusoidal:
840       return m * 2 * cos(y * M_PI);
841     case mdMollweide:
842       return m * 2 * sqrt(1 - y*y*4);
843     case mdCollignon: {
844       if(pconf.collignon_reflected && y > 0) y = -y;
845       y += signed_sqrt(pconf.collignon_parameter);
846       return abs(m*y*2/1.2);
847       }
848     default:
849       return m * 2;
850     }
851   }
852 
adjust(bool tinf)853 void adjust(bool tinf) {
854 
855   periods.resize(isize(glcoords));
856 
857   if(!models::model_straight)
858     for(auto& g: glcoords)
859       models::apply_orientation(g[0], g[1]);
860 
861   for(int i = 0; i<isize(glcoords); i++) periods[i] = period_at(glcoords[i][1]);
862 
863   auto dist = [] (ld a, ld b) { return max(b, a-b); };
864 
865   ld chypot = hypot(dist(vid.xres, current_display->xcenter), dist(vid.yres, current_display->ycenter));
866 
867   ld cmin = -chypot/2, cmax = chypot/2, dmin = -chypot, dmax = chypot;
868 
869   ld z = pconf.stretch * current_display->radius;
870 
871   switch(pmodel) {
872     case mdSinusoidal: case mdBandEquidistant: case mdMollweide:
873       dmax = z/2, dmin = -dmax;
874       break;
875 
876     case mdBandEquiarea:
877       dmax = z/M_PI, dmin = -dmax;
878       break;
879 
880     case mdCollignon:
881       dmin = z * (signed_sqrt(pconf.collignon_parameter - 1) - signed_sqrt(pconf.collignon_parameter));
882       if(pconf.collignon_reflected) dmax = -dmin;
883       else dmax = z * (signed_sqrt(pconf.collignon_parameter + 1) - signed_sqrt(pconf.collignon_parameter));
884       break;
885 
886     default: ;
887     }
888 
889   bool had = false;
890   ld first, next;
891 
892   for(int i = 0; i<isize(glcoords); i++) if(periods[i] > 1) {
893     if(!had) {
894       next = first = glcoords[i][0] / periods[i];
895       had = true;
896       }
897     else {
898       glcoords[i][0] /= periods[i];
899       glcoords[i][0] -= round_nearest(glcoords[i][0]-next);
900       next = glcoords[i][0];
901       glcoords[i][0] *= periods[i];
902       }
903     loop_min = min<int>(loop_min, floor((cmin - glcoords[i][0]) / periods[i]));
904     loop_max = max<int>(loop_max, ceil((cmax - glcoords[i][0]) / periods[i]));
905     }
906 
907   if(!had) return;
908 
909   ld last = first - round_nearest(first-next);
910 
911   if(loop_max > 100) loop_max = 100;
912   if(loop_min < -100) loop_min = -100;
913 
914   if(abs(first - last) < 1e-6) {
915     if(!models::model_straight)
916       for(auto& g: glcoords)
917         models::apply_orientation(g[1], g[0]);
918     }
919   else {
920     if(tinf) {
921       // this cannot work after cycled
922       loop_min = 1; loop_max = 0; return;
923       }
924     if(last < first) {
925       reverse(glcoords.begin(), glcoords.end());
926       reverse(periods.begin(), periods.end());
927       swap(first, last);
928       }
929 
930     for(int i=0; i<isize(glcoords); i++) glcoords[i][0] += periods[i] * loop_min;
931 
932     int base = isize(glcoords);
933 
934     for(int i=loop_min; i<loop_max; i++) {
935       for(int j=0; j<base; j++) {
936         glcoords.push_back(glcoords[isize(glcoords)-base]);
937         glcoords.back()[0] += periods[j];
938         }
939       }
940     glcoords.push_back(glcoords.back());
941     glcoords.push_back(glcoords[0]);
942     for(int u=1; u<=2; u++) {
943       auto& v = glcoords[isize(glcoords)-u][1];
944       v = v < 0 ? dmin : dmax;
945       }
946     if(!models::model_straight)
947       for(auto& g: glcoords)
948         models::apply_orientation(g[1], g[0]);
949     // we have already looped
950     loop_min = loop_max = 0;
951     }
952   }
953 }
954 
955 bool in_twopoint = false;
956 
glhypot2(glvertex a,glvertex b)957 ld glhypot2(glvertex a, glvertex b) {
958   return (a[0]-b[0]) * (a[0]-b[0]) + (a[1]-b[1]) * (a[1]-b[1]) + (a[2]-b[2]) * (a[2]-b[2]);
959   }
960 
compute_side_by_centerin(dqi_poly * p,bool & nofill)961 void compute_side_by_centerin(dqi_poly *p, bool& nofill) {
962 
963   hyperpoint hscr;
964   shiftpoint h1 = p->V * p->intester;
965   if(is_behind(h1.h)) {
966     if(sphere) {
967       for(int i=0; i<3; i++) h1[i] = -h1[i];
968       poly_flags &= ~POLY_CENTERIN;
969       }
970     else
971       nofill = true;
972     }
973   applymodel(h1, hscr);
974   if(!vrhr::rendering()) {
975     hscr[0] *= current_display->radius; hscr[1] *= current_display->radius * pconf.stretch;
976     }
977   for(int i=0; i<isize(glcoords)-1; i++) {
978     double x1 = glcoords[i][0] - hscr[0];
979     double y1 = glcoords[i][1] - hscr[1];
980     double x2 = glcoords[i+1][0] - hscr[0];
981     double y2 = glcoords[i+1][1] - hscr[1];
982     if(asign(y1, y2)) {
983       ld x = xcross(x1, y1, x2, y2);
984       if(x < -1e-6) poly_flags ^= POLY_CENTERIN;
985       else if (x < 1e-6) nofill = true;
986       }
987     }
988 
989   poly_flags &= ~POLY_INVERSE;
990   if(poly_flags & POLY_CENTERIN) {
991     poly_flags |= POLY_INVERSE;
992     if(abs(zlevel(tC0(p->V.T)) - 1) > 1e-6) nofill = true;
993 
994     /* nofill = true;
995     outline = (flags & POLY_CENTERIN) ? 0x00FF00FF : 0xFF0000FF;
996     addpoint(hscr); */
997     }
998 
999   /*
1000   if(poly_flags & POLY_BADCENTERIN) {
1001     glcoords.push_back(glhr::makevertex(hscr[0]+10, hscr[1]*pconf.stretch, hscr[2]));
1002     glcoords.push_back(glhr::makevertex(hscr[0], hscr[1]*pconf.stretch+10, hscr[2]));
1003     glcoords.push_back(glhr::makevertex(hscr[0]-10, hscr[1]*pconf.stretch, hscr[2]));
1004     glcoords.push_back(glhr::makevertex(hscr[0], hscr[1]*pconf.stretch-10, hscr[2]));
1005     glcoords.push_back(glhr::makevertex(hscr[0]+10, hscr[1]*pconf.stretch, hscr[2]));
1006     } */
1007   }
1008 
compute_side_by_area()1009 void compute_side_by_area() {
1010 
1011   double rarea = 0;
1012   for(int i=0; i<isize(glcoords)-1; i++)
1013     rarea += glcoords[i][0] * glcoords[i+1][1] - glcoords[i][1] * glcoords[i+1][0];
1014   rarea += glcoords.back()[0] * glcoords[0][1] - glcoords.back()[1] * glcoords[0][0];
1015 
1016   if(rarea>0)
1017     poly_flags ^= POLY_INVERSE;
1018   }
1019 
get_width(dqi_poly * p)1020 ld get_width(dqi_poly* p) {
1021   if((p->flags & POLY_FORCEWIDE) || pmodel == mdPixel)
1022     return p->linewidth;
1023   else if(p->flags & POLY_PRECISE_WIDE) {
1024     ld maxwidth = 0;
1025     for(int i=0; i<p->cnt; i++) {
1026       shiftpoint h1 = p->V * glhr::gltopoint((*p->tab)[p->offset+i]);
1027       maxwidth = max(maxwidth, linewidthat(h1));
1028       }
1029     return maxwidth * p->linewidth;
1030     }
1031   else
1032     return linewidthat(tC0(p->V)) * p->linewidth;
1033   }
1034 
debug_this()1035 void debug_this() { }
1036 
1037 glvertex junk = glhr::makevertex(0,0,1);
1038 
1039 EX namespace s2xe {
1040 
1041   int maxgen;
1042   bool with_zero;
1043   ld minz, maxy, miny;
1044 
1045   typedef array<ld, 5> pt;
1046 
1047   basic_textureinfo stinf;
1048 
lerp(const pt & h0,const pt & h1,ld x)1049   pt lerp(const pt& h0, const pt& h1, ld x) {
1050     pt s;
1051     for(int i=0; i<5; i++) s[i] = h0[i] + (h1[i]-h0[i]) * x;
1052     return s;
1053     }
1054 
add2(pt h,int gen)1055   void add2(pt h, int gen) {
1056     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])));
1057     stinf.tvertices.push_back(glhr::makevertex(h[3], h[4], 0));
1058     }
1059 
addall(pt h0,pt h1,pt h2)1060   void addall(pt h0, pt h1, pt h2) {
1061     for(int gen=-maxgen; gen <= maxgen; gen++) if(gen || with_zero) {
1062       add2(h0, gen);
1063       add2(h1, gen);
1064       add2(h2, gen);
1065       }
1066     }
1067 
1068   void draw_s2xe0(dqi_poly *p);
1069 
to_right(const pt & h2,const pt & h1)1070   bool to_right(const pt& h2, const pt& h1) {
1071     ld x2 = h2[0];
1072     ld x1 = h1[0];
1073     if(x2 < x1) x2 += 2 * M_PI;
1074     return x2 >= x2 && x2 <= x1 + M_PI;
1075     }
1076 
1077   EX int qrings = 32;
1078 
seg()1079   ld seg() { return 2 * M_PI / qrings; }
1080 
add_ortho_triangle(pt bl,pt tl,pt br,pt tr)1081   void add_ortho_triangle(pt bl, pt tl, pt br, pt tr) {
1082 
1083     auto sg = seg();
1084 
1085     int s0 = ceil(bl[0] / sg);
1086     int s1 = floor(br[0] / sg);
1087 
1088     pt bat[1000], tat[1000];
1089 
1090     bat[0] = bl; tat[0] = tl;
1091 
1092     int s = 1;
1093 
1094     for(int i = s0; i <= s1; i++) {
1095       ld f = (i*sg-bl[0]) / (br[0]-bl[0]);
1096 
1097       bat[s] = lerp(bl, br, f);
1098       tat[s] = lerp(tl, tr, f);
1099 
1100       s++;
1101       }
1102 
1103     bat[s] = br; tat[s] = tr;
1104 
1105     while(s--) {
1106       addall(bat[s], bat[s+1], tat[s+1]);
1107       addall(bat[s], tat[s+1], tat[s]);
1108       }
1109     }
1110 
add_ordered_triangle(array<pt,3> v)1111   void add_ordered_triangle(array<pt, 3> v) {
1112     if(v[1][0] < v[0][0]) v[1][0] += 2 * M_PI;
1113     if(v[2][0] < v[1][0]) v[2][0] += 2 * M_PI;
1114     if(v[2][0] - v[0][0] < 1e-6) return;
1115     ld x = (v[1][0] - v[0][0]) / (v[2][0] - v[0][0]);
1116 
1117     if(v[2][0] < v[0][0] + M_PI / 4 && maxy < M_PI - M_PI/4 && sightranges[geometry] <= 5) {
1118       addall(v[0], v[1], v[2]);
1119       return;
1120       }
1121 
1122     auto mv = lerp(v[0], v[2], x);
1123 
1124     add_ortho_triangle(v[0], v[0], mv, v[1]);
1125     add_ortho_triangle(mv, v[1], v[2], v[2]);
1126 
1127     /*
1128     int zl = floor(v[1][0] / seg());
1129     int zr = ceil(v[1][0] / seg());
1130     if(zl < zr && zl * seg > v[0][0] && zr * seg < v[2][0]) {
1131 
1132       ld fl = (zl*seg-v[0][0]) / (v[2][0]-v[0][0]);
1133       ld fr = (zr*seg-v[0][0]) / (v[2][0]-v[0][0]);
1134 
1135       addall(lerp(v[0], v[2], fl), v[1], lerp(v[0], v[2], fr));
1136       }
1137     */
1138 
1139     // add_ortho_triangle(v[0], tv[0], v[1], tv[1], v[2], tv[2], v[2], tv[2]);
1140     }
1141 
add_triangle_around(array<pt,3> v)1142   void add_triangle_around(array<pt, 3> v) {
1143     ld baseheight = (v[0][1] > M_PI/2) ? M_PI : 0;
1144     ld tu = (v[0][3] + v[1][3] + v[2][3]) / 3;
1145     ld tv = (v[0][4] + v[1][4] + v[2][4]) / 3;
1146     array<pt, 3> vhigh;
1147     for(int i=0; i<3; i++) { vhigh[i] = v[i]; vhigh[i][1] = baseheight; vhigh[i][3] = tu; vhigh[i][4] = tv; }
1148     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;
1149     add_ortho_triangle(v[0], vhigh[0], v[1], vhigh[1]);
1150     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;
1151     add_ortho_triangle(v[1], vhigh[1], v[2], vhigh[2]);
1152     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;
1153     add_ortho_triangle(v[2], vhigh[2], v[0], vhigh[0]);
1154     }
1155 
add_s2xe_triangle(array<pt,3> v)1156   void add_s2xe_triangle(array<pt, 3> v) {
1157     bool r0 = to_right(v[1], v[0]);
1158     bool r1 = to_right(v[2], v[1]);
1159     bool r2 = to_right(v[0], v[2]);
1160 
1161     minz = min(abs(v[0][2]), max(abs(v[1][2]), abs(v[2][2])));
1162     auto& s = sightranges[geometry];
1163     maxgen = sqrt(s * s - minz * minz) / (2 * M_PI) + 1;
1164 
1165     maxy = max(v[0][1], max(v[1][1], v[2][1]));
1166     miny = min(v[0][1], min(v[1][1], v[2][1]));
1167     with_zero = true;
1168     if(maxy < M_PI / 4) {
1169       add2(v[0], 0);
1170       add2(v[1], 0);
1171       add2(v[2], 0);
1172       with_zero = false;
1173       }
1174 
1175     rotated:
1176     if(r0 && r1 && r2) {
1177       add_triangle_around(v);
1178       }
1179     else if(r0 && r1) {
1180       add_ordered_triangle(v);
1181       }
1182     else if(r2 && !r0 && !r1) {
1183       add_ordered_triangle(make_array(v[2], v[1], v[0]));
1184       }
1185     else if(!r0 && !r1 && !r2) {
1186       add_triangle_around(make_array(v[2], v[1], v[0]));
1187       }
1188     else {
1189       tie(r0, r1, r2) = make_tuple(r1, r2, r0);
1190       tie(v[0], v[1], v[2]) = make_tuple(v[1], v[2], v[0]);
1191       goto rotated;
1192       }
1193     }
1194 
1195 #if CAP_GL
draw_s2xe(dqi_poly * p)1196 void draw_s2xe(dqi_poly *p) {
1197   if(!p->cnt) return;
1198   if(p->flags & POLY_TRIANGLES) {
1199     dqi_poly npoly = *p;
1200     npoly.offset = 0;
1201     npoly.tab = &glcoords;
1202     if(p->tinf) {
1203       npoly.tinf = p->tinf ? &stinf : NULL;
1204       npoly.offset_texture = 0;
1205       stinf.texture_id = p->tinf->texture_id;
1206       }
1207     else {
1208       npoly.tinf = NULL;
1209       }
1210     npoly.V = shiftless(Id);
1211     auto& pV = p->V.T;
1212     set_width(1);
1213     glcoords.clear();
1214     stinf.tvertices.clear();
1215     for(int i=0; i<p->cnt; i+=3) {
1216       array<pt, 3> v;
1217       for(int k=0; k<3; k++) {
1218         hyperpoint h = pV * glhr::gltopoint( (*p->tab)[p->offset+i+k]);
1219         v[k][2] = hypot_d(3, h);
1220 
1221         auto dp = product_decompose(h);
1222         v[k][2] = dp.first;
1223         v[k][0] = atan2(h[0], h[1]);
1224         v[k][1] = acos_auto_clamp(dp.second[2]);
1225         if(p->tinf) {
1226           auto& tv = p->tinf->tvertices[p->offset_texture+i+k];
1227           v[k][3] = tv[0];
1228           v[k][4] = tv[1];
1229           }
1230         }
1231       add_s2xe_triangle(v);
1232       }
1233     npoly.cnt = isize(glcoords);
1234     npoly.gldraw();
1235     }
1236   else draw_s2xe0(p);
1237   }
1238 #endif
1239 
1240 struct point_data {
1241   hyperpoint direction;
1242   ld distance;
1243   ld z;
1244   int bad;
1245   };
1246 
1247 #if CAP_GL
draw_s2xe0(dqi_poly * p)1248 void draw_s2xe0(dqi_poly *p) {
1249   if(!p->cnt) return;
1250   dqi_poly npoly = *p;
1251   npoly.offset = 0;
1252   npoly.tab = &glcoords;
1253   npoly.V = shiftless(Id);
1254   npoly.flags &= ~ (POLY_INVERSE | POLY_FORCE_INVERTED);
1255   set_width(1);
1256   glcoords.clear();
1257 
1258   int maxgen = sightranges[geometry] / (2 * M_PI) + 1;
1259 
1260   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]); };
1261   vector<point_data> pd;
1262   for(int i=0; i<p->cnt; i++) {
1263     hyperpoint h = p->V.T * glhr::gltopoint( (*p->tab)[p->offset+i]);
1264     pd.emplace_back();
1265     auto& next = pd.back();
1266     auto dp = product_decompose(h);
1267     next.direction = dp.second;
1268     next.z = dp.first;
1269     // next.tpoint = p->tinf ? p->tinf->tvertices[p->offset+i] : glvertex();
1270     ld hyp = hypot_d(2, next.direction);
1271     next.distance = acos_auto_clamp(next.direction[2]);
1272     if(hyp == 0) {
1273       next.direction = point2(1, 0);
1274       }
1275     else {
1276       next.direction[0] /= hyp;
1277       next.direction[1] /= hyp;
1278       }
1279     if(next.distance < 1e-3) next.bad = 1;
1280     else if(next.distance > M_PI - 1e-3) next.bad = 2;
1281     else next.bad = 0;
1282     }
1283 
1284   glcoords.resize(p->cnt);
1285   for(auto c: pd) if(c.bad == 2) return;
1286   bool no_gens = false;
1287   for(int i=0; i<p->cnt; i++) {
1288     auto &c1 = pd[i];
1289     auto &c0 = pd[i==0?p->cnt-1 : i-1];
1290     if(c1.distance > M_PI/2 && c0.distance > M_PI/2 && crossdot(c0.direction, c1.direction).second < 0) return;
1291     if(c1.bad == 2) return;
1292     if(c1.bad == 1) no_gens = true;
1293     }
1294 
1295   if(!no_gens) {
1296 
1297     vector<ld> angles(p->cnt);
1298     for(int i=0; i<p->cnt; i++) {
1299       angles[i] = atan2(pd[i].direction[1], pd[i].direction[0]);
1300       }
1301     sort(angles.begin(), angles.end());
1302     angles.push_back(angles[0] + 2 * M_PI);
1303     bool ok = false;
1304     for(int i=1; i<isize(angles); i++)
1305       if(angles[i] >= angles[i-1] + M_PI) ok = true;
1306     if(!ok) {
1307       for(auto &c: pd) if(c.distance > M_PI/2) return;
1308       no_gens = true;
1309       }
1310     }
1311 
1312   int g = no_gens ? 0 : maxgen;
1313 
1314   for(int gen=-g; gen<=g; gen++) {
1315     for(int i=0; i<p->cnt; i++) {
1316       auto& cur = pd[i];
1317       ld d = cur.distance + 2 * M_PI * gen;
1318       hyperpoint h;
1319       h[0] = cur.direction[0] * d;
1320       h[1] = cur.direction[1] * d;
1321       h[2] = cur.z;
1322       glcoords[i] = glhr::pointtogl(h);
1323       }
1324     npoly.gldraw();
1325     }
1326   }
1327 #endif
1328 EX }
1329 
1330 EX int berger_limit = 2;
1331 
draw_stretch(dqi_poly * p)1332 void draw_stretch(dqi_poly *p) {
1333 
1334   dqi_poly npoly = *p;
1335 
1336   npoly.offset = 0;
1337   npoly.tab = &glcoords;
1338   npoly.V = shiftless(Id);
1339   npoly.flags &= ~(POLY_INVERSE | POLY_FORCE_INVERTED);
1340 
1341   transmatrix T2 = stretch::translate( tC0(iso_inverse(View)) );
1342   transmatrix U = View * T2;
1343 
1344   transmatrix iUV = iso_inverse(U) * p->V.T;
1345 
1346   vector<hyperpoint> hs;
1347   vector<hyperpoint> ths;
1348   hs.resize(p->cnt);
1349   ths.resize(p->cnt);
1350   for(int i=0; i<p->cnt; i++)
1351     hs[i] = iUV * glhr::gltopoint( (*p->tab)[p->offset+i] );
1352 
1353   vector<vector<hyperpoint> > results;
1354   results.resize(p->cnt);
1355 
1356   auto& stinf = s2xe::stinf;
1357 
1358   if(p->tinf) {
1359     npoly.tinf = &stinf;
1360     npoly.offset_texture = 0;
1361     stinf.texture_id = p->tinf->texture_id;
1362     stinf.tvertices.clear();
1363     }
1364   else {
1365     npoly.tinf = NULL;
1366     }
1367   npoly.V = shiftless(Id);
1368   set_width(1);
1369   glcoords.clear();
1370 
1371   for(int i=0; i<p->cnt; i++) results[i] = stretch::inverse_exp_all(hs[i], berger_limit);
1372 
1373   auto test = [] (hyperpoint a, hyperpoint b) -> bool {
1374     return sqhypot_d(3, a-b) < 2;
1375     };
1376 
1377   #if CAP_GL
1378   if(p->flags & POLY_TRIANGLES) {
1379     for(int i=0; i<p->cnt; i+=3) {
1380       auto &la = results[i];
1381       auto &lb = results[i+1];
1382       auto &lc = results[i+2];
1383 
1384       int ia = 0, ib = 0, ic = 0;
1385 
1386       for(auto& ha: la) for(auto& hb: lb) if(test(ha, hb))
1387         for(auto& hc: lc) if(test(ha, hc) && test(hb, hc)) {
1388 
1389         glcoords.push_back(glhr::pointtogl(U * ha));
1390         glcoords.push_back(glhr::pointtogl(U * hb));
1391         glcoords.push_back(glhr::pointtogl(U * hc));
1392         if(p->tinf)
1393           for(int j=0; j<3; j++)
1394             stinf.tvertices.push_back(p->tinf->tvertices[p->offset_texture+i+j]);
1395         ia++; ib++; ic++;
1396         }
1397       }
1398     npoly.cnt = isize(glcoords);
1399     npoly.gldraw();
1400     }
1401   else if(p->cnt) {
1402     for(auto& ha: results[0]) {
1403       vector<hyperpoint> has;
1404       has.push_back(ha);
1405       glcoords.push_back(glhr::pointtogl(U * ha));
1406       for(int i=1; i<p->cnt; i++) {
1407         hyperpoint best = C0;
1408         ld dist = 10;
1409         for(auto& hb: results[i]) {
1410           ld d = sqhypot_d(3, hb-has.back());
1411           if(d < dist) dist = d, best = hb;
1412           }
1413         if(dist < 2) has.push_back(best);
1414         }
1415       if(isize(has) < 3) continue;
1416       glcoords.clear();
1417       for(auto& h: has) glcoords.push_back(glhr::pointtogl(U * h));
1418       npoly.cnt = isize(glcoords);
1419       npoly.gldraw();
1420       }
1421     }
1422   #endif
1423   }
1424 
1425 EX namespace ods {
1426 #if CAP_ODS
1427 
project(hyperpoint h,hyperpoint & h1,hyperpoint & h2,bool eye)1428   EX bool project(hyperpoint h, hyperpoint& h1, hyperpoint& h2, bool eye) {
1429     ld tanalpha = tan_auto(vid.ipd/2);
1430     if(eye) tanalpha = -tanalpha;
1431     if(!sphere) tanalpha = -tanalpha;
1432 
1433     ld& x = h[0];
1434     ld z = -h[1];
1435     ld y = -h[2];
1436     ld& t = h[3];
1437 
1438     ld y02 = (x*x + y*y - tanalpha*tanalpha*t*t);
1439     if(y02 < 0) return false;
1440     ld y0 = sqrt(y02);
1441     ld theta = atan(z / y0);
1442 
1443     for(int i=0; i<2; i++) {
1444       hyperpoint& h = (i ? h1 : h2);
1445       if(i == 1) theta = -theta, y0 = -y0;
1446 
1447       ld x0 = t * tanalpha;
1448 
1449       ld phi = atan2(y, x) - atan2(y0, x0) + M_PI;
1450 
1451       ld delta;
1452       if(euclid) delta = hypot(y0, z);
1453       else if(sphere) delta = atan2_auto(z / sin(theta), t / cos_auto(vid.ipd/2));
1454       else {
1455         // ld delta = euclid ? hypot(y0,z) : atan2_auto(z / sin(theta), t / cos_auto(vid.ipd/2));
1456         ld p = z / sin(theta) / t * cos_auto(vid.ipd / 2);
1457         delta = (p > 1) ? 13 : (p < -1) ? -13 : atanh(p);
1458         }
1459 
1460       if(euclid || hyperbolic) phi -= M_PI;
1461       if(hyperbolic) delta = -delta;
1462 
1463       h[0] = phi;
1464       h[1] = theta;
1465       h[2] = delta;
1466       if(euclid || hyperbolic) h[1] = -theta;
1467       }
1468 
1469     return true;
1470     }
1471 
draw_ods(dqi_poly * p)1472   void draw_ods(dqi_poly *p) {
1473     auto& stinf = s2xe::stinf;
1474 
1475     if(!p->cnt) return;
1476     if(!(p->flags & POLY_TRIANGLES)) return;
1477 
1478     dqi_poly npoly = *p;
1479     npoly.offset = 0;
1480     npoly.tab = &glcoords;
1481     npoly.V = Id;
1482     npoly.tinf = p->tinf ? &stinf : NULL;
1483     if(npoly.tinf) {
1484       npoly.offset_texture = 0;
1485       stinf.texture_id = p->tinf->texture_id;
1486       stinf.tvertices.clear();
1487       }
1488     npoly.V = Id;
1489     glcoords.clear();
1490 
1491     array<hyperpoint, 6> h;
1492 
1493     if(0) for(int i=0; i<p->cnt; i+=3) {
1494       for(int j=0; j<3; j++)
1495         h[j] = p->V * glhr::gltopoint((*p->tab)[p->offset+i+j]);
1496 
1497       for(int j=0; j<3; j++) {
1498         glcoords.push_back(glhr::makevertex(h[j][0], h[j][1], h[j][2]));
1499         if(npoly.tinf) stinf.tvertices.push_back(p->tinf->tvertices[i+j]);
1500         }
1501       }
1502 
1503     if(1) for(int i=0; i<p->cnt; i+=3) {
1504 
1505       for(int j=0; j<3; j++) {
1506         hyperpoint o = p->V * glhr::gltopoint((*p->tab)[p->offset+i+j]);
1507         if(nonisotropic || prod) {
1508           o = lp_apply(inverse_exp(o, iTable, false));
1509           o[3] = 1;
1510           dynamicval<eGeometry> g(geometry, gEuclid);
1511           if(!project(o, h[j], h[j+3], global_projection == -1))
1512             goto next_i;
1513           }
1514         else if(!project(o, h[j], h[j+3], global_projection == -1))
1515           goto next_i;
1516         }
1517 
1518       for(int j=0; j<6; j++) {
1519         // let Delta be from 0 to 2PI
1520         if(h[j][2]<0) h[j][2] += 2 * M_PI;
1521         // Theta is from -PI/2 to PI/2. Let it be from 0 to PI
1522         h[j][1] += global_projection * M_PI/2;
1523         h[j][3] = 1;
1524         }
1525 
1526       /* natsph here */
1527 
1528       if(h[0][2] < 0) swap(h[0], h[3]);
1529       if(h[1][2] < 0) swap(h[1], h[4]);
1530       if(h[2][2] < 0) swap(h[2], h[5]);
1531 
1532       cyclefix(h[0][0], 0);
1533       cyclefix(h[1][0], h[0][0]);
1534       cyclefix(h[2][0], h[0][0]);
1535       cyclefix(h[3][0], 0);
1536       cyclefix(h[4][0], h[3][0]);
1537       cyclefix(h[5][0], h[3][0]);
1538 
1539       if(abs(h[1][1] - h[0][1]) > M_PI/2) goto next_i;
1540       if(abs(h[2][1] - h[0][1]) > M_PI/2) goto next_i;
1541 
1542       if(h[0][0] < -M_PI || h[0][0] > M_PI) println(hlog, h[0][0]);
1543 
1544       if(1) {
1545         int fst = 0, lst = 0;
1546         if(h[1][0] < -M_PI || h[2][0] < -M_PI) lst++;
1547         if(h[1][0] > +M_PI || h[2][0] > +M_PI) fst--;
1548         for(int x=fst; x<=lst; x++) for(int j=0; j<3; j++) {
1549           glcoords.push_back(glhr::makevertex(h[j][0] + 2 * M_PI * x, h[j][1], h[j][2]));
1550           if(npoly.tinf) stinf.tvertices.push_back(p->tinf->tvertices[p->offset_texture+i+j]);
1551           }
1552         }
1553 
1554       /* natsph here */
1555 
1556       next_i: ;
1557       }
1558 
1559     npoly.cnt = isize(glcoords);
1560     // npoly.color = 0xFFFFFFFF;
1561     npoly.gldraw();
1562     }
1563 #endif
1564   EX }
1565 
1566 /** @brief render in a broken projection; return false if normal rendering is not applicable */
broken_projection(dqi_poly & p0)1567 bool broken_projection(dqi_poly& p0) {
1568   int broken_coord = models::get_broken_coord(pmodel);
1569   static bool in_broken = false;
1570   if(broken_coord && !in_broken) {
1571 
1572     int zcoord = broken_coord;
1573     int ycoord = 3 - zcoord;
1574 
1575     vector<hyperpoint> all;
1576     for(int i=0; i<p0.cnt; i++)
1577       all.push_back(p0.V.T * glhr::gltopoint((*p0.tab)[p0.offset+i]));
1578     int fail = 0;
1579     int last_fail;
1580 
1581     for(auto& h: all) models::apply_orientation(h[0], h[1]);
1582 
1583     auto break_in = [&] (hyperpoint a, hyperpoint b) {
1584       return a[0] * b[0] <= 0 && (a[0] * b[zcoord] - b[0] * a[zcoord]) * (a[0] - b[0]) < 0;
1585       };
1586 
1587     for(int i=0; i<p0.cnt-1; i++)
1588       if(break_in(all[i], all[i+1]))
1589         last_fail = i, fail++;
1590     vector<glvertex> v;
1591     dqi_poly p = p0;
1592     p.tab = &v;
1593     p.offset = 0;
1594     p.V.T = Id;
1595 
1596     /* we don't rotate h's back, just change p.V */
1597     for(int i=0; i<3; i++)
1598       models::apply_orientation(p.V.T[i][0], p.V.T[i][1]);
1599 
1600     if(fail) {
1601       if(p0.tinf) return true;
1602       dynamicval<bool> ib(in_broken, true);
1603       ld part = ilerp(all[last_fail][0], all[last_fail+1][0], 0);
1604       hyperpoint initial = normalize(lerp(all[last_fail], all[last_fail+1], 1 - (1-part) * .99));
1605       bool have_initial = true;
1606       v.push_back(glhr::pointtogl(initial));
1607       last_fail++;
1608       int at = last_fail;
1609       do {
1610         v.push_back(glhr::pointtogl(all[at]));
1611         if(at == p0.cnt-1 && all[at] != all[0]) {
1612           p.cnt = isize(v); p.draw(); v.clear(); at = 0;
1613           have_initial = false;
1614           }
1615         int next = at+1;
1616         if(next == p0.cnt) next = 0;
1617         if(break_in(all[at], all[next])) {
1618           ld part = ilerp(all[at][0], all[next][0], 0);
1619           hyperpoint final = normalize(lerp(all[at], all[next], part * .99));
1620           v.push_back(glhr::pointtogl(final));
1621           if(have_initial) {
1622             int max = 4 << vid.linequality;
1623             if(final[0] * initial[0] > 0) {
1624               for(int i=1; i<=max; i++)
1625                 v.push_back(glhr::pointtogl(lerp(final, initial, i * 1. / max)));
1626               }
1627             else {
1628               hyperpoint end = Hypc;
1629               end[ycoord] = final[ycoord] > 0 ? 1 : -1;
1630               for(int i=1; i<=max; i++)
1631                 v.push_back(glhr::pointtogl(lerp(final, end, i * 1. / max)));
1632               for(int i=1; i<=max; i++)
1633                 v.push_back(glhr::pointtogl(lerp(end, initial, i * 1. / max)));
1634               }
1635             }
1636           p.cnt = isize(v); p.draw(); v.clear();
1637           initial = normalize(lerp(all[at], all[next], 1 - (1-part) * .99));
1638           have_initial = true;
1639           v.push_back(glhr::pointtogl(initial));
1640           }
1641         at = next;
1642         }
1643       while(at != last_fail);
1644       return true;
1645       }
1646     }
1647   return false;
1648   }
1649 
draw()1650 void dqi_poly::draw() {
1651   if(flags & POLY_DEBUG) debug_this();
1652 
1653   if(debugflags & DF_VERTEX) {
1654     println(hlog, int(prio), ": V=", V, " o=", offset, " c=", cnt, " ot=", offset_texture, " ol=", outline, " lw=", linewidth, " f=", flags, " i=", intester, " c=", cache, " ti=", (cell*) tinf);
1655     for(int i=0; i<cnt; i++) print(hlog, (*tab)[offset+i]);
1656     println(hlog);
1657     }
1658 
1659   #if CAP_ODS
1660   if(vid.stereo_mode == sODS) {
1661     ods::draw_ods(this);
1662     return;
1663     }
1664   #endif
1665 
1666   #if CAP_GL
1667   if(in_s2xe() && vid.usingGL && pmodel == mdPerspective && (current_display->set_all(global_projection, 0), (get_shader_flags() & SF_DIRECT))) {
1668     s2xe::draw_s2xe(this);
1669     return;
1670     }
1671   #endif
1672 
1673   if(!hyperbolic && among(pmodel, mdPolygonal, mdPolynomial)) {
1674     bool any = false;
1675     for(int i=0; i<cnt; i++) {
1676       hyperpoint h1 = V.T * glhr::gltopoint((*tab)[offset+i]);
1677       if(h1[2] > 0) any = true;
1678       }
1679     if(!any) return;
1680     }
1681 
1682   if(sphere && tinf && GDIM == 2 && cnt > 3) {
1683     int i = cnt;
1684     cnt = 3;
1685     for(int j=0; j<i; j+=3) {
1686       offset += j;
1687       offset_texture += j;
1688       draw();
1689       offset -= j;
1690       offset_texture -= j;
1691       }
1692     cnt = i;
1693     return;
1694     }
1695 
1696   if(broken_projection(*this)) return;
1697 
1698   if(sphere && pmodel == mdTwoPoint && !in_twopoint) {
1699     #define MAX_PHASE 4
1700     vector<glvertex> phases[MAX_PHASE];
1701     extern int twopoint_sphere_flips;
1702     extern bool twopoint_do_flips;
1703     int pha;
1704     if(twopoint_do_flips) {
1705       for(int i=0; i<cnt; i++) {
1706         shiftpoint h1 = V * glhr::gltopoint((*tab)[offset+i]);
1707         for(int j=0; j<MAX_PHASE; j++) {
1708           twopoint_sphere_flips = j;
1709           hyperpoint h2; applymodel(h1, h2);
1710           glvertex h = glhr::pointtogl(h2 * current_display->radius); h[1] *= pconf.stretch;
1711           if(i == 0)
1712             phases[j].push_back(h);
1713           else {
1714             int best = -1;
1715             ld bhypot = 1e60;
1716             for(int j0=0; j0<MAX_PHASE; j0++)
1717               if(isize(phases[j0]) == i) {
1718                 ld chypot = glhypot2(phases[j0].back(), h);
1719                 if(chypot < bhypot || best == -1) bhypot = chypot, best = j0;
1720                 }
1721             phases[best].push_back(h);
1722             }
1723           }
1724         }
1725       twopoint_sphere_flips = 0;
1726       pha = MAX_PHASE-1;
1727       }
1728     else {
1729       pha = 1;
1730       if(true) {
1731         // a
1732         // b
1733         // lin(a,b) is of form (x, 0, z)
1734         int cpha = 0;
1735         for(int i=0; i<cnt; i++) {
1736 
1737           shiftpoint h1 = V * glhr::gltopoint((*tab)[offset+i]);
1738           hyperpoint mh1; applymodel(h1, mh1); mh1[1] *= pconf.stretch;
1739           phases[cpha].push_back(glhr::pointtogl(mh1 * current_display->radius));
1740 
1741           // check if the i-th edge intersects the boundary of the ellipse
1742           // (which corresponds to the segment between the antipodes of foci)
1743           // if yes, switch cpha to the opposite
1744           shiftpoint h2 = V * glhr::gltopoint((*tab)[offset+(i+1)%cnt]);
1745 
1746           hyperpoint ah1 = h1.h, ah2 = h2.h;
1747           models::apply_orientation(ah1[0], ah1[1]);
1748           models::apply_orientation(ah2[0], ah2[1]);
1749           if(ah1[1] * ah2[1] > 0) continue;
1750           ld c1 = ah1[1], c2 = -ah2[1];
1751           if(c1 < 0) c1 = -c1, c2 = -c2;
1752           hyperpoint h = ah1 * c1 + ah2 * c2;
1753           h /= hypot_d(3, h);
1754           if(h[2] < 0 && abs(h[0]) < sin(pconf.twopoint_param)) cpha = 1-cpha, pha = 2;
1755           }
1756         if(cpha == 1) pha = 0;
1757         }
1758       }
1759     dynamicval<eModel> d1(pmodel, mdPixel);
1760     dynamicval<transmatrix> d2(V.T, Id);
1761     dynamicval<int> d3(offset, 0);
1762     dynamicval<decltype(tab)> d4(tab, tab);
1763     for(int j=0; j<pha; j++) {
1764       dynamicval<int> d5(cnt, isize(phases[j]));
1765       tab = &phases[j];
1766       draw();
1767       }
1768     return;
1769     }
1770 
1771   /* if(spherespecial && prio == PPR::MOBILE_ARROW) {
1772     if(spherephase == 0) return;
1773     dynamicval<int> ss(spherespecial, 0);
1774     draw();
1775     return;
1776     } */
1777 
1778   #if CAP_GL
1779   if(vid.usingGL && (current_display->set_all(global_projection, V.shift), get_shader_flags() & SF_DIRECT) && sphere && (stretch::factor || ray::in_use)) {
1780     draw_stretch(this);
1781     return;
1782     }
1783   #endif
1784 
1785 #if CAP_GL
1786   if(vid.usingGL && (current_display->set_all(global_projection, V.shift), get_shader_flags() & SF_DIRECT)) {
1787     if(sl2 && pmodel == mdGeodesic && hybrid::csteps) {
1788       ld z = atan2(V.T[2][3], V.T[3][3]) + V.shift;
1789       auto zr = sightranges[geometry];
1790       ld ns = stretch::not_squared();
1791       ld db = cgi.psl_steps / M_PI / ns / hybrid::csteps;
1792 
1793       min_slr = floor((-zr - z) * db);
1794       max_slr = ceil((zr - z) * db);
1795       if(min_slr > max_slr) return;
1796       if(flags & POLY_ONE_LEVEL) min_slr = max_slr = 0;
1797       max_slr++;
1798       }
1799     else min_slr = 0, max_slr = 0;
1800     set_width(get_width(this));
1801     flags &= ~POLY_INVERSE;
1802     gldraw();
1803     return;
1804     }
1805 #endif
1806 
1807   glcoords.clear();
1808   poly_flags = flags;
1809 
1810   double d = 0, curradius = 0;
1811   if(sphere) {
1812     d = det(V.T);
1813     curradius = pow(abs(d), 1/3.);
1814     }
1815 
1816   /* outline = 0x80808080;
1817   color = 0; */
1818 
1819   last_infront = false;
1820 
1821   addpoly(V, *tab, offset, cnt);
1822   if(!(sphere && pconf.alpha < .9)) if(pmodel != mdJoukowsky) if(!(flags & POLY_ALWAYS_IN)) for(int i=1; i<isize(glcoords); i++) {
1823     ld dx = glcoords[i][0] - glcoords[i-1][0];
1824     ld dy = glcoords[i][1] - glcoords[i-1][1];
1825     if(dx > vid.xres * 2 || dy > vid.yres * 2) return;
1826     }
1827   if(poly_flags & POLY_BEHIND) return;
1828   if(isize(glcoords) <= 1) return;
1829 
1830   cyl::loop_min = cyl::loop_max = 0;
1831   if(sphere && mdBandAny())
1832     cyl::adjust(tinf);
1833 
1834   int poly_limit = max(vid.xres, vid.yres) * 2;
1835 
1836 
1837   if(0) for(auto& p: glcoords) {
1838     if(abs(p[0]) > poly_limit || abs(p[1]) > poly_limit)
1839       return; // too large!
1840     }
1841 
1842   bool equi = mdAzimuthalEqui() || pmodel == mdFisheye;
1843 
1844   bool nofill = false;
1845 
1846   if(poly_flags & POLY_NIF_ERROR) return;
1847 
1848   if(spherespecial == 1 && sphere && (poly_flags & POLY_INFRONT) && (poly_flags & POLY_NOTINFRONT) && pconf.alpha <= 1) {
1849     bool around_center = false;
1850     for(int i=0; i<isize(glcoords)-1; i++) {
1851       double x1 = glcoords[i][0];
1852       double y1 = glcoords[i][1];
1853       double x2 = glcoords[i+1][0];
1854       double y2 = glcoords[i+1][1];
1855       if(asign(y1, y2)) {
1856         ld x = xcross(x1, y1, x2, y2);
1857         if(x < -1e-6) around_center = !around_center;
1858         }
1859       }
1860     if(around_center) return;
1861     }
1862 
1863   bool can_have_inverse = false;
1864   if(sphere && pmodel == mdDisk && (spherespecial > 0 || equi)) can_have_inverse = true;
1865   if(vrhr::rendering()) can_have_inverse = false;
1866   if(sphere && among(pmodel, mdEquidistant, mdEquiarea)) can_have_inverse = true;
1867   if(pmodel == mdJoukowsky) can_have_inverse = true;
1868   if(pmodel == mdJoukowskyInverted && pconf.skiprope) can_have_inverse = true;
1869   if(pmodel == mdDisk && hyperbolic && pconf.alpha <= -1) can_have_inverse = true;
1870   if(pmodel == mdSpiral && pconf.skiprope) can_have_inverse = true;
1871   if(pmodel == mdCentralInversion) can_have_inverse = true;
1872 
1873   if(can_have_inverse && !(poly_flags & POLY_ISSIDE)) {
1874 
1875     if(!tinf)
1876       compute_side_by_centerin(this, nofill);
1877 
1878     else {
1879       if(d < 0) poly_flags ^= POLY_INVERSE;
1880       if(pmodel == mdCentralInversion) poly_flags ^= POLY_INVERSE;
1881       compute_side_by_area();
1882       }
1883 
1884     if(poly_flags & POLY_INVERSE) {
1885       if(curradius < pconf.alpha - 1e-6) return;
1886       if(!sphere) return;
1887       }
1888 
1889     }
1890   else poly_flags &=~ POLY_INVERSE;
1891 
1892   if(spherespecial) {
1893     if(!(poly_flags & POLY_INFRONT)) return;
1894     }
1895 
1896   int lastl = 0;
1897 
1898   for(int l=cyl::loop_min; l <= cyl::loop_max; l++) {
1899 
1900     if(l || lastl) {
1901       for(int i=0; i<isize(glcoords); i++) {
1902         glcoords[i][0] += models::ocos * cyl::periods[i] * (l - lastl);
1903         glcoords[i][1] += models::osin * cyl::periods[i] * (l - lastl);
1904         }
1905       lastl = l;
1906       }
1907 
1908     if(equi && (poly_flags & POLY_INVERSE)) {
1909       if(abs(zlevel(V.T * C0) - 1) < 1e-6 && !tinf) {
1910         // we should fill the other side
1911         ld h = atan2(glcoords[0][0], glcoords[0][1]);
1912         for(int i=0; i<=360; i++) {
1913           ld a = i * degree + h;
1914           glcoords.push_back(glhr::makevertex(current_display->radius * sin(a), current_display->radius * pconf.stretch * cos(a), 0));
1915           }
1916         poly_flags ^= POLY_INVERSE;
1917         }
1918       else {
1919         // If we are on a zlevel, the algorithm above will not work correctly.
1920         // It is hard to tell what to do in this case. Just fill neither side
1921         nofill = true;
1922         }
1923       }
1924 
1925   #if CAP_GL
1926     if(vid.usingGL) {
1927       poly_flags &= ~(POLY_VCONVEX | POLY_CCONVEX);
1928       // if(pmodel == 0) for(int i=0; i<qglcoords; i++) glcoords[i][2] = current_display->scrdist;
1929       if(tinf && (poly_flags & POLY_INVERSE)) {
1930         return;
1931         }
1932       set_width(get_width(this));
1933       dqi_poly npoly = (*this);
1934       npoly.V = shiftless(Id, V.shift);
1935       npoly.tab = &glcoords;
1936       npoly.offset = 0;
1937       npoly.cnt = isize(glcoords);
1938       if(nofill) npoly.color = 0, npoly.tinf = NULL;
1939       npoly.flags = poly_flags;
1940       npoly.gldraw();
1941       continue;
1942       }
1943   #endif
1944 
1945   #if CAP_SVG
1946     if(svg::in) {
1947       coords_to_poly();
1948       color_t col = color;
1949       if(poly_flags & POLY_INVERSE) col = 0;
1950       if(poly_flags & POLY_TRIANGLES) {
1951         for(int i=0; i<polyi; i+=3)
1952           svg::polygon(polyx+i, polyy+i, 3, col, outline, get_width(this));
1953         }
1954       else
1955         svg::polygon(polyx, polyy, polyi, col, outline, get_width(this));
1956       continue;
1957       }
1958   #endif
1959 
1960     coords_to_poly();
1961 
1962   #if CAP_XGD
1963     gdpush(1); gdpush(color); gdpush(outline); gdpush(polyi);
1964     for(int i=0; i<polyi; i++) gdpush(polyx[i]), gdpush(polyy[i]);
1965   #elif CAP_SDLGFX
1966 
1967     if(tinf) {
1968       #if CAP_TEXTURE
1969       if(!(poly_flags & POLY_INVERSE))
1970         for(int i=0; i<polyi; i += 3)
1971           drawTexturedTriangle(s, polyx+i, polyy+i, &tinf->tvertices[offset_texture + i], color);
1972       #endif
1973       }
1974     else if(poly_flags & POLY_INVERSE) {
1975       int i = polyi;
1976       if(true) {
1977         polyx[i] = 0; polyy[i] = 0; i++;
1978         polyx[i] = vid.xres; polyy[i] = 0; i++;
1979         polyx[i] = vid.xres; polyy[i] = vid.yres; i++;
1980         polyx[i] = 0; polyy[i] = vid.yres; i++;
1981         polyx[i] = 0; polyy[i] = 0; i++;
1982         }
1983       filledPolygonColorI(srend, polyx, polyy, polyi+5, color);
1984       }
1985     else if(poly_flags & POLY_TRIANGLES) {
1986       for(int i=0; i<polyi; i+=3)
1987       filledPolygonColorI(srend, polyx+i, polyy+i, 3, color);
1988       }
1989     else
1990       filledPolygonColorI(srend, polyx, polyy, polyi, color);
1991 
1992     if(current_display->stereo_active()) filledPolygonColorI(auxrend, polyxr, polyy, polyi, color);
1993 
1994     ((vid.antialias & AA_NOGL) ?aapolylineColor:polylineColor)(srend, polyx, polyy, polyi, outline);
1995     if(current_display->stereo_active()) aapolylineColor(auxrend, polyxr, polyy, polyi, outline);
1996 
1997     if(vid.xres >= 2000 || fatborder) {
1998       int xmi = 3000, xma = -3000;
1999       for(int t=0; t<polyi; t++) xmi = min(xmi, polyx[t]), xma = max(xma, polyx[t]);
2000 
2001       if(xma > xmi + 20) for(int x=-1; x<2; x++) for(int y=-1; y<=2; y++) if(x*x+y*y == 1) {
2002         for(int t=0; t<polyi; t++) polyx[t] += x, polyy[t] += y;
2003         aapolylineColor(srend, polyx, polyy, polyi, outline);
2004         for(int t=0; t<polyi; t++) polyx[t] -= x, polyy[t] -= y;
2005         }
2006       }
2007   #endif
2008     }
2009   }
2010 
2011 vector<glvertex> prettylinepoints;
2012 
prettypoint(const hyperpoint & h)2013 EX void prettypoint(const hyperpoint& h) {
2014   prettylinepoints.push_back(glhr::pointtogl(h));
2015   }
2016 
prettylinesub(const hyperpoint & h1,const hyperpoint & h2,int lev)2017 EX void prettylinesub(const hyperpoint& h1, const hyperpoint& h2, int lev) {
2018   if(lev >= 0 && pmodel != mdPixel) {
2019     hyperpoint h3 = midz(h1, h2);
2020     prettylinesub(h1, h3, lev-1);
2021     prettylinesub(h3, h2, lev-1);
2022     }
2023   else prettypoint(h2);
2024   }
2025 
prettyline(hyperpoint h1,hyperpoint h2,ld shift,color_t col,int lev,int flags,PPR prio)2026 EX void prettyline(hyperpoint h1, hyperpoint h2, ld shift, color_t col, int lev, int flags, PPR prio) {
2027   prettylinepoints.clear();
2028   prettypoint(h1);
2029   prettylinesub(h1, h2, lev);
2030   dqi_poly ptd;
2031   ptd.V = shiftless(Id, shift);
2032   ptd.tab = &prettylinepoints;
2033   ptd.offset = 0;
2034   ptd.cnt = isize(prettylinepoints);
2035   ptd.linewidth = vid.linewidth;
2036   ptd.color = 0;
2037   ptd.outline = col;
2038   ptd.flags = POLY_ISSIDE | POLY_PRECISE_WIDE | flags;
2039   ptd.tinf = NULL;
2040   ptd.intester = C0;
2041   ptd.prio = prio;
2042   ptd.draw();
2043   }
2044 
prettypoly(const vector<hyperpoint> & t,color_t fillcol,color_t linecol,int lev)2045 EX void prettypoly(const vector<hyperpoint>& t, color_t fillcol, color_t linecol, int lev) {
2046   prettylinepoints.clear();
2047   prettypoint(t[0]);
2048   for(int i=0; i<isize(t); i++)
2049     prettylinesub(t[i], t[(i+1)%3], lev);
2050   dqi_poly ptd;
2051   ptd.V = shiftless(Id);
2052   ptd.tab = &prettylinepoints;
2053   ptd.offset = 0;
2054   ptd.cnt = isize(prettylinepoints);
2055   ptd.linewidth = vid.linewidth;
2056   ptd.color = fillcol;
2057   ptd.outline = linecol;
2058   ptd.flags = POLY_ISSIDE | POLY_PRECISE_WIDE;
2059   ptd.tinf = NULL;
2060   ptd.intester = C0;
2061   ptd.draw();
2062   }
2063 
2064 vector<glvertex> curvedata;
2065 int curvestart = 0;
2066 bool keep_curvedata = false;
2067 
queuereset(eModel m,PPR prio)2068 EX void queuereset(eModel m, PPR prio) {
2069   queueaction(prio, [m] () { glflush(); pmodel = m; });
2070   }
2071 
draw()2072 void dqi_line::draw() {
2073   dynamicval<ld> d(vid.linewidth, width);
2074   prettyline(H1.h, unshift(H2, H1.shift), H1.shift, color, prf, 0, prio);
2075   }
2076 
draw()2077 void dqi_string::draw() {
2078   #if CAP_SVG
2079   if(svg::in) {
2080     svg::text(x, y, size, str, frame, color, align);
2081     return;
2082     }
2083   #endif
2084   #if !ISMOBILE
2085   int fr = frame & 255;
2086   displayfrSP(x, y, shift, fr, size, str, color, align, frame >> 8);
2087   #else
2088   displayfr(x, y, frame, size, str, color, align);
2089   #endif
2090   }
2091 
draw()2092 void dqi_circle::draw() {
2093   #if CAP_SVG
2094   if(svg::in) {
2095     svg::circle(x, y, size, color, fillcolor, linewidth);
2096     }
2097   else
2098   #endif
2099   drawCircle(x, y, size, color, fillcolor);
2100   }
2101 
initquickqueue()2102 EX void initquickqueue() {
2103   ptds.clear();
2104   poly_outline = OUTLINE_NONE;
2105   }
2106 
sortquickqueue()2107 EX void sortquickqueue() {
2108   for(int i=1; i<isize(ptds);)
2109     if(i && ptds[i]->prio < ptds[i-1]->prio) {
2110       swap(ptds[i], ptds[i-1]);
2111       i--;
2112       }
2113     else i++;
2114   }
2115 
quickqueue()2116 EX void quickqueue() {
2117   current_display->next_shader_flags = 0;
2118   spherespecial = 0;
2119   reset_projection(); current_display->set_all(0, 0);
2120   int siz = isize(ptds);
2121   for(int i=0; i<siz; i++) ptds[i]->draw();
2122   ptds.clear();
2123   if(!keep_curvedata) {
2124     curvedata.clear();
2125     finf.tvertices.clear();
2126     curvestart = 0;
2127     }
2128   }
2129 
2130 /* todo */
xintval(const shiftpoint & h)2131 ld xintval(const shiftpoint& h) {
2132   if(sphereflipped()) return -h.h[2];
2133   if(hyperbolic) return -h.h[2];
2134   return -intval(h.h, C0);
2135   }
2136 
2137 EX ld backbrightness = .25;
2138 
2139 EX purehookset hooks_drawqueue;
2140 
2141 constexpr int PMAX = int(PPR::MAX);
2142 int qp[PMAX], qp0[PMAX];
2143 
darken_color(color_t & color,bool outline)2144 color_t darken_color(color_t& color, bool outline) {
2145   int alpha = color & 255;
2146   if(sphere && pmodel == mdDisk && pconf.alpha <= 1)
2147     return 0;
2148   else {
2149     if(outline && alpha < 255)
2150       return color - alpha + int(backbrightness * alpha);
2151     else
2152       return (gradient(modelcolor>>8, color>>8, 0, backbrightness, 1)<<8) | 0xFF;
2153     }
2154   }
2155 
draw_back()2156 void dqi_poly::draw_back() {
2157   dynamicval<color_t> dvo(outline, darken_color(outline, true));
2158   dynamicval<color_t> dvc(color, darken_color(color, false));
2159   draw();
2160   }
2161 
draw_back()2162 void dqi_line::draw_back() {
2163   dynamicval<color_t> dvc(color, darken_color(color, true));
2164   draw();
2165   }
2166 
sort_drawqueue()2167 EX void sort_drawqueue() {
2168   DEBBI(DF_GRAPH, ("sort_drawqueue"));
2169 
2170   for(int a=0; a<PMAX; a++) qp[a] = 0;
2171 
2172   int siz = isize(ptds);
2173 
2174   #if MINIMIZE_GL_CALLS
2175   map<color_t, vector<unique_ptr<drawqueueitem>>> subqueue;
2176   for(auto& p: ptds) subqueue[(p->prio == PPR::CIRCLE || p->prio == PPR::OUTCIRCLE) ? 0 : p->outline_group()].push_back(move(p));
2177   ptds.clear();
2178   for(auto& p: subqueue) for(auto& r: p.second) ptds.push_back(move(r));
2179   subqueue.clear();
2180   for(auto& p: ptds) subqueue[(p->prio == PPR::CIRCLE || p->prio == PPR::OUTCIRCLE) ? 0 : p->color].push_back(move(p));
2181   ptds.clear();
2182   for(auto& p: subqueue) for(auto& r: p.second) ptds.push_back(move(r));
2183   #endif
2184 
2185   for(auto& p: ptds) {
2186     int pd = p->prio - PPR::ZERO;
2187     if(pd < 0 || pd >= PMAX) {
2188       printf("Illegal priority %d\n", pd);
2189       p->prio = PPR(rand() % int(PPR::MAX));
2190       }
2191     qp[pd]++;
2192     }
2193 
2194   int total = 0;
2195   for(int a=0; a<PMAX; a++) {
2196     int b = qp[a];
2197     qp0[a] = qp[a] = total; total += b;
2198     }
2199 
2200   vector<unique_ptr<drawqueueitem>> ptds2;
2201   ptds2.resize(siz);
2202 
2203   for(int i = 0; i<siz; i++) ptds2[qp[int(ptds[i]->prio)]++] = move(ptds[i]);
2204   swap(ptds, ptds2);
2205   }
2206 
reverse_priority(PPR p)2207 EX void reverse_priority(PPR p) {
2208   reverse(ptds.begin()+qp0[int(p)], ptds.begin()+qp[int(p)]);
2209   }
2210 
reverse_side_priorities()2211 EX void reverse_side_priorities() {
2212   for(PPR p: {PPR::REDWALLs, PPR::REDWALLs2, PPR::REDWALLs3, PPR::WALL3s,
2213     PPR::LAKEWALL, PPR::INLAKEWALL, PPR::BELOWBOTTOM, PPR::BSHALLOW, PPR::ASHALLOW})
2214       reverse_priority(p);
2215   }
2216 
2217 // on the sphere, parts on the back are drawn first
draw_backside()2218 EX void draw_backside() {
2219   DEBBI(DF_GRAPH, ("draw_backside"));
2220   if(pmodel == mdHyperboloid && hyperbolic && pconf.show_hyperboloid_flat) {
2221     dynamicval<eModel> dv (pmodel, mdHyperboloidFlat);
2222     for(auto& ptd: ptds)
2223       if(!among(ptd->prio, PPR::MOBILE_ARROW, PPR::OUTCIRCLE, PPR::CIRCLE))
2224         ptd->draw();
2225     }
2226 
2227   spherespecial = sphereflipped() ? 1 : -1;
2228   reset_projection();
2229 
2230   if(pmodel == mdRotatedHyperboles) {
2231     for(auto& ptd: ptds)
2232       if(!among(ptd->prio, PPR::MOBILE_ARROW, PPR::OUTCIRCLE, PPR::CIRCLE))
2233         ptd->draw();
2234     glflush();
2235     }
2236   else {
2237     reverse_side_priorities();
2238     for(int i=isize(ptds)-1; i>=0; i--)
2239       if(!among(ptds[i]->prio, PPR::MOBILE_ARROW, PPR::OUTCIRCLE, PPR::CIRCLE))
2240         ptds[i]->draw_back();
2241 
2242     glflush();
2243     reverse_side_priorities();
2244     }
2245 
2246   spherespecial *= -1;
2247   spherephase = 1;
2248   reset_projection();
2249   }
2250 
2251 extern bool lshiftclick, lctrlclick;
2252 
reverse_transparent_walls()2253 EX void reverse_transparent_walls() {
2254   int pt = int(PPR::TRANSPARENT_WALL);
2255   reverse(&ptds[qp0[pt]], &ptds[qp[pt]]);
2256   }
2257 
set_vr_sphere()2258 EX void set_vr_sphere() {
2259   in_vr_sphere = false;
2260   #if CAP_VR
2261   in_vr_sphere = vrhr::rendering() && among(pmodel, mdDisk, mdBall, mdHyperboloid, mdHalfplane, mdHemisphere) && sphere;
2262   if(in_vr_sphere) {
2263     hyperpoint a, b;
2264     applymodel(shiftless(point3(0, 0, 1)), a);
2265     applymodel(shiftless(point3(0, 0, -1)), b);
2266     vr_sphere_center = (a + b) / 2;
2267     vr_sphere_center[3] = 1;
2268     E4;
2269     vr_sphere_center = vrhr::hmd_mv * vr_sphere_center;
2270     }
2271   #endif
2272   }
2273 
draw_main()2274 EX void draw_main() {
2275   DEBBI(DF_GRAPH, ("draw_main"));
2276 
2277   set_vr_sphere();
2278 
2279   if(sphere && GDIM == 3 && pmodel == mdPerspective && !stretch::in() && !ray::in_use) {
2280 
2281     if(ray::in_use && !ray::comparison_mode) {
2282       ray::cast();
2283       reset_projection();
2284       }
2285 
2286     #if CAP_GL
2287     for(int p: {1, 0, 2, 3}) {
2288       if(elliptic && p < 2) continue;
2289       glhr::set_depthwrite(true);
2290       if(p == 0 || p == 3) {
2291   #ifdef GL_ES
2292         glClearDepthf(1.0f);
2293   #else
2294         glClearDepth(1.0f);
2295   #endif
2296         glDepthFunc(GL_LEQUAL);
2297         }
2298       else {
2299   #ifdef GL_ES
2300         glClearDepthf(0.0f);
2301   #else
2302         glClearDepth(0.0f);
2303   #endif
2304         glDepthFunc(GL_GEQUAL);
2305         }
2306       glClear(GL_DEPTH_BUFFER_BIT);
2307       glhr::be_nontextured();
2308       spherephase = p;
2309       reset_projection();
2310       for(auto& ptd: ptds) ptd->draw();
2311       if(elliptic) {
2312         spherephase = p | 4;
2313         reset_projection();
2314         for(auto& ptd: ptds) ptd->draw();
2315         }
2316       // glflush();
2317       }
2318     #endif
2319     }
2320   else if(pmodel == mdAxial && sphere) {
2321     for(auto& ptd: ptds) if(ptd->prio == PPR::OUTCIRCLE)
2322       ptd->draw();
2323     for(axial_x=-4; axial_x<=4; axial_x++)
2324     for(axial_y=-4; axial_y<=4; axial_y++)
2325     for(auto& ptd: ptds) if(ptd->prio != PPR::OUTCIRCLE) {
2326       ptd->draw();
2327       }
2328     glflush();
2329     }
2330   else {
2331     DEBB(DF_GRAPH, ("draw_main1"));
2332     if(ray::in_use && !ray::comparison_mode) {
2333       ray::cast();
2334       reset_projection();
2335       }
2336 
2337     DEBB(DF_GRAPH, ("outcircle"));
2338     for(auto& ptd: ptds) if(ptd->prio == PPR::OUTCIRCLE)
2339       ptd->draw();
2340 
2341     if(two_sided_model()) draw_backside();
2342 
2343     for(auto& ptd: ptds) if(ptd->prio != PPR::OUTCIRCLE) {
2344       DEBBI(DF_VERTEX, ("prio: ", int(ptd->prio), " color ", ptd->color));
2345       dynamicval<int> ss(spherespecial, among(ptd->prio, PPR::MOBILE_ARROW, PPR::OUTCIRCLE, PPR::CIRCLE) ? 0 : spherespecial);
2346       ptd->draw();
2347       }
2348     glflush();
2349 
2350 #if CAP_RAY
2351     if(ray::in_use && ray::comparison_mode) {
2352       glDepthFunc(GL_LEQUAL);
2353 #ifdef GLES_ONLY
2354       glClearDepthf(1.0f);
2355 #else
2356       glClearDepth(1.0f);
2357 #endif
2358       glClear(GL_DEPTH_BUFFER_BIT);
2359       ray::cast();
2360       }
2361 #endif
2362     }
2363   }
2364 
drawqueue()2365 EX void drawqueue() {
2366 
2367   DEBBI(DF_GRAPH, ("drawqueue"));
2368 
2369   #if CAP_WRL
2370   if(wrl::in) { wrl::render(); return; }
2371   #endif
2372 
2373   #if MAXMDIM >= 4 && CAP_GL
2374   if(WDIM == 2 && GDIM == 3 && hyperbolic && !vrhr::rendering()) make_air();
2375   #endif
2376 
2377   #if CAP_VR
2378   if(vrhr::should_render() == 1) {
2379     vrhr::render();
2380     return;
2381     }
2382   #endif
2383 
2384   callhooks(hooks_drawqueue);
2385   current_display->next_shader_flags = 0;
2386   reset_projection();
2387   // reset_projection() is not sufficient here, because we need to know shaderside_projection
2388 
2389 #if CAP_GL
2390   if(vid.usingGL)
2391     glClear(GL_STENCIL_BUFFER_BIT);
2392 #endif
2393 
2394   sort_drawqueue();
2395 
2396   DEBB(DF_GRAPH, ("sort walls"));
2397 
2398   if(GDIM == 2)
2399   for(PPR p: {PPR::REDWALLs, PPR::REDWALLs2, PPR::REDWALLs3, PPR::WALL3s,
2400     PPR::LAKEWALL, PPR::INLAKEWALL, PPR::BELOWBOTTOM, PPR::ASHALLOW, PPR::BSHALLOW}) {
2401     int pp = int(p);
2402     if(qp0[pp] == qp[pp]) continue;
2403     for(int i=qp0[pp]; i<qp[pp]; i++) {
2404       auto ap = (dqi_poly&) *ptds[i];
2405       ap.cache = xintval(ap.V * xpush0(.1));
2406       }
2407     sort(&ptds[qp0[pp]], &ptds[qp[pp]],
2408       [] (const unique_ptr<drawqueueitem>& p1, const unique_ptr<drawqueueitem>& p2) {
2409         auto ap1 = (dqi_poly&) *p1;
2410         auto ap2 = (dqi_poly&) *p2;
2411         return ap1.cache < ap2.cache;
2412         });
2413     }
2414 
2415   for(PPR p: {PPR::TRANSPARENT_WALL}) {
2416     int pp = int(p);
2417     if(qp0[pp] == qp[pp]) continue;
2418     sort(&ptds[qp0[int(p)]], &ptds[qp[int(p)]],
2419       [] (const unique_ptr<drawqueueitem>& p1, const unique_ptr<drawqueueitem>& p2) {
2420         return p1->subprio > p2->subprio;
2421         });
2422     }
2423 
2424 #if CAP_SDL
2425   if(current_display->stereo_active() && !vid.usingGL) {
2426 
2427     if(aux && (aux->w != s->w || aux->h != s->h)) {
2428       SDL_FreeSurface(aux);
2429       #if CAP_SDL2
2430       SDL_DestroyRenderer(auxrend);
2431       #endif
2432       }
2433 
2434     if(!aux) {
2435       aux = SDL_CreateRGBSurface(SDL_SWSURFACE,s->w,s->h,32,0,0,0,0);
2436       #if CAP_SDL2
2437       auxrend = SDL_CreateSoftwareRenderer(aux);
2438       #endif
2439       }
2440 
2441     // SDL_LockSurface(aux);
2442     // memset(aux->pixels, 0, vid.xres * vid.yres * 4);
2443     // SDL_UnlockSurface(aux);
2444     SDL_BlitSurface(s, NULL, aux, NULL);
2445     }
2446 #endif
2447 
2448   spherespecial = 0;
2449   spherephase = 0;
2450   reset_projection();
2451 
2452   #if CAP_GL
2453   if(model_needs_depth() && current_display->stereo_active()) {
2454     global_projection = -1;
2455     draw_main();
2456     #if CAP_GL
2457     glClear(GL_DEPTH_BUFFER_BIT);
2458     #endif
2459     global_projection = +1;
2460     draw_main();
2461     global_projection = 0;
2462     }
2463   else
2464 #endif
2465   {
2466     draw_main();
2467     }
2468 
2469 #if CAP_SDL
2470   if(vid.stereo_mode == sAnaglyph && !vid.usingGL) {
2471     int qty = s->w * s->h;
2472     int *a = (int*) s->pixels;
2473     int *b = (int*) aux->pixels;
2474     SDL_LockSurface(aux);
2475     while(qty) {
2476       *a = ((*a) & 0xFF0000) | ((*b) & 0x00FFFF);
2477       a++; b++; qty--;
2478       }
2479     SDL_UnlockSurface(aux);
2480     }
2481 
2482   if(vid.stereo_mode == sLR && !vid.usingGL) {
2483     SDL_LockSurface(aux);
2484     for(int y=0; y<vid.yres; y++)
2485     for(int x=vid.xres/2; x<vid.xres; x++)
2486       qpixel(s,x,y) = qpixel(aux,x,y);
2487     SDL_UnlockSurface(aux);
2488     }
2489 #endif
2490 
2491   if(!keep_curvedata) {
2492     curvedata.clear();
2493     finf.tvertices.clear();
2494     curvestart = 0;
2495     }
2496 
2497   #if CAP_GL
2498   GLERR("drawqueue");
2499   #endif
2500   }
2501 
2502 #if HDR
2503 template<class T, class... U> T& queuea(PPR prio, U... u) {
2504   ptds.push_back(unique_ptr<T>(new T (u...)));
2505   ptds.back()->prio = prio;
2506   return (T&) *ptds.back();
2507   }
2508 #endif
2509 
2510 #if CAP_SHAPES
queuepolyat(const shiftmatrix & V,const hpcshape & h,color_t col,PPR prio)2511 EX dqi_poly& queuepolyat(const shiftmatrix& V, const hpcshape& h, color_t col, PPR prio) {
2512   if(prio == PPR::DEFAULT) prio = h.prio;
2513 
2514   auto& ptd = queuea<dqi_poly> (prio);
2515 
2516   ptd.V = V;
2517   ptd.offset = h.s;
2518   ptd.cnt = h.e-h.s;
2519   ptd.tab = &cgi.ourshape;
2520   apply_neon_color(col, ptd.color, ptd.outline, h.flags);
2521   ptd.linewidth = vid.linewidth;
2522   ptd.flags = h.flags;
2523   ptd.tinf = h.tinf;
2524   if(neon_mode != eNeon::none && (h.flags & POLY_TRIANGLES))
2525     ptd.tinf = nullptr;
2526   ptd.offset_texture = h.texture_offset;
2527   ptd.intester = h.intester;
2528   return ptd;
2529   }
2530 #endif
2531 
queuetable(const shiftmatrix & V,const vector<glvertex> & f,int cnt,color_t linecol,color_t fillcol,PPR prio)2532 EX dqi_poly& queuetable(const shiftmatrix& V, const vector<glvertex>& f, int cnt, color_t linecol, color_t fillcol, PPR prio) {
2533 
2534   auto& ptd = queuea<dqi_poly> (prio);
2535 
2536   ptd.V = V;
2537   ptd.tab = &f;
2538   ptd.offset = 0;
2539   ptd.cnt = cnt;
2540   ptd.color = fillcol;
2541   ptd.outline = linecol;
2542   ptd.linewidth = vid.linewidth;
2543   ptd.flags = POLY_ISSIDE | POLY_PRECISE_WIDE;
2544   ptd.tinf = NULL;
2545   ptd.intester = C0;
2546   return ptd;
2547   }
2548 
2549 #if CAP_SHAPES
queuepoly(const shiftmatrix & V,const hpcshape & h,color_t col)2550 EX dqi_poly& queuepoly(const shiftmatrix& V, const hpcshape& h, color_t col) {
2551   return queuepolyat(V,h,col,h.prio);
2552   }
2553 
queuepolyb(const shiftmatrix & V,const hpcshape & h,color_t col,int b)2554 void queuepolyb(const shiftmatrix& V, const hpcshape& h, color_t col, int b) {
2555   queuepolyat(V,h,col,h.prio+b);
2556   }
2557 #endif
2558 
curvepoint(const hyperpoint & H1)2559 EX void curvepoint(const hyperpoint& H1) {
2560   curvedata.push_back(glhr::pointtogl(H1));
2561   }
2562 
queuecurve(const shiftmatrix & V,color_t linecol,color_t fillcol,PPR prio)2563 EX dqi_poly& queuecurve(const shiftmatrix& V, color_t linecol, color_t fillcol, PPR prio) {
2564   auto &res = queuetable(V, curvedata, isize(curvedata)-curvestart, linecol, fillcol, prio);
2565   res.offset = curvestart;
2566   curvestart = isize(curvedata);
2567   return res;
2568   }
2569 
queueaction(PPR prio,const reaction_t & action)2570 EX dqi_action& queueaction(PPR prio, const reaction_t& action) {
2571   return queuea<dqi_action> (prio, action);
2572   }
2573 
2574 EX dqi_line& queueline(const shiftpoint& H1, const shiftpoint& H2, color_t col, int prf IS(0), PPR prio IS(PPR::LINE)) {
2575   auto& ptd = queuea<dqi_line> (prio);
2576 
2577   ptd.H1 = H1;
2578   ptd.H2 = H2;
2579   ptd.prf = prf;
2580   ptd.width = vid.linewidth;
2581   ptd.color = (darkened(col >> 8) << 8) + (col & 0xFF);
2582 
2583   return ptd;
2584   }
2585 
2586 EX void queuestr(int x, int y, int shift, int size, string str, color_t col, int frame IS(0), int align IS(8)) {
2587   auto& ptd = queuea<dqi_string> (PPR::TEXT);
2588   ptd.x = x;
2589   ptd.y = y;
2590   ptd.str = str;
2591   ptd.align = align;
2592   ptd.shift = shift;
2593   ptd.size = size;
2594   ptd.color = darkened(col);
2595   ptd.frame = frame ? ((poly_outline & ~ 255)+frame) : 0;
2596   }
2597 
2598 EX void queuecircle(int x, int y, int size, color_t color, PPR prio IS(PPR::CIRCLE), color_t fillcolor IS(0)) {
2599   auto& ptd = queuea<dqi_circle>(prio);
2600   ptd.x = x;
2601   ptd.y = y;
2602   ptd.size = size;
2603   ptd.color = color;
2604   ptd.fillcolor = fillcolor;
2605   ptd.linewidth = vid.linewidth;
2606   }
2607 
getcoord0(const shiftpoint & h,int & xc,int & yc,int & sc)2608 EX void getcoord0(const shiftpoint& h, int& xc, int &yc, int &sc) {
2609   hyperpoint hscr;
2610   applymodel(h, hscr);
2611   xc = current_display->xcenter + current_display->radius * hscr[0];
2612   yc = current_display->ycenter + current_display->radius * pconf.stretch * hscr[1];
2613   sc = 0;
2614   // EYETODO sc = vid.eye * current_display->radius * hscr[2];
2615   }
2616 
scale_in_pixels(const shiftmatrix & V)2617 EX ld scale_in_pixels(const shiftmatrix& V) {
2618   return scale_at(V) * cgi.scalefactor * current_display->radius / 2.5;
2619   }
2620 
getcoord0_checked(const shiftpoint & h,int & xc,int & yc,int & zc)2621 EX bool getcoord0_checked(const shiftpoint& h, int& xc, int &yc, int &zc) {
2622   if(invalid_point(h)) return false;
2623   if(point_behind(h)) return false;
2624   getcoord0(h, xc, yc, zc);
2625   return true;
2626   }
2627 
2628 EX void queuestr(const shiftpoint& h, int size, const string& chr, color_t col, int frame IS(0)) {
2629   int xc, yc, sc;
2630   if(getcoord0_checked(h, xc, yc, sc))
2631     queuestr(xc, yc, sc, size, chr, col, frame);
2632   }
2633 
2634 EX basic_textureinfo finf;
2635 
2636 #if CAP_GL
2637 #if HDR
2638 using pointfunction = function<hyperpoint(ld, ld)>;
2639 #endif
2640 
default_pointfunction(ld x,ld y)2641 EX hyperpoint default_pointfunction(ld x, ld y) {
2642   return xpush(x) * ypush(y) * C0;
2643   }
2644 
2645 #if !CAP_EXTFONT
2646 EX void write_in_space(const shiftmatrix& V, int fsize, double size, const string& s, color_t col, int frame IS(0), int align IS(8), PPR prio IS(PPR::TEXT), pointfunction pf IS(default_pointfunction)) {
2647   init_glfont(fsize);
2648   glfont_t& f(*(glfont[fsize]));
2649   finf.texture_id = f.texture;
2650 
2651   int fstart = isize(finf.tvertices);
2652 
2653   vector<int> chars;
2654   int i = 0;
2655   while(i < isize(s)) { chars.push_back(getnext(s.c_str(), i)); }
2656 
2657   ld tw = 0;
2658   for(int c: chars) tw += f.chars[c].w;
2659   ld th = f.chars[32].h;
2660 
2661   ld xpos = -tw * align / 16;
2662 
2663   ld scale = cgi.scalefactor * size / fsize / 2;
2664 
__anona7874de90902(ld tx, ld ty, ld ix, ld iy) 2665   auto pt = [&] (ld tx, ld ty, ld ix, ld iy) {
2666     finf.tvertices.push_back(glhr::makevertex(tx, ty, 0));
2667     curvedata.push_back(glhr::pointtogl(pf(ix*scale, iy*scale)));
2668     };
2669 
2670   for(int ch: chars) {
2671     auto& c = f.chars[ch];
2672 
2673     pt(c.tx0, c.ty0, xpos, -th/2);
2674     pt(c.tx0, c.ty1, xpos, +th/2);
2675     pt(c.tx1, c.ty1, xpos+c.w, +th/2);
2676     pt(c.tx1, c.ty1, xpos+c.w, +th/2);
2677     pt(c.tx1, c.ty0, xpos+c.w, -th/2);
2678     pt(c.tx0, c.ty0, xpos, -th/2);
2679 
2680     xpos += c.w;
2681     }
2682 
2683   if(frame) for(int i=0; i<360; i+=45) {
2684     auto &res = queuetable(V * xspinpush(i*degree, frame*scale), curvedata, isize(curvedata)-curvestart, col & 0xFF, col & 0xFF, prio);
2685     res.offset = curvestart;
2686     res.offset_texture = fstart;
2687     res.tinf = &finf;
2688     res.flags |= POLY_TRIANGLES;
2689     }
2690 
2691   auto &res = queuetable(V, curvedata, isize(curvedata)-curvestart, col, col, prio);
2692   res.offset = curvestart;
2693   res.offset_texture = fstart;
2694   res.tinf = &finf;
2695   res.flags |= POLY_TRIANGLES;
2696 
2697   curvestart = isize(curvedata);
2698   }
2699 #endif
2700 #endif
2701 
2702 EX void queuestr(const shiftmatrix& V, double size, const string& chr, color_t col, int frame IS(0), int align IS(8)) {
2703   #if CAP_GL && !CAP_EXTFONT
2704   if(vid.usingGL) {
2705     shiftmatrix V1 ;
2706     if(GDIM == 3)
2707       V1 = face_the_player(V);
2708     else {
2709       V1 = V;
2710       V1.T = rgpushxto0(tC0(V1.T));
2711       }
2712     auto col1 = (col << 8) | 0xFF;
2713     write_in_space(V1, max_glfont_size, size, chr, col1, frame, align);
2714     return;
2715     }
2716   #endif
2717   int xc, yc, sc;
2718   if(getcoord0_checked(tC0(V), xc, yc, sc))
2719     queuestr(xc, yc, sc, scale_in_pixels(V) * size, chr, col, frame, align);
2720   }
2721 
2722 EX void queuestrn(const shiftmatrix& V, double size, const string& chr, color_t col, int frame IS(0), int align IS(8)) {
2723   switch(neon_mode) {
2724     case eNeon::none:
2725       queuestr(V, size, chr, col, frame, align);
2726       break;
2727     case eNeon::neon: {
2728       dynamicval<color_t> c(poly_outline, col << 8);
2729       queuestr(V, size, chr, 0, frame, align);
2730       break;
2731       }
2732     case eNeon::no_boundary: {
2733       queuestr(V, size, chr, col, 0, align);
2734       break;
2735       }
2736     case eNeon::neon2: {
2737       dynamicval<color_t> c(poly_outline, (col << 8) | 0xFF);
2738       queuestr(V, size, chr, (col & 0xFEFEFE) >> 1, frame, align);
2739       break;
2740       }
2741     case eNeon::illustration: {
2742       dynamicval<color_t> c(poly_outline, poly_outline);
2743       if(poly_outline && (poly_outline>>8) != bordcolor) {
2744         col = magentize(col << 8) >> 8;
2745         poly_outline = 0xFF;
2746         }
2747       else {
2748         col = monochromatize(col << 8) >> 8;
2749         }
2750       queuestr(V, size, chr, col, frame, align);
2751       }
2752     }
2753   }
2754 
queuecircle(const shiftmatrix & V,double size,color_t col)2755 EX void queuecircle(const shiftmatrix& V, double size, color_t col) {
2756   int xc, yc, sc;
2757   if(!getcoord0_checked(tC0(V), xc, yc, sc)) return;
2758   int xs, ys, ss; getcoord0(V * xpush0(.01), xs, ys, ss);
2759   queuecircle(xc, yc, scale_in_pixels(V) * size, col);
2760   }
2761 
2762 #endif
2763 
2764 }
2765