mirror of
				https://github.com/zenorogue/hyperrogue.git
				synced 2025-10-26 03:17:39 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1289 lines
		
	
	
		
			43 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1289 lines
		
	
	
		
			43 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "rogueviz.h"
 | |
| 
 | |
| #define PSHIFT 0
 | |
| 
 | |
| namespace rogueviz {
 | |
| 
 | |
| #if CAP_MODELS
 | |
| #ifndef RV_ALL
 | |
| namespace cylon {
 | |
|   extern void enable();
 | |
|   extern bool cylanim;
 | |
|   }
 | |
| 
 | |
| namespace nilcompass {
 | |
|   bool draw_compass(cell *c, const shiftmatrix& V);
 | |
|   extern int zeroticks;
 | |
|   }
 | |
| 
 | |
| namespace balls {
 | |
|   struct ball {
 | |
|     hyperpoint at;
 | |
|     hyperpoint vel;
 | |
|     };
 | |
|   extern vector<ball> balls;
 | |
|   extern void initialize(int);
 | |
|   }
 | |
| }
 | |
| 
 | |
| namespace hr { 
 | |
| 
 | |
| namespace bricks {
 | |
|   extern int animation;
 | |
|   void enable();
 | |
|   extern void build(bool in_pair);
 | |
|   extern void build_stair();
 | |
| 
 | |
|   struct brick {
 | |
|     euc::coord co;
 | |
|     color_t col;
 | |
|     int walls;
 | |
|     hyperpoint location;
 | |
|     hpcshape shRotWall[6];
 | |
|     };
 | |
|   
 | |
|   extern vector<brick> bricks;
 | |
|   }
 | |
| 
 | |
| namespace pentaroll {
 | |
|   extern void create_pentaroll(bool);
 | |
|   }
 | |
| 
 | |
| namespace ply {
 | |
|   extern bool animated;
 | |
|   void enable();
 | |
|   extern rogueviz::objmodels::model staircase;
 | |
|   }
 | |
| #endif
 | |
| }
 | |
| 
 | |
