// Banach-Tarski animation in RogueViz. // Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details // good parameters: -fixx 10 -W Cros -bantar_anim // use -bantar_map to see how it works #include "rogueviz.h" namespace rogueviz { namespace banachtarski { bool on; typedef vector<int> cwpath; cwpath invertpath(cwpath p) { cwpath res; for(int i=0; i<isize(p); i++) res.push_back(-p[isize(p)-1-i]); return res; } array<cwpath, 4> gens; cellwalker bttargets[4]; cellwalker trace(cellwalker cw, cwpath& p) { for(int i: p) if(i == 0) cw += wstep; else cw += i; return cw; } set<cell*> testlist; map<cell*, cell*> parent; bool test_uniq(cellwalker cw, int z, int lev, cell *par) { if(testlist.count(cw.at)) return false; testlist.insert(cw.at); if(par) parent[cw.at] = par; if(celldist(cw.at) > 9) return true; /* if(cw.at->wall == waSea) { printf("test_uniq failed\n"); cw.at->wall = waEternalFire; } cw.at->wall = waSea; */ if(lev) for(int y=0; y<4; y++) if(y != z) if(!test_uniq(trace(cw, gens[y]), y^2, lev-1, cw.at)) return false; return true; } template<class T> void recursively(cell *c, cell *c1, const T& t) { t(c); for(int i=0; i<c->type; i++) if(c->move(i) && c->c.spin(i) == 0 && c->move(i) != c1) recursively(c->move(i), c, t); } vector<cell*> allcells; set<cell*> seen; struct cellinfo { cell *c; int gid; int spdist; eWall w; eItem it; eLand land; eMonster mo; vector<int> way; cwpath pinv; }; map<cell*, cellinfo> infos; int cidd = 0; int more = 5; void debugpath(vector<int>& way, cwpath& pinv) { printf("pinv:"); for(int i: pinv) printf(" %d", i); printf("\n"); cellwalker cw = cwt; cellwalker last_cw = cw; int lastd = 0; printf("way:"); for(int i: way) { cellwalker cw2 = trace(cw, pinv); printf(" [%d]", lastd = celldist(cw2.at)); printf(" %d", i); last_cw = cw; cw = trace(cw, gens[i]); } cellwalker cw2 = trace(cw, pinv); int curd; printf(" [%d]", curd = celldist(cw2.at)); printf("\n"); if(lastd == 10 && curd == 2) { int b = way.back(); way.pop_back(); printf("CW:"); for(int w: way) for(int wp: gens[w]) printf(" %d", wp); printf(" {"); for(int wp: gens[b]) printf(" %d", wp); printf(" }"); for(int i: pinv) printf(" %d", i); way.push_back(b); printf("\n"); } } void recursive_paint(cwpath& pinv, vector<int>& way, int noway) { cellwalker cw = cwt; for(int i: way) cw = trace(cw, gens[i]); cw = trace(cw, pinv); cell *c = cw.at; /* if(cidd == 1 && way == vector<int>{1,2,3}) c->item = itPirate; */ if(seen.count(c)) { printf("seen error [%d]\n", celldist(c)); debugpath(way, pinv); debugpath(infos[c].way, infos[c].pinv); return; } seen.insert(c); int hsh = 7; for(int w: way) hsh = 11301 * hsh + w * 37121; bool all0 = true; for(int w: way) if(w) all0 = false; int gid; /* if(d) c->landparam = 0x202020; else */ if(all0 || way[0] == 2) gid = 0; else if(way[0] == 0) gid = 1; else if(way[0] == 1) gid = 2; else if(way[0] == 3) gid = 3; else gid = 3; infos[c] = cellinfo{c, gid, 0, waNone, itNone, laNone, moNone, way, pinv}; // c->landparam ^= ((isize(way)&1) * 0x3F3F3F); // c->landparam = hsh; // d * 5 + 256 * (hsh&0xFFFF) + 0x400000; if(cidd>112899) c->landparam = 0x101010; // c->landparam = cidd * 0x1241C3; if(celldist(c) <= 11+more) for(int i=0; i<4; i++) if(i != noway) { vector<int> newway = {i}; for(int ii: way) newway.push_back(ii); recursive_paint(pinv, newway, i^2); } } bool once = true; cwpath path_to(cell *c, int dir = 0) { cwpath p; cellwalker cw(c, dir); while(cw != cwt) { if(celldist((cw+wstep).at) < celldist(cw.at)) p.push_back(0), cw += wstep; else { if(p.size() && p.back()) p.back()++; else p.push_back(1); cw += 1; } } return p; } void bantar_note(cell *c) { if(seen.count(c)) return; cwpath pinv = invertpath(path_to(c)); vector<int> way; recursive_paint(pinv, way, 4); cidd++; } using bantar_config = pair<cell*, cell*>; tuple<ld,bool,ld> quality(bantar_config cp) { shiftpoint h1 = tC0(ggmatrix(cp.first)); shiftpoint h2 = tC0(ggmatrix(cp.second)); return make_tuple(hdist0(h1) * hdist0(h2), h2[1] > 0, abs(h2[0] / h2[1])); } int notry = 0; void bantar() { if(!on) return; cwt = cellwalker(currentmap->gamestart(), 0); centerover = cwt.at; infos.clear(); vector<bantar_config> genchoices; int lnotry = notry; { celllister clgen(cwt.at, 4, 1000000, NULL); for(cell *c1: clgen.lst) if(c1->type == S7) for(cell *c2: clgen.lst) if(c2->type == S7) genchoices.emplace_back(c1, c2); stable_sort(genchoices.begin(), genchoices.end(), [] (const bantar_config b1, const bantar_config b2) { return quality(b1) < quality(b2); }); for(bantar_config bc: genchoices) { if(get<0>(quality(bc)) >= 4) exit(1); for(int i=0; i<S7; i++) for(int j=0; j<S7; j++) { gens[0] = path_to(bc.first, i); gens[1] = path_to(bc.second, j); gens[2] = invertpath(gens[0]); gens[3] = invertpath(gens[1]); testlist.clear(); parent.clear(); bool tres = test_uniq(cwt, -1, 15, NULL); auto q = quality(bc); if(tres) { DEBB(DF_LOG, ("gens = ", gens)); DEBB(DF_LOG, ("testing quality ", q, " ", make_pair(celldist(bc.first), celldist(bc.second)), ", result = ", tres)); lnotry--; if(lnotry <= 0) goto picked; } // if(tres) goto picked; } } } picked: /* if(S7 == 8) { gens[1] = {0,4,0,4}; gens[0] = {2,0,2}; } else { gens[0] = {0,4,0,4}; gens[1] = {2,0,4,0,2}; } gens[2] = invertpath(gens[0]); gens[3] = invertpath(gens[1]); */ for(int i=0; i<4; i++) bttargets[i] = trace(cwt, gens[i]); celllister cl(cwt.at, 8+more, 1000000, NULL); // recursively(cwt.at, NULL, [] (cell *c) { allcells.push_back(c); } ); for(cell* c: cl.lst) bantar_note(c); for(cell *c: cl.lst) if(infos.count(c) && infos[c].gid == 0) forCellEx(c2, c) if(infos.count(c2) && infos[c2].gid != 0) c->bardir = NOBARRIERS; for(int it=0; it<ittypes; it++) if(itemclass(eItem(it)) == IC_TREASURE) items[it] = 200; } vector<int> spdist; int curpart; /* bool hidebad(cell *c, const transmatrix& V) { if(c->wparam != curpart && curpart < 4) return true; return false; } */ heptspin cth(cellwalker cw) { return heptspin(cw.at->master, cw.spin, cw.mirrored); } ld alphaof(hyperpoint h) { return atan2(h[1], h[0]); } #define ForInfos for(auto& cci: infos) void bantar_frame() { ForInfos cci.second.w = cci.second.c->wall, cci.second.it = cci.second.c->item, cci.second.mo = cci.second.c->monst, cci.second.land = cci.second.c->land, cci.second.c->wparam = cci.second.gid; calcparam(); vector<unique_ptr<drawqueueitem>> subscr[4]; compute_graphical_distance(); const int tmax = 2000; int t = ticks % (5*tmax); int tphase = t / tmax, tsub = t % tmax; ld xdst, ydst; for(int i=0; i<4; i++) { ptds.clear(); cellwalker xcw; ld part = 1; if(i == 1) xcw = bttargets[0]; else if(i == 2) xcw = bttargets[1], part = .5; else if(i == 3) xcw = bttargets[3], part = .5; else xcw = cwt; View = Id; transmatrix tView = actualV(cth(xcw), Id) * calc_relative_matrix(cwt.at, xcw.at, C0) * inverse(actualV(cth(cwt), Id)); if(tphase < 2) part = 0; else if(tphase == 2) part = part * tsub / tmax; transmatrix itView = inverse(tView); transmatrix z = rspintox(itView*C0) * xpush(hdist0(itView*C0) * part) * spintox(itView*C0); transmatrix ful = rspintox(itView*C0) * xpush(hdist0(itView*C0) * tmax / tmax) * spintox(itView*C0); // rgpushxto0(itView*C0); hyperpoint C1 = xpush0(1); ld bof = alphaof(tView * ful * C1); z = z * spin(bof * part); View = inverse(z); if(tphase == 0 && tsub > tmax/2) { ld alpha = rand() % 10; ld d = (rand() % 1000) / 20000. * (tsub-tmax/2) / (tmax/2); View = spin(alpha) * xpush(d) * spin(-alpha); } /* int phasemask = 3; if(tphase == 0) phasemask = 0; if(tphase == 4) phasemask = 2; */ ForInfos if(cci.second.gid == i) cci.second.c->wall = cci.second.w, cci.second.c->item = cci.second.it, cci.second.c->monst = cci.second.mo, cci.second.c->land = cci.second.land; else cci.second.c->wall = waInvisibleFloor, cci.second.c->item = itNone, cci.second.c->monst = moNone, cci.second.c->land = laNone; mapeditor::drawplayer = cwt.at->wparam == i; switch(tphase) { case 0: xdst = ydst = 0; curpart = 4; break; case 1: xdst = ydst = .5 * tsub / tmax; break; case 2: xdst = ydst = .5; break; case 3: xdst = .5, ydst = .5 * (tmax-tsub) / tmax; break; case 4: xdst = .5, ydst = 0; break; default: xdst = ydst = 0; } /* ld xpos = (!(i&2)) ? xdst : -xdst; ld ypos = (!(i&1)) ? ydst : -ydst; */ gmatrix.clear(); drawthemap(); if(0) for(auto p: parent) if(gmatrix.count(p.first) && gmatrix.count(p.second) && infos[p.first].gid == i && infos[p.second].gid == i) queueline(tC0(gmatrix[p.first]), tC0(gmatrix[p.second]), 0xFFFFFFFF, 2); subscr[i] = std::move(ptds); } map<int, map<int, vector<unique_ptr<drawqueueitem>>>> xptds; for(int i=0; i<4; i++) for(auto& p: subscr[i]) xptds[int(p->prio)][i].push_back(std::move(p)); for(auto& sm: xptds) for(auto& sm2: sm.second) { int i = sm2.first; ptds.clear(); for(auto& p: sm2.second) ptds.push_back(std::move(p)); pconf.scale = .5; pconf.xposition = (!(i&2)) ? xdst : -xdst; pconf.yposition = (!(i&1)) ? ydst : -ydst; calcparam(); drawqueue(); } ForInfos cci.second.c->wall = cci.second.w, cci.second.c->item = cci.second.it, cci.second.c->monst = cci.second.mo, cci.second.c->land = cci.second.land; ptds.clear(); } void bantar_anim() { vid.aurastr = 0; bool breakanim = false; int t = SDL_GetTicks(); drawthemap(); while(!breakanim) { ticks = SDL_GetTicks() - t; pushScreen(bantar_frame); drawscreen(); popScreen(); SDL_Event ev; while(SDL_PollEvent(&ev)) if(ev.type == SDL_KEYDOWN || ev.type == SDL_MOUSEBUTTONDOWN) breakanim = true; } mapeditor::drawplayer = true; pconf.xposition = pconf.yposition = 0; pconf.scale = 1; } bool bmap; void bantar_stats() { if(bmap) { vid.linewidth *= (inHighQual ? 10 : 2); for(auto p: parent) if(gmatrix.count(p.first) && gmatrix.count(p.second)) queueline(tC0(gmatrix[p.first]), tC0(gmatrix[p.second]), 0x00FF00FF, 4); double x = cgi.hexvdist; for(auto gm: gmatrix) for(cell *c: {gm.first}) if(euclid || !pseudohept(c)) for(int t=0; t<c->type; t++) if(infos.count(c) && infos.count(c->move(t)) && c->move(t) && infos[c].gid != infos[c->move(t)].gid) if(euclid ? c->move(t)<c : (((t^1)&1) || c->move(t) < c)) queueline(gm.second * ddspin(c,t,-M_PI/S6) * xpush(x) * C0, gm.second * ddspin(c,t,+M_PI/S6) * xpush(x) * C0, 0xFF0000FF, 1); vid.linewidth /= (inHighQual ? 10 : 2); drawqueue(); } } void init_bantar() { if(!on) { stop_game(); on = true; start_game(); } } void init_bantar_map() { bmap = true; ForInfos { int hsh = 0x202047; for(int w: cci.second.way) hsh = (11301 * hsh + w * 37121) & 0x7F7F7F; cci.second.c->landparam = hsh; cci.second.c->land = laCanvas; cci.second.c->wall = waNone; cci.second.c->item = itNone; cci.second.c->monst = moNone; } } // see: https://twitter.com/ZenoRogue/status/1001127253747658752 // see also: https://twitter.com/ZenoRogue/status/1000043540985057280 (older version) void bantar_record() { int TSIZE = rug::texturesize; // recommended 2048 resetbuffer rb; renderbuffer rbuf(TSIZE, TSIZE, true); int fr = 0; for(int i=0; i < 10000; i += 33) { if(i % 1000 == 999) i++; ticks = i; rbuf.enable(); vid.xres = vid.yres = TSIZE; banachtarski::bantar_frame(); IMAGESAVE(rbuf.render(), ("bantar/" + format("%05d", fr) + IMAGEEXT).c_str()); printf("GL %5d/%5d\n", i, 10000); fr++; } rb.reset(); } int readArgs() { using namespace arg; if(0) ; else if(argis("-bantar_anim")) { PHASE(3); init_bantar(); peace::on = true; airmap.clear(); ForInfos if(cci.second.c->monst == moAirElemental) cci.second.c->monst = moFireElemental; bantar_anim(); } else if(argis("-bantar_test")) { PHASE(3); init_bantar(); peace::on = true; airmap.clear(); ForInfos if(cci.second.c->monst == moAirElemental) cci.second.c->monst = moFireElemental; ForInfos if(cci.second.gid != 2) cci.second.c->wall = waInvisibleFloor, cci.second.c->item = itNone, cci.second.c->monst = moNone, cci.second.c->land = laNone; airmap.clear(); havewhat = 0; } else if(argis("-bantar_map")) { init_bantar(); init_bantar_map(); } else if(argis("-btry")) { shift(); notry = argi(); } else if(argis("-bantar_record")) { using namespace banachtarski; PHASE(3); peace::on = true; airmap.clear(); ForInfos if(cci.second.c->monst == moAirElemental) cci.second.c->monst = moFireElemental; bantar_record(); } else return 1; return 0; } auto hook = addHook(hooks_args, 100, readArgs) + addHook(hooks_initgame, 100, bantar) + addHook(hooks_frame, 100, bantar_stats) + addHook_rvslides(140, [] (string s, vector<tour::slide>& v) { if(s != "mixed") return; using namespace pres; v.push_back( tour::slide{"Banach-Tarski-like", 62, LEGAL::NONE, "Banach-Tarski-like decomposition. Break a hyperbolic plane into two hyperbolic planes.\n\n" "Press '5' to show the decomposition. Press any key to stop.\n\n" "You will see a map of the decomposition. Press '5' again to return.", [] (presmode mode) { slidecommand = "Banach-Tarski switch"; slide_url(mode, 't', "Twitter link", "https://twitter.com/ZenoRogue/status/1001127253747658752"); if(mode == 3) { while(gamestack::pushed()) stop_game(), gamestack::pop(); banachtarski::bmap = false; banachtarski::on = false; } if(mode == 4) { if(!banachtarski::on) { bool b = mapeditor::drawplayer; specialland = cwt.at->land; gamestack::push(); banachtarski::init_bantar(); airmap.clear(); dynamicval<int> vs(sightrange_bonus, 3); dynamicval<int> vg(genrange_bonus, 3); doOvergenerate(); banachtarski::bantar_anim(); quitmainloop = false; mapeditor::drawplayer = b; banachtarski::init_bantar_map(); resetview(); } else if(banachtarski::on && banachtarski::bmap) { banachtarski::bmap = false; banachtarski::on = false; gamestack::pop(); } } }} ); }); }}