// Hyperbolic Rogue // Copyright (C) 2011-2016 Zeno Rogue, see 'hyper.cpp' for details // Hyperbolic geometry is a good tool to visualize data, // especially trees and scale-free networks. This file // uses HyperRogue to browse such vizualizations. // Since it is not of use for general HyperRogue players, // it is disabled by default -- compile with the ROGUEVIZ flag to enable this. // How to use: // hyper -embed -- visualize a social network // embedded into hyperbolic plane, see: // https://bitbucket.org/HaiZhung/hyperbolic-embedder/overview // (it uses the same format) // hyper -tol -- visualize the tree of life, // based on a XML dump from https://tree.opentreeoflife.org/ // hyper -tess -- visualize a horocyclic tesselation, namespace rogueviz { void init(); bool showlabels = false; bool specialmark = false; const char *fname; const char *cfname; enum eVizkind { kNONE, kAnyGraph, kTree, kSpiral, kSAG }; eVizkind kind; bool on; struct edgeinfo { int i, j; double weight, weight2; bool visible; vector prec; cell *orig; int lastdraw; edgeinfo() { visible = true; orig = NULL; lastdraw = -1; } }; struct colorpair { int color1, color2; char shade; colorpair(int col = 0xC0C0C0FF) { shade = 0; color1 = col; } }; colorpair parse(const string& s) { colorpair cp; cp.shade = 0; cp.color2 = 0; sscanf(s.c_str(), "%x:%c%x", &cp.color1, &cp.shade, &cp.color2); return cp; } struct vertexdata { vector > edges; string name; colorpair cp; edgeinfo *virt; bool special; int data; string *info; shmup::monster *m; vertexdata() { virt = NULL; m = NULL; info = NULL; special = false; } }; vector vdata; transmatrix cspin(int i, int ch) { return spin(M_PI + (2 * M_PI * (i+1)) / (ch+1)); } map labeler; int getid(const string& s) { if(labeler.count(s)) return labeler[s]; else { int id = vdata.size(); vdata.resize(vdata.size() + 1); vdata[id].name = s; return labeler[s] = id; } } int getnewid(string s) { while(labeler.count(s)) s += "'"; return getid(s); } void addedge0(int i, int j, edgeinfo *ei) { vdata[i].edges.push_back(make_pair(j, ei)); vdata[j].edges.push_back(make_pair(i, ei)); } void createViz(int id, cell *c, transmatrix at) { vertexdata& vd(vdata[id]); vd.m = new shmup::monster; vd.m->pid = id; vd.m->type = moRogueviz; vd.m->base = c; vd.m->at = at; } void addedge(int i, int j, edgeinfo *ei) { double d = hdist(vdata[i].m->at * C0, vdata[j].m->at * C0); if(d >= 4) { // printf("splitting %lf\n", d); hyperpoint h = mid(vdata[i].m->at * C0, vdata[j].m->at * C0); int id = size(vdata); vdata.resize(id+1); vertexdata& vd(vdata[id]); vd.cp = colorpair(0x400000FF); vd.virt = ei; createViz(id, origin.c7, rgpushxto0(h)); addedge(i, id, ei); addedge(id, j, ei); } else addedge0(i, j, ei); } void addedge(int i, int j, double wei, bool subdiv) { edgeinfo *ei = new edgeinfo; ei->i = i; ei->j = j; ei->weight = wei; if(subdiv) addedge(i, j, ei); else addedge0(i, j, ei); } void storeall() { for(int i=0; istore(); } int dftcolor = 0x282828FF; namespace spiral { void place(int N, ld mul) { init(); kind = kSpiral; vdata.resize(N); for(int i=0; i= 0 && j0 < N) addedge(i, j0, 0, false); } } void color(ld start, ld period, colorpair c) { int N = size(vdata); int maxw = N; while(start >= 0 && start < N) { int i = int(start); vdata[i].cp = c; start += period; maxw--; if(maxw <= 0) break; } } } int readLabel(FILE *f) { char xlabel[10000]; if(fscanf(f, "%9500s", xlabel) <= 0) return -1; return getid(xlabel); } void readAnyGraph(string fn) { init(); kind = kAnyGraph; FILE *f = fopen((fn + "-coordinates.txt").c_str(), "rt"); if(!f) { printf("Missing file: %s-coordinates.txt\n", fname); exit(1); } printf("Reading coordinates...\n"); char buf[100]; double x; int N; int err; err = fscanf(f, "%s%s%s%s%d%lf%lf%lf", buf, buf, buf, buf, &N, &x, &x, &x); if(err < 8) { printf("Error: incorrect format of the first line\n"); exit(1); } vdata.resize(N); while(true) { int id = readLabel(f); if(id < 0) break; vertexdata& vd(vdata[id]); vd.name = its(id); vd.cp = colorpair(dftcolor); double r, alpha; int err = fscanf(f, "%lf%lf", &r, &alpha); if(err < 2) { printf("Error: incorrect format of r/alpha\n"); exit(1); } transmatrix h = spin(alpha * 2 * M_PI / 360) * xpush(r); createViz(id, origin.c7, h); } fclose(f); f = fopen((fn + "-links.txt").c_str(), "rt"); if(!f) { printf("Missing file: %s-links.txt\n", fname); exit(1); } printf("Reading links...\n"); int qlink = 0; while(true) { int i = readLabel(f), j = readLabel(f); if(i == -1 || j == -1) break; addedge(i, j, 0, true); if(qlink % 10000 == 0) printf("%d\n", qlink); qlink++; } fclose(f); printf("Rebasing...\n"); for(int i=0; i children; }; vector tol; void child(int pid, int id) { if(size(tol) <= id) tol.resize(id+1); treevertex& v = tol[id]; v.parent = pid; tol.push_back(v); if(pid >= 0) tol[pid].children.push_back(id); } void readnode(FILE *f, int pid) { string lab = ""; while(true) { int c = fgetc(f); if(c == EOF) { fprintf(stderr, "Ended prematurely\n"); exit(1); } if(c == ',') break; if(c == ')') { int id = getnewid(lab); child(pid, id); return; } lab += c; } int id = getnewid(lab); child(pid, id); while(true) { int c = fgetc(f); // printf("c=%c at %d/%d\n", c, pid, id); if(c == EOF) { fprintf(stderr, "Ended prematurely\n"); exit(1); } if(c == ' ' || c == 10 || c == 13 || c == 9 || c == ',') continue; else if(c == '(') readnode(f, id); else if(c == ')') break; } } int xpos; void spos(int at, int d) { tol[at].spos = xpos++; tol[at].depth = d; for(int i=0; ipid = i; vd.data = lv.parent; createViz(i, cwt.c, h); vd.cp = dftcolor; if(tol[i].parent >= 0) addedge(i, tol[i].parent, 0, true); } for(int i=0; i snakecells; vector snakefirst, snakelast; vector snakenode; vector snakeid; vector lpbak; vector wpbak; bool snake_enabled; void setsnake(cellwalker& cw, int i) { lpbak[i] = cw.c->landparam; wpbak[i] = cw.c->wparam; cw.c->landparam = i; cw.c->wparam = INSNAKE; // cw.c->monst = moWormtail; cw.c->mondir = cw.spin; snakecells[i] = cw.c; } void snakeswitch() { for(int i=0; ilandparam; c->landparam = x; x = wpbak[i]; wpbak[i] = c->wparam; c->wparam = x; } snake_enabled = !snake_enabled; } void enable_snake() { if(!snake_enabled) snakeswitch(); } void disable_snake() { if(snake_enabled) snakeswitch(); } int snakedist(int i, int j) { if(i < insnaketab && j < insnaketab) return sdist[i][j]; if(euclid || sphere) return celldistance(snakecells[i], snakecells[j]); int i0 = i, i1 = i, j0 = j, j1 = j; int cost = 0; // intersect while(true) { if(j0 > i1+1) { j0 = snakefirst[j0], j1 = snakelast[j1]; cost++; } else if(i0 > j1+1) { i0 = snakefirst[i0], i1 = snakelast[i1]; cost++; } else if(j1+1 == i0) return cost+1; else if(i1+1 == j0) return cost+1; else return cost; } } void initSnake(int n) { if(sphere && purehepta) n = 12; else if(sphere) n = 32; numsnake = n; snakecells.resize(numsnake); snakefirst.resize(numsnake); snakelast.resize(numsnake); snakenode.resize(numsnake); lpbak.resize(numsnake); wpbak.resize(numsnake); cellwalker cw = cwt; setsnake(cw, 0); cwstep(cw); setsnake(cw, 1); for(int i=2; i<=numsnake; i++) { if(i == numsnake && sphere) break; cwstep(cw); snakefirst[i-1] = cw.c->landparam; while(cw.c->wparam == INSNAKE) { snakelast[i-1] = cw.c->landparam; cwstep(cw); cwspin(cw, 1); cwstep(cw); } if(i == numsnake) break; setsnake(cw, i); cwspin(cw, 1); } int stab = min(numsnake, MAXSNAKETAB); for(int i=0; iweight2; } /* cell *c = snakecells[id]; for(int i=0; itype; i++) { cell *c2 = c->mov[i]; if(c2 && c2->wparam == INSNAKE && snakenode[c2->landparam] >= 0) cost += 100; } */ return cost; } MTRand53 los; bool infullsa; double cost; int N; vector chgs; void forgetedges(int id) { for(int i=0; iorig = NULL; } void saiter() { aiter: int t1 = hrand(N); int sid1 = snakeid[t1]; int sid2; int s = hrand(6); if(s == 3) s = 2; if(s == 4) s = 5; if(s == 5) sid2 = hrand(numsnake); else { cell *c; if(s>=2 && size(vdata[t1].edges)) c = snakecells[snakeid[hrand(size(vdata[t1].edges))]]; else c = snakecells[sid1]; int it = s<2 ? (s+1) : s-2; for(int ii=0; iitype; c = c->mov[d]; if(!c) goto aiter; if(c->wparam != INSNAKE) goto aiter; } sid2 = c->landparam; } int t2 = snakenode[sid2]; snakenode[sid1] = -1; snakeid[t1] = -1; snakenode[sid2] = -1; if(t2 >= 0) snakeid[t2] = -1; double change = costat(t1,sid2) + costat(t2,sid1) - costat(t1,sid1) - costat(t2,sid2); snakenode[sid1] = t1; snakeid[t1] = sid1; snakenode[sid2] = t2; if(t2 >= 0) snakeid[t2] = sid2; if(change < 0) chgs.push_back(-change); if(change > 0 && (sagmode == sagHC || los() > exp(-change * exp(-temperature)))) return; snakenode[sid1] = t2; snakenode[sid2] = t1; snakeid[t1] = sid2; if(t2 >= 0) snakeid[t2] = sid1; if(vdata[t1].m) vdata[t1].m->base = snakecells[sid2]; if(t2 >= 0 && vdata[t2].m) vdata[t2].m->base = snakecells[sid1]; cost += 2*change; if(t1 >= 0) forgetedges(t1); if(t2 >= 0) forgetedges(t2); } void organize() { for(int i=0; i freenodes; for(int i=0; i= numsnake || err < 1) sid = -1; if(!labeler.count(lab)) { printf("unknown vertex: %s\n", lab.c_str()); } else { int id = getid(lab); snakeid[id] = sid; } } afterload: if(sf) fclose(sf); organize(); for(int i=0; ibase = snakecells[sag::snakeid[i]]; forgetedges(i); } } vector sagedges; /* bool totcmp(int i, int j) { return totwei[i] > totwei[j]; } */ int ipturn = 100; int numiter = 0; void dofullsa(int satime) { sagmode = sagSA; enable_snake(); int t1 = SDL_GetTicks(); while(true) { int t2 = SDL_GetTicks(); double d = (t2-t1) / (1000. * satime); if(d > 1) break; temperature = 10 - (d*25); chgs.clear(); for(int i=0; i<50000; i++) { numiter++; sag::saiter(); } printf("it %8d temp %6.4" PLDF" [1/e at %13.6" PLDF"] cost = %lf ", numiter, sag::temperature, (ld) exp(sag::temperature), sag::cost); sort(chgs.begin(), chgs.end()); int cc = chgs.size() - 1; printf("%9.4lf .. %9.4lf .. %9.4lf .. %9.4lf .. %9.4lf\n", chgs[0], chgs[cc/4], chgs[cc/2], chgs[cc*3/4], chgs[cc]); fflush(stdout); } temperature = -5; disable_snake(); sagmode = sagOff; } void iterate() { if(!sagmode) return; int t1 = SDL_GetTicks(); enable_snake(); for(int i=0; i 200) ipturn /= 2; else ipturn = ipturn * 100 / t; printf("it %8d temp %6.4" PLDF" [2:%8.6" PLDF",10:%8.6" PLDF",50:%8.6" PLDF"] cost = %lf\n", numiter, sag::temperature, exp(-2 * exp(-sag::temperature)), exp(-10 * exp(-sag::temperature)), exp(-50 * exp(-sag::temperature)), sag::cost); } void savesnake(const char *fname) { FILE *f = fopen(fname, "wt"); for(int i=0; i= 0.1; // (ei.weight >= maxwei[ei.i] / 5 || ei.weight >= maxwei[ei.j] / 5); ei.weight2 = pow(ei.weight, edgepower) * edgemul; // LANG:: pow(ei.weight, .4) / 50; // ei.weight2 = 0; int w = ei.weight; while(w) { w >>= 1; ei.weight2++; } /* if(totwei[ei.i] <= 0 || totwei[ei.j] <= 0) { printf("BAD TOTWEI\n"); exit(1); } ei.weight2 = 3 * ( sqrt(ei.weight * 1. / totwei[ei.i]) * log(totwei[ei.i]) * log(totwei[ei.i]) + sqrt(ei.weight * 1. / totwei[ei.j]) * log(totwei[ei.j]) * log(totwei[ei.j])); */ // printf("%f\n", ei.weight2); addedge0(ei.i, ei.j, &ei); } initSnake(N*2); printf("numsnake = %d\n", numsnake); if(numsnake < N) { printf("Error: snake does not fit\n"); exit(1); } snakeid.resize(N); for(int i=0; iweight > e2->weight; } string describe(shmup::monster *m) { int i = m->pid; vertexdata& vd = vdata[i]; string o = vd.name+", "+its(size(vd.edges))+" edges"; /* if(size(vd.edges) < 10) { for(int i=0; isnakeid)); } */ vector alledges; for(int j=0; jvisible) continue; int k = ei->i ^ ei->j ^ i; help += vdata[k].name; help += "/" + fts(ei->weight)+":" + fts(ei->weight2) + " "; } return o; } void activate(shmup::monster *m) { int i = m->pid; vertexdata& vd = vdata[i]; vd.cp = colorpair(rand() & 0xFFFFFFFF); for(int i=0; iorig = NULL; /* if(ealpha == 1) ealpha = 8; else if(ealpha == 8) ealpha = 32; else if(ealpha == 32) ealpha = 255; else ealpha = 1; */ } void storevertex(vector& tab, const hyperpoint& h) { for(int i=0; i<3; i++) tab.push_back(h[i]); } double linequality = .1; void storelineto(vector& tab, const hyperpoint& h1, const hyperpoint& h2) { if(intval(h1, h2) < linequality) storevertex(tab, h2); else { hyperpoint h3 = mid(h1, h2); storelineto(tab, h1, h3); storelineto(tab, h3, h2); } } void storeline(vector& tab, const hyperpoint& h1, const hyperpoint& h2) { storevertex(tab, h1); storelineto(tab, h1, h2); } void queuedisk(const transmatrix& V, const colorpair& cp, bool legend) { if(legend && (int) cp.color1 == (int) 0x000000FF && backcolor == 0) poly_outline = 0x606060FF; else poly_outline = 0x000000FF; queuepoly(V, shDisk, cp.color1); if(cp.shade == 't') queuepoly(V, shDiskT, cp.color2); if(cp.shade == 's') queuepoly(V, shDiskS, cp.color2); if(cp.shade == 'q') queuepoly(V, shDiskSq, cp.color2); if(cp.shade == 'm') queuepoly(V, shDiskM, cp.color2); } ld maxweight; ld ggamma = .5; void drawVertex(const transmatrix &V, cell *c, shmup::monster *m) { int i = m->pid; vertexdata& vd = vdata[i]; bool ghilite = false; if(vd.special && specialmark) ghilite = true; if(!gmatrix.count(m->base)) printf("base not in gmatrix\n"); int lid = shmup::lmousetarget ? shmup::lmousetarget->pid : -2; if(!leftclick) for(int j=0; jvisible) continue; vertexdata& vd1 = vdata[ei->i]; vertexdata& vd2 = vdata[ei->j]; int oi = ei->i, oj = ei->j; bool hilite = false; if(vdata[oi].special && vdata[oj].special && specialmark) hilite = true; else if(svg::in || inHighQual) hilite = false; else if(vd1.m == shmup::lmousetarget) hilite = true; else if(vd2.m == shmup::lmousetarget) hilite = true; else if(oi == lid || oj == lid) hilite = true; if(hilite) ghilite = true; if(ei->lastdraw < frameid) { ei->lastdraw = frameid; int xlalpha = (hilite || hiliteclick) ? 64 : 20; if(kind == kSAG) { if(ei->weight2 > maxweight) maxweight = ei->weight2; xlalpha = int(pow(ei->weight2/ maxweight, ggamma) * 255); } else xlalpha = int(pow(.5, ggamma) * 255); if(svg::in && xlalpha < 16) continue; transmatrix& gm1 = shmup::ggmatrix(vd1.m->base); transmatrix& gm2 = shmup::ggmatrix(vd2.m->base); hyperpoint h1 = gm1 * vd1.m->at * C0; hyperpoint h2 = gm2 * vd2.m->at * C0; /* if(hdist0(h1) < .001 || hdist0(h2) < .001) { printf("h1 = %s\n", display(h1)); printf("h2 = %s\n", display(h2)); display(m->at); display(vd2.m->at); display(V); display(gmatrix[vd2.m->base]); display(shmup::calc_gmatrix(vd2.m->base)); } */ if(ei->orig && ei->orig->cpdist >= 3) ei->orig = NULL; if(!ei->orig) { ei->orig = cwt.c; ei->prec.clear(); transmatrix T = inverse(shmup::ggmatrix(ei->orig)); storeline(ei->prec, T*h1, T*h2); } queuetable(shmup::ggmatrix(ei->orig), &ei->prec[0], size(ei->prec)/3, ((hilite ? 0xFF0000 : backcolor ? 0x808080 : 0xFFFFFF) << 8) + xlalpha, 0, PPR_STRUCT0); } /* */ } if(!vd.virt) { queuedisk(V * m->at, ghilite ? colorpair(0xFF0000FF) : vd.cp, false); lastptd().info = vd.info; } if(showlabels) { bool doshow = true; if(kind == kTree && i > 0) { vertexdata& vdp = vdata[vd.data]; hyperpoint h2 = shmup::ggmatrix(vdp.m->base) * vdp.m->at * C0; if(hdist(h2, V * m->at * C0) < 0.1) doshow = false; } hyperpoint h = tC0(V * m->at); transmatrix V2 = rgpushxto0(h) * ypush(purehepta ? .3 : .2); if(doshow) queuestr(V2, (svg::in ? .28 : .2) * crossf / hcrossf, vd.name, backcolor ? 0x000000 : 0xFFFF00, svg::in ? 0 : 1); lastptd().info = vd.info; } } bool virt(shmup::monster *m) { if(m->type != moRogueviz) return false; vertexdata& vd = vdata[m->pid]; return vd.virt; } vector legend; void drawExtra() { for(int i=0; i 0) vdata[getid(lab)].info = new string(buf); // replace with std::shared_ptr in C++111 continue; } else { ungetc(c2, f); char buf[60]; int err = fscanf(f, "%30s", buf); if(err > 0) x = parse(buf); } if(size(lab) && lab[0] == '*') { lab = lab.substr(1); for(int i=0; i= 0) goto again; } } } } void init() { on = autocheat = true; #ifndef WEB mapeditor::drawplayer = false; firstland = euclidland = laCanvas; if(!shmup::on) restartGame('s'); #else firstland = euclidland = laCanvas; restartGame(); #endif items[itOrbLife] = 0; timerghost = false; gmatrix.clear(); drawthemap(); gmatrix0 = gmatrix; addMessage("RogueViz enabled"); } void close() { vdata.clear(); labeler.clear(); legend.clear(); } void turn(int delta) { if(!on) return; if(kind == kSAG) sag::iterate(); // shmup::pc[0]->rebase(); } void fixparam() { if((svg::in || inHighQual) && size(legend)) vid.xres = vid.xres * 22/16; while(vid.xres & 15) vid.xres++; if(size(legend)) vid.xcenter = vid.ycenter; } void rvvideo(const char *fname) { for(int i=0; i<1050; i++) { char buf[500]; snprintf(buf, 500, fname, i); shmup::pc[0]->base = origin.c7; shmup::pc[0]->at = spin(i * 2 * M_PI / 1000.) * xpush(1.7); if(i == 0) drawthemap(); shmup::turn(100); if(i == 0) drawthemap(); centerpc(100); printf("%s\n", buf); saveHighQualityShot(buf); } } int readArgs() { using namespace arg; // options before reading if(0) ; else if(argis("-dftcolor")) { shift(); dftcolor = strtol(args(), NULL, 16); } // tree visualizer (e.g. Tree of Life) //------------------------------------- else if(argis("-tree")) { PHASE(3); shift(); tree::read(args()); } // SAG visualizer (e.g. Reddit roguelikes, GitHub languages) //----------------------------------------------------------- // (1) configure edge weights else if(argis("-edgepower")) { shift(); sag::edgepower = argf(); shift(); sag::edgemul = argf(); } // (2) read the edge data else if(argis("-sag")) { PHASE(3); shift(); sag::read(args()); } // (3) load the initial positioning else if(argis("-gload")) { PHASE(3); shift(); sag::loadsnake(args()); } // (4) perform simulated annealing: -fullsa