| namespace hr {
 | |
| 
 | |
| namespace dmv {
 | |
| 
 | |
| transmatrix xyzscale(ld x) {
 | |
|   transmatrix T = Id;
 | |
|   T[0][0] = T[1][1] = T[2][2] = x;
 | |
|   return T;
 | |
|   }
 | |
| 
 | |
| using namespace rogueviz::pres;
 | |
| using namespace hr::tour;
 | |
| 
 | |
| struct dmv_grapher : grapher {
 | |
| 
 | |
|   dmv_grapher(transmatrix U) : grapher(-4, -4, 11, 11) {
 | |
|     T = T * U;
 | |
|     
 | |
|     for(int x=-3; x<=10; x++) if(x) {
 | |
|       line(p2(x,-3), p2(x,10), 0x8080FFFF);
 | |
|       line(p2(-3,x), p2(10,x), 0x8080FFFF);
 | |
|       }
 | |
|     
 | |
|     vid.linewidth *= 2;
 | |
|     arrow(p2(0,-3), p2(0,10), .5);
 | |
|     arrow(p2(-3,0), p2(10,0), .5);
 | |
|     vid.linewidth /= 2;
 | |
|     }
 | |
|   };
 | |
| 
 | |
| void nil_screen(presmode mode, int id) {
 | |
|   use_angledir(mode, id == 0);
 | |
|   setPlainCanvas(mode);
 | |
|   if(mode == pmStart) {
 | |
|     slide_backup(pmodel);
 | |
|     slide_backup(pconf.clip_min);
 | |
|     slide_backup(pconf.clip_max);
 | |
|     slide_backup(vid.cells_drawn_limit);
 | |
|     stop_game(), pmodel = mdHorocyclic, geometry = gCubeTiling, variation = eVariation::pure, pconf.clip_min = -10000, pconf.clip_max = +100, start_game();
 | |
|     }
 | |
|   add_stat(mode, [id] {
 | |
|     cmode |= sm::SIDE;
 | |
|     calcparam();
 | |
| 
 | |
|     vid.cells_drawn_limit = 0;
 | |
|     drawthemap();
 | |
| 
 | |
|     // flat_model_enabler fme;
 | |
|     initquickqueue();
 | |
|     
 | |
|     dmv_grapher g(MirrorZ * cspin(1, 2, .8 * angle) * spin(angle/2));
 | |
|     
 | |
|     vid.linewidth *= 3;
 | |
|     
 | |
|     ld t = 1e-3;
 | |
| 
 | |
|     if(id == 2) {
 | |
|       t = inHighQual ? ticks * 4. / anims::period : ticks / 1000.;
 | |
|       if(t - floor(t) > .5) t = ceil(t);
 | |
|       else t = floor(t) + 2 * (t - floor(t));
 | |
|       t -= floor(t/4)*4;
 | |
|       
 | |
|       ld t2 = 90._deg * t;
 | |
|       
 | |
|       curvepoint(p2(0,0));
 | |
|       curvepoint(p2(5,5));
 | |
|       curvepoint(p2(5 + 2 * cos(t2),5 + 2 * sin(t2)));
 | |
|       curvepoint(p2(0,0));
 | |
|       
 | |
|       color_t col = cos(t2) > sin(t2) ? 0xFF808000 : 0x8080FF00;
 | |
|       
 | |
|       queuecurve(g.T, col | 0xFF, col | 0x20, PPR::LINE);  
 | |
|       }
 | |
|     
 | |
|     if(id < 3) {
 | |
|       g.arrow(p2(5,5), p2(7,5), .3);
 | |
|       g.arrow(p2(5,5), p2(5,7), .3);
 | |
|       g.arrow(p2(5,5), p2(3,5), .3);
 | |
|       g.arrow(p2(5,5), p2(5,3), .3);
 | |
|       }
 | |
| 
 | |
|     vid.linewidth /= 3;
 | |
|     
 | |
|     if(id < 3) {
 | |
| 
 | |
|       if(id == 2) {
 | |
|         drawMonsterType(moEagle, nullptr, g.pos(5,5,1.5) * spin(-t * 90._deg) * xyzscale(1.5), 0x40C040, ticks / 1000., 0);
 | |
|         }
 | |
|     
 | |
|       color_t dark = 0xFF;
 | |
| 
 | |
|       write_in_space(g.pos(7.5, 5, 1) * MirrorY, max_glfont_size, 1., "E", dark);
 | |
|       write_in_space(g.pos(5, 7.5, 1) * MirrorY, max_glfont_size, 1., "N", dark);
 | |
|       write_in_space(g.pos(2.5, 5, 1) * MirrorY, max_glfont_size, 1., "W", dark);
 | |
|       write_in_space(g.pos(5, 2.5, 1) * MirrorY, max_glfont_size, 1., "S", dark);
 | |
|       }
 | |
|     
 | |
|     if(id == 3) {
 | |
|       vid.linewidth *= 3;
 | |
|       t = ticks / anims::period;
 | |
|       ld ti = ticks / 1000.;
 | |
|       t = frac(t);
 | |
|       vector<hyperpoint> loop = { p2(9, 4), p2(9, 9), p2(1, 9), p2(1, 5), p2(6, 6), p2(9,4) };
 | |
|       int q = isize(loop) - 1;
 | |
| 
 | |
|       for(hyperpoint h: loop) curvepoint(h);
 | |
|       queuecurve(g.T, 0x40C040FF, 0, PPR::LINE);
 | |
|       ld total_length = 0;
 | |
|       for(int i=0; i<q; i++) total_length += hypot_d(2, loop[i+1] - loop[i]);
 | |
|       t *= total_length;
 | |
|       t *= 1.2;
 | |
|       curvepoint(p2(0,0));
 | |
|       shiftmatrix T1 = g.pos(loop[0][0], loop[0][1], 1.5);
 | |
|       shiftmatrix T2 = g.pos(loop[0][0], loop[0][1], 1.5);
 | |
| 
 | |
|       ld t1 = t;
 | |
| 
 | |
|       for(int i=0; i<=q; i++) {
 | |
|         curvepoint(loop[i]);
 | |
|         if(i == q) {
 | |
|           T1 = g.pos(loop[i][0],loop[i][1],1.5) * rspintox(loop[i] - loop[i-1]);
 | |
|           break;
 | |
|           }
 | |
|         ld len = hypot_d(2, loop[i+1] - loop[i]);
 | |
|         if(len < t1) { t1 -= len; continue; }
 | |
|         hyperpoint cur = lerp(loop[i], loop[i+1], t1 / len);
 | |
|         T1 = g.pos(cur[0],cur[1],1.5) * rspintox(loop[i+1] - loop[i]);
 | |
|         curvepoint(cur);
 | |
|         break;
 | |
|         }
 | |
|       curvepoint(p2(0,0));
 | |
|       color_t col = 0x8080FF00;
 | |
|       queuecurve(g.T, col | 0xFF, col | 0x40, PPR::LINE);
 | |
| 
 | |
|       ld z = 0;
 | |
| 
 | |
|       ld zsca = .05;
 | |
| 
 | |
|       vector<pair<hyperpoint, hyperpoint> > vlines;
 | |
| 
 | |
|       for(int i=0; i<=q; i++) {
 | |
|         vlines.emplace_back(loop[i], loop[i] + ztangent(z));
 | |
|         curvepoint(loop[i] + ztangent(z));
 | |
|         if(i == q) {
 | |
|           T2 = g.pos(loop[i][0],loop[i][1],1.5) * cpush(2, z) * rspintox(loop[i] - loop[i-1]);
 | |
|           break;
 | |
|           }
 | |
|         ld len = hypot_d(2, loop[i+1] - loop[i]);
 | |
|         if(len < t) {
 | |
|           t -= len;
 | |
|           z += (loop[i+1][1] * loop[i][0] - loop[i+1][0] * loop[i][1]) * zsca;
 | |
|           continue;
 | |
|           }
 | |
|         hyperpoint cur = lerp(loop[i], loop[i+1], t / len);
 | |
|         z += (cur[1] * loop[i][0] - cur[0] * loop[i][1]) * zsca;
 | |
|         T2 = g.pos(cur[0],cur[1],1.5) * cpush(2, z) * rspintox(loop[i+1] - loop[i]);
 | |
|         curvepoint(cur + ztangent(z));
 | |
|         break;
 | |
|         }
 | |
|       queuecurve(g.T, 0x40C040FF, 0, PPR::LINE);
 | |
| 
 | |
|       for(auto l: vlines) queueline(g.T*l.first, g.T*l.second, 0x40, 0, PPR::MONSTER_BODY);
 | |
| 
 | |
|       vid.linewidth /= 3;
 | |
| 
 | |
|       drawMonsterType(moEagle, nullptr, T2, 0x40C040, ti, 0);
 | |
|       auto& bp = cgi.shEagle;
 | |
|       if(bp.she > bp.shs && bp.she < bp.shs + 1000) {
 | |
|         auto& p = queuepolyat(T1, bp, 0x80, PPR::TRANSPARENT_SHADOW);
 | |
|         p.outline = 0;
 | |
|         p.subprio = -100;
 | |
|         p.offset = bp.shs;
 | |
|         p.cnt = bp.she - bp.shs;
 | |
|         p.flags &=~ POLY_TRIANGLES;
 | |
|         p.tinf = NULL;
 | |
|         }
 | |
|       // queuepolyat(T2, cgi.shEagle, 0x40C040FF, PPR::SUPERLINE);
 | |
|       }
 | |
| 
 | |
|     quickqueue();
 | |
|     glflush();
 | |
| 
 | |
|     dialog::init();        
 | |
|     // dialog::addTitle(id ? "Nil coordinates" : "Cartesian coordinates", forecolor, 150);
 | |
|     
 | |
|     poly_outline = 0xFFFFFFFF;
 | |
|     
 | |
|     dialog::addBreak(100);
 | |
|     dialog::addBreak(50);
 | |
|     auto dirbox = [] (string s) {
 | |
|       return "\\makebox[5em][r]{\\textsf{" + s + "}} ";
 | |
|       };
 | |
|     auto cbox = [] (string s) {
 | |
|       return "\\makebox[9em][l]{$" + s + "$} ";
 | |
|       };
 | |
|     dialog_may_latex(dirbox("start:") + cbox("(x,y,z)"), "start: (x,y,z)");
 | |
|     dialog::addBreak(50);
 | |
| 
 | |
|     if(id == 0) {
 | |
|       dialog_may_latex(dirbox("N:") + cbox("(x,y+d,z)"), "N: (x,y+d,z)");
 | |
|       dialog_may_latex(dirbox("W:") + cbox("(x-d,y,z)"), "W: (x-d,y,z)");
 | |
|       dialog_may_latex(dirbox("S:") + cbox("(x,y-d,z)"), "S: (x,y-d,z)");
 | |
|       dialog_may_latex(dirbox("E:") + cbox("(x+d,y,z)"), "E: (x+d,y,z)");
 | |
|       }
 | |
|     else {
 | |
|       dialog_may_latex(dirbox("N:") + cbox("(x,y+d,z+\\frac{xd}{2})"), "N: (x,y+d,z+xd/2)", t == 1 ? 0xFFD500 : dialog::dialogcolor);
 | |
|       dialog_may_latex(dirbox("W:") + cbox("(x-d,y,z+\\frac{yd}{2})"), "W: (x-d,y,z+yd/2)", t == 2 ? 0xFFD500 : dialog::dialogcolor);
 | |
|       dialog_may_latex(dirbox("S:") + cbox("(x,y-d,z-\\frac{xd}{2})"), "S: (x,y-d,z-xd/2)", t == 3 ? 0xFFD500 : dialog::dialogcolor);
 | |
|       dialog_may_latex(dirbox("E:") + cbox("(x+d,y,z-\\frac{yd}{2})"), "E: (x+d,y,z-yd/2)", t == 0 ? 0xFFD500 : dialog::dialogcolor);
 | |
|       }
 | |
|     dialog::addBreak(50);
 | |
|     dialog_may_latex(dirbox("U:") + cbox("(x,y,z-d)"), "U: (x,y,z-d)");
 | |
|     dialog_may_latex(dirbox("D:") + cbox("(x,y,z+d)"), "D: (x,y,z+d)");
 | |
|     dialog::display();
 | |
|     
 | |
|     return false;
 | |
|     });
 | |
|   }
 | |
| 
 | |
| ld geo_zero;
 | |
| 
 | |
| void geodesic_screen(presmode mode, int id) {
 | |
|   if(mode == pmStart) geo_zero = ticks;
 | |
| 
 | |
|   use_angledir(mode, id == 0);
 | |
|   
 | |
|   setPlainCanvas(mode);
 | |
|   if(mode == pmStart) {
 | |
|     slide_backup(pmodel);
 | |
|     slide_backup(pconf.clip_min);
 | |
|     slide_backup(pconf.clip_max);
 | |
|     slide_backup(vid.cells_drawn_limit);
 | |
|     stop_game(), pmodel = mdHorocyclic, geometry = gCubeTiling, variation = eVariation::pure, pconf.clip_min = -10000, pconf.clip_max = +100, start_game();
 | |
|     }
 | |
|   
 | |
|   add_stat(mode, [id] {
 | |
|     cmode |= sm::SIDE;
 | |
|     calcparam();
 | |
|     
 | |
|     vid.cells_drawn_limit = 0;
 | |
|     drawthemap();
 | |
| 
 | |
|     // flat_model_enabler fme;
 | |
|     initquickqueue();
 | |
| 
 | |
|     dmv_grapher g(MirrorZ * cspin(1, 2, .3 * angle / 90._deg) * spin(angle/2));
 | |
|     
 | |
|     ld val = 25;
 | |
|     
 | |
|     ld rv = sqrt(val);
 | |
|     
 | |
|     // pi*rad*rad == 25
 | |
|     ld rad = sqrt(val / M_PI);
 | |
| 
 | |
|     ld rr = rad * sqrt(1/2.);
 | |
|     
 | |
|     ld radh = sqrt(val / M_PI - 2);
 | |
|     ld rrh = radh * sqrt(1/2.);
 | |
|     
 | |
|     ld zmove = val - M_PI * radh * radh;
 | |
|     ld len = hypot(TAU * radh, zmove);
 | |
|     
 | |
|     ld t = inHighQual ? ticks / 1000. : (ticks - geo_zero) / 500;
 | |
| 
 | |
|     auto frac_of = [&] (ld z) { return t - z * floor(t/z); };
 | |
|     
 | |
|     t = frac_of(val);
 | |
| 
 | |
|     auto draw_path = [&] (auto f, color_t col) {
 | |
|       vid.linewidth *= 5;
 | |
|       for(ld t=0; t<=25; t+=1/16.) curvepoint(f(t));
 | |
|       queuecurve(g.T, col, 0, PPR::LINE);
 | |
|       
 | |
|       auto be_shadow = [&] (hyperpoint& h) {
 | |
|         // ld part = 1 - angle / 90._deg;
 | |
|         // h[0] += h[2] * part / 10;
 | |
|         h[2] = 0;
 | |
|         };
 | |
| 
 | |
|       for(ld t=0; t<=25; t+=1/16.) {
 | |
|         hyperpoint h = f(t);
 | |
|         be_shadow(h);
 | |
|         curvepoint(h);
 | |
|         }
 | |
|       queuecurve(g.T, col & 0xFFFFFF40, 0, PPR::LINE);
 | |
|       vid.linewidth /= 5;
 | |
|       
 | |
|       hyperpoint eaglepos = f(t);
 | |
|       hyperpoint next_eaglepos = f(t + 1e-2);
 | |
| 
 | |
|       // queuepolyat(g.pos(x+z * .1,y,1.5) * spin(s), cgi.shEagle, 0x40, PPR::MONSTER_SHADOW).outline = 0;
 | |
|       drawMonsterType(moEagle, nullptr, g.T * eupush(eaglepos) * rspintox(next_eaglepos - eaglepos) * xyzscale(2), col >> 8, t, 0);            
 | |
|       
 | |
|       be_shadow(eaglepos);
 | |
|       be_shadow(next_eaglepos);
 | |
|       
 | |
|       auto& bp = cgi.shEagle;
 | |
|       
 | |
|       println(hlog, tie(bp.shs, bp.she));
 | |
| 
 | |
|       if(bp.she > bp.shs && bp.she < bp.shs + 1000) {
 | |
|         auto& p = queuepolyat(g.T * eupush(eaglepos) * rspintox(next_eaglepos - eaglepos) * xyzscale(2), bp, 0x18, PPR::TRANSPARENT_SHADOW); 
 | |
|         p.outline = 0;
 | |
|         p.subprio = -100;
 | |
|         p.offset = bp.shs;
 | |
|         p.cnt = bp.she - bp.shs;
 | |
|         p.flags &=~ POLY_TRIANGLES;
 | |
|         p.tinf = NULL;
 | |
|         return;
 | |
|         }
 | |
|       };
 | |
|       
 | |
|     color_t straight = 0x80FF80FF;
 | |
|     color_t square = 0xcd7f32FF;
 | |
|     color_t circle = 0xaaa9adFF;
 | |
|     color_t helix  = 0xFFD500FF;
 | |
| 
 | |
|     if(id >= 0)
 | |
|       draw_path([&] (ld t) { return point31(0, 0, t); }, straight);
 | |
| 
 | |
|     if(id >= 1)
 | |
|       draw_path([&] (ld t) { 
 | |
|         if(t < rv)
 | |
|           return point31(t, 0, 0);
 | |
|         else if(t < rv*2)
 | |
|           return point31(rv, t-rv, rv*(t-rv)/2);
 | |
|         else if(t < rv*3)
 | |
|           return point31(rv-(t-rv*2), rv, rv*rv/2 + rv*(t-2*rv)/2);
 | |
|         else if(t < rv*4)
 | |
|           return point31(0, rv-(t-rv*3), val);
 | |
|         else
 | |
|           return point31(0, 0, val);
 | |
|         }, square);
 | |
| 
 | |
|     if(id >= 2) 
 | |
|       draw_path([&] (ld t) { 
 | |
|         ld tx = min(t, TAU * rad);
 | |
|         ld ta = tx / rad - 135._deg;
 | |
|         ld x = rr + rad * cos(ta);
 | |
|         ld y = rr + rad * sin(ta);
 | |
|         ld z = rad * tx / 2 - ((rr * x) - (rr * y)) / 2;
 | |
|         return point31(x, y, z);
 | |
|         }, circle);
 | |
|       
 | |
|     if(id >= 3)
 | |
|       draw_path([&] (ld t) { 
 | |
|         ld tx = min(t, len);
 | |
|         ld ta = tx / len * TAU - 135._deg;
 | |
|         ld x = rrh + radh * cos(ta);
 | |
|         ld y = rrh + radh * sin(ta);
 | |
|         ld z = radh * radh * (tx/len*TAU) / 2 - ((rrh * x) - (rrh * y)) / 2 + zmove * tx / len;
 | |
|         
 | |
|         return point31(x, y, z);
 | |
|         }, helix);
 | |
|     
 | |
|     auto cat = [] (PPR x) { 
 | |
|       if(x == PPR::MONSTER_SHADOW) return 1;
 | |
|       else if(x == PPR::MONSTER_BODY) return 2;
 | |
|       else return 0;
 | |
|       };
 | |
|       
 | |
|     for(int i=1; i<isize(ptds);)
 | |
|       if(i && cat(ptds[i]->prio) < cat(ptds[i-1]->prio)) {
 | |
|         swap(ptds[i], ptds[i-1]);
 | |
|         i--;
 | |
|         }
 | |
|       else i++;
 | |
| 
 | |
|     quickqueue();
 | |
| 
 | |
|     dialog::init();
 | |
|     dialog_may_latex("\\textsf{from $(0,0,0)$ to $(0,0,25)$}", "from (0,0,0) to (0,0,25)", forecolor, 150);
 | |
| 
 | |
|     dialog::addBreak(100);
 | |
|     dialog_may_latex("\\textsf{straight upwards}", "straight upwards", straight >> 8);
 | |
|     dialog_may_latex("$25$", "25", straight >> 8);
 | |
|     
 | |
|     if(id >= 1) {
 | |
|       dialog::addBreak(100);
 | |
|       dialog_may_latex("\\textsf{square}", "square", square >> 8);
 | |
|       dialog_may_latex("$20$", "20", square >> 8);
 | |
|       }
 | |
|     else dialog::addBreak(300);
 | |
| 
 | |
|     if(id >= 2) {
 | |
|       dialog::addBreak(100);
 | |
|       dialog_may_latex("\\textsf{circle}", "circle", circle >> 8);
 | |
|       dialog_may_latex("$"+fts(TAU * rad)+"$", fts(TAU * rad), circle >> 8);
 | |
|       }
 | |
|     else dialog::addBreak(300);
 | |
| 
 | |
|     if(id >= 3) {
 | |
|       dialog::addBreak(100);
 | |
|       dialog_may_latex("\\textsf{helix}", "helix", helix >> 8);
 | |
|       dialog_may_latex("$"+fts(len)+"$", fts(len), helix >> 8);
 | |
|       }
 | |
|     else dialog::addBreak(300);
 | |
|     
 | |
|     dialog::display();
 | |
|     
 | |
|     return false;
 | |
|     });
 | |
|   }
 | |
| 
 | |
| // i==0 - stairs
 | |
| // i==1 - triangle
 | |
| // i==2 - double triangle
 | |
| 
 | |
| void brick_slide(int i, presmode mode, eGeometry geom, eModel md, int anim) {
 | |
|   using namespace tour;
 | |
|   setPlainCanvas(mode);
 | |
|   if(mode == pmStart) {
 | |
|     set_geometry(geom);
 | |
|     start_game();
 | |
|     cgi.require_shapes();
 | |
|     if(i == 0)
 | |
|       bricks::build_stair();
 | |
|     if(i == 1)
 | |
|       bricks::build(false);
 | |
|     if(i == 2)
 | |
|       bricks::build(true);
 | |
|     bricks::enable();
 | |
|     tour::slide_backup(pconf.clip_min, -100.);
 | |
|     tour::slide_backup(pconf.clip_max, +10.);
 | |
|     tour::slide_backup(pconf.scale, i ? .2 : 2.);
 | |
|     tour::slide_backup(mapeditor::drawplayer, false);
 | |
|     tour::slide_backup(pconf.rotational_nil, 0.);
 | |
|     tour::slide_backup(vid.axes3, false);
 | |
|     bricks::animation = anim;
 | |
|     pmodel = md;
 | |
|     View = Id;
 | |
|     }
 | |
|   clearMessages();
 | |
|   no_other_hud(mode);
 | |
|   }
 | |
| 
 | |
| void ply_slide(tour::presmode mode, eGeometry geom, eModel md, bool anim) {
 | |
|   using namespace tour;
 | |
|   if(!ply::staircase.available())  {
 | |
|     slide_error(mode, "(model not available)");
 | |
|     return;
 | |
|     }
 | |
|   if(mode == pmStartAll) {
 | |
|     rogueviz::objmodels::prec = 10;
 | |
|     dynamicval<eGeometry> g(geometry, gNil);
 | |
|     dynamicval<eVariation> v(variation, eVariation::pure);
 | |
|     dynamicval<int> s(vid.texture_step, 1);
 | |
|     check_cgi();
 | |
|     cgi.require_shapes();
 | |
|     }
 | |
|   setPlainCanvas(mode);
 | |
|   if(mode == pmStart) {
 | |
|     set_geometry(geom);
 | |
|     start_game();
 | |
|     ply::enable();
 | |
|     tour::slide_backup(anims::period, 40000.);
 | |
|     tour::slide_backup(mapeditor::drawplayer, false);
 | |
|     tour::slide_backup(pconf.rotational_nil, 0.);
 | |
|     tour::slide_backup(ply::animated, anim);
 | |
|     tour::slide_backup(vid.axes3, false);
 | |
|     tour::slide_backup(no_find_player, true);
 | |
|     tour::slide_backup(vid.texture_step, 1);
 | |
|     tour::slide_backup(sightranges[geom], 10.);
 | |
|     tour::slide_backup(vid.cells_drawn_limit, 50);
 | |
|     pmodel = md;
 | |
|     View = Id;
 | |
|     }
 | |
|   clearMessages();
 | |
|   no_other_hud(mode);
 | |
|   }
 | |
| 
 | |
| void impossible_ring_slide(tour::presmode mode) {
 | |
|   using namespace tour;
 | |
|   setPlainCanvas(mode);
 | |
|   if(mode == pmStart) {
 | |
|     set_geometry(gCubeTiling);
 | |
|     start_game();
 | |
|     tour::slide_backup(pconf.clip_min, -100.);
 | |
|     tour::slide_backup(pconf.clip_max, +10.);
 | |
|     tour::slide_backup(mapeditor::drawplayer, false);
 | |
|     tour::slide_backup(vid.axes3, false);
 | |
|     pmodel = mdHorocyclic;
 | |
|     View = Id;
 | |
|     }
 | |
|   clearMessages();
 | |
|   no_other_hud(mode);
 | |
|   
 | |
|   use_angledir(mode, true);
 | |
|   
 | |
|   add_temporary_hook(mode, hooks_frame, 200, [] {
 | |
|     for(int id=0; id<2; id++) {
 | |
|       shiftmatrix T = ggmatrix(currentmap->gamestart());
 | |
|       println(hlog, "angle = ", angle);
 | |
|       if(id == 1) T = T * spin180() * xpush(1.5) * cspin(0, 2, angle) * xpush(-1.5);
 | |
|       
 | |
|       for(ld z: {+.5, -.5}) {
 | |
|         for(ld d=0; d<=180; d++)
 | |
|           curvepoint(C0 + spin(d*degree) * xtangent(1) + ztangent(z));
 | |
|         for(ld d=180; d>=0; d--)
 | |
|           curvepoint(C0 + spin(d*degree) * xtangent(2) + ztangent(z));
 | |
|         curvepoint(C0 + spin(0) * xtangent(1) + ztangent(z));
 | |
|         queuecurve(T, 0xFF, 0xFF8080FF, PPR::LINE);
 | |
|         }
 | |
|       for(ld d=0; d<180; d+=5) for(ld x: {1, 2}) {
 | |
|         for(int i=0; i<=5; i++)
 | |
|           curvepoint(C0 + spin((d+i)*degree) * xtangent(x) + ztangent(.5));
 | |
|         for(int i=5; i>=0; i--)
 | |
|           curvepoint(C0 + spin((d+i)*degree) * xtangent(x) + ztangent(-.5));
 | |
|         curvepoint(C0 + spin((d+0)*degree) * xtangent(x) + ztangent(.5));
 | |
|         queuecurve(T, 0xFF, 0xC06060FF, PPR::LINE);
 | |
|         }
 | |
|       
 | |
|       for(ld sgn: {-1, 1}) {
 | |
|         curvepoint(C0 + xtangent(sgn * 1) + ztangent(+.5));
 | |
|         curvepoint(C0 + xtangent(sgn * 2) + ztangent(+.5));
 | |
|         curvepoint(C0 + xtangent(sgn * 2) + ztangent(-.5));
 | |
|         curvepoint(C0 + xtangent(sgn * 1) + ztangent(-.5));
 | |
|         curvepoint(C0 + xtangent(sgn * 1) + ztangent(+.5));
 | |
|         
 | |
|         queuecurve(T, 0xFF, 0x804040FF, PPR::LINE);
 | |
|         }
 | |
|       }
 | |
|     });  
 | |
|   }
 | |
| 
 | |
| void enable_earth() {
 | |
|   texture::texture_aura = true;
 | |
|   stop_game();
 | |
|   set_geometry(gSphere);
 | |
|   enable_canvas();
 | |
|   ccolor::which = &ccolor::football;
 | |
|   start_game();        
 | |
|   texture::config.configname = "textures/earth.txc";
 | |
|   texture::config.load();
 | |
|   pmodel = mdDisk;
 | |
|   pconf.alpha = 1000; pconf.scale = 999;
 | |
|   texture::config.color_alpha = 255;
 | |
|   mapeditor::drawplayer = false;
 | |
|   fullcenter();
 | |
|   View = spin(234._deg) * View;
 | |
|   }
 | |
| 
 | |
| slide dmv_slides[] = {
 | |
|   {"Title Page", 123, LEGAL::ANY | QUICKSKIP | NOTITLE, "", 
 | |
|     [] (presmode mode) {
 | |
|       empty_screen(mode);
 | |
|       show_picture(mode, "rogueviz/nil/penrose-triangle.png");
 | |
|       add_stat(mode, [] {
 | |
|         cmode |= sm::DARKEN;
 | |
|         gamescreen();
 | |
|         dialog::init();
 | |
|         dialog::addBreak(400);
 | |
|         dialog::addTitle("playing with impossibility", dialog::dialogcolor, 150);
 | |
|         dialog::addBreak(1600);
 | |
|         dialog::addTitle("a presentation about Nil geometry", 0xFFC000, 75);
 | |
|         dialog::display();
 | |
|         return true;
 | |
|         });
 | |
|       no_other_hud(mode);
 | |
|       }
 | |
|     },
 | |
|   {"Euclidean plane", 999, LEGAL::NONE | QUICKGEO, 
 | |
|     "The sum of angles of a triangle is 180 degrees.\n\n",
 | |
|     [] (presmode mode) {
 | |
|       if(mode == pmStartAll) enable_canvas();
 | |
|       setCanvas(mode, &ccolor::football, [] {
 | |
|         set_geometry(gArchimedean); arcm::current.parse("3^6");
 | |
|         set_variation(eVariation::pure);
 | |
|         slide_backup(ccolor::which->ctab, colortable{0xC0FFC0, 0x80FF80});
 | |
|         });
 | |
|       if(mode == pmStart) {
 | |
|         slide_backup(pconf.alpha, 1); 
 | |
|         slide_backup(pconf.scale, 1); 
 | |
|         slide_backup(patterns::whichShape, '9');
 | |
|         slide_backup(vid.use_smart_range, 2);
 | |
|         slide_backup(mapeditor::drawplayer, false);
 | |
|         }
 | |
|       add_temporary_hook(mode, hooks_frame, 200, [] {
 | |
|         shiftmatrix T = ggmatrix(currentmap->gamestart());
 | |
|         vid.linewidth *= 4;
 | |
|         shiftpoint h1 = T * xspinpush0(0, 2);
 | |
|         shiftpoint h2 = T * xspinpush0(120._deg, 2);
 | |
|         shiftpoint h3 = T * xspinpush0(240._deg, 2);
 | |
|         queueline(h1, h2, 0xFF0000FF, 4);
 | |
|         queueline(h2, h3, 0xFF0000FF, 4);
 | |
|         queueline(h3, h1, 0xFF0000FF, 4);
 | |
|         vid.linewidth /= 4;
 | |
|         });
 | |
|       no_other_hud(mode);
 | |
|       }
 | |
|     },
 | |
|   {"spherical triangles", 999, LEGAL::NONE | QUICKGEO, 
 | |
|     "The simplest non-Euclidean geometry is the geometry on the sphere.\n\n"
 | |
|     "Here we see a spherical triangle with three right angles.\n\n"
 | |
|     "For creatures restricted to just this surface, they are indeed striaght lines!\n\n"
 | |
|     ,
 | |
|     [] (presmode mode) {
 | |
|       setPlainCanvas(mode);
 | |
|       if(mode == pmStart) {
 | |
|         tour::slide_backup(mapeditor::drawplayer, false);
 | |
|         enable_earth();
 | |
|       
 | |
|         View = Id;
 | |
|         View = spin(108._deg) * View;
 | |
|         View = spin(90._deg) * View;
 | |
|         View = cspin(2, 0, 45._deg) * View;
 | |
|         View = cspin(1, 2, 30._deg) * View;
 | |
|         playermoved = false;
 | |
|         tour::slide_backup(vid.axes, 0);
 | |
|         tour::slide_backup(vid.drawmousecircle, false);
 | |
|         tour::slide_backup(draw_centerover, false);
 | |
|         }
 | |
|       add_temporary_hook(mode, hooks_frame, 200, [] {
 | |
|         shiftmatrix T = ggmatrix(currentmap->gamestart()) * spin(-108._deg);
 | |
|         vid.linewidth *= 4;
 | |
|         shiftpoint h1 = T * C0;
 | |
|         shiftpoint h2 = T * xpush0(90._deg);
 | |
|         shiftpoint h3 = T * ypush0(90._deg);
 | |
|         queueline(h1, h2, 0xFF0000FF, 3);
 | |
|         queueline(h2, h3, 0xFF0000FF, 3);
 | |
|         queueline(h3, h1, 0xFF0000FF, 3);
 | |
|         vid.linewidth /= 4;
 | |
|         });
 | |
|       if(mode == pmStop) {
 | |
|         texture::config.tstate = texture::tsOff;
 | |
|         }
 | |
|       no_other_hud(mode);
 | |
|       }
 | |
|     },
 | |
|   {"hyperbolic plane", 999, LEGAL::NONE | QUICKGEO, 
 | |
|     "Hyperbolic geometry works the opposite way to spherical geometry."
 | |
|     "In hyperbolic geometry, the sum of angles of a triangle is less than 180 degrees.\n\n",
 | |
|     [] (presmode mode) {
 | |
|       if(mode == pmStartAll) enable_canvas();
 | |
|       setCanvas(mode, &ccolor::football, [] {
 | |
|         set_geometry(gNormal);
 | |
|         set_variation(eVariation::bitruncated);
 | |
|         slide_backup(ccolor::which->ctab, colortable{0xC0FFC0, 0x80FF80});
 | |
|         });
 | |
|       if(mode == pmStart) {
 | |
|         slide_backup(pconf.alpha, 1); 
 | |
|         slide_backup(pconf.scale, 1); 
 | |
|         slide_backup(rug::mouse_control_rug, true);
 | |
|         slide_backup(patterns::whichShape, '9');
 | |
|         }
 | |
|       if(mode == pmStart) {
 | |
|         rug::modelscale = 1;
 | |
|         // rug::rug_perspective = false;
 | |
|         rug::gwhere = gCubeTiling;
 | |
|         rug::texturesize = 1024;
 | |
|         slide_backup(sightrange_bonus, -1);
 | |
|         drawthemap();
 | |
|         rug::init();
 | |
|         }
 | |
|       if(mode == pmStart) {
 | |
|         stop_game();
 | |
|         set_geometry(gArchimedean); arcm::current.parse("3^7");
 | |
|         set_variation(eVariation::pure);
 | |
|         start_game();
 | |
|         }
 | |
|       add_temporary_hook(mode, hooks_frame, 200, [] {
 | |
|         shiftmatrix T = ggmatrix(currentmap->gamestart());
 | |
|         vid.linewidth *= 16;
 | |
|         shiftpoint h1 = T * xspinpush0(0, 2);
 | |
|         shiftpoint h2 = T * xspinpush0(120._deg, 2);
 | |
|         shiftpoint h3 = T * xspinpush0(240._deg, 2);
 | |
|         queueline(h1, h2, 0xFF0000FF, 4);
 | |
|         queueline(h2, h3, 0xFF0000FF, 4);
 | |
|         queueline(h3, h1, 0xFF0000FF, 4);
 | |
|         vid.linewidth /= 16;
 | |
|         });
 | |
|       if(mode == 3) {
 | |
|         rug::close();
 | |
|         }
 | |
|       no_other_hud(mode);
 | |
|       }
 | |
|     },
 | |
|   {"A right-angled pentagon", 999, LEGAL::NONE | QUICKGEO, 
 | |
|     "There is also three-dimensional hyperbolic geometry.\n"
 | |
|     "Here are some right-angled pentagons in three-dimensional hyperbolic space.\n"
 | |
|     ,
 | |
|     
 | |
|     [] (presmode mode) {
 | |
|       if(mode == pmStart) {
 | |
|         slide_backup(ccolor::rwalls, 10);
 | |
|         slide_backup(vid.fov, 120);
 | |
|         }
 | |
| 
 | |
|       setPlainCanvas(mode, [] { set_geometry(gSpace534); });
 | |
|       
 | |
|       if(mode == pmStart) {
 | |
|         /*
 | |
|         static bool solved = false;
 | |
|         if(!solved) {
 | |
|           stop_game();
 | |
|           set_geometry(gSpace534);
 | |
|           start_game();
 | |
|           stop_game();
 | |
|           cgi.require_basics();
 | |
|           fieldpattern::field_from_current();
 | |
|           set_geometry(gFieldQuotient);
 | |
|           currfp.Prime = 5; currfp.force_hash = 0x72414D0C;
 | |
|           currfp.solve();
 | |
|           solved = true;
 | |
|           }          
 | |
|         set_geometry(gFieldQuotient);
 | |
|         */
 | |
|         start_game();
 | |
|         tour::slide_backup(mapeditor::drawplayer, false);
 | |
|         pentaroll::create_pentaroll(true);
 | |
|         tour::slide_backup(anims::period, 30000.);
 | |
|         tour::slide_backup(sightranges[geometry], 4);
 | |
|         start_game();
 | |
|         playermoved = false;
 | |
|         }
 | |
|       no_other_hud(mode);
 | |
|       }
 | |
|     },
 | |
| 
 | |
|   {"Penrose triangle (1958), Oscar Reutersvärd's triangle (1934), Penrose Stairs (1959)", 999, LEGAL::NONE, 
 | |
|     "The Penrose Triangle, "
 | |
|     "constructed by Lionel Penrose and Roger Penrose in 1958, "
 | |
|     "is an example of an impossible figure. "
 | |
|     "Many artists have used the Penrose Triangle to create "
 | |
|     "more complex constructions, such as the \"Waterfall\" "
 | |
|     "by M. C. Escher.\n\n"
 | |
|     
 | |
|     "While it is known as Penrose Triangle, a very similar construction "
 | |
|     "has actually been discovered earlier by Oscar Reutersvärd (in 1934)!\n\n"
 | |
| 
 | |
|     "In 1959 Lionel Penrose and Roger Penrose have constructed another "
 | |
|     "example of an impossible figure, called the Penrose staircase.",
 | |
|     [] (presmode mode) {
 | |
|       empty_screen(mode);
 | |
|       show_picture(mode, "rogueviz/nil/penrose-all-small.png");
 | |
|       no_other_hud(mode);
 | |
|       }
 | |
|     },
 | |
| 
 | |
|   {"Ascending & Descending", 999, LEGAL::NONE | QUICKGEO, 
 | |
|     "It is the most well known from \"Ascending and Descending\" by M. C. Escher.\n\n"
 | |
|     "This is a 3D model of Ascending and Descending by Lucian B. It is based on an optical illusion."
 | |
|     ,
 | |
|     [] (presmode mode) {
 | |
|       slide_url(mode, 'm', "link to the original model", "https://3dwarehouse.sketchup.com/model/3e6df6c24a95f583cefabc2ae69d584c/MC-Escher-Ascending-and-Descending");
 | |
|       ply_slide(mode, gCubeTiling, mdPerspective, false);
 | |
|       if(!ply::staircase.available()) return;
 | |
|       if(mode == pmStart) {
 | |
|         tour::slide_backup(sightranges[geometry], 200);
 | |
|         tour::slide_backup(vid.cells_drawn_limit, 200);
 | |
|         tour::slide_backup(camera_speed, 5);
 | |
|         centerover = euc::get_at(euc::coord{12,-23,8})->c7;
 | |
|         playermoved = false;
 | |
|         int cid = 0;
 | |
|         for(ld val: {0.962503,0.254657,-0.0934754,0.000555891,0.0829357,-0.604328,-0.792408,0.0992114,-0.258282,0.754942,-0.602787,0.0957558,0.,0.,0.,1.})
 | |
|           View[0][cid++] = val;
 | |
| 
 | |
|         // tour::slide_backup(vid.fov, 120);
 | |
|         }
 | |
|       non_game_slide_scroll(mode);
 | |
|       if(mode == pmKey) {
 | |
|         println(hlog, ggmatrix(currentmap->gamestart()));
 | |
|         println(hlog, View);
 | |
|         println(hlog, euc::get_ispacemap()[centerover->master]);
 | |
|         }
 | |
|       }
 | |
|     },
 | |
| 
 | |
| /*
 | |
|   {"Penrose triangle (1958)", 999, LEGAL::NONE, 
 | |
|     "The Penrose Triangle, "
 | |
|     "constructed by Lionel Penrose and Roger Penrose in 1958, "
 | |
|     "is an example of an impossible figure. "
 | |
|     "Many artists have used the Penrose Triangle to create "
 | |
|     "more complex constructions, such as the \"Waterfall\" "
 | |
|     "by M. C. Escher.",
 | |
|     
 | |
|     [] (presmode mode) {
 | |
|       empty_screen(mode);
 | |
|       show_picture(mode, "rogueviz/nil/penrose-triangle.png");
 | |
|       no_other_hud(mode);
 | |
|       }
 | |
|     },
 | |
|   {"Oscar Reutersvärd's triangle (1934)", 999, LEGAL::NONE, 
 | |
|     "While it is known as Penrose Triangle, a very similar construction "
 | |
|     "has actually been discovered earlier by Oscar Reutersvärd (in 1934)!",
 | |
|     [] (presmode mode) {
 | |
|       empty_screen(mode);
 | |
|       show_picture(mode, "rogueviz/nil/reutersvard.png");
 | |
|       no_other_hud(mode);
 | |
|       }
 | |
|     },
 | |
|   {"Penrose staircase (1959)", 999, LEGAL::NONE, 
 | |
|     "In 1959 Lionel Penrose and Roger Penrose have constructed another "
 | |
|     "example of an impossible figure, called the Penrose staircase.\n\n"
 | |
|     "It is the most well known from \"Ascending and Descending\" by M. C. Escher.\n\n",
 | |
|     [] (presmode mode) {
 | |
|       empty_screen(mode);
 | |
|       show_picture(mode, "rogueviz/nil/penrose-stairs.png");
 | |
|       no_other_hud(mode);
 | |
|       }
 | |
|     }, */
 | |
|   {"non-Euclidean geometry so far", 123, LEGAL::ANY, 
 | |
|     "People sometimes call such impossible constructions \"non-Euclidean\".\n\n"
 | |
|     "These people generally use this name because they do not know the usual "
 | |
|     "mathematical meaning of \"non-Euclidean\".\n\n"
 | |
|     "It seems that the geometries we know so far are something completely different...",
 | |
|   
 | |
|     [] (presmode mode) {
 | |
|       empty_screen(mode);
 | |
|       add_stat(mode, [] {
 | |
|         dialog::init();
 | |
|         color_t d = dialog::dialogcolor;
 | |
| 
 | |
|         dialog::addTitle("Euclidean geometry", 0xC00000, 200);
 | |
|         dialog::addTitle("parallel lines stay in the same distance", d, 150);
 | |
|         
 | |
|         dialog::addBreak(100);
 | |
| 
 | |
|         dialog::addTitle("spherical geometry", 0xC00000, 200);
 | |
|         dialog::addTitle("no parallel lines -- they converge", d, 150);
 | |
| 
 | |
|         dialog::addBreak(100);
 | |
| 
 | |
|         dialog::addTitle("hyperbolic geometry", 0xC00000, 200);
 | |
|         dialog::addTitle("parallel lines diverge", d, 150);
 | |
| 
 | |
|         dialog::display();
 | |
|         return true;
 | |
|         });
 | |
|       non_game_slide_scroll(mode);
 | |
|       }
 | |
|     },
 | |
| 
 | |
|   {"Compasses in Nil", 123, LEGAL::ANY | QUICKGEO,
 | |
|     "However, it turns out that there actually exists a non-Euclidean geometry, "
 | |
|     "known as the Nil geometry, where constructions such as Penrose staircases and "
 | |
|     "triangles naturally appear!\n\n"
 | |
|     "Nil is a three-dimensional geometry, which gives new possibilities -- "
 | |
|     "lines 'diverge in the third dimension' there. "
 | |
|     "Every point has "
 | |
|     "well-defined North, East, South, West, Up and Down direction.\n\n"
 | |
|     "(press Home/End and arrow keys to move)",
 | |
| 
 | |
|     [] (presmode mode) {
 | |
|       setWhiteCanvas(mode, [] { set_geometry(gNil); });
 | |
|       slidecommand = "highlight dimensions";
 | |
|       if(mode == pmStart) {
 | |
|         tour::slide_backup(pmodel, mdGeodesic);
 | |
|         rogueviz::rv_hook(hooks_drawcell, 100, rogueviz::nilcompass::draw_compass);
 | |
|         View = Id;
 | |
|         shift_view(ztangent(.5));
 | |
|         }
 | |
|       non_game_slide_scroll(mode);
 | |
|       if(mode == pmStart || mode == pmKey)
 | |
|         rogueviz::nilcompass::zeroticks = ticks;
 | |
|       }
 | |
|     },
 | |
| 
 | |
|   {"a Puzzle about a Bear", 123, LEGAL::ANY,
 | |
|     "To explain Nil geometry, we will start with a well-known puzzle.",
 | |
|   
 | |
|     [] (presmode mode) {
 | |
|       empty_screen(mode);
 | |
|       add_stat(mode, [] {
 | |
|         dialog::init();
 | |
|         color_t d = dialog::dialogcolor;
 | |
|         dialog::addTitle("A bear walked five kilometers north, ", d, 150);
 | |
|         dialog::addTitle("five kilometers east, five kilometers south, ", d, 150);
 | |
|         dialog::addTitle("and returned exactly to the place it started.", d, 150);
 | |
|         dialog::addBreak(50);
 | |
|         dialog::addTitle("What color is the bear?", 0xC00000, 200);
 | |
|         dialog::display();
 | |
|         return true;
 | |
|         });
 | |
|       no_other_hud(mode);
 | |
|       }
 | |
|     },
 | |
| 
 | |
|   {"Cartesian coordinates", 999, LEGAL::NONE | QUICKGEO, 
 | |
|     "The puzzle shows an important fact: every point on Earth has defined directions "
 | |
|     "(North, East, South, West), and in most life situations, we can assume that these "
 | |
|     "directions work the same as in the Cartesian system of coordinates."
 | |
|     ,
 | |
|     [] (presmode mode) {
 | |
|       empty_screen(mode);
 | |
|       nil_screen(mode, 0);
 | |
|       no_other_hud(mode);
 | |
|       }
 | |
|     },
 | |
|   {"Nil coordinates", 999, LEGAL::NONE | QUICKGEO,
 | |
|     "However, because Earth is curved (non-Euclidean), these directions actually "
 | |
|     "work different! If you are closer to the pole, moving East or West changes "
 | |
|     "your longitude much more quickly.\n\n"
 | |
|     "Nil is a three-dimensional geometry which is similar: while every point also has "
 | |
|     "well-defined NSEWUD directions, but they affect the coordinates in a different way " 
 | |
|     "than in the Euclidean space with the usual coordinate system.\n\n"
 | |
| 
 | |
|     "You may want to use the Pythagorean theorem to compute the length of these -- "
 | |
|     "this is not correct, all the moves are of length d. You would need to use the Pythagorean "
 | |
|     "theorem if you wanted to compute the length from (x,y,z) to (x,y-d,z).\n\n"
 | |
|     ,
 | |
|     [] (presmode mode) {
 | |
|       empty_screen(mode);
 | |
|       nil_screen(mode, 1);
 | |
|       no_other_hud(mode);
 | |
|       }
 | |
|     },
 | |
|   {"Nil coordinates (area)", 999, LEGAL::NONE | QUICKGEO,
 | |
|     "The formulas look strange at a first glance, but the idea is actually simple: "
 | |
|     "the change in the 'z' coordinate is the area of a triangle, as shown in the picture. "
 | |
|     "The change is positive if we go counterclockwise, and negative if we go clockwise.\n\n"
 | |
|     ,
 | |
|     [] (presmode mode) {
 | |
|       empty_screen(mode);
 | |
|       nil_screen(mode, 2);
 | |
|       no_other_hud(mode);
 | |
|       }
 | |
|     },
 | |
|   {"Nil coordinates (loop)", 999, LEGAL::NONE | QUICKGEO,
 | |
|     "If we make a tour in Nil moving only in the directions N, W, S, E, such that "
 | |
|     "the analogous tour in Euclidean space would return us to the starting point, "
 | |
|     "then the tour in Nil would return us directly above or below the starting point, "
 | |
|     "with the difference in the z-coordinate proportional to the area of the loop."
 | |
|     ,
 | |
|     [] (presmode mode) {
 | |
|       empty_screen(mode);
 | |
|       nil_screen(mode, 3);
 | |
|       no_other_hud(mode);
 | |
|       }
 | |
|     },
 | |
|   {"Simple Penrose stairs", 999, LEGAL::NONE | QUICKGEO, 
 | |
|     "This lets us easily make a simple realization of the Penrose staircase in Nil. "
 | |
|     "Here is an attempt to create a Penrose staircase in Euclidean geometry...\n\n"
 | |
|     "(you can rotate this with mouse or arrow keys)"
 | |
|     ,
 | |
|     [] (presmode mode) {
 | |
|       brick_slide(0, mode, gCubeTiling, mdHorocyclic, 0);
 | |
|       non_game_slide_scroll(mode);
 | |
|       }
 | |
|     },
 | |
|   {"Simple Penrose stairs in Nil", 999, LEGAL::NONE | QUICKGEO, 
 | |
|     "We can use the magic of the Nil geometry to recompensate the lost height.\n\n"
 | |
|     "Press 5 to see how it looks when we walk around the stairs. When you rotate this slide, "
 | |
|     "you will notice that the stairs change shape when far from the central point -- "
 | |
|     "this is because we use the Nil rules of movement."
 | |
|     ,
 | |
|     [] (presmode mode) {
 | |
|       brick_slide(0, mode, gNil, mdHorocyclic, 0);
 | |
|       if(mode == pmKey) bricks::animation = !bricks::animation;
 | |
|       non_game_slide_scroll(mode);
 | |
|       }
 | |
|     },
 | |
|   {"Simple Penrose stairs in Nil (FPP)", 999, LEGAL::NONE | QUICKGEO, 
 | |
|     "This slide shows our stairs in the first person perspective, from the inside."
 | |
|     ,
 | |
|     [] (presmode mode) {
 | |
|       brick_slide(0, mode, gNil, mdPerspective, 3);
 | |
|       if(mode == pmKey) bricks::animation ^= 1;
 | |
|       }
 | |
|     },
 | |
|   {"Geodesics in Nil", 999, LEGAL::NONE | QUICKGEO, 
 | |
|     "But, was the first person perspective in the last slide 'correct'?\n\n"
 | |
|     "According to Fermat's Principle, the path taken by a light ray is "
 | |
|     "always one which is the shortest. Our previous visualization assumed "
 | |
|     "that light rays move in a fixed 'direction', which may be not the case.\n\n"
 | |
|     "Let's think a bit about moving from (0,0,0) to (0,0,25). We can of course "
 | |
|     "take the obvious path of length 25. Can we do it better?"
 | |
|     ,
 | |
|     [] (presmode mode) {
 | |
|       empty_screen(mode);
 | |
|       geodesic_screen(mode, 0);
 | |
|       no_other_hud(mode);
 | |
|       }
 | |
|     },
 | |
|   {"Geodesics: square", 999, LEGAL::NONE | QUICKGEO, 
 | |
|     "Yes, we can! Here is a square of edge length 5. Since such a square has an "
 | |
|     "area of 25 and perimeter of 20, it takes us to (0,0,25) in just 20 steps!"
 | |
|     ,
 | |
|     [] (presmode mode) {
 | |
|       empty_screen(mode);
 | |
|       geodesic_screen(mode, 1);
 | |
|       no_other_hud(mode);
 | |
|       }
 | |
|     },
 | |
|   {"Geodesics: circle", 999, LEGAL::NONE | QUICKGEO, 
 | |
|     "We can do even better. Queen Dido already knew that among shapes with the "
 | |
|     "given area, the circle has the shortest perimeter. A circle with area 25 "
 | |
|     "has even shorter length."
 | |
|     ,
 | |
|     [] (presmode mode) {
 | |
|       empty_screen(mode);
 | |
|       geodesic_screen(mode, 2);
 | |
|       no_other_hud(mode);
 | |
|       }
 | |
|     },
 | |
|   {"Geodesics: helix", 999, LEGAL::NONE | QUICKGEO, 
 | |
|     "But that was just the silver medal.\n\n"
 | |
|     "For the gold medal, we need to combine the 'silver' and 'green' paths. "
 | |
|     "We make the circle slightly smaller, and we satisfy the difference by moving "
 | |
|     "slightly upwards. The length of such path can be computed using the Pythagorean "
 | |
|     "theorem, and minimized by differentiation. There is an optimal radius which "
 | |
|     "yields the best path.",
 | |
|     
 | |
|     [] (presmode mode) {
 | |
|       empty_screen(mode);
 | |
|       geodesic_screen(mode, 3);
 | |
|       no_other_hud(mode);
 | |
|       }
 | |
|     },
 | |
|   {"Simple Penrose stairs in Nil (geodesics)", 999, LEGAL::NONE | QUICKGEO, 
 | |
|     "The light ray paths ('geodesics') in Nil are like the ones constructed in "
 | |
|     "the last slide: they are helices, the steeper the helix, the smaller "
 | |
|     "its radius.\n\n"
 | |
|     "This slide presents the staircase in model perspective and the "
 | |
|     "geodesically correct view. The geodesically correct view appears to spin."
 | |
|     ,
 | |
|     [] (presmode mode) {
 | |
|       brick_slide(0, mode, gNil, mdGeodesic, 3);
 | |
|       
 | |
|       compare_projections(mode, mdPerspective, mdGeodesic);      
 | |
|       }
 | |
|     },
 | |
|   {"Penrose triangle (illusion)", 999, LEGAL::NONE | QUICKGEO, 
 | |
|     "Can we also construct the Penrose triangle? "
 | |
|     "Yes, we can! In our space, we can construct an illusion "
 | |
|     "which looks like the Penrose triangle (rotate the scene and press '5'). "
 | |
|     "If we rotate this illusion in such a way that the 'paradox line' "
 | |
|     "is vertical, we can recompensate the difference by using the Nil geometry. "
 | |
|     "We need to scale our scene in such a way that the length of the white line "
 | |
|     "equals the area contained in the projection of the red line."
 | |
|     ,
 | |
|     [] (presmode mode) {
 | |
|       brick_slide(1, mode, gCubeTiling, mdHorocyclic, 0);
 | |
|       static bool draw = false;
 | |
|       if(mode == pmKey) draw = !draw;
 | |
|       add_temporary_hook(mode, hooks_prestats, 200, [] {
 | |
|         if(draw) {
 | |
|           shiftmatrix Zero = ggmatrix(currentmap->gamestart());
 | |
|           
 | |
|           initquickqueue();
 | |
|           
 | |
|           // two first bricks are fake
 | |
| 
 | |
|           int id = 0;
 | |
|           for(auto& b: bricks::bricks) {
 | |
|             id++;
 | |
|             if(id >= 2) curvepoint(b.location);
 | |
|             }
 | |
|           vid.linewidth *= 10;
 | |
|           queuecurve(Zero, 0x0000FFFF, 0, PPR::SUPERLINE).flags |= POLY_FORCEWIDE;
 | |
|           vid.linewidth /= 10;
 | |
| 
 | |
|           curvepoint(bricks::bricks[2].location);
 | |
|           curvepoint(bricks::bricks.back().location);
 | |
|           vid.linewidth *= 10;
 | |
|           queuecurve(Zero, 0xFFFFFFFF, 0, PPR::SUPERLINE).flags |= POLY_FORCEWIDE;
 | |
|           vid.linewidth /= 10;
 | |
|           
 | |
|           quickqueue();
 | |
|           
 | |
|           }
 | |
|         return false;
 | |
|         });
 | |
|       non_game_slide_scroll(mode);
 | |
|       // pmodel = (pmodel == mdGeodesic ? mdPerspective : mdGeodesic);
 | |
|       }
 | |
|     },
 | |
|   {"Penrose triangle (Nil)", 999, LEGAL::NONE | QUICKGEO, 
 | |
|     "Here we move around the Penrose triangle..."
 | |
|     ,
 | |
|     [] (presmode mode) {
 | |
|       brick_slide(1, mode, gNil, mdHorocyclic, 1);
 | |
|       // if(mode == pmKey) DRAW 
 | |
|       // pmodel = (pmodel == mdGeodesic ? mdPerspective : mdGeodesic);
 | |
|       non_game_slide_scroll(mode);
 | |
|       }
 | |
|     },
 | |
|   {"Penrose triangle (FPP)", 999, LEGAL::NONE | QUICKGEO, 
 | |
|     "... and see the Penrose triangle in first-person perspective. "
 | |
|     "Since the Penrose triangle is larger (we need stronger Nil effects "
 | |
|     "to make it work), the geodesic effects are also much stronger."
 | |
|     ,
 | |
|     [] (presmode mode) {
 | |
|       brick_slide(1, mode, gNil, mdPerspective, 3);
 | |
|       compare_projections(mode, mdPerspective, mdGeodesic);
 | |
|       }
 | |
|     },
 | |
| 
 | |
|   {"Improbawall by Matt Taylor (emty01)", 999, LEGAL::NONE | QUICKGEO, 
 | |
|     "This impossible construction by Matt Taylor was popular in early 2020. "
 | |
|     "How does it even work?\n\n"
 | |
|     "(the animation is not included with RogueViz)"
 | |
|     ,
 | |
|     [] (presmode mode) {
 | |
|       static bool pic_exists, video_exists;
 | |
|       if(mode == pmStartAll || mode == pmStart) {
 | |
|         pic_exists = file_exists("rogueviz/nil/emty-ring.png");
 | |
|         video_exists = file_exists("rogueviz/nil/emty-ring.mp4");
 | |
|         }
 | |
|       slide_url(mode, 'i', "Instagram link", "https://www.instagram.com/p/B756GCynErw/");
 | |
|       empty_screen(mode);
 | |
|       if(video_exists)
 | |
|         show_animation(mode, "rogueviz/nil/emty-ring.mp4", 720, 900, 300, 30);
 | |
|       else if(pic_exists)
 | |
|         show_picture(mode, "rogueviz/nil/emty-ring.png");
 | |
|       else
 | |
|         slide_error(mode, "(image not available)");
 | |
|       no_other_hud(mode);
 | |
|       }
 | |
|     },
 | |
|   {"how is this made", 999, LEGAL::NONE | QUICKGEO, 
 | |
|     "Rotate this ring and press '5' to rotate a half of it by 90 degrees. "
 | |
|     "After rotating this ring so that the endpoints agree, we get another "
 | |
|     "case that can be solved in Nil geometry."
 | |
|     ,
 | |
|     [] (presmode mode) {
 | |
|       impossible_ring_slide(mode);
 | |
|       non_game_slide_scroll(mode);
 | |
|       }
 | |
|     },
 | |
|   {"impossible ring in Nil", 18, LEGAL::NONE | QUICKGEO, 
 | |
|     "Here is how it looks in Nil. Press '5' to animate.\n",
 | |
|    
 | |
|   [] (presmode mode) {
 | |
|     setWhiteCanvas(mode, [] {
 | |
|       set_geometry(gNil);
 | |
|       tour::on_restore(nilv::set_flags);
 | |
|       tour::slide_backup(nilv::nilperiod, make_array(3, 3, 3));
 | |
|       nilv::set_flags();
 | |
|       });
 | |
|     
 | |
|     slidecommand = "animation";
 | |
|     if(mode == pmKey) {
 | |
|       tour::slide_backup(rogueviz::cylon::cylanim, !rogueviz::cylon::cylanim);
 | |
|       }
 | |
|     
 | |
|     if(mode == pmStart) rogueviz::cylon::enable();
 | |
|     non_game_slide_scroll(mode);
 | |
|     }},
 | |
| 
 | |
|   {"3D model (geodesic)", 999, LEGAL::NONE | QUICKGEO, 
 | |
|     "What if we try to move something more complex, rather than a simple geometric shape?\n\n"
 | |
|     "This slide is based on a 3D model of Ascending and Descending by Lucian B. "
 | |
|     "We have used the trick mentioned before to move into the Nil space. Here are the results."
 | |
|     ,
 | |
|     [] (presmode mode) {
 | |
|       slide_url(mode, 'y', "YouTube link", "https://www.youtube.com/watch?v=DurXAhFrmkE");
 | |
|       ply_slide(mode, gNil, mdGeodesic, true);
 | |
|       }
 | |
|     },
 | |
|   {"3D model (perspective)", 999, LEGAL::NONE | QUICKGEO, 
 | |
|     "The same in the simple model."
 | |
|     ,
 | |
|     [] (presmode mode) {
 | |
|       ply_slide(mode, gNil, mdPerspective, true);
 | |
|       }
 | |
|     },
 | |
|   {"two Penrose triangles (Euc)", 999, LEGAL::NONE | QUICKGEO, 
 | |
|     "Here are two Penrose triangles. Can we move that to Nil?"
 | |
|     ,
 | |
|     [] (presmode mode) {
 | |
|       brick_slide(2, mode, gCubeTiling, mdHorocyclic, 0);
 | |
|       non_game_slide_scroll(mode);
 | |
|       }
 | |
|     },
 | |
|   {"two Penrose triangles (Nil)", 999, LEGAL::NONE | QUICKGEO, 
 | |
|     "No, we cannot -- one of the triangles has opposite orientation!\n\n"
 | |
|     "That is still impossible in Nil, so not all "
 | |
|     "impossible constructions can be realized in Nil.\n\n"
 | |
|     "For example, \"Waterfall\" by M. C. Escher is based on three "
 | |
|     "triangles with two different orientations.",
 | |
|     [] (presmode mode) {
 | |
|       brick_slide(2, mode, gNil, mdHorocyclic, 0);
 | |
|       non_game_slide_scroll(mode);
 | |
|       }
 | |
|     },
 | |
| 
 | |
|   {"Balls in Nil", 999, LEGAL::NONE | QUICKGEO | FINALSLIDE, 
 | |
|     "A perpetuum mobile in Nil as the final slide. That's all for today!"
 | |
|     ,
 | |
|     [] (presmode mode) {
 | |
|       slide_url(mode, 'y', "YouTube link", "https://www.youtube.com/watch?v=mxvUAcgN3go");
 | |
|       slide_url(mode, 'n', "Nil Rider", "https://zenorogue.itch.io/nil-rider");
 | |
|       setWhiteCanvas(mode, [] { set_geometry(gNil); });
 | |
|       if(mode == pmStart) {
 | |
|         rogueviz::balls::initialize(1);
 | |
|         rogueviz::balls::balls.resize(3);
 | |
|         pmodel = mdEquidistant;
 | |
|         View = cspin90(1, 2);
 | |
|         }
 | |
|       non_game_slide_scroll(mode);
 | |
|       }
 | |
|     },
 | |
| 
 | |
|   {"final slide", 123, LEGAL::ANY | NOTITLE | QUICKSKIP | FINALSLIDE, 
 | |
|     "FINAL SLIDE",
 | |
|   
 | |
|     [] (presmode mode) {
 | |
|       empty_screen(mode);
 | |
|       add_stat(mode, [] {
 | |
|         dialog::init();
 | |
|         color_t d = dialog::dialogcolor;
 | |
| 
 | |
|         dialog::addTitle("Thanks for your attention!", 0xC00000, 200);
 | |
| 
 | |
|         dialog::addBreak(100);
 | |
| 
 | |
|         dialog::addTitle("twitter.com/zenorogue/", d, 150);
 | |
|         
 | |
|         dialog::display();
 | |
|         return true;
 | |
|         });
 | |
|       no_other_hud(mode);
 | |
|       }
 | |
|     }
 | |
|   };
 | |
|   
 | |
| int phooks = 
 | |
|   0 +
 | |
|   addHook_slideshows(100, [] (tour::ss::slideshow_callback cb) {
 | |
|     cb(XLAT("Playing with Impossibility"), &dmv_slides[0], 'i');
 | |
|     });
 | |
|  
 | |
| }
 | |
| #endif
 | |
| }
 | |
| 
 | |
| // kolor zmienic dziada
 | 
