1
0
mirror of https://github.com/zenorogue/hyperrogue.git synced 2024-11-18 19:24:48 +00:00
hyperrogue/rogueviz/playing-with-impossibility.cpp
2023-04-15 01:30:05 +02:00

1314 lines
44 KiB
C++

#include "rogueviz.h"
#define PSHIFT 0
#define RVPATH HYPERPATH "rogueviz/"
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);
setCanvas(mode, '0');
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, 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();
dynamicval<eGeometry> gg(geometry, gNil);
return false;
});
}
ld geo_zero;
void geodesic_screen(presmode mode, int id) {
if(mode == pmStart) geo_zero = ticks;
use_angledir(mode, id == 0);
setCanvas(mode, '0');
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, 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;
setCanvas(mode, '0');
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();
}
setCanvas(mode, '0');
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;
setCanvas(mode, '0');
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();
patterns::whichCanvas = 'F';
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, 'F');
if(mode == pmStart) {
stop_game();
slide_backup(firstland, laCanvas);
slide_backup(specialland, laCanvas);
set_geometry(gArchimedean); arcm::current.parse("3^6");
set_variation(eVariation::pure);
slide_backup(colortables['F'][0], 0xC0FFC0);
slide_backup(colortables['F'][1], 0x80FF80);
slide_backup(pconf.alpha, 1);
slide_backup(pconf.scale, 1);
start_game();
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) {
setCanvas(mode, '0');
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, 'F');
if(mode == pmStart) {
stop_game();
slide_backup(firstland, laCanvas);
slide_backup(specialland, laCanvas);
set_geometry(gNormal);
set_variation(eVariation::bitruncated);
slide_backup(colortables['F'][0], 0xC0FFC0);
slide_backup(colortables['F'][1], 0x80FF80);
slide_backup(pconf.alpha, 1);
slide_backup(pconf.scale, 1);
slide_backup(rug::mouse_control_rug, true);
start_game();
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(patterns::rwalls, 10);
slide_backup(vid.fov, 120);
}
setCanvas(mode, '0');
if(mode == pmStart) {
set_geometry(gSpace534);
/*
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) {
setCanvas(mode, '0');
slidecommand = "highlight dimensions";
if(mode == pmStart) {
tour::slide_backup(pmodel, mdGeodesic);
set_geometry(gNil);
start_game();
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) {
setCanvas(mode, '0');
slidecommand = "animation";
if(mode == pmKey) {
tour::slide_backup(rogueviz::cylon::cylanim, !rogueviz::cylon::cylanim);
}
if(mode == pmStart) {
stop_game();
set_geometry(gNil);
rogueviz::cylon::enable();
tour::on_restore(nilv::set_flags);
tour::slide_backup(nilv::nilperiod, make_array(3, 3, 3));
nilv::set_flags();
start_game();
}
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");
setCanvas(mode, '0');
if(mode == pmStart) {
stop_game();
set_geometry(gNil);
check_cgi();
cgi.require_shapes();
start_game();
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