1 #include "rogueviz.h"
2 
3 namespace rogueviz {
4 
5 namespace magic { void magic(int i); }
6 
7 namespace colorpicker {
8 
9 int current_step;
10 color_t current_color;
11 cell *current_center;
12 
get_color_at(cell * c)13 color_t get_color_at(cell *c) {
14   crystal::coord oldc = crystal::get_coord(current_center->master);
15   crystal::coord newc = crystal::get_coord(c->master);
16   color_t res;
17   for(int i=0; i<3; i++) {
18     int val = part(current_color, 2-i) + current_step * ((newc[i] - oldc[i]) / 2);
19     if(val < 0) val = 0;
20     if(val > 255) val = 255;
21     part(res, 2-i) = val;
22     }
23   return res;
24   }
25 
color_markers(cell * c,const shiftmatrix & V)26 bool color_markers(cell *c, const shiftmatrix& V) {
27   if(!centerover) return false;
28   if(centerover != current_center) {
29     current_color = get_color_at(centerover);
30     current_center = centerover;
31     }
32   c->landparam = get_color_at(c);
33   return false;
34   }
35 
color_key(int sym,int uni)36 bool color_key(int sym, int uni) {
37   if((cmode & sm::NORMAL) && (uni >= '0' && uni <= '6')) {
38     current_step = (1 << (uni - '0'));
39     return true;
40     }
41   if((cmode & sm::NORMAL) && uni == '[') {
42     current_step = (1 + current_step) / 2;
43     return true;
44     }
45   if((cmode & sm::NORMAL) && uni == ']') {
46     current_step = 2 * current_step;
47     return true;
48     }
49   if((cmode & sm::NORMAL) && uni >= 1000 && uni < 1010) {
50     current_step = 1 << (uni - 1000);
51     return true;
52     }
53   return false;
54   }
55 
color_prestats()56 bool color_prestats() {
57   nohelp = true;
58   for(int k= 0; k <= 6; k++) {
59     int v = 1<< k;
60     if(displayButtonS(10 + k * vid.fsize * 2, 10 + vid.fsize, its(k), v == current_step ? 0xFF2020 : 0xC0C0C0, 0, vid.fsize))
61       getcstat = 1000 + k;
62     }
63   if(mouseover) {
64     displayButtonS(10 + 7 * vid.fsize * 2, 10 + vid.fsize, itsh(mouseover->landparam & 0xFFFFFF), mouseover->landparam, 0, vid.fsize);
65     }
66   return true;
67   }
68 
run_cpick()69 void run_cpick() {
70   crystal::compass_probability = 0;
71   stop_game();
72   crystal::set_crystal(6);
73   set_variation(eVariation::pure);
74   firstland = specialland = laCanvas;
75   patterns::whichCanvas = 'g';
76   patterns::canvasback = 0;
77   check_cgi();
78   start_game();
79   current_center = currentmap->gamestart();
80   current_color = 0x808080;
81   current_step = 32;
82   mapeditor::drawplayer = false;
83   vid.smart_range_detail = 1;
84   vid.use_smart_range = 2;
85   rv_hook(hooks_drawcell, 100, color_markers);
86   rv_hook(hooks_handleKey, 50, color_key);
87   rv_hook(hooks_prestats, 150, color_prestats);
88   }
89 
__anonf14c1a130102null90 auto cphook = addHook(hooks_args, 100, [] {
91   using namespace arg;
92 
93   if(0) ;
94   else if(argis("-cpick")) {
95     PHASEFROM(2);
96     run_cpick();
97     }
98   else return 1;
99   return 0;
100   });
101   }
102 
103 namespace sokoban {
104 
105 bool on;
106 
107 bool created;
108 
109 hpcshape sokowall[10][7];
110 
111 hyperpoint tpoint[6];
112 
push3(hyperpoint h)113 void push3(hyperpoint h) {
114   cgi.first = false;
115   cgi.hpc.push_back(h);
116   }
117 
create_sokowalls(cell * c)118 void create_sokowalls(cell *c) {
119   for(int a=0; a<6; a++) tpoint[a] = currentmap->adj(c, a) * C0;
120   const int qfr = 8;
121   auto v = [&] (int a, int b, int fr) {
122     hyperpoint h0 = get_corner_position(c, b);
123     hyperpoint h1 = get_corner_position(c, b+1);
124     hyperpoint h2 = normalize(h0 * (qfr-fr) + h1 * fr);
125     return mscale(h2, 1 / (1 - a / 6.1));
126     };
127 
128   for(int a=0; a<9; a++)
129   for(int b=0; b<6; b++) {
130     cgi.bshape(sokowall[a][b], PPR::FLOOR + 2 * a + 1);
131     for(int f=0; f<=qfr; f++)
132       push3(v(a, b, f));
133     for(int f=0; f<=qfr; f++)
134       push3(v(a+1, b, qfr-f));
135     push3(v(a, b, 0));
136     }
137 
138   for(int a=0; a<9; a++) {
139     cgi.bshape(sokowall[a][6], PPR::FLOOR + 2 * a);
140     for(int b=0; b<6; b++)
141       for(int f=0; f<qfr; f++)
142         push3(v(a, b, f));
143     push3(v(a, 0, 0));
144     }
145 
146   cgi.finishshape();
147   cgi.extra_vertices();
148   }
149 
sokomap(cell * c,const shiftmatrix & V)150 bool sokomap(cell *c, const shiftmatrix& V) {
151   if(!created) {
152     created = true;
153     create_sokowalls(c);
154     }
155 
156   crystal::coord v = crystal::get_coord(c->master);
157   bool high = (v[0] ^ v[1] ^ v[2]) & 2;
158 
159 
160   color_t col = 0x00C000FF;
161 
162   poly_outline = 0xFF;
163 
164   int lev = 0;
165 
166   int phase = ((v[0] ^ v[2] ^ v[4]) & 2) >> 1;
167 
168   // high = v[0] >= 0 && v[0] <= 8 && v[1] >= 0 && v[1] <= 8 && v[2] == 0 && !(v[0] > 0 && v[0] < 8 && v[1] > 0 && v[1] < 8);
169   // if(high) col = 0xFFFF00FF, lev = 3;
170 
171   high = true;
172   lev = ((v[0] + v[1] + v[2])/2+1) & 3;
173   if(lev == 0) col = 0x008000FF;
174   if(lev == 1) col = 0x00C000FF;
175   if(lev == 2) col = 0x40C000FF;
176   if(lev == 3) col = 0x80FF00FF;
177 
178   // if(v[0] == 4 && v[1] == 4 && v[2] == 0) col = 0xFFFFFFFF;
179 
180   c->landparam = lev;
181 
182   if(high) {
183     ld d = hdist0(tC0(V));
184     color_t dark1 = col - ((col & 0xFCFCFC00) >> 1);
185     color_t dark2 = col - ((col & 0xFCFCFC00) >> 2);
186     for(int b=0; b<6; b++)
187       for(int a=c->move(b)->landparam; a<lev; a++) if(hdist0(V * tpoint[b]) < d) {
188         /* auto& p = */
189         queuepoly(V, sokowall[a][b], (((b+phase)&1) ? dark1 : dark2));
190         // p.flags |= POLY_PRECISE_WIDE;
191         // p.linewidth = 2;
192         }
193     queuepoly(V, sokowall[lev][6], col);
194     }
195   else {
196     queuepoly(V, sokowall[0][6], col);
197     }
198 
199   c->wall = waInvisibleFloor;
200   return false;
201   }
202 
run_sb()203 void run_sb() {
204   crystal::compass_probability = 0;
205   stop_game();
206   crystal::set_crystal(6);
207   set_variation(eVariation::pure);
208   firstland = specialland = laCanvas;
209   patterns::whichCanvas = 'g';
210   patterns::canvasback = 0;
211   check_cgi();
212   rv_hook(hooks_drawcell, 100, sokomap);
213   start_game();
214   mapeditor::drawplayer = false;
215   vid.smart_range_detail = 1;
216   vid.use_smart_range = 2;
217   }
218 
__anonf14c1a130302null219 auto sbhook = addHook(hooks_args, 100, [] {
220   using namespace arg;
221 
222   if(0) ;
223   else if(argis("-sb")) {
224     PHASEFROM(2);
225     sokoban::run_sb();
226     }
227   else return 1;
228   return 0;
229   });
230 
231 
232 }
233 
234 const flagtype VC = 1;
235 const flagtype NO_VC = 2;
236 const flagtype PLAYER = 4;
237 
sync(int mode,flagtype flags)238 void sync(int mode, flagtype flags) {
239   using namespace tour;
240   if(mode == pmStart) {
241     crystal::compass_probability = 0;
242     crystal::crystal_period = 0;
243     firstland = specialland = laCanvas;
244     mapeditor::drawplayer = (flags & PLAYER);
245     vid.smart_range_detail = 1;
246     vid.use_smart_range = 2;
247     crystal::view_coordinates = (flags & VC);
248     smooth_scrolling = true;
249     }
250   if(mode == pmKey && !(flags & NO_VC))
251     crystal::view_coordinates = !crystal::view_coordinates;
252   if(cwt.at != centerover && !playermoved && !(flags & PLAYER)) {
253     cwt.at = centerover;
254     current_display->which_copy = gmatrix[cwt.at].T;
255     }
256   }
257 
258 vector<tour::slide> high_slides;
259 
260 int shapeid;
261 
mycanvas(cell * c)262 int mycanvas(cell *c) {
263 
264   int dim = crystal::get_dim();
265 
266   auto d = crystal::get_coord(c->master);
267   for(int i=0; i<dim; i++) d[i] >>= 1;
268 
269   color_t col = 0;
270 
271   d[0] ++;
272 
273   int ones = 0;
274   for(int i=0; i<dim; i++) if((d[i] & 1) == 1) ones++;
275 
276   switch(shapeid) {
277 
278     case 0: {
279       auto dx = d; dx[0] -= 2;
280       bool grid = false;
281       for(int i=1; i<dim; i++) if((dx[i] & 3) == 2) grid = true;
282 
283       for(int i=0; i<3; i++) part(col, i) = 0xFF + 0x30 * (d[i]-2);
284       if(grid) col |= 0x1000000;
285       if(dx == crystal::c0) col = 0x1FFD500;
286       if(dx[0] == 2 && dx[1] == 0 && dx[2] == 0 && dx[3] == 0 && dx[4] == 0 && dx[5] == 0) col = 0;
287       if(dx[0] == 6 && dx[1] == 0 && dx[2] == 0 && dx[3] == 0 && dx[4] == 0 && dx[5] == 0) col = 0;
288       return col;
289       }
290 
291     case 1: {
292       if(d[1] == 0 && d[2] == 0 && d[3] == 0 && d[4] == 0 && d[5] == 0) ;
293       else col = gradient(0x1FF0000, 0x1FFFF00, 9, d[0], 16);
294       part(col, 0) = 0x80 + d[1] * 0x70;
295       return col;
296       }
297 
298     case 2: {
299       col = gradient(0xFFFF00, 0x00FF00, 17, d[0], 24);
300       for(int i=0; i<1; i++) part(col, i) = 0xFF + 0x30 * (d[i+1]-2);
301       c->landparam = col;
302       if(ones == dim-1) col |= 0x1000000;
303       return col;
304       }
305 
306     case 3: {
307       if(d[3] == 1) col = (ones & 1) ? 0x1C0FFC0 : 0x180FF80;
308       if(d[3] == -1) col = (ones & 1) ? 0x18080FF : 0x14040FF;
309       if(d[2] == 1) col = (ones & 1) ? 0x1FFCC00 : 0x1FF8080;
310       if(d[2] == -1) col = (ones & 1) ? 0x180FFFF : 0x1408080;
311 
312       if(d[4] == 1) col = 0x1FFFFFF;
313       if(d[4] == -1) col = 0x1FFFFFF;
314       if(d[5] == 1) col = 0x1FFFFFF;
315       if(d[5] == -1) col = 0x1FFFFFF;
316       return col;
317       }
318 
319     case 4: {
320       if(d[3] == 1) col = (ones & 1) ? 0x1C0FFC0 : 0x180FF80;
321       if(d[3] == -1) col = (ones & 1) ? 0x18080FF : 0x14040FF;
322 
323       if(d[4] == 1) col = 0x1FFFFFF;
324       if(d[4] == -1) col = 0x1FFFFFF;
325       return col;
326       }
327 
328     case 5: {
329       if(d[3] == 1) col = (ones & 1) ? 0x1C0FFC0 : 0x180FF80;
330       if(d[3] == -2) col = (ones & 1) ? 0x180FFFF : 0x140FFFF;
331       return col;
332       }
333 
334     case 6: {
335       if(d[3] == 1) col = (ones & 1) ? 0x1FF8080 : 0x1FF6060;
336       if(d[2] == -1) col = (ones & 1) ? 0x1FFFF70 : 0x1E0E060;
337       return col;
338       }
339 
340     case 7: {
341       if(d[1] == 0 || d[2] == 0) ;
342       else if(d[1] > 0 && d[2] > 0) col = 0x1FF0000;
343       else if(d[1] > 0 && d[2] < 0) col = 0x1FFFF00;
344       else if(d[1] < 0 && d[2] < 0) col = 0x100FFFF;
345       else if(d[1] < 0 && d[2] > 0) col = 0x10000FF;
346       return col;
347       }
348 
349     case 8: {
350       int s = d[1] + d[2] + d[3] + d[4] + d[5];
351       if(s > 0) col = 0x1FFD500;
352       else if (s < -1) col = 0x17851a9;
353       return col;
354       }
355 
356     case 9: {
357       int s = d[1] + d[2] + d[3] + d[4] + d[5] + d[0];
358       if(s > 0) col = 0x1FF20FF;
359       else if (s < -1) col = 0x1C0C0C0;
360       return col;
361       }
362 
363     case 10: /* house */ {
364       d[0]--;
365       int is0 = 0, is1 = 0, is2 = 0, ismore = 0;
366       for(int a=0; a<dim; a++) {
367         int v = abs(d[a]);
368         if(v == 0) is0++;
369         else if(v == 1) is1++;
370         else if(v == 2) is2++;
371         else if(v > 2) ismore++;
372         }
373       if(d[dim-1]) return 0x101010;
374       else if(ismore) return 0x101010;
375       else if(is2) return 0x1800000;
376       else if(is1) return 0xFFFF00;
377       else return 0x1FFFFFF;
378       }
379 
380     case 11: /* orthoplex */ {
381       int s = abs(d[0] - 4) + abs(d[1] - 3) + abs(d[2] - 2) + abs(d[3] - 3);
382       if(s == 0) return 0x1FFFFFF;
383       else if(s == 12) return 0x1FF0000;
384       else if(s < 12) return 0x800000;
385       // else if(s == 13) return 0x1FF8000;
386       else return 0x202020;
387       }
388 
389     default:
390       return -1;
391     }
392   }
393 
enable()394 void enable() {
395   rv_hook(patterns::hooks_generate_canvas, 100, mycanvas);
396   }
397 
explore_structure(int _shapeid)398 auto explore_structure(int _shapeid) {
399   using namespace tour;
400   return [=] (presmode mode) {
401     sync(mode, NO_VC);
402     if(mode == pmStart) {
403       tour::slide_backup(mapeditor::drawplayer, false);
404       tour::slide_backup(smooth_scrolling, true);
405       stop_game();
406       set_geometry(geometry == gCrystal534 ? gCrystal534 : gCrystal344);
407       firstland = specialland = laCanvas;
408       patterns::whichCanvas = ' ';
409       shapeid = _shapeid;
410       enable();
411       crystal::crystal_period = 4;
412       start_game();
413       #if CAP_RAY
414       ray::max_cells = 4096;
415       #endif
416       }
417     if(mode == pmKey || mode == pmGeometrySpecial) {
418       stop_game();
419       set_geometry(geometry == gCrystal534 ? gCrystal344 : gCrystal534);
420       enable();
421       start_game();
422       }
423     };
424   }
425 
add_explore_structure(vector<tour::slide> & v,int id,string nshort,string nlong)426 void add_explore_structure(vector<tour::slide>& v, int id, string nshort, string nlong) {
427   string hds = "high-dimensional shapes/";
428   v.emplace_back(
429     tour::slide{hds+nshort, 999, tour::LEGAL::SPECIAL,
430       nlong + "\n\n"
431       "In these slides, press 5 to switch between 4-dimensional and 6-dimensional space."
432       "Press End/Home to move forward/backward.",
433       explore_structure(id)
434       });
435   }
436 
house(int sides,int shape=10)437 void house(int sides, int shape = 10) {
438   stop_game();
439   if(sides < 0)
440     set_geometry(gCrystal344);
441   else
442     crystal::set_crystal(sides);
443   set_variation(eVariation::pure);
444   firstland = specialland = laCanvas;
445   patterns::whichCanvas = ' ';
446   shapeid = shape;
447   check_cgi();
448   enable();
449   start_game();
450   }
451 
452 #if CAP_RVSLIDES
gen_high_demo()453 tour::slide *gen_high_demo() {
454   high_slides.clear();
455   using namespace tour;
456   auto& v = high_slides;
457   v.emplace_back(
458     slide{"Introduction/Three-dimensional space", 999, LEGAL::NONE,
459       "This is our 2D visualization of 3-dimensional space.\n\n"
460       "In most slides you can press '5' to enable or disable the coordinate display. "
461       "You can move the focus with numpad, arrowkeys, or clicking cells with mouse."
462       ,
463       [] (presmode mode) {
464         sync(mode, VC);
465         if(mode == pmStart) {
466           crystal::set_crystal(6);
467           patterns::whichCanvas = 'K';
468           start_game();
469           }
470         }
471       });
472 
473   v.emplace_back(
474     slide{"Introduction/Four-dimensional space", 999, LEGAL::NONE,
475       "This is our 2D visualization of 4-dimensional space.",
476       [] (presmode mode) {
477         sync(mode, VC);
478         if(mode == pmStart) {
479           crystal::set_crystal(8);
480           patterns::whichCanvas = 'K';
481           start_game();
482           }
483         }
484       });
485 
486   v.emplace_back(
487     slide{"Introduction/Color picker", 999, LEGAL::NONE,
488       "Color picker. You can press '0' to '6' to adjust how fast the colors change.",
489       [] (presmode mode) {
490         sync(mode, 0);
491         if(mode == pmStart)
492           colorpicker::run_cpick();
493         }
494       });
495 
496   v.emplace_back(
497     slide{"games and puzzles/house 3D", 999, LEGAL::NONE,
498       "A house in three dimensions. This is a 5x5 square, its center is white, rest of its interior is yellow, and its perimeter is red. "
499       "By using the third dimension, you can leave the square, or enter it back.",
500       [] (presmode mode) {
501         sync(mode, 0);
502         if(mode == pmStart) {
503           house(6);
504           }
505         }
506       });
507 
508   v.emplace_back(
509     slide{"games and puzzles/house 4D", 999, LEGAL::NONE,
510       "A house in four dimensions. This is a 5x5x5 cube."
511       ,
512       [] (presmode mode) {
513         sync(mode, 0);
514         if(mode == pmStart) {
515           house(8);
516           }
517         }
518       });
519 
520   v.emplace_back(
521     slide{"games and puzzles/house, 3D visualization", 999, LEGAL::NONE,
522       "A house in four dimensions, using the 3D version of our visualization. This visualization is harder to understand. Press End/Home to move forward/backward.",
523       [] (presmode mode) {
524         sync(mode, 0);
525         if(mode == pmStart) {
526           house(-1);
527           }
528         }
529       });
530 
531   v.emplace_back(
532     slide{"games and puzzles/4D orthoplex using 2D", 999, LEGAL::NONE,
533       "Try to find the center of the orthoplex in four dimensions. This is a 4D analog of the octahedron.\n\n"
534       "The faces of the orthoplex are bright red, the outside is dark gray, and the center is white."
535       ,
536       [] (presmode mode) {
537         sync(mode, 0);
538         if(mode == pmStart) {
539           house(8, 11);
540           }
541         }
542       });
543 
544   v.emplace_back(
545     slide{"games and puzzles/4D orthoplex using 3D", 999, LEGAL::NONE,
546       "The same visualization in 3D.",
547       [] (presmode mode) {
548         sync(mode, 0);
549         if(mode == pmStart) {
550           house(8, 11);
551           }
552         }
553       });
554 
555   v.emplace_back(
556     slide{"games and puzzles/basic roguelike", 999, LEGAL::NONE,
557       "This is a basic roguelike in three dimensions. Even though it appears that it should be possible to move in such a way that "
558       "one of the enemies is no longer adjacent to us, it turns out that one of its other 'copies' will always manage "
559       "to chase us. This is clear when we imagine the actual higher-dimensional space.",
560       [] (presmode mode) {
561         sync(mode, NO_VC | PLAYER);
562         if(mode == pmStart) {
563           crystal::set_crystal(6);
564           patterns::whichCanvas = 'c';
565           colortables['c'][0] = 0x208020;
566           colortables['c'][1] = 0x105010;
567           patterns::canvasback = 0x101010;
568           start_game();
569           auto & us = vid.cs;
570           us.charid = 4;
571           /* us.skincolor = 0xB55239FF;
572           us.haircolor = 0xB55239FF;
573           us.dresscolor = 0xB55239FF; */
574           us.skincolor = 0x202020FF;
575           us.haircolor = 0x202020FF;
576           us.dresscolor = 0x202020FF;
577           us.eyecolor = 0xC000FF;
578           cwt.at->move(0)->monst = moRedFox;
579           cwt.at->move(1)->monst = moLavaWolf;
580           minf[moLavaWolf].color = 0x909090;
581           minf[moLavaWolf].name = "Wolf";
582           minf[moRedFox].name = "Fox";
583           }
584        }
585       });
586 
587   v.emplace_back(
588     slide{"games and puzzles/gravity mockup", 999, LEGAL::NONE,
589       "A mockup of a 4D game with gravity. We use the 3D version of our visualization, while the gravity dimension is shown using perspective.",
590       [] (presmode mode) {
591         sync(mode, 0);
592         if(mode == pmStart) {
593           sokoban::run_sb();
594           }
595        }
596       });
597 
598   add_explore_structure(v, 0, "Cage", "In this series of slides we show various structures in four-dimensional space, using the three-dimensional variant of our visualization.\n\nWe start with a 4x4x4x4 cage with a golden point in the center.");
599   add_explore_structure(v, 1, "Tunnel", "One-dimensional tunnel");
600   add_explore_structure(v, 2, "Skeleton", "The 1-skeleton of the tessellation of Z^4 with cubes of edge 2");
601   add_explore_structure(v, 3, "Tunnel2", "Two-dimensional tunnel");
602   add_explore_structure(v, 4, "Hyperplanes", "Two hyperplanes in distance 2, i.e., three-dimensional tunnel.");
603   add_explore_structure(v, 5, "Far hyperplanes", "Two hyperplanes in distance 3.");
604   add_explore_structure(v, 6, "Orthogonal", "Two orthogonal hyperplanes.");
605   add_explore_structure(v, 7, "Quarterspaces", "Four quarterspaces.");
606   add_explore_structure(v, 8, "Diagonal", "Diagonal tunnel in all coordinates except one.");
607   add_explore_structure(v, 9, "Diagonal", "Diagonal tunnel in all coordinates.");
608 
609   v.emplace_back(
610     slide{"Magic cube/Standard magic cube", 999, LEGAL::NONE,
611       "Magic Cube (aka Rubik's Cube) using two dimensions. This is an example of a visualization which is difficult for our method, because we are moving complex objects in the 3D space."
612       "Press 'r' to rotate the face (while the mouse pointer is on its center -- two coodinates must be 0 and one must be non-zero), Shift+R to reset.",
613       [] (presmode mode) {
614         sync(mode, 0);
615         if(mode == pmStart) {
616           magic::magic(6);
617           }
618         }
619       });
620 
621   v.emplace_back(
622     slide{"Magic cube/Four-dimensional magic cube", 999, LEGAL::NONE,
623       "Magic Cube (4D) using two dimensions. Keys are the same as in the previous slide.\n\n"
624       "Use 'r' to rotate the 2D face under the mouse pointer (two coodinates must be 0 and two must be non-zero)."
625       ,
626       [] (presmode mode) {
627         sync(mode, 0);
628         if(mode == pmStart) {
629           magic::magic(8);
630           }
631         }
632       });
633 
634   v.emplace_back(
635     slide{"Magic cube/Four-dimensional magic cube, 3D visualization", 999, LEGAL::NONE,
636       "Magic Cube (4D) using three dimensions.",
637       [] (presmode mode) {
638         sync(mode, 0);
639         if(mode == pmStart) {
640           magic::magic(-1);
641           }
642         }
643       });
644 
645   callhooks(pres::hooks_build_rvtour, "highdim", high_slides);
646   pres::add_end(v);
647   return &high_slides[0];
648   }
649 #endif
650 
651 auto highdim_hooks  =
__anonf14c1a131202(tour::ss::slideshow_callback cb) 652     addHook_slideshows(120, [] (tour::ss::slideshow_callback cb) {
653 
654     if(high_slides.empty()) gen_high_demo();
655 
656     cb(XLAT("visualizing higher-dimensional spaces"), &high_slides[0], 'h');
657     });
658 
659 }