1 // Hyperbolic Rogue -- screenshots and animations
2 // Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file screenshot.cpp
5  *  \brief screenshots, SVG format, animations, start animations
6  */
7 
8 #include "hyper.h"
9 namespace hr {
10 
11 EX bool hide_hud = true;
12 
13 #if HDR
14 namespace shot { void default_screenshot_content(); }
15 #endif
16 
17 // svg renderer
18 EX namespace svg {
19 
20 #if !CAP_SVG
21 EX always_false in;
22 #endif
23 
24 #if CAP_SVG
25   #if ISWEB
26   shstream f;
27   #else
28   fhstream f;
29   #endif
30 
31   EX bool in = false;
32 
cta(color_t col)33   ld cta(color_t col) {
34     // col >>= 24;
35     col &= 0xFF;
36     return col / 255.0;
37     }
38 
invisible(color_t col)39   bool invisible(color_t col) { return (col & 0xFF) == 0; }
40 
fixgamma(unsigned int & color)41   void fixgamma(unsigned int& color) {
42     unsigned char *c = (unsigned char*) (&color);
43     for(int i=1; i<4; i++) c[i] = 255 * pow(float(c[i] / 255.0), float(shot::gamma));
44     }
45 
46   int svgsize;
47   EX int divby = 10;
48 
coord(int val)49   const char* coord(int val) {
50     static char buf[10][20];
51     static int id;
52     id++; id %= 10;
53     if(divby == 1) {
54       sprintf(buf[id], "%d", val); return buf[id];
55       }
56     else if(divby <= 10) {
57       sprintf(buf[id], "%.1f", val*1./divby); return buf[id];
58       }
59     else {
60       sprintf(buf[id], "%.2f", val*1./divby); return buf[id];
61       }
62     }
63 
stylestr(color_t fill,color_t stroke,ld width=1)64   char* stylestr(color_t fill, color_t stroke, ld width=1) {
65     fixgamma(fill);
66     fixgamma(stroke);
67     static char buf[600];
68     // printf("fill = %08X stroke = %08x\n", fill, stroke);
69 
70     if(stroke == 0xFF00FF && false) {
71       stroke = 0x000000FF;
72 
73       if(fill == 0x332a22ff) fill = 0x000000FF;
74       else if(fill == 0x686868FF) fill = 0x000000FF;
75       else if(fill == 0xd0d0d0FF) fill = 0x000000FF;
76       else fill = 0xFFFFFFFF;
77       }
78 
79     sprintf(buf, "style=\"stroke:#%06x;stroke-opacity:%.3" PLDF ";stroke-width:%" PLDF "px;fill:#%06x;fill-opacity:%.3" PLDF "\"",
80       (stroke>>8) & 0xFFFFFF, cta(stroke),
81       width/divby,
82       (fill>>8) & 0xFFFFFF, cta(fill)
83       );
84     return buf;
85     }
86 
circle(int x,int y,int size,color_t col,color_t fillcol,double linewidth)87   EX void circle(int x, int y, int size, color_t col, color_t fillcol, double linewidth) {
88     if(!invisible(col) || !invisible(fillcol)) {
89       if(pconf.stretch == 1)
90         println(f, "<circle cx='", coord(x), "' cy='", coord(y), "' r='", coord(size), "' ", stylestr(fillcol, col, linewidth), "/>");
91       else
92         println(f, "<ellipse cx='", coord(x), "' cy='", coord(y), "' rx='", coord(size), "' ry='", coord(size*pconf.stretch), "' ", stylestr(fillcol, col), "/>");
93       }
94     }
95 
96   EX string link;
97 
startstring()98   void startstring() {
99     if(link != "") print(f, "<a xlink:href=\"", link, "\" xlink:show=\"replace\">");
100     }
101 
stopstring()102   void stopstring() {
103     if(link != "") print(f, "</a>");
104     }
105 
106   string font = "Times";
107 
108   ld text_width_multiplier = 1/40.;
109   int min_text = 3;
110 
text(int x,int y,int size,const string & str,bool frame,color_t col,int align)111   EX void text(int x, int y, int size, const string& str, bool frame, color_t col, int align) {
112     if(size < min_text) return;
113 
114     double dfc = (x - current_display->xcenter) * (x - current_display->xcenter) +
115       (y - current_display->ycenter) * (y - current_display->ycenter);
116     dfc /= current_display->radius;
117     dfc /= current_display->radius;
118     // 0 = center, 1 = edge
119     dfc = 1 - dfc;
120 
121     col = 0xFF + (col << 8);
122 
123     bool uselatex = font == "latex";
124 
125     if(!invisible(col)) {
126       startstring();
127       string str2 = "";
128       for(int i=0; i<(int) str.size(); i++)
129         if(str[i] == '&')
130           str2 += "&amp;";
131         else if(str[i] == '<')
132           str2 += "&lt;";
133         else if(str[i] == '>')
134           str2 += "&gt;";
135         else if(uselatex && str[i] == '#')
136           str2 += "\\#";
137         else str2 += str[i];
138       if(uselatex) str2 = string("\\myfont{")+coord(size)+"}{" + str2 + "}";
139 
140       print(f, "<text x='", coord(x), "' y='", coord(y+size*.4), "' text-anchor='", align == 8 ? "middle" :
141         align < 8 ? "start" :
142         "end", "' ");
143       if(!uselatex)
144         print(f, "font-family='", font, "' font-size='", coord(size), "' ");
145       print(f,
146         stylestr(col, frame ? 0x0000000FF : 0, (1<<get_sightrange())*dfc*text_width_multiplier),
147         ">", str2, "</text>");
148       stopstring();
149       println(f);
150       }
151     }
152 
polygon(int * polyx,int * polyy,int polyi,color_t col,color_t outline,double linewidth)153   EX void polygon(int *polyx, int *polyy, int polyi, color_t col, color_t outline, double linewidth) {
154 
155     if(invisible(col) && invisible(outline)) return;
156     if(polyi < 2) return;
157 
158     startstring();
159     for(int i=0; i<polyi; i++) {
160       if(i == 0)
161         print(f, "<path d=\"M ");
162       else
163         print(f, " L ");
164       print(f, coord(polyx[i]), " ", coord(polyy[i]));
165       }
166 
167     print(f, "\" ", stylestr(col, outline, (hyperbolic ? current_display->radius : current_display->scrsize) * linewidth/256), "/>");
168     stopstring();
169     println(f);
170     }
171 
render(const string & fname,const function<void ()> & what IS (shot::default_screenshot_content))172   EX void render(const string& fname, const function<void()>& what IS(shot::default_screenshot_content)) {
173     dynamicval<bool> v2(in, true);
174     dynamicval<bool> v3(vid.usingGL, false);
175 
176     #if ISWEB
177     f.s = "";
178     #else
179     f.f = fopen(fname.c_str(), "wt");
180     #endif
181 
182     println(f, "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"", coord(vid.xres), "\" height=\"", coord(vid.yres), "\">");
183     if(!shot::transparent)
184       println(f, "<rect width=\"", coord(vid.xres), "\" height=\"", coord(vid.yres), "\" ", stylestr((backcolor << 8) | 0xFF, 0, 0), "/>");
185     what();
186     println(f, "</svg>");
187 
188     #if ISWEB
189     EM_ASM_({
190       var x=window.open();
191       x.document.open();
192       x.document.write(UTF8ToString($0));
193       x.document.close();
194       }, f.s.c_str());
195     #else
196     fclose(f.f); f.f = NULL;
197     #endif
198     }
199 
200 #if CAP_COMMANDLINE && CAP_SHOT
read_args()201 int read_args() {
202   using namespace arg;
203   if(argis("-svgsize")) {
204     shift(); sscanf(argcs(), "%d/%d", &shot::shoty, &svg::divby);
205     if(shot::shotformat == -1) shot::shotformat = 0;
206     }
207   else if(argis("-svgfont")) {
208     shift(); svg::font = args();
209     // note: use '-svgfont latex' to produce text output as: \myfont{size}{text}
210     // (this is helpful with Inkscape's PDF+TeX output feature; define \myfont yourself)
211     }
212   else if(argis("-svggamma")) {
213     shift_arg_formula(shot::gamma);
214     }
215   else if(argis("-svgfade")) {
216     shift_arg_formula(shot::fade);
217     }
218   else if(argis("-svgshot")) {
219     PHASE(3); shift(); start_game();
220     printf("saving SVG screenshot to %s\n", argcs());
221     shot::format = shot::screenshot_format::svg;
222     shot::take(argcs());
223     }
224   else if(argis("-svgtwm")) {
225     shift_arg_formula(svg::text_width_multiplier);
226     }
227   else if(argis("-svgmt")) {
228     shift(); svg::min_text = argi();
229     }
230   else return 1;
231   return 0;
232   }
233 
234 auto ah = addHook(hooks_args, 0, read_args);
235 #endif
__anonba777e5b0102null236 auto ah2 = addHook(hooks_configfile, 100, [] {
237   #if CAP_CONFIG
238   addsaver(shot::shotx, "shotx");
239   addsaver(shot::shoty, "shoty");
240   addsaverenum(shot::format, "shotsvg");
241   addsaver(shot::transparent, "shottransparent");
242   param_f(shot::gamma, "shotgamma");
243   addsaver(shot::caption, "shotcaption");
244   param_f(shot::fade, "shotfade");
245   #endif
246   });
247 
248 #endif
249 EX }
250 
251 /** wrl renderer */
252 EX namespace wrl {
253 #if !CAP_WRL
254 EX always_false in;
255 #endif
256 
257 #if CAP_WRL
258   EX bool in;
259 
260   EX bool print;
261   EX bool textures = true;
262 
263   EX ld rug_width = .01;
264 
265   fhstream f;
266   string filename;
267 
coord(ld val)268   string coord(ld val) {
269     char buf[100];
270     snprintf(buf, 100, "%f", val);
271     return buf;
272     }
273 
coord(const hyperpoint & v,int q)274   string coord(const hyperpoint& v, int q) {
275     char buf[100];
276     if(q == 3) snprintf(buf, 100, "%f, %f, %f", v[0], v[1], v[2]);
277     if(q == 2) snprintf(buf, 100, "%f, %f", v[0], v[1]);
278     return buf;
279     }
280 
color(color_t col,ld v)281   string color(color_t col, ld v) {
282     char buf[100];
283     ld cols[4];
284     for(int i=0; i<4; i++) {
285 
286       cols[i] = part(col, i);
287       cols[i] /= 255;
288       cols[i] = pow(cols[i], shot::gamma) * shot::fade * v;
289       }
290 
291     snprintf(buf, 100, "%.3f %.3f %.3f", cols[3], cols[2], cols[1]);
292     return buf;
293     }
294 
295   typedef unsigned long long hashtype;
hash(ld x)296   hashtype hash(ld x) { return hashtype(x * 1000000 + .5); }
297 
hash(hyperpoint h)298   hashtype hash(hyperpoint h) {
299     return hash(h[0]) + 7 * hash(h[1]) + 13 * hash(h[2]);
300     }
301 
fatten(vector<hyperpoint> & data,vector<glvertex> & tdata)302   EX void fatten(vector<hyperpoint>& data, vector<glvertex>& tdata) {
303     map<hashtype, hyperpoint> normals;
304     for(int i=0; i<isize(data); i++)
305       normals[hash(data[i])] = Hypc;
306     for(int i=0; i<isize(data); i++) {
307       int j = i%3 ? i-1 : i+2;
308       int k = j%3 ? j-1 : j+2;
309       hyperpoint normal = (data[j] - data[i]) ^ (data[k] - data[i]);
310       #if MAXMDIM >= 4
311       normal[3] = 0;
312       #endif
313       if(sqhypot_d(3, normal) < 1e-6) {
314         println(hlog, "bug ", tie(data[i], data[j], data[k]));
315         }
316       normal /= hypot_d(3, normal);
317       auto& res = normals[hash(data[i])];
318       ld q = res[3];
319       if((res | normal) < 0) res -= normal;
320       else res += normal;
321       res[3] = q + 1;
322       }
323     for(auto& p: normals) {
324       auto w = hypot_d(3, p.second);
325       if(w == 0) println(hlog, "width is 0, ", p.second, " appeared ", p.second[3], " times");
326       if(isnan(w)) println(hlog, "width is NAN, ", p.second, " appeared ", p.second[3], " times");
327       p.second = p.second * (rug_width / w);
328       }
329     vector<hyperpoint> data2;
330     vector<glvertex> tdata2;
331     for(int i=0; i<isize(data); i+=3) {
332       auto a = data[i], b = data[i+1], c = data[i+2];
333       hyperpoint normal = (b-a) ^ (c-a);
334       auto na = normals[hash(a)];
335       auto nb = normals[hash(b)];
336       auto nc = normals[hash(c)];
337       if((normal | na) > 0) na = -na;
338       if((normal | nb) > 0) nb = -nb;
339       if((normal | nc) > 0) nc = -nc;
340       bool bad = false;
341       for(int i=0; i<3; i++) {
342         if(isnan(na[i]) || isnan(nb[i]) || isnan(nc[i])) bad = true;
343         }
344       if(bad) {
345         println(hlog, "bad vertex");
346         continue;
347         }
348       data2.push_back(a+na); data2.push_back(b+nb); data2.push_back(c+nc);
349       data2.push_back(b+nb); data2.push_back(a+na); data2.push_back(a-na);
350       data2.push_back(b+nb); data2.push_back(a-na); data2.push_back(b-nb);
351       data2.push_back(c+nc); data2.push_back(b+nb); data2.push_back(b-nb);
352       data2.push_back(c+nc); data2.push_back(b-nb); data2.push_back(c-nc);
353       data2.push_back(a+na); data2.push_back(c+nc); data2.push_back(c-nc);
354       data2.push_back(a+na); data2.push_back(c-nc); data2.push_back(a-na);
355       data2.push_back(b-nb); data2.push_back(a-na); data2.push_back(c-nc);
356       if(isize(tdata)) {
357         auto ta = tdata[i], tb = tdata[i+1], tc = tdata[i+2];
358         for(auto p: {ta, tb, tc, tb, ta, ta, tb, ta, tb, tc, tb, tb, tc, tb, tc, ta, tc, tc, ta, tc, ta, tb, ta, tc})
359           tdata2.push_back(p);
360         }
361       }
362     data = data2;
363     tdata = tdata2;
364     }
365 
366   bool used_rug;
367 
368   map<pair<color_t, glvertex>, int> texture_position;
369   map<color_t, int> gradient_position;
370 
texid(dqi_poly & p)371   pair<color_t, glvertex> texid(dqi_poly& p) {
372     return make_pair(p.color, p.tinf->tvertices[0]);
373     }
374 
375   /** 0 = no/unknown/disabled texture, 1 = rug, 2 = gradient, 3 = floor texture */
texture_type(dqi_poly & p)376   EX int texture_type(dqi_poly& p) {
377     if(!p.tinf) return 0;
378 #if CAP_PNG
379     if(!textures) return 0;
380     if(p.tinf == &rug::tinf) return 1;
381     #if MAXMDIM >= 4
382     if(p.tinf->texture_id == (int) floor_textures->renderedTexture)
383       return (p.tinf->tvertices[0][0] == 0) ? 2 : 3;
384     #endif
385 #endif
386     return 0;
387     }
388 
prepare(dqi_poly & p)389   EX void prepare(dqi_poly& p) {
390     if(print && !(p.flags & POLY_PRINTABLE)) return;
391     if(!(p.flags & POLY_TRIANGLES)) return;
392     int tt = texture_type(p);
393     if(tt == 2) gradient_position[p.color] = 0;
394     if(tt == 3) texture_position[texid(p)] = 0;
395     }
396 
397   #if MAXMDIM >= 4
398   int fts_int, fts, fts_row;
399   #endif
400 
401   map<string, pair<vector<hyperpoint>, vector<glvertex>>> all_data;
402 
polygon(dqi_poly & p)403   EX void polygon(dqi_poly& p) {
404     if(print && !(p.flags & POLY_PRINTABLE)) return;
405     if(!(p.flags & POLY_TRIANGLES)) return;
406     int tt = texture_type(p);
407 
408     vector<hyperpoint> data;
409     vector<glvertex> tdata;
410     for(int i=0; i<p.cnt; i++) {
411       glvertex v = p.tab[0][p.offset+i];
412       data.push_back(glhr::gltopoint(v));
413       if(p.tinf)
414         tdata.push_back(p.tinf->tvertices[p.offset_texture+i]);
415       }
416     for(auto& d: data) {
417       shiftpoint h = p.V * d;
418       applymodel(h, d);
419       }
420     if(print && (p.flags & POLY_FAT)) {
421       fatten(data, tdata);
422       p.cnt = isize(data);
423       }
424     else if(print) {
425       hyperpoint ctr1;
426       applymodel(p.V * p.intester, ctr1);
427       println(hlog, "intester = ", p.intester);
428       ld sdet = 0;
429       if(1) {
430         dynamicval<eGeometry> g(geometry, gEuclid);
431         for(int i=0; i<p.cnt; i+=3) {
432           transmatrix T;
433           T[0] = data[i] - ctr1;
434           T[1] = data[i+1] - ctr1;
435           T[2] = data[i+2] - ctr1;
436           sdet += det(T);
437           }
438         println(hlog, "sdet = ", sdet);
439         if(sdet > 0)
440           for(int i=0; i<p.cnt; i+=3) {
441             swap(data[i+1], data[i+2]);
442             if(!tdata.empty())
443               swap(tdata[i+1], tdata[i+2]);
444             }
445         }
446       }
447 
448     shstream app;
449     println(app, "    material Material {");
450     if(!tt) println(app, "      diffuseColor ", color(p.color, .8));
451     if(part(p.color, 0) != 255) println(app, "      transparency ", (255 - part(p.color, 0)) / 255.);
452     println(app, "      }");
453     if(tt == 1) {
454       println(f, "    texture ImageTexture {");
455       println(app, "      url \"", filename, "-rug.png\"");
456       println(app, "      }");
457       used_rug = true;
458       }
459     if(tt == 2 || tt == 3) {
460       println(app, "    texture ImageTexture {");
461       println(app, "      url \"", filename, "-floors.png\"");
462       println(app, "      }");
463       }
464 
465     auto &ad = all_data[app.s];
466     for(auto& d: data) ad.first.push_back(d);
467 
468     #if MAXMDIM >= 4
469     if(tt == 2) {
470       ld x = (fts - .5 - gradient_position[p.color]) / fts;
471       for(auto& d: tdata) d[0] = x;
472       }
473 
474     #if CAP_GL
475     if(tt == 3) {
476       int tp = texture_position[texid(p)];
477       auto xy = make_array<int>(tp % fts_row, tp / fts_row);
478       auto zero = p.tinf->tvertices[0];
479       ld sca = FLOORTEXTURESIZE*1./fts_int;
480       for(auto& d: tdata)
481         for(int c: {0, 1})
482           d[c] = ((d[c] - zero[c])*sca + xy[c] + .5) * fts_int / fts;
483       }
484     #endif
485     #endif
486 
487     for(auto& d: tdata) ad.second.push_back(d);
488     }
489 
render()490   EX void render() {
491     #if MAXMDIM >= 4
492     for(auto& p: ptds) {
493       auto p2 = dynamic_cast<dqi_poly*>(&*p);
494       if(p2)
495         prepare(*p2);
496       }
497 
498     int tps = 0;
499     for(auto& p: texture_position) p.second = tps++;
500     int gps = 0;
501     for(auto& p: gradient_position) p.second = gps++;
502 
503     #if CAP_TEXTURE
504     fts_int = floor_texture_square_size * FLOORTEXTURESIZE + 4;
505     fts = 64;
506 
507     while(fts < gps || (fts-gps)/fts_int * fts/fts_int < tps)
508       fts *= 2;
509 
510     fts_row = (fts-gps)/fts_int;
511     #endif
512     #endif
513 
514     for(auto& p: ptds) {
515       auto p2 = dynamic_cast<dqi_poly*>(&*p);
516       if(p2)
517         polygon(*p2);
518       }
519     }
520 
take(const string & fname,const function<void ()> & what IS (shot::default_screenshot_content))521   EX void take(const string& fname, const function<void()>& what IS(shot::default_screenshot_content)) {
522     dynamicval<bool> v2(in, true);
523     dynamicval<bool> v3(noshadow, true);
524     filename = fname;
525 
526     ptds.clear();
527     all_data.clear();
528     what();
529 
530     f.f = fopen(fname.c_str(), "wt");
531 
532     println(f, "#VRML V2.0 utf8");
533     println(f, "WorldInfo { title \"3D model exported from HyperRogue\" info [ \"3D models exported from HyperRogue are public domain\" ] }");
534 
535     for(auto& p: all_data) {
536       const string& app = p.first;
537       auto& data = p.second.first;
538       auto& tdata = p.second.second;
539 
540       println(f, "Shape {");
541       println(f, "  appearance Appearance {");
542       println(f, app);
543       println(f, "    }");
544       // println(f, "# V = ", p.V);
545       println(f, "  geometry IndexedFaceSet {");
546       println(f, "    coord Coordinate {");
547 
548       println(f, "      point [");
549       for(auto& d: data) println(f, "       ", coord(d, 3), ",");
550       println(f, "        ]");
551       println(f, "      }");
552 
553       if(!tdata.empty()) {
554         println(f, "      texCoord TextureCoordinate {");
555         println(f, "        point [");
556 
557         for(auto& d: tdata)
558           println(f, "          ", coord(glhr::gltopoint(d), 2), ",");
559         println(f, "        ]");
560         println(f, "      }");
561         }
562 
563       println(f, "    coordIndex [");
564       for(int i=0; i<isize(data); i+=3) {
565         println(f, "        ", i, " ", i+1, " ", i+2, " -1,");
566         }
567       println(f, "      ]");
568       if(print)
569         println(f, "    creaseAngle 0.0 convex FALSE solid TRUE ccw FALSE");
570       else
571         println(f, "    creaseAngle 0.0 convex FALSE solid FALSE");
572       println(f, "    }");
573       println(f, "  }");
574       }
575 
576     #if CAP_PNG
577     if(used_rug) {
578       resetbuffer rb;
579       rug::glbuf->enable();
580       SDL_Surface *s = rug::glbuf->render();
581       dynamicval<int> dx(shot::shotx, rug::texturesize);
582       dynamicval<int> dy(shot::shoty, rug::texturesize);
583       shot::postprocess(filename + "-rug.png", s, s);
584       }
585 
586     #if MAXMDIM >= 4
587     if(isize(texture_position) || isize(gradient_position)) {
588       SDL_Surface *s = shot::empty_surface(fts, fts, false);
589       for(auto& p: gradient_position) {
590         int x = fts - p.second - 1;
591         for(int y=0; y<fts; y++) {
592           qpixel(s, x, fts-y-1) = gradient(0, p.first, 0, y, fts-1) >> 8;
593           part(qpixel(s, x, y), 3) = 0xFF;
594           }
595         }
596 
597       SDL_Surface *floor = floor_textures->render();
598       for(auto& p: texture_position) {
599         int nx = p.second % fts_row;
600         int ny = p.second / fts_row;
601         color_t col = p.first.first;
602         int xs = p.first.second[0] * FLOORTEXTURESIZE - fts_int/2;
603         int ys = p.first.second[1] * FLOORTEXTURESIZE - fts_int/2;
604         swap(xs, ys); // I do not understand why
605         for(int y=0; y<fts_int; y++)
606         for(int x=0; x<fts_int; x++) {
607           auto& tgt = qpixel(s, nx*fts_int+x, fts-1-(ny*fts_int+y));
608           auto& src = qpixel(floor, xs+x, FLOORTEXTURESIZE-1-(ys+y));
609           for(int p=0; p<3; p++)
610             part(tgt, p) = (part(src, p) * part(col, p+1) + 127) / 255;
611           part(tgt, 3) = 0xFF;
612           }
613         }
614       IMAGESAVE(s, (filename + "-floors.png").c_str());
615       SDL_FreeSurface(s);
616       }
617     #endif
618     #endif
619 
620     fclose(f.f);
621     f.f = nullptr;
622     }
623 #endif
624 EX }
625 
626 #if CAP_PNG
IMAGESAVE(SDL_Surface * s,const char * fname)627 void IMAGESAVE(SDL_Surface *s, const char *fname) {
628   SDL_Surface *s2 = SDL_PNGFormatAlpha(s);
629   SDL_SavePNG(s2, fname);
630   SDL_FreeSurface(s2);
631   }
632 #endif
633 
634 #if CAP_SHOT
635 EX namespace shot {
636 
637 purehookset hooks_hqshot;
638 
639 #if HDR
640 enum screenshot_format { png, svg, wrl, rawfile };
641 #endif
642 
643 EX int rawfile_handle;
644 
645 EX int shotx = 2000;
646 EX int shoty = 2000;
647 EX screenshot_format format;
648 EX bool transparent = true;
649 EX ld gamma = 1;
650 EX int shotformat = -1;
651 EX string caption;
652 EX ld fade = 1;
653 
set_shotx()654 void set_shotx() {
655   if(shotformat == -1) return;
656   shotx = shoty;
657   if(shotformat == 1) shotx = shotx * 4/3;
658   if(shotformat == 2) shotx = shotx * 16/9;
659   if(shotformat == 3) {
660     shotx = shotx * 22/16;
661     while(shotx & 15) shotx++;
662     }
663   }
664 
665 EX int shot_aa = 1;
666 
default_screenshot_content()667 EX void default_screenshot_content() {
668 
669   gamescreen(0);
670 
671   if(caption != "")
672     displayfr(vid.xres/2, vid.fsize+vid.fsize/4, 3, vid.fsize*2, caption, forecolor, 8);
673   callhooks(hooks_hqshot);
674   drawStats();
675   }
676 
677 #if CAP_SDL
empty_surface(int x,int y,bool alpha)678 EX SDL_Surface *empty_surface(int x, int y, bool alpha) {
679   return SDL_CreateRGBSurface(SDL_SWSURFACE,x,y,32,0xFF<<16,0xFF<<8,0xFF, (alpha) ? (0xFF<<24) : 0);
680   }
681 #endif
682 
683 #if CAP_PNG
684 
output(SDL_Surface * s,const string & fname)685 void output(SDL_Surface* s, const string& fname) {
686   if(format == screenshot_format::rawfile) {
687     for(int y=0; y<shoty; y++)
688       ignore(write(rawfile_handle, &qpixel(s, 0, y), 4 * shotx));
689     }
690   else
691     IMAGESAVE(s, fname.c_str());
692   }
693 
postprocess(string fname,SDL_Surface * sdark,SDL_Surface * sbright)694 EX void postprocess(string fname, SDL_Surface *sdark, SDL_Surface *sbright) {
695   if(gamma == 1 && shot_aa == 1 && sdark == sbright) {
696     output(sdark, fname);
697     return;
698     }
699 
700   SDL_Surface *sout = empty_surface(shotx, shoty, sdark != sbright);
701   for(int y=0; y<shoty; y++)
702   for(int x=0; x<shotx; x++) {
703     int val[2][4];
704     for(int a=0; a<2; a++) for(int b=0; b<3; b++) val[a][b] = 0;
705     for(int ax=0; ax<shot_aa; ax++) for(int ay=0; ay<shot_aa; ay++)
706     for(int b=0; b<2; b++) for(int p=0; p<3; p++)
707       val[b][p] += part(qpixel((b?sbright:sdark), x*shot_aa+ax, y*shot_aa+ay), p);
708 
709     int transparent = 0;
710     int maxval = 255 * 3 * shot_aa * shot_aa;
711 
712     for(int p=0; p<3; p++) transparent += val[1][p] - val[0][p];
713 
714     color_t& pix = qpixel(sout, x, y);
715     pix = 0;
716     part(pix, 3) = 255 - (255 * transparent + (maxval/2)) / maxval;
717 
718     if(transparent < maxval) for(int p=0; p<3; p++) {
719       ld v = (val[0][p] * 3. / maxval) / (1 - transparent * 1. / maxval);
720       v = pow(v, gamma) * fade;
721       v *= 255;
722       if(v > 255) v = 255;
723       part(pix, p) = v;
724       }
725     }
726   output(sout, fname);
727   SDL_FreeSurface(sout);
728   }
729 #endif
730 
731 EX purehookset hooks_take;
732 
733 #if CAP_PNG
render_png(string fname,const function<void ()> & what)734 void render_png(string fname, const function<void()>& what) {
735   resetbuffer rb;
736 
737   renderbuffer glbuf(vid.xres, vid.yres, vid.usingGL);
738   glbuf.enable();
739   current_display->set_viewport(0);
740 
741   dynamicval<color_t> v8(backcolor, transparent ? 0xFF000000 : backcolor);
742   #if CAP_RUG
743   if(rug::rugged && !rug::renderonce) rug::prepareTexture();
744   #endif
745   glbuf.clear(backcolor);
746   what();
747 
748   SDL_Surface *sdark = glbuf.render();
749 
750   if(transparent) {
751     renderbuffer glbuf1(vid.xres, vid.yres, vid.usingGL);
752     backcolor = 0xFFFFFFFF;
753     #if CAP_RUG
754     if(rug::rugged && !rug::renderonce) rug::prepareTexture();
755     #endif
756     glbuf1.enable();
757     glbuf1.clear(backcolor);
758     current_display->set_viewport(0);
759     what();
760 
761     postprocess(fname, sdark, glbuf1.render());
762     }
763   else postprocess(fname, sdark, sdark);
764   }
765 #endif
766 
take(string fname,const function<void ()> & what IS (default_screenshot_content))767 EX void take(string fname, const function<void()>& what IS(default_screenshot_content)) {
768 
769   if(cheater) doOvergenerate();
770 
771   #if CAP_SVG
772   int multiplier = (format == screenshot_format::svg) ? svg::divby : shot_aa;
773   #else
774   int multiplier = shot_aa;
775   #endif
776 
777   vector<bool> chg;
778   for(auto ap: anims::aps) chg.push_back(*ap.value == ap.last);
779   finalizer f([&] {
780     for(int i=0; i<isize(anims::aps); i++)
781       if(chg[i]) *anims::aps[i].value = anims::aps[i].last;
782     });
783 
784   dynamicval<videopar> v(vid, vid);
785   dynamicval<bool> v2(inHighQual, true);
786   dynamicval<bool> v6(auraNOGL, true);
787   dynamicval<bool> vn(nohud, nohud || hide_hud);
788 
789   vid.smart_range_detail *= multiplier;
790   darken = 0;
791 
792   set_shotx();
793   vid.xres = shotx * multiplier;
794   vid.yres = shoty * multiplier;
795   calcparam();
796   models::configure();
797   callhooks(hooks_take);
798 
799   switch(format) {
800     case screenshot_format::wrl:
801       #if CAP_WRL
802       wrl::take(fname);
803       #endif
804       return;
805 
806     case screenshot_format::svg:
807       #if CAP_SVG
808       svg::render(fname, what);
809       #endif
810       return;
811 
812     case screenshot_format::png:
813     case screenshot_format::rawfile:
814       #if CAP_PNG
815       render_png(fname, what);
816       #endif
817       return;
818     }
819   }
820 
821 #if CAP_COMMANDLINE
png_read_args()822 int png_read_args() {
823   using namespace arg;
824   if(argis("-pngshot")) {
825     PHASE(3); shift(); start_game();
826     printf("saving PNG screenshot to %s\n", argcs());
827     format = screenshot_format::png;
828     shot::take(argcs());
829     }
830   else if(argis("-pngsize")) {
831     shift(); shoty = argi(); if(shotformat == -1) shotformat = 0;
832     }
833   else if(argis("-pngformat")) {
834     shift(); shotformat = argi();
835     }
836   else if(argis("-shotxy")) {
837     shift(); shotformat = -1; shotx = argi(); shift(); shoty = argi();
838     }
839   else if(argis("-shothud")) {
840     shift(); hide_hud = !argi();
841     }
842   else if(argis("-shott")) {
843     shift(); shot::transparent = argi();
844     }
845   else if(argis("-shot-fhd")) {
846     shot::shotformat = -1;
847     shot::shotx = 1920;
848     shot::shoty = 1080;
849     shot::transparent = false;
850     }
851   else if(argis("-shot-hd")) {
852     shot::shotformat = -1;
853     shot::shotx = 1280;
854     shot::shoty = 720;
855     shot::transparent = false;
856     }
857   else if(argis("-shot-qfhd")) {
858     shot::shotformat = -1;
859     shot::shotx = 960;
860     shot::shoty = 540;
861     shot::transparent = false;
862     }
863   else if(argis("-shot-qhd")) {
864     shot::shotformat = -1;
865     shot::shotx = 640;
866     shot::shoty = 360;
867     shot::transparent = false;
868     }
869   else if(argis("-shot-1000")) {
870     shot::shotformat = -1;
871     shot::shotx = 1000;
872     shot::shoty = 1000;
873     shot::transparent = false;
874     }
875   else if(argis("-shot-500")) {
876     shot::shotformat = -1;
877     shot::shotx = 500;
878     shot::shoty = 500;
879     shot::transparent = false;
880     }
881   else if(argis("-shot-vertical")) {
882     shot::shotformat = -1;
883     shot::shotx = 720;
884     shot::shoty = 1080;
885     shot::transparent = false;
886     }
887   else if(argis("-shotaa")) {
888     shift(); shot_aa = argi();
889     }
890   #if CAP_WRL
891   else if(argis("-modelshot")) {
892     PHASE(3); shift(); start_game();
893     printf("saving WRL model to %s\n", argcs());
894     shot::format = screenshot_format::wrl; wrl::print = false;
895     shot::take(argcs());
896     }
897   else if(argis("-printshot")) {
898     PHASE(3); shift(); start_game();
899     printf("saving 3D printable model to %s\n", argcs());
900     shot::format = screenshot_format::wrl; wrl::print = true;
901     shot::take(argcs());
902     }
903   #endif
904   else return 1;
905   return 0;
906   }
907 
908 auto ah_png = addHook(hooks_args, 0, png_read_args);
909 #endif
910 
format_name()911 EX string format_name() {
912   if(format == screenshot_format::svg) return "SVG";
913   if(format == screenshot_format::wrl) return "WRL";
914   if(format == screenshot_format::png) return "PNG";
915   return "?";
916   }
917 
format_extension()918 EX string format_extension() {
919   if(format == screenshot_format::svg) return ".svg";
920   if(format == screenshot_format::wrl) return ".wrl";
921   if(format == screenshot_format::png) return ".png";
922   return "?";
923   }
924 
925 
choose_screenshot_format()926 EX void choose_screenshot_format() {
927   cmode = sm::SIDE;
928   gamescreen(0);
929   dialog::init(XLAT("screenshots"), iinf[itPalace].color, 150, 100);
930   #if CAP_PNG
931   dialog::addItem(XLAT("PNG"), 'p');
932   dialog::add_action([] { format = screenshot_format::png; popScreen(); });
933   #endif
934   #if CAP_SVG
935   dialog::addItem(XLAT("SVG"), 's');
936   dialog::add_action([] { format = screenshot_format::svg; popScreen(); });
937   #endif
938   #if CAP_WRL
939   dialog::addItem(XLAT("WRL"), 'w');
940   dialog::add_action([] { format = screenshot_format::wrl; popScreen(); });
941   #endif
942   dialog::addBack();
943   dialog::display();
944   }
945 
menu()946 EX void menu() {
947   cmode = sm::SIDE;
948   gamescreen(0);
949   if(format == screenshot_format::svg && !CAP_SVG)
950     format = screenshot_format::png;
951   if(format == screenshot_format::png && !CAP_PNG)
952     format = screenshot_format::svg;
953   dialog::init(XLAT("screenshots"), iinf[itPalace].color, 150, 100);
954   dialog::addSelItem(XLAT("format"), format_name(), 'f');
955   dialog::add_action_push(choose_screenshot_format);
956   bool dowrl = format == screenshot_format::wrl;
957   if(!dowrl) {
958     dialog::addSelItem(XLAT("pixels (X)"), its(shotx), 'x');
959     dialog::add_action([] { shotformat = -1; dialog::editNumber(shotx, 500, 8000, 100, 2000, XLAT("pixels (X)"), ""); });
960     dialog::addSelItem(XLAT("pixels (Y)"), its(shoty), 'y');
961     dialog::add_action([] { shotformat = -1; dialog::editNumber(shoty, 500, 8000, 100, 2000, XLAT("pixels (Y)"), ""); });
962     }
963 
964   switch(format) {
965     case screenshot_format::svg: {
966       #if CAP_SVG
967       using namespace svg;
968       dialog::addSelItem(XLAT("precision"), "1/"+its(divby), 'p');
969       dialog::add_action([] { divby *= 10; if(divby > 1000000) divby = 1; });
970       #endif
971 
972       if(models::is_3d(vpconf) || rug::rugged) {
973         dialog::addInfo("SVG screenshots do not work in this 3D mode", 0xFF0000);
974         if(GDIM == 2 && !rug::rugged)
975           menuitem_projection('1');
976         #if CAP_WRL
977         else {
978           dialog::addItem(XLAT("WRL"), 'w');
979           dialog::add_action([] { format = screenshot_format::wrl; });
980           }
981         #endif
982         }
983 
984       #if CAP_TEXTURE
985       if(texture::config.tstate == texture::tsActive)
986         dialog::addInfo("SVG screenshots do not work with textures", 0xFF0000);
987       #endif
988       break;
989       }
990 
991     case screenshot_format::rawfile:
992     case screenshot_format::png: {
993       #if CAP_PNG
994       dialog::addSelItem(XLAT("supersampling"), its(shot_aa), 's');
995       dialog::add_action([] { shot_aa *= 2; if(shot_aa > 16) shot_aa = 1; });
996       #endif
997       break;
998       }
999 
1000     case screenshot_format::wrl: {
1001       #if CAP_WRL
1002       if(!models::is_3d(vpconf) && !rug::rugged) {
1003         dialog::addInfo("this format is for 3D projections", 0xFF0000);
1004         #if CAP_RUG
1005         if(GDIM == 2) {
1006           dialog::addItem(XLAT("hypersian rug mode"), 'u');
1007           dialog::add_action_push(rug::show);
1008           }
1009         #endif
1010         }
1011       #if CAP_RUG
1012       else if(rug::rugged ? rug::perspective() : models::is_perspective(vpconf.model)) {
1013       #else
1014       else if(models::is_perspective(vpconf.model)) {
1015       #endif
1016         dialog::addInfo("this does not work well in perspective projections", 0xFF8000);
1017         menuitem_projection('1');
1018         }
1019       dialog::addBoolItem_action("generate a model for 3D printing", wrl::print, 'p');
1020       #if CAP_PNG
1021       dialog::addBoolItem_action("use textures", wrl::textures, 'u');
1022       #endif
1023       #endif
1024       }
1025     }
1026   if(!dowrl) dialog::addBoolItem_action(XLAT("transparent"), transparent, 't');
1027 
1028   dialog::addSelItem(XLAT("gamma"), fts(gamma), 'g');
1029   dialog::add_action([] { dialog::editNumber(gamma, 0, 2, .1, .5, XLAT("gamma"), "higher value = darker"); });
1030 
1031   dialog::addSelItem(XLAT("brightness"), fts(fade), 'b');
1032   dialog::add_action([] { dialog::editNumber(fade, 0, 2, .1, 1, XLAT("brightness"), "higher value = lighter"); });
1033 
1034   if(!dowrl) dialog::addBoolItem_action(XLAT("disable the HUD"), hide_hud, 'h');
1035 
1036   dialog::addBoolItem_action_neg(XLAT("hide the player"), mapeditor::drawplayer, 'H');
1037   #if CAP_WRL
1038   if(dowrl && wrl::print) dialog::lastItem().value = XLAT("N/A");
1039   #endif
1040 
1041   if(WDIM == 2) {
1042     dialog::addItem(XLAT("centering"), 'C');
1043     dialog::add_action([] {
1044       dialog::editNumber(vid.fixed_facing_dir, 0, 360, 15, 90, XLAT("centering"),
1045         XLAT("You can pick the angle. Note: the direction the PC is facing matters."));
1046       dialog::reaction = fullcenter;
1047       dialog::extra_options = [] () {
1048         dialog::addBoolItem(XLAT("rotate PC"), centering == eCentering::face, 'R');
1049         dialog::add_action([] {
1050           flipplayer = false;
1051           cwt++;
1052           mirror::act(1, mirror::SPINSINGLE);
1053           cwt.at->mondir++;
1054           cwt.at->mondir %= cwt.at->type;
1055           fullcenter();
1056           });
1057         dialog::addBoolItem(XLAT("face"), centering == eCentering::face, 'F');
1058         dialog::add_action([] { centering = eCentering::face; fullcenter(); });
1059         dialog::addBoolItem(XLAT("edge"), centering == eCentering::edge, 'E');
1060         dialog::add_action([] { centering = eCentering::edge; fullcenter(); });
1061         dialog::addBoolItem(XLAT("vertex"), centering == eCentering::vertex, 'V');
1062         dialog::add_action([] { centering = eCentering::vertex; fullcenter(); });
1063         };
1064       });
1065     }
1066 
1067   dialog::addItem(XLAT("colors & aura"), 'c');
1068   dialog::add_action_push(show_color_dialog);
1069 
1070   menuitem_sightrange('r');
1071 
1072   dialog::addBreak(100);
1073 
1074   dialog::addItem(XLAT("take screenshot"), 'z');
1075   dialog::add_action([] () {
1076     #if ISWEB
1077     shot::take("new window");
1078     #else
1079     static string pngfile = "hqshot.png";
1080     static string svgfile = "svgshot.svg";
1081     static string wrlfile = "model.wrl";
1082     string& file =
1083       format == screenshot_format::png ? pngfile :
1084       format == screenshot_format::svg ? svgfile :
1085       wrlfile;
1086 
1087     dialog::openFileDialog(file, XLAT("screenshot"), format_extension(), [&file] () {
1088       dynamicval<int> cgl(vid.cells_generated_limit, 9999999);
1089       shot::take(file);
1090       return true;
1091       });
1092     #endif
1093     });
1094   dialog::addBack();
1095   dialog::display();
1096   }
1097 
1098 EX }
1099 #endif
1100 
1101 #if CAP_ANIMATIONS
1102 EX namespace anims {
1103 
1104 #if HDR
1105 enum eMovementAnimation {
1106   maNone, maTranslation, maRotation, maCircle, maParabolic, maTranslationRotation
1107   };
1108 #endif
1109 
1110 EX eMovementAnimation ma;
1111 
1112 EX ld shift_angle, movement_angle, movement_angle_2;
1113 EX ld normal_angle = 90;
1114 EX ld period = 10000;
1115 EX int noframes = 30;
1116 EX ld cycle_length = 2 * M_PI;
1117 EX ld parabolic_length = 1;
1118 EX ld skiprope_rotation;
1119 
1120 int lastticks, bak_turncount;
1121 
1122 EX ld rug_rotation1, rug_rotation2, rug_forward, ballangle_rotation, env_ocean, env_volcano, rug_movement_angle, rug_shift_angle;
1123 EX bool env_shmup;
1124 EX ld rug_angle;
1125 
1126 EX ld rotation_distance;
1127 cell *rotation_center;
1128 transmatrix rotation_center_View;
1129 
1130 color_t circle_display_color = 0x00FF00FF;
1131 
1132 EX ld circle_radius = acosh(2.);
1133 EX ld circle_spins = 1;
1134 
moved()1135 EX void moved() {
1136   optimizeview();
1137   if(cheater || autocheat) {
1138     if(hyperbolic && memory_saving_mode && centerover && gmatrix.size() && cwt.at != centerover && !quotient) {
1139       if(isNeighbor(cwt.at, centerover)) {
1140         cwt.spin = neighborId(centerover, cwt.at);
1141         flipplayer = true;
1142         }
1143       animateMovement(match(cwt.at, centerover), LAYER_SMALL);
1144       cwt.at = centerover;
1145       save_memory();
1146       return;
1147       }
1148     setdist(centerover, 7 - getDistLimit() - genrange_bonus, NULL);
1149     }
1150   playermoved = false;
1151   }
1152 
1153 #if HDR
1154 struct animated_parameter {
1155   ld *value;
1156   ld last;
1157   string formula;
1158   reaction_t reaction;
1159   };
1160 #endif
1161 
1162 EX vector<animated_parameter> aps;
1163 
deanimate(ld & x)1164 EX void deanimate(ld &x) {
1165   for(int i=0; i<isize(aps); i++)
1166     if(aps[i].value == &x)
1167       aps.erase(aps.begin() + (i--));
1168   }
1169 
get_parameter_animation(ld & x,string & s)1170 EX void get_parameter_animation(ld &x, string &s) {
1171   for(auto &ap: aps)
1172     if(ap.value == &x && ap.last == x)
1173       s = ap.formula;
1174   }
1175 
animate_parameter(ld & x,string f,const reaction_t & r)1176 EX void animate_parameter(ld &x, string f, const reaction_t& r) {
1177   deanimate(x);
1178   aps.emplace_back(animated_parameter{&x, x, f, r});
1179   }
1180 
1181 int ap_changes;
1182 
apply_animated_parameters()1183 void apply_animated_parameters() {
1184   ap_changes = 0;
1185   for(auto &ap: aps) {
1186     if(*ap.value != ap.last) continue;
1187     try {
1188       *ap.value = parseld(ap.formula);
1189       }
1190     catch(hr_parse_exception&) {
1191       continue;
1192       }
1193     if(*ap.value != ap.last) {
1194       if(ap.reaction) ap.reaction();
1195       ap_changes++;
1196       ap.last = *ap.value;
1197       }
1198     }
1199   }
1200 
1201 bool needs_highqual;
1202 
1203 bool joukowsky_anim;
1204 
reflect_view()1205 EX void reflect_view() {
1206   if(centerover) {
1207     shiftmatrix T = shiftless(Id);
1208     cell *mbase = centerover;
1209     cell *c = centerover;
1210     if(shmup::reflect(c, mbase, T))
1211       View = iso_inverse(T.T) * View;
1212     }
1213   }
1214 
1215 bool clearup;
1216 
1217 EX purehookset hooks_anim;
1218 
animate_rug_movement(ld t)1219 EX void animate_rug_movement(ld t) {
1220   rug::using_rugview urv;
1221   shift_view(
1222     cspin(0, GDIM-1, rug_movement_angle * degree) * spin(rug_shift_angle * degree) * xtangent(t)
1223     );
1224   }
1225 
1226 vector<reaction_t> on_rollback;
1227 
apply()1228 EX void apply() {
1229   int t = ticks - lastticks;
1230   lastticks = ticks;
1231 
1232   callhooks(hooks_anim);
1233 
1234   switch(ma) {
1235     case maTranslation:
1236       if(history::on) {
1237         history::phase = (isize(history::v) - 1) * ticks * 1. / period;
1238         history::movetophase();
1239         }
1240       else if(centerover) {
1241         reflect_view();
1242         if((hyperbolic && !quotient &&
1243           (centerover->land != cwt.at->land || memory_saving_mode) && among(centerover->land, laHaunted, laIvoryTower, laDungeon, laEndorian) && centerover->landparam >= 10
1244           ) ) {
1245           if(memory_saving_mode) {
1246             activateSafety(laIce);
1247             return;
1248             }
1249           else {
1250             fullcenter(); View = spin(rand() % 1000) * View;
1251             }
1252           }
1253         shift_view(
1254           cspin(0, GDIM-1, movement_angle * degree) * spin(shift_angle * degree) * xtangent(cycle_length * t / period)
1255           );
1256         moved();
1257         if(clearup) {
1258           centerover->wall = waNone;
1259           forCellEx(c1, centerover) c1->wall = waNone;
1260           }
1261         }
1262       break;
1263 
1264     case maRotation:
1265       shift_view(ztangent(-rotation_distance));
1266       if(GDIM == 3) {
1267         rotate_view(spin(-movement_angle * degree));
1268         rotate_view(cspin(1, 2, normal_angle * degree));
1269         rotate_view(spin(-movement_angle_2 * degree));
1270         }
1271       rotate_view(spin(2 * M_PI * t / period));
1272       if(GDIM == 3) {
1273         rotate_view(spin(movement_angle_2 * degree));
1274         rotate_view(cspin(2, 1, normal_angle * degree));
1275         rotate_view(spin(movement_angle * degree));
1276         }
1277       shift_view(ztangent(rotation_distance));
1278       moved();
1279       break;
1280 
1281     case maTranslationRotation:
1282       shift_view(
1283         cspin(0, GDIM-1, movement_angle * degree) * spin(shift_angle * degree) * xtangent(cycle_length * t / period)
1284         );
1285       moved();
1286       rotate_view(cspin(0, GDIM-1, 2 * M_PI * t / period));
1287       if(clearup) {
1288         centerover->wall = waNone;
1289         }
1290       break;
1291 
1292     #if CAP_BT
1293     case maParabolic:
1294       reflect_view();
1295       View = ypush(-shift_angle * degree) * spin(-movement_angle * degree) * View;
1296       if(GDIM == 2)
1297         View = bt::parabolic(parabolic_length * t / period) * View;
1298       else
1299         View = bt::parabolic3(parabolic_length * t / period, 0) * View;
1300       View = spin(movement_angle * degree) * ypush(shift_angle * degree) * View;
1301       moved();
1302       break;
1303     #endif
1304     case maCircle: {
1305       centerover = rotation_center;
1306       ld alpha = circle_spins * 2 * M_PI * ticks / period;
1307       View = spin(-cos_auto(circle_radius)*alpha) * xpush(circle_radius) * spin(alpha) * rotation_center_View;
1308       moved();
1309       break;
1310       }
1311     default:
1312       break;
1313     }
1314   if(env_ocean) {
1315     turncount += env_ocean * ticks * tidalsize / period;
1316     calcTidalPhase();
1317     for(auto& p: gmatrix) if(p.first->land == laOcean) checkTide(p.first);
1318     turncount -= ticks * tidalsize / period;
1319     }
1320   if(env_volcano) {
1321     auto bak_turncount = turncount;
1322     on_rollback.push_back([bak_turncount] { turncount = bak_turncount; });
1323     turncount += env_volcano * ticks * 64 / period;
1324     for(auto& p: gmatrix) if(p.first->land == laVolcano) checkTide(p.first);
1325     }
1326   #if CAP_RUG
1327   if(rug::rugged) {
1328     if(rug_rotation1) {
1329       rug::using_rugview rv;
1330       rotate_view(cspin(1, 2, -rug_angle * degree) * cspin(0, 2, rug_rotation1 * 2 * M_PI * t / period) * cspin(1, 2, rug_angle * degree));
1331       }
1332     if(rug_rotation2) {
1333       rug::using_rugview rv;
1334       View = View * cspin(0, 1, rug_rotation2 * 2 * M_PI * t / period);
1335       }
1336     if(rug_forward)
1337       animate_rug_movement(rug_forward * t / period);
1338     }
1339   #endif
1340   pconf.skiprope += skiprope_rotation * t * 2 * M_PI / period;
1341 
1342   if(ballangle_rotation) {
1343     if(models::has_orientation(vpconf.model))
1344       vpconf.model_orientation += ballangle_rotation * 360 * t / period;
1345     else
1346       vpconf.ballangle += ballangle_rotation * 360 * t / period;
1347     }
1348   if(joukowsky_anim) {
1349     ld t = ticks / period;
1350     t = t - floor(t);
1351     if(pmodel == mdBand) {
1352       vpconf.model_transition = t * 4 - 1;
1353       }
1354     else {
1355       vpconf.model_transition = t / 1.1;
1356       vpconf.scale = (1 - vpconf.model_transition) / 2.;
1357       }
1358     }
1359   apply_animated_parameters();
1360   calcparam();
1361   }
1362 
rollback()1363 EX void rollback() {
1364   while(!on_rollback.empty()) {
1365     on_rollback.back()();
1366     on_rollback.pop_back();
1367     }
1368   }
1369 
1370 #if CAP_FILES && CAP_SHOT
1371 EX string animfile = "animation-%04d.png";
1372 
1373 EX string videofile = "animation.mp4";
1374 
1375 int min_frame = 0, max_frame = 999999;
1376 
1377 int numturns = 0;
1378 
1379 EX hookset<void(int, int)> hooks_record_anim;
1380 
record_animation_of(reaction_t content)1381 EX bool record_animation_of(reaction_t content) {
1382   lastticks = 0;
1383   ticks = 0;
1384   int oldturn = -1;
1385   for(int i=0; i<noframes; i++) {
1386     if(i < min_frame || i > max_frame) continue;
1387     printf("%d/%d\n", i, noframes);
1388     callhooks(hooks_record_anim, i, noframes);
1389     int newticks = i * period / noframes;
1390     cmode = (env_shmup ? sm::NORMAL : 0);
1391     while(ticks < newticks) shmup::turn(1), ticks++;
1392     if(cheater && numturns) {
1393       int nturn = numturns * i / noframes;
1394       if(nturn != oldturn) monstersTurn();
1395       oldturn = nturn;
1396       }
1397     if(playermoved) centerpc(INF), optimizeview();
1398     dynamicval<bool> v2(inHighQual, true);
1399     models::configure();
1400     if(history::on) {
1401       ld len = (isize(history::v)-1) + 2 * history::extra_line_steps;
1402       history::phase = len * i / (noframes-1);
1403       if(history::lvspeed < 0) history::phase = len - history::phase;
1404       history::phase -= history::extra_line_steps;
1405       history::movetophase();
1406       }
1407 
1408     char buf[1000];
1409     snprintf(buf, 1000, animfile.c_str(), i);
1410     shot::take(buf, content);
1411     }
1412   lastticks = ticks = SDL_GetTicks();
1413   return true;
1414   }
1415 
record_animation()1416 EX bool record_animation() {
1417   return record_animation_of(shot::default_screenshot_content);
1418   }
1419 #endif
1420 
1421 EX purehookset hooks_after_video;
1422 
1423 #if CAP_VIDEO
record_video(string fname IS (videofile),bool_reaction_t rec IS (record_animation))1424 EX bool record_video(string fname IS(videofile), bool_reaction_t rec IS(record_animation)) {
1425 
1426   array<int, 2> tab;
1427   if(pipe(&tab[0])) {
1428     addMessage(format("Error: %s", strerror(errno)));
1429     return false;
1430     }
1431   println(hlog, "tab = ", tab);
1432 
1433   int pid = fork();
1434   if(pid == 0) {
1435     close(0);
1436     if(dup(tab[0]) != 0) exit(1);
1437     if(close(tab[1]) != 0) exit(1);
1438     if(close(tab[0]) != 0) exit(1);
1439     string fformat = "ffmpeg -y -f rawvideo -pix_fmt bgra -s " + its(shot::shotx) + "x" + its(shot::shoty) + " -r 60 -i - -pix_fmt yuv420p -codec:v libx264 \"" + fname + "\"";
1440     ignore(system(fformat.c_str()));
1441     exit(0);
1442     }
1443 
1444   close(tab[0]);
1445   shot::rawfile_handle = tab[1];
1446   dynamicval<shot::screenshot_format> sf(shot::format, shot::screenshot_format::rawfile);
1447   rec();
1448   close(tab[1]);
1449   wait(nullptr);
1450   callhooks(hooks_after_video);
1451   return true;
1452   }
1453 
record_video_std()1454 EX bool record_video_std() {
1455   return record_video(videofile, record_animation);
1456   }
1457 #endif
1458 
display_animation()1459 void display_animation() {
1460   if(ma == maCircle && (circle_display_color & 0xFF)) {
1461     for(int s=0; s<10; s++) {
1462       if(s == 0) curvepoint(xpush0(circle_radius - .1));
1463       for(int z=0; z<100; z++) curvepoint(xspinpush0((z+s*100) * 2 * M_PI / 1000., circle_radius));
1464       queuecurve(ggmatrix(rotation_center), circle_display_color, 0, PPR::LINE);
1465       }
1466     if(sphere) for(int s=0; s<10; s++) {
1467       if(s == 0) curvepoint(xpush0(circle_radius - .1));
1468       for(int z=0; z<100; z++) curvepoint(xspinpush0((z+s*100) * 2 * M_PI / 1000., circle_radius));
1469       queuecurve(ggmatrix(rotation_center) * centralsym, circle_display_color, 0, PPR::LINE);
1470       }
1471     }
1472   }
1473 
animator(string caption,ld & param,char key)1474 void animator(string caption, ld& param, char key) {
1475   dialog::addBoolItem(caption, param, key);
1476   if(param) dialog::lastItem().value = fts(param);
1477   dialog::add_action([&param, caption] () {
1478     if(param == 0) {
1479       param = 1;
1480       string s =
1481         XLAT(
1482           "The value of 1 means that the period of this animation equals the period set in the animation menu. "
1483           "Larger values correspond to faster animations.");
1484 
1485       dialog::editNumber(param, 0, 10, 1, 1, caption, s);
1486       }
1487     else param = 0;
1488     });
1489   }
1490 
1491 EX ld a, b;
1492 
1493 ld animation_period;
1494 
rug_angle_options()1495 EX void rug_angle_options() {
1496   dialog::addSelItem(XLAT("shift"), fts(rug_shift_angle) + "°", 'C');
1497   dialog::add_action([] () {
1498     popScreen();
1499     dialog::editNumber(rug_shift_angle, 0, 90, 15, 0, XLAT("shift"), "");
1500     });
1501   dialog::addSelItem(XLAT("movement angle"), fts(rug_movement_angle) + "°", 'M');
1502   dialog::add_action([] () {
1503     popScreen();
1504     dialog::editNumber(rug_movement_angle, 0, 360, 15, 0, XLAT("movement angle"), "");
1505     });
1506   }
1507 
show()1508 EX void show() {
1509   cmode = sm::SIDE; needs_highqual = false;
1510   animation_lcm = 1;
1511   gamescreen(0);
1512   animation_period = 2 * M_PI * animation_lcm / animation_factor;
1513   dialog::init(XLAT("animations"), iinf[itPalace].color, 150, 100);
1514   dialog::addSelItem(XLAT("period"), fts(period)+ " ms", 'p');
1515   dialog::add_action([] () { dialog::editNumber(period, 0, 10000, 1000, 200, XLAT("period"),
1516     XLAT("This is the period of the whole animation, though in some settings the animation can have a different period or be aperiodic. "
1517       "Changing the value will make the whole animation slower or faster."
1518     )); });
1519   if(animation_lcm > 1) {
1520     dialog::addSelItem(XLAT("game animation period"), fts(animation_period)+ " ms", 'G');
1521     dialog::add_action([] () {
1522       dialog::editNumber(animation_period, 0, 10000, 1000, 1000, XLAT("game animation period"),
1523         XLAT("Least common multiple of the animation periods of all the game objects on screen, such as rotating items.")
1524         );
1525       dialog::reaction = [] () { animation_factor = 2 * M_PI * animation_lcm / animation_period; };
1526       dialog::extra_options = [] () {
1527         dialog::addItem("default", 'D');
1528         dialog::add_action([] () {
1529           animation_factor = 1;
1530           popScreen();
1531           });
1532         };
1533       });
1534     }
1535   dialog::addBoolItem_choice(XLAT("no movement animation"), ma, maNone, '0');
1536   dialog::addBoolItem_choice(XLAT("translation"), ma, maTranslation, '1');
1537   dialog::addBoolItem_choice(XLAT("rotation"), ma, maRotation, '2');
1538   if(hyperbolic) {
1539     dialog::addBoolItem_choice(XLAT("parabolic"), ma, maParabolic, '3');
1540     }
1541   if(among(pmodel, mdJoukowsky, mdJoukowskyInverted)) {
1542     dialog::addBoolItem_action(XLAT("joukowsky_anim"), joukowsky_anim, 'j');
1543     }
1544   if(among(pmodel, mdJoukowsky, mdJoukowskyInverted)) {
1545     animator(XLAT("Möbius transformations"), skiprope_rotation, 'S');
1546     }
1547   if(!prod) {
1548     dialog::addBoolItem(XLAT("circle"), ma == maCircle, '4');
1549     dialog::add_action([] () { ma = maCircle;
1550       rotation_center = centerover;
1551       rotation_center_View = View;
1552       });
1553     }
1554   dialog::addBoolItem_choice(XLAT("translation")+"+"+XLAT("rotation"), ma, maTranslationRotation, '5');
1555   switch(ma) {
1556     case maCircle: {
1557       animator(XLAT("circle spins"), circle_spins, 'C');
1558       dialog::addSelItem(XLAT("circle radius"), fts(circle_radius), 'c');
1559       dialog::add_action([] () {
1560         dialog::editNumber(circle_radius, 0, 10, 0.1, acosh(1.), XLAT("circle radius"), "");
1561         dialog::extra_options = [] () {
1562           if(hyperbolic) {
1563             // area = 2pi (cosh(r)-1)
1564             dialog::addSelItem(XLAT("double spin"), fts(acosh(2.)), 'A');
1565             dialog::add_action([] () { circle_radius = acosh(2.); });
1566             dialog::addSelItem(XLAT("triple spin"), fts(acosh(3.)), 'B');
1567             dialog::add_action([] () { circle_radius = acosh(3.); });
1568             }
1569           if(sphere) {
1570             dialog::addSelItem(XLAT("double spin"), fts(acos(1/2.)), 'A');
1571             dialog::add_action([] () { circle_radius = acos(1/2.); });
1572             dialog::addSelItem(XLAT("triple spin"), fts(acos(1/3.)), 'B');
1573             dialog::add_action([] () { circle_radius = acos(1/3.); });
1574             }
1575           };
1576         });
1577       dialog::addColorItem(XLAT("draw the circle"), circle_display_color, 'd');
1578       dialog::add_action([] () {
1579         dialog::openColorDialog(circle_display_color, NULL);
1580         });
1581       dialog::addBreak(100);
1582       break;
1583       }
1584     case maTranslation:
1585     case maTranslationRotation:
1586     case maParabolic: {
1587       if(ma == maTranslation && history::on)
1588         dialog::addBreak(300);
1589       else if(ma == maTranslation) {
1590         dialog::addSelItem(XLAT("cycle length"), fts(cycle_length), 'c');
1591         dialog::add_action([] () {
1592           dialog::editNumber(cycle_length, 0, 10, 0.1, 2*M_PI, "shift", "");
1593           dialog::extra_options = [] () {
1594             dialog::addSelItem(XLAT("full circle"), fts(2 * M_PI), 'A');
1595             dialog::add_action([] () { cycle_length = 2 * M_PI; });
1596             dialog::addSelItem(XLAT("Zebra period"), fts(2.898149445355172), 'B');
1597             dialog::add_action([] () { cycle_length = 2.898149445355172; });
1598             dialog::addSelItem(XLAT("Bolza period"), fts(2 * 1.528571), 'C');
1599             dialog::add_action([] () { cycle_length = 2 * 1.528571; });
1600             };
1601           });
1602         }
1603       else
1604         add_edit(parabolic_length);
1605       dialog::addSelItem(XLAT("shift"), fts(shift_angle) + "°", 'C');
1606       dialog::add_action([] () {
1607         dialog::editNumber(shift_angle, 0, 90, 15, 0, XLAT("shift"), "");
1608         });
1609       dialog::addSelItem(XLAT("movement angle"), fts(movement_angle) + "°", 'm');
1610       dialog::add_action([] () {
1611         dialog::editNumber(movement_angle, 0, 360, 15, 0, XLAT("movement angle"), "");
1612         });
1613       break;
1614       }
1615     case maRotation:
1616       if(GDIM == 3) {
1617         dialog::addSelItem(XLAT("angle to screen normal"), fts(normal_angle) + "°", 'C');
1618         dialog::add_action([] () {
1619           dialog::editNumber(normal_angle, 0, 360, 15, 0, XLAT("angle to screen normal"), "");
1620           });
1621         dialog::addSelItem(XLAT("movement angle"), fts(movement_angle) + "°", 'm');
1622         dialog::add_action([] () {
1623           dialog::editNumber(movement_angle, 0, 360, 15, 0, XLAT("movement angle"), "");
1624           });
1625         dialog::addBreak(100);
1626         dialog::addSelItem(XLAT("distance from rotation center"), fts(rotation_distance), 'r');
1627         dialog::add_action([] () {
1628           dialog::editNumber(rotation_distance, 0, 10, .1, 0, XLAT("distance from rotation center"), "");
1629           });
1630         dialog::addBreak(100);
1631         }
1632       else
1633         dialog::addBreak(300);
1634       break;
1635     default: {
1636       dialog::addBreak(300);
1637       }
1638     }
1639   animator(XLATN("Ocean"), env_ocean, 'o');
1640   animator(XLATN("Volcanic Wasteland"), env_volcano, 'v');
1641   if(shmup::on) dialog::addBoolItem_action(XLAT("shmup action"), env_shmup, 'T');
1642   #if CAP_FILES && CAP_SHOT
1643   if(cheater) {
1644     dialog::addSelItem(XLAT("monster turns"), its(numturns), 'n');
1645     dialog::add_action([] {
1646       dialog::editNumber(numturns, 0, 100, 1, 0, XLAT("monster turns"), XLAT("Number of turns to pass. Useful when simulating butterflies or cellular automata."));
1647       });
1648     }
1649   #endif
1650 
1651   #if CAP_RUG
1652   if(rug::rugged) {
1653     animator(XLAT("screen-relative rotation"), rug_rotation1, 'r');
1654     if(rug_rotation1) {
1655       dialog::addSelItem(XLAT("angle"), fts(rug_angle) + "°", 'a');
1656       dialog::add_action([] () {
1657         dialog::editNumber(rug_angle, 0, 360, 15, 0, "Rug angle", "");
1658         });
1659       }
1660     else dialog::addBreak(100);
1661     animator(XLAT("model-relative rotation"), rug_rotation2, 'r');
1662     animator(XLAT("automatic move speed"), rug_forward, 'M');
1663     dialog::add_action([] () {
1664       dialog::editNumber(rug_forward, 0, 10, 1, 1, XLAT("automatic move speed"), XLAT("Move automatically without pressing any keys."));
1665       dialog::extra_options = [] () {
1666         if(among(rug::gwhere, gSphere, gElliptic))  {
1667           dialog::addItem(XLAT("synchronize"), 'S');
1668           dialog::add_action([] () { rug_forward = 2 * M_PI; popScreen(); });
1669           }
1670         rug_angle_options();
1671         };
1672       });
1673     }
1674   #endif
1675   if(models::has_orientation(vpconf.model))
1676     animator(XLAT("model rotation"), ballangle_rotation, 'I');
1677   else if(models::is_3d(vpconf))
1678     animator(XLAT("3D rotation"), ballangle_rotation, '3');
1679 
1680   dialog::addSelItem(XLAT("animate parameters"), fts(a), 'a');
1681   dialog::add_action([] () {
1682     dialog::editNumber(a, -100, 100, 1, 0, XLAT("animate parameters"), "");
1683     });
1684 
1685   dialog::addSelItem(XLAT("animate parameters"), fts(b), 'b');
1686   dialog::add_action([] () {
1687     dialog::editNumber(b, -100, 100, 1, 0, XLAT("animate parameters"), "");
1688     });
1689 
1690   dialog::addBoolItem(XLAT("history mode"), (history::on || history::includeHistory), 'h');
1691   dialog::add_action_push(history::history_menu);
1692 
1693   #if CAP_FILES && CAP_SHOT
1694   dialog::addItem(XLAT("shot settings"), 's');
1695   dialog::add_action_push(shot::menu);
1696 
1697   if(needs_highqual)
1698     dialog::addInfo(XLAT("some parameters will only change in recorded animation"));
1699   else
1700     dialog::addBreak(100);
1701   dialog::addSelItem(XLAT("frames to record"), its(noframes), 'n');
1702   dialog::add_action([] () { dialog::editNumber(noframes, 0, 300, 30, 5, XLAT("frames to record"), ""); });
1703   dialog::addSelItem(XLAT("record to sequence of image files"), animfile, 'R');
1704   dialog::add_action([] () {
1705     dialog::openFileDialog(animfile, XLAT("record to sequence of image files"),
1706       shot::format_extension(), record_animation);
1707     });
1708   #endif
1709   #if CAP_VIDEO
1710   dialog::addSelItem(XLAT("record to video file"), videofile, 'M');
1711   dialog::add_action([] () {
1712     dialog::openFileDialog(videofile, XLAT("record to video file"),
1713       ".mp4", record_video_std);
1714     });
1715   #endif
1716   dialog::addBack();
1717   dialog::display();
1718   }
1719 
1720 #if CAP_COMMANDLINE
readArgs()1721 int readArgs() {
1722   using namespace arg;
1723 
1724   if(0) ;
1725 #if CAP_ANIMATIONS
1726   else if(argis("-animmenu")) {
1727     PHASE(3); showstartmenu = false; pushScreen(show);
1728     }
1729   else if(argis("-animperiod")) {
1730     PHASEFROM(2); shift_arg_formula(period);
1731     }
1732 #if CAP_SHOT
1733   else if(argis("-animrecordf")) {
1734     PHASEFROM(2); shift(); noframes = argi();
1735     shift(); animfile = args();
1736     }
1737   else if(argis("-animrecord") || argis("-animrec")) {
1738     PHASE(3); shift(); noframes = argi();
1739     shift(); animfile = args(); record_animation();
1740     }
1741   else if(argis("-record-only")) {
1742     PHASEFROM(2);
1743     shift(); min_frame = argi();
1744     shift(); max_frame = argi();
1745     }
1746 #endif
1747 #if CAP_VIDEO
1748   else if(argis("-animvideo")) {
1749     start_game();
1750     PHASE(3); shift(); noframes = argi();
1751     shift(); videofile = args(); record_video();
1752     }
1753 #endif
1754   else if(argis("-animcircle")) {
1755     PHASE(3); start_game();
1756     ma = maCircle;
1757     rotation_center = centerover;
1758     rotation_center_View = View;
1759     shift_arg_formula(circle_spins);
1760     shift_arg_formula(circle_radius);
1761     shift(); circle_display_color = arghex();
1762     }
1763   else if(argis("-animmove")) {
1764     ma = maTranslation;
1765     shift_arg_formula(cycle_length);
1766     shift_arg_formula(shift_angle);
1767     shift_arg_formula(movement_angle);
1768     }
1769   else if(argis("-animmoverot")) {
1770     ma = maTranslationRotation;
1771     shift_arg_formula(cycle_length);
1772     shift_arg_formula(shift_angle);
1773     shift_arg_formula(movement_angle);
1774     }
1775   else if(argis("-wallopt")) {
1776     wallopt = true;
1777     }
1778   else if(argis("-animpar")) {
1779     ma = maParabolic;
1780     shift_arg_formula(parabolic_length);
1781     shift_arg_formula(shift_angle);
1782     shift_arg_formula(movement_angle);
1783     }
1784   else if(argis("-animclear")) { clearup = true; }
1785   else if(argis("-animrot")) {
1786     ma = maRotation;
1787     if(GDIM == 3) {
1788       shift_arg_formula(movement_angle);
1789       shift_arg_formula(normal_angle);
1790       }
1791     }
1792   else if(argis("-animrotd")) {
1793     start_game();
1794     ma = maRotation;
1795     shift_arg_formula(rotation_distance);
1796     }
1797   else if(argis("-animrug")) {
1798     shift_arg_formula(rug_rotation1);
1799     shift_arg_formula(rug_angle);
1800     shift_arg_formula(rug_rotation2);
1801     }
1802   else if(argis("-animenv")) {
1803     shift_arg_formula(env_ocean);
1804     shift_arg_formula(env_volcano);
1805     }
1806   else if(argis("-animball")) {
1807     shift_arg_formula(ballangle_rotation);
1808     }
1809   else if(argis("-animj")) {
1810     shift(); joukowsky_anim = true;
1811     }
1812 #endif
1813   else return 1;
1814   return 0;
1815   }
1816 #endif
1817 
1818 auto animhook = addHook(hooks_frame, 100, display_animation)
1819   #if CAP_COMMANDLINE
1820   + addHook(hooks_args, 100, readArgs)
1821   #endif
__anonba777e5b3902null1822   + addHook(hooks_configfile, 100, [] {
1823     #if CAP_CONFIG
1824     param_f(anims::period, "aperiod", "animation period");
1825     addsaver(anims::noframes, "animation frames");
1826     param_f(anims::cycle_length, "acycle", "animation cycle length");
1827     param_f(anims::parabolic_length, "aparabolic", "animation parabolic length")
1828       ->editable(0, 10, 1, "cells to go", "", 'c');
1829     param_f(anims::rug_angle, "arugangle", "animation rug angle");
1830     param_f(anims::circle_radius, "acradius", "animation circle radius");
1831     param_f(anims::circle_spins, "acspins", "animation circle spins");
1832     addsaver(anims::rug_movement_angle, "rug forward movement angle", 90);
1833     addsaver(anims::rug_shift_angle, "rug forward shift angle", 0);
1834     addsaver(anims::a, "a", 0);
1835     addsaver(anims::b, "b", 0);
1836     param_f(anims::movement_angle_2, "movement angle 2", 0);
1837     #endif
1838     });
1839 
any_animation()1840 EX bool any_animation() {
1841   if(history::on) return true;
1842   if(ma) return true;
1843   if(ballangle_rotation || rug_rotation1 || rug_rotation2) return true;
1844   if(ap_changes) return true;
1845   return false;
1846   }
1847 
any_on()1848 EX bool any_on() {
1849   return any_animation() || history::includeHistory;
1850   }
1851 
center_music()1852 EX bool center_music() {
1853   return among(ma, maParabolic, maTranslation);
1854   }
1855 
1856 EX }
1857 #endif
1858 
1859 EX namespace startanims {
1860 
1861 int ticks_start = 0;
1862 
1863 #if HDR
1864 struct startanim {
1865   string name;
1866   reaction_t init;
1867   reaction_t render;
1868   };
1869 
1870 const int EXPLORE_START_ANIMATION = 2003;
1871 #endif
1872 
1873 reaction_t exploration;
1874 
explorable(reaction_t ee)1875 void explorable(reaction_t ee) {
1876   if(displayButtonS(4, vid.yres - 4 - vid.fsize/2, XLAT("explore this animation"), 0x202020, 0, vid.fsize/2))
1877     getcstat = EXPLORE_START_ANIMATION;
1878   exploration = ee;
1879   }
1880 
no_init()1881 void no_init() { }
1882 
__anonba777e5b3a02null1883 startanim null_animation { "", no_init, [] { gamescreen(2); }};
1884 
1885 #if CAP_STARTANIM
__anonba777e5b3b02null1886 startanim joukowsky { "Joukowsky transform", no_init, [] {
1887   dynamicval<eModel> dm(pmodel, mdJoukowskyInverted);
1888   dynamicval<ld> dt(pconf.model_orientation, ticks / 25.);
1889   dynamicval<int> dv(vid.use_smart_range, 2);
1890   dynamicval<ld> ds(pconf.scale, 1/4.);
1891   models::configure();
1892   dynamicval<color_t> dc(ringcolor, 0);
1893   gamescreen(2);
1894   explorable([] { pmodel = mdJoukowskyInverted; pushScreen(models::model_menu); });
1895   }};
1896 
__anonba777e5b3d02null1897 startanim bandspin { "spinning in the band model", no_init, [] {
1898   dynamicval<eModel> dm(pmodel, mdBand);
1899   dynamicval<ld> dt(pconf.model_orientation, ticks / 25.);
1900   dynamicval<int> dv(vid.use_smart_range, 2);
1901   models::configure();
1902   gamescreen(2);
1903   explorable([] { pmodel = mdJoukowskyInverted; pushScreen(models::model_menu); });
1904   }};
1905 
__anonba777e5b3f02null1906 startanim perspective { "projection distance", no_init, [] {
1907   ld x = sin(ticks / 1500.);
1908   x += 1;
1909   x /= 2;
1910   x *= 1.5;
1911   x = tan(x);
1912   dynamicval<ld> da(pconf.alpha, x);
1913   dynamicval<ld> ds(pconf.scale, (1+x)/2);
1914   calcparam();
1915   gamescreen(2);
1916   explorable(projectionDialog);
1917   }};
1918 
__anonba777e5b4002null1919 startanim rug { "Hypersian Rug", [] {
1920 #if CAP_RUG
1921   rug::init();
1922   rug::rugged = false;
1923 #else
1924   pick();
1925 #endif
1926   }, [] {
1927   #if CAP_RUG
1928   dynamicval<bool> b(rug::rugged, true);
1929   rug::physics();
1930   dynamicval<transmatrix> t(rug::rugView, cspin(1, 2, ticks / 3000.) * rug::rugView);
1931   gamescreen(2);
1932   if(!rug::rugged) current = &null_animation;
1933   explorable([] { rug::rugged = true; pushScreen(rug::show); });
1934   #endif
1935   }};
1936 
__anonba777e5b4302null1937 startanim spin_around { "spinning around", no_init,  [] {
1938   dynamicval<ld> da(pconf.alpha, 999);
1939   dynamicval<ld> ds(pconf.scale, 500);
1940   ld alpha = 2 * M_PI * ticks / 10000.;
1941   ld circle_radius = acosh(2.);
1942   dynamicval<transmatrix> dv(View, spin(-cos_auto(circle_radius)*alpha) * xpush(circle_radius) * spin(alpha) * View);
1943   gamescreen(2);
1944   }};
1945 #endif
1946 
1947 reaction_t add_to_frame;
1948 
1949 #if CAP_STARTANIM
draw_ghost(const transmatrix V,int id)1950 void draw_ghost(const transmatrix V, int id) {
1951   auto sV = shiftless(V);
1952   if(id % 13 == 0) {
1953     queuepoly(sV, cgi.shMiniGhost, 0xFFFF00C0);
1954     queuepoly(sV, cgi.shMiniEyes, 0xFF);
1955     }
1956   else {
1957     queuepoly(sV, cgi.shMiniGhost, 0xFFFFFFC0);
1958     queuepoly(sV, cgi.shMiniEyes, 0xFF);
1959     }
1960   }
1961 
__anonba777e5b4402null1962 startanim row_of_ghosts { "row of ghosts", no_init, [] {
1963   dynamicval<reaction_t> r(add_to_frame, [] {
1964     int t = ticks/400;
1965     ld mod = (ticks-t*400)/400.;
1966     for(int x=-25; x<=25; x++)
1967       for(int y=-25; y<=25; y++) {
1968         ld ay = (y + mod)/5.;
1969         draw_ghost(xpush(x/5.) * spin(M_PI/2) * xpush(ay), int(y-t));
1970         }
1971     });
1972   dynamicval<bool> rd(mapeditor::drawplayer, false);
1973   gamescreen(2);
1974   }};
1975 
__anonba777e5b4602null1976 startanim army_of_ghosts { "army of ghosts", no_init, [] {
1977   dynamicval<bool> rd(mapeditor::drawplayer, false);
1978   dynamicval<reaction_t> r(add_to_frame, [] {
1979     int tt = ticks - ticks_start + 1200;
1980     int t = tt/400;
1981     ld mod = (tt-t*400)/400.;
1982     for(int x=-12; x<=12; x++) {
1983       ld ax = x/4.;
1984       transmatrix T = spin(-M_PI/2) * xpush(ax) * spin(M_PI/2);
1985       for(int y=0;; y++) {
1986         ld ay = (mod - y)/4.;
1987         transmatrix U = spin(M_PI/2) * xpush(ay / cosh(ax)) * T;
1988         if(!in_smart_range(shiftless(U))) break;
1989         draw_ghost(U, (-y - t));
1990         if(y) {
1991           ay = (mod + y)/4.;
1992           transmatrix U = spin(M_PI/2) * xpush(ay / cosh(ax)) * T;
1993           draw_ghost(U, (y - t));
1994           }
1995         }
1996       }
1997     });
1998   gamescreen(2);
1999   }};
2000 
__anonba777e5b4802null2001 startanim ghost_spiral { "ghost spiral", no_init, [] {
2002   dynamicval<reaction_t> r(add_to_frame, [] {
2003     ld t = (ticks - ticks_start - 2000) / 150000.;
2004     for(ld i=3; i<=40; i++) {
2005       draw_ghost(spin(t * i * 2 * M_PI) * xpush(asinh(15. / i)) * spin(M_PI/2), 1);
2006       }
2007     });
2008   gamescreen(2);
2009   }};
2010 
__anonba777e5b4a02null2011 startanim fib_ghosts { "Fibonacci ghosts", no_init, [] {
2012   dynamicval<bool> rd(mapeditor::drawplayer, false);
2013   dynamicval<reaction_t> r(add_to_frame, [] {
2014     ld phase = (ticks - ticks_start - 2000) / 1000.;
2015     for(int i=0; i<=500; i++) {
2016       ld step = M_PI * (3 - sqrt(5));
2017       ld density = 0.01;
2018       ld area = 1 + (i+.5) * density;
2019       ld r = acosh(area);
2020       ld length = sinh(r);
2021       transmatrix T = spin(i * step + phase / length) * xpush(r) * spin(M_PI/2);
2022       draw_ghost(T, i);
2023       }
2024     });
2025   gamescreen(2);
2026   }};
2027 
__anonba777e5b4c02null2028 startanim fpp { "first-person perspective", no_init, [] {
2029   if(MAXMDIM == 3) { current = &null_animation; return; }
2030   geom3::switch_fpp();
2031   View = cspin(0, 2, ticks / 5000.) * View;
2032   gamescreen(2);
2033   View = cspin(0, 2, -ticks / 5000.) * View;
2034   geom3::switch_fpp();
2035   }};
2036 
2037 // more start animations:
2038 // - fly a ghost around center, in Gans model
2039 // - triangle edges?
2040 
2041 EX startanim *current = &null_animation;
2042 
pick()2043 EX void pick() {
2044   if(((gold() > 0 || tkills() > 0) && canmove) || geometry != gNormal || ISWEB || ISMOBILE || vid.always3 || pmodel || rug::rugged || vid.wallmode < 2 || vid.monmode < 2 || glhr::noshaders || !vid.usingGL) {
2045     current = &null_animation;
2046     return;
2047     }
2048   vector<startanim*> known = { &null_animation, &perspective, &joukowsky, &bandspin, &rug, &spin_around, &row_of_ghosts, &ghost_spiral, &army_of_ghosts, &fib_ghosts, &fpp };
2049   int id = rand() % 11;
2050   current = known[id];
2051   ticks_start = ticks;
2052   current->init();
2053   }
2054 
__anonba777e5b4d02() 2055 auto sanimhook = addHook(hooks_frame, 100, []() { if(add_to_frame) add_to_frame(); });
2056 
display()2057 EX void display() {
2058   current->render();
2059   int z = vid.fsize/2 + 2;
2060   if(displayButtonS(4, vid.yres - 4 - z*3, VER, 0x202020, 0, vid.fsize/2))
2061     getcstat = SDLK_F5;
2062   if(displayButtonS(4, vid.yres - 4 - z*2, XLAT(current->name), 0x202020, 0, vid.fsize/2))
2063     getcstat = SDLK_F5;
2064   }
2065 
explore()2066 EX void explore() { exploration();  }
2067 #endif
2068 
2069 EX }
2070 }
2071