1 // Hyperbolic Rogue
2 // Copyright (C) 2011-2016 Zeno Rogue, see 'hyper.cpp' for details
3 
4 // Hyperbolic geometry is a good tool to visualize data,
5 // especially trees and scale-free networks. This file
6 // uses HyperRogue to browse such vizualizations.
7 
8 // Since it is not of use for general HyperRogue players,
9 // it is disabled by default -- compile with the ROGUEVIZ flag to enable this.
10 
11 // How to use:
12 
13 // hyper -embed <start of filename> -- visualize a social network
14 //   embedded into hyperbolic plane, see:
15 //   https://bitbucket.org/HaiZhung/hyperbolic-embedder/overview
16 //   (it uses the same format)
17 
18 // hyper -tess <parameter file> -- visualize a horocyclic tesselation,
19 
20 #include "rogueviz.h"
21 
22 namespace rogueviz {
23 
24 string weight_label;
25 
26 ld fat_edges = 0;
27 ld ggamma = 1;
28 
29 using namespace hr;
30 
31 edgetype default_edgetype = { .1, .1, .1, DEFAULT_COLOR, 0xFF0000FF, "default" };
32 
33 bool showlabels = false;
34 bool specialmark = false;
35 bool edge_legend = false;
36 
37 bool rog3 = false;
38 int vertex_shape = 1;
39 
40 string edgename;
41 string fname;
42 
43 // const char *fname;
44 // const char *cfname;
45 
46 vector<shared_ptr<edgetype>> edgetypes;
47 
add_edgetype(const string & name)48 edgetype *add_edgetype(const string& name) {
49   auto e = make_shared<edgetype> (default_edgetype);
50   e->name = name;
51   edgetypes.push_back(e);
52   return &*e;
53   }
54 
55 map<color_t, array<color_t, 16> > next_hue;
56 
parse1(const string & s)57 color_t parse1(const string& s) {
58   // color can be given as RRGGBB
59   // or as 'Rmax,min,alpha,step,start', for rainbow Collatz
60   if(s[0] == 'R') {
61     color_t mh = 192, minh = 0, alpha = 255;
62     int step = 50, start = 0;
63     sscanf(s.c_str(), "R%x,%x,%x,%d,%d", &mh, &minh, &alpha, &step, &start);
64     vector<color_t> hues;
65     color_t difh = mh - minh;
66     color_t base = alpha + minh * 0x1010100;
67 
68     for(unsigned y=0; y<difh; y++)
69       hues.push_back(base + 0x1000000*mh + 0x10000 * y);
70     for(unsigned y=0; y<difh; y++)
71       hues.push_back(base + 0x1010000*mh - 0x1000000 * y);
72     for(unsigned y=0; y<difh; y++)
73       hues.push_back(base + 0x0010000*mh + 0x100 * y);
74     for(unsigned y=0; y<difh; y++)
75       hues.push_back(base + 0x0010100*mh - 0x10000 * y);
76     for(unsigned y=0; y<difh; y++)
77       hues.push_back(base + 0x0000100*mh + 0x1000000 * y);
78     for(unsigned y=0; y<difh; y++)
79       hues.push_back(base + 0x1000100*mh - 0x100 * y);
80 
81     for(int t=0; t<isize(hues); t++)
82       for(int a=0; a<16; a++)
83         next_hue[hues[t]][a] = hues[gmod(t + rand() % step - rand() % step, isize(hues))];
84 
85     return hues[gmod(start, isize(hues))];
86     }
87   else {
88     color_t res;
89     sscanf(s.c_str(), "%x", &res);
90     return res;
91     }
92   }
93 
94 
perturb(color_t c)95 color_t perturb(color_t c) {
96   if(!next_hue.count(c)) return c;
97   return next_hue[c][rand() % 16];
98   }
99 
perturb(colorpair cp)100 colorpair perturb(colorpair cp) {
101   cp.color1 = perturb(cp.color1);
102   cp.color2 = perturb(cp.color2);
103   return cp;
104   }
105 
parse(string s)106 colorpair parse(string s) {
107   colorpair cp;
108   auto pospng = s.find(":PNG:");
109   if(pospng != string::npos) {
110     string fname = s.substr(pospng+5);
111     s = s.substr(0, pospng);
112     #if CAP_TEXTURE
113     cp.img = make_shared<rvimage>();
114     auto& i = *cp.img;
115     i.tdata.twidth = 1024;
116     if(!(i.tdata.readtexture(fname) && i.tdata.loadTextureGL())) {
117       println(hlog, "failed to load: ", fname);
118       cp.img = NULL;
119       return cp;
120       }
121     println(hlog, "loaded texture: ", fname);
122     for(int x=0; x<16; x++)
123     for(int y=0; y<16; y++) {
124       auto addv = [&] (ld x, ld y) {
125         x -= 8;
126         y -= 8;
127         x /= 16.;
128         y /= 16.;
129         ld r = max(abs(x), abs(y)) / hypot(x, y);
130         if(x || y) {
131           x *= r;
132           y *= r;
133           }
134         i.tinf.tvertices.push_back(glhr::makevertex(x + .5, y + .5, 0));
135         i.vertices.push_back(hpxy(x * .4, y * .4));
136         };
137       addv(x, y);
138       addv(x, y+1);
139       addv(x+1, y);
140       addv(x, y+1);
141       addv(x+1, y);
142       addv(x+1, y+1);
143       }
144     i.tinf.texture_id = i.tdata.textureid;
145     #endif
146     }
147   auto pos = s.find(":");
148   if(pos != string::npos) {
149     cp.color1 = parse1(s.substr(0, pos));
150     cp.shade = s[pos+1];
151     cp.color2 = parse1(s.substr(pos+2));
152     }
153   else {
154     cp.shade = 0; cp.color2 = 0;
155     cp.color1 = parse1(s);
156     }
157   return cp;
158   }
159 
160 vector<vertexdata> vdata;
161 
162 map<string, int> labeler;
163 
id_known(const string & s)164 bool id_known(const string& s) {
165   return labeler.count(s);
166   }
167 
getid(const string & s)168 int getid(const string& s) {
169   if(labeler.count(s)) return labeler[s];
170   else {
171     int id = isize(vdata);
172     vdata.resize(isize(vdata) + 1);
173     vdata[id].name = s;
174     return labeler[s] = id;
175     }
176   }
177 
getnewid(string s)178 int getnewid(string s) {
179   while(labeler.count(s)) s += "'";
180   return getid(s);
181   }
182 
addedge0(int i,int j,edgeinfo * ei)183 void addedge0(int i, int j, edgeinfo *ei) {
184   vdata[i].edges.push_back(make_pair(j, ei));
185   vdata[j].edges.push_back(make_pair(i, ei));
186   }
187 
createViz(int id,cell * c,transmatrix at)188 void createViz(int id, cell *c, transmatrix at) {
189   vertexdata& vd(vdata[id]);
190   vd.m = new shmup::monster;
191   vd.m->pid = id;
192   vd.m->type = moRogueviz;
193   vd.m->base = c;
194   vd.m->at = at;
195   vd.m->isVirtual = false;
196   }
197 
notimpl()198 void notimpl() {
199   printf("Not implemented\n"); exit(1);
200   }
201 
where(int i,cell * base)202 hyperpoint where(int i, cell *base) {
203   auto m = vdata[i].m;
204   if(m->base == base) return tC0(m->at);
205   else if(confusingGeometry()) {
206     return calc_relative_matrix(m->base, base, C0) * tC0(m->at);
207     }
208   else {
209     // notimpl(); // actually probably that's a buug
210     return inverse_shift(ggmatrix(currentmap->gamestart()), ggmatrix(m->base) * tC0(m->at));
211     }
212   }
213 
addedge(int i,int j,edgeinfo * ei)214 void addedge(int i, int j, edgeinfo *ei) {
215   cell *base =
216     confusingGeometry() ? vdata[i].m->base : currentmap->gamestart();
217   hyperpoint hi = where(i, base);
218   hyperpoint hj = where(j, base);
219   double d = hdist(hi, hj);
220   if(d >= 4) {
221     hyperpoint h = mid(hi, hj);
222     int id = isize(vdata);
223     vdata.resize(id+1);
224     vertexdata& vd(vdata[id]);
225     vd.cp = colorpair(0x400000FF);
226     vd.virt = ei;
227 
228     createViz(id, base, rgpushxto0(h));
229     vd.m->no_targetting = true;
230 
231     addedge(i, id, ei);
232     addedge(id, j, ei);
233     virtualRebase(vdata[i].m);
234     }
235   else addedge0(i, j, ei);
236   }
237 
238 vector<edgeinfo*> edgeinfos;
239 
addedge(int i,int j,double wei,bool subdiv,edgetype * t)240 void addedge(int i, int j, double wei, bool subdiv, edgetype *t) {
241   edgeinfo *ei = new edgeinfo(t);
242   edgeinfos.push_back(ei);
243   ei->i = i;
244   ei->j = j;
245   ei->weight = wei;
246   if(subdiv) addedge(i, j, ei);
247   else addedge0(i, j, ei);
248   }
249 
storeall(int from)250 void storeall(int from) {
251   for(int i=from; i<isize(vdata); i++)
252     if(vdata[i].m)
253       vdata[i].m->store();
254   }
255 
256 colorpair dftcolor = 0x282828FF;
257 
readLabel(fhstream & f)258 int readLabel(fhstream& f) {
259   string s = scan<string>(f);
260   if(s == "") return -1;
261   return getid(s);
262   }
263 
264 namespace anygraph {
265   double R, alpha, T;
266   vector<pair<double, double> > coords;
267 
268   edgetype *any;
269 
270   int N;
271 
fixedges()272   void fixedges() {
273     for(int i=N; i<isize(vdata); i++) if(vdata[i].m) vdata[i].m->dead = true;
274     for(int i=0; i<isize(vdata); i++) vdata[i].edges.clear();
275     vdata.resize(N);
276     for(auto e: edgeinfos) {
277       e->orig = NULL;
278       addedge(e->i, e->j, e);
279       }
280     }
281 
tst()282   void tst() {}
283 
read(string fn,bool subdiv,bool doRebase,bool doStore)284   void read(string fn, bool subdiv, bool doRebase, bool doStore) {
285     init(RV_GRAPH);
286     any = add_edgetype("embedded edges");
287     fname = fn;
288     fhstream f(fn + "-coordinates.txt", "rt");
289     if(!f.f) {
290       printf("Missing file: %s-coordinates.txt\n", fname.c_str());
291       exit(1);
292       }
293     printf("Reading coordinates...\n");
294     string ignore;
295     if(!scan(f, ignore, ignore, ignore, ignore, N, anygraph::R, anygraph::alpha, anygraph::T)) {
296       printf("Error: incorrect format of the first line\n"); exit(1);
297       }
298     vdata.reserve(N);
299     while(true) {
300       string s = scan<string>(f);
301       println(hlog, "s: ", s.c_str());
302       if(s == "D11.11") tst();
303       if(s == "" || s == "#ROGUEVIZ_ENDOFDATA") break;
304       int id = getid(s);
305       vertexdata& vd(vdata[id]);
306       vd.name = s;
307       vd.cp = colorpair(dftcolor);
308 
309       double r, alpha;
310       if(!scan(f, r, alpha)) { printf("Error: incorrect format of r/alpha\n"); exit(1); }
311       coords.push_back(make_pair(r, alpha));
312 
313       transmatrix h = spin(alpha * degree) * xpush(r);
314 
315       createViz(id, currentmap->gamestart(), h);
316       }
317 
318     fhstream g(fn + "-links.txt", "rt");
319     if(!g.f) {
320       println(hlog, "Missing file: ", fname, "-links.txt");
321       exit(1);
322       }
323     println(hlog, "Reading links...");
324     int qlink = 0;
325     while(true) {
326       int i = readLabel(g), j = readLabel(g);
327       if(i == -1 || j == -1) break;
328       addedge(i, j, 1, subdiv, any);
329       qlink++;
330       }
331 
332     if(doRebase) {
333       printf("Rebasing...\n");
334       for(int i=0; i<isize(vdata); i++) {
335         if(i % 10000 == 0) printf("%d/%d\n", i, isize(vdata));
336         if(vdata[i].m) virtualRebase(vdata[i].m);
337         }
338       printf("Done.\n");
339       }
340 
341     if(doStore) storeall();
342     }
343 
344   }
345 
346 ld maxweight;
347 
edgecmp(edgeinfo * e1,edgeinfo * e2)348 bool edgecmp(edgeinfo *e1, edgeinfo *e2) {
349   return e1->weight > e2->weight;
350   }
351 
352 bool which_weight = false;
353 
rogueviz_help(int id,int pagenumber)354 void rogueviz_help(int id, int pagenumber) {
355 
356   vertexdata& vd = vdata[id];
357   int noedges = isize(vd.edges);
358   help = helptitle(vd.name, vd.cp.color1 >> 8);
359 
360   if(vd.info) {
361     #if CAP_URL
362     help_extension hex;
363     hex.key = 'L';
364     hex.text = "open link";
365     hex.subtext = *vd.info;
366     hex.action = [&vd] () { open_url(*vd.info); };
367     help_extensions.push_back(hex);
368     #else
369     help += "\n\nlink: " + *vd.info;
370     #endif
371     }
372 
373   vector<edgeinfo*> alledges;
374 
375   for(int j=0; j<isize(vd.edges); j++)
376     alledges.push_back(vd.edges[j].second);
377 
378   sort(alledges.begin(), alledges.end(), edgecmp);
379 
380   for(int i=0; i<10 && i+pagenumber < noedges; i++) {
381     help_extension hex;
382     hex.key = 'a' + i;
383 
384     edgeinfo *ei = alledges[pagenumber + i];
385     if(ei->weight < ei->type->visible_from_help) continue;
386     int k = ei->i ^ ei->j ^ id;
387     hex.text = vdata[k].name;
388     hex.color = vdata[k].cp.color1 >> 8;
389     if(vizflags & RV_WHICHWEIGHT) {
390       if(which_weight)
391         hex.subtext = fts(ei->weight2);
392       else
393         hex.subtext = fts(ei->weight);
394       }
395 
396     hex.action = [k] () { help_extensions.clear(); rogueviz_help(k, 0); };
397     help_extensions.push_back(hex);
398     }
399 
400   if(noedges > pagenumber + 10) {
401     help_extension hex;
402     hex.key = 'z';
403     hex.text = "next page";
404     hex.subtext = its(pagenumber+10) + "/" + its(noedges) + " edges";
405     hex.action = [id, pagenumber] () { help_extensions.clear(); rogueviz_help(id, pagenumber + 10); };
406     help_extensions.push_back(hex);
407     }
408 
409   if((vizflags & RV_WHICHWEIGHT) && noedges) {
410     help_extension hex;
411     hex.key = 'w';
412     hex.text = "displayed weight";
413     hex.subtext = which_weight ? "attraction force" : "weight from the data";
414     hex.action = [id, pagenumber] () { which_weight = !which_weight; help_extensions.clear(); rogueviz_help(id, pagenumber); };
415     help_extensions.push_back(hex);
416     }
417   }
418 
describe_monster(shmup::monster * m,string & out)419 bool describe_monster(shmup::monster *m, string& out) {
420 
421   if(m->type != moRogueviz) return false;
422 
423   int i = m->pid;
424   vertexdata& vd = vdata[i];
425 
426   string o = vd.name + ", "+its(isize(vd.edges))+" edges";
427   /* if(isize(vd.edges) < 10) {
428     for(int i=0; i<isize(vd.edges); i++)
429       o += " " + its(snakedist(vd.snakeid, vd.edges[i]->snakeid));
430     } */
431 
432   help = bygen([i] () { rogueviz_help(i, 0); });
433 
434   if(out == XLATN("Canvas")) out = o;
435   else out = out + ", " + o;
436 
437   return true;
438   }
439 
activate(shmup::monster * m)440 bool activate(shmup::monster *m) {
441   if(m->type != moRogueviz) return false;
442   int i = m->pid;
443   vertexdata& vd = vdata[i];
444 
445   vd.cp = colorpair(rand() & 0xFFFFFFFF);
446 
447   for(int i=0; i<isize(vd.edges); i++)
448       vd.edges[i].second->orig = NULL;
449 
450   return true;
451 
452   /* if(ealpha == 1) ealpha = 8;
453   else if(ealpha == 8) ealpha = 32;
454   else if(ealpha == 32) ealpha = 255;
455   else ealpha = 1; */
456   }
457 
storevertex(vector<glvertex> & tab,const hyperpoint & h)458 void storevertex(vector<glvertex>& tab, const hyperpoint& h) {
459   tab.push_back(glhr::pointtogl(h));
460   }
461 
462 double linequality = .1;
463 
storelineto(vector<glvertex> & tab,const hyperpoint & h1,const hyperpoint & h2)464 void storelineto(vector<glvertex>& tab, const hyperpoint& h1, const hyperpoint& h2) {
465   if(intval(h1, h2) < linequality)
466     storevertex(tab, h2);
467   else {
468     hyperpoint h3 = mid(h1, h2);
469     storelineto(tab, h1, h3);
470     storelineto(tab, h3, h2);
471     }
472   }
473 
storeline(vector<glvertex> & tab,const hyperpoint & h1,const hyperpoint & h2)474 void storeline(vector<glvertex>& tab, const hyperpoint& h1, const hyperpoint& h2) {
475   storevertex(tab, h1);
476   storelineto(tab, h1, h2);
477   }
478 
darken_a(color_t c)479 color_t darken_a(color_t c) {
480   for(int p=0; p<3; p++)
481   for(int i=0; i<darken; i++) part(c, i+1) = (part(c, i+1) + part(backcolor, i)) >> 1;
482   return c;
483   }
484 
485 #if CAP_SVG
486 #define SVG_LINK(x) svg::link = (x)
487 #else
488 #define SVG_LINK(x)
489 #endif
490 
queuedisk(const shiftmatrix & V,const colorpair & cp,bool legend,const string * info,int i)491 void queuedisk(const shiftmatrix& V, const colorpair& cp, bool legend, const string* info, int i) {
492   if(legend && (int) cp.color1 == (int) 0x000000FF && backcolor == 0)
493     poly_outline = 0x606060FF;
494   else
495     poly_outline = (bordcolor << 8) | 0xFF;
496 
497   if(cp.img) {
498     for(hyperpoint h: cp.img->vertices)
499       curvepoint(h);
500     auto& qc = queuecurve(V, 0, 0xFFFFFFFF, PPR::MONSTER_HEAD);
501     qc.tinf = &cp.img->tinf;
502     qc.flags |= POLY_TRIANGLES;
503     return;
504     }
505 
506   shiftmatrix V1;
507 
508   auto& sh =
509     vertex_shape == 2 ? cgi.shHeptaMarker :
510     vertex_shape == 3 ? cgi.shSnowball :
511     cgi.shDisk;
512 
513   if(vertex_shape == 0) ;
514   else if(GDIM == 3 && among(cp.shade, 'b', 'f', 'g', 'B', 'F', 'G')) {
515     V1 = V;
516     }
517   else if(GDIM == 3) {
518     V1 = face_the_player(V);
519     if(info) queueaction(PPR::MONSTER_HEAD, [info] () { SVG_LINK(*info); });
520     queuepolyat(V1, sh, darken_a(cp.color1), PPR::MONSTER_HEAD);
521     if(info) queueaction(PPR::MONSTER_HEAD, [] () { SVG_LINK(""); });
522     V1 = V;
523     }
524   else if(rog3) {
525     int p = poly_outline; poly_outline = OUTLINE_TRANS;
526     queuepolyat(V, sh, 0x80, PPR::MONSTER_SHADOW);
527     poly_outline = p;
528     if(info) queueaction(PPR::MONSTER_HEAD, [info] () { SVG_LINK(*info); });
529     queuepolyat(V1 = mscale(V, cgi.BODY), sh, darken_a(cp.color1), PPR::MONSTER_HEAD);
530     if(info) queueaction(PPR::MONSTER_HEAD, [] () { SVG_LINK(""); });
531     }
532   else {
533     if(info) queueaction(PPR::MONSTER_HEAD, [info] () { SVG_LINK(*info); });
534     queuepoly(V1 = V, sh, darken_a(cp.color1));
535     if(info) queueaction(PPR::MONSTER_HEAD, [] () { SVG_LINK(""); });
536     }
537   switch(cp.shade) {
538     case 't': queuepoly(V1, cgi.shDiskT, darken_a(cp.color2)); return;
539     case 's': queuepoly(V1, cgi.shDiskS, darken_a(cp.color2)); return;
540     case 'q': queuepoly(V1, cgi.shDiskSq, darken_a(cp.color2)); return;
541     case 'm': queuepoly(V1, cgi.shDiskM, darken_a(cp.color2)); return;
542     case 'b': queuepoly(V1, GDIM == 3 ? cgi.shAnimatedTinyEagle[wingphase(200)] : cgi.shTinyBird, darken_a(cp.color2)); return;
543     case 'f': queuepoly(V1, cgi.shTinyShark, darken_a(cp.color2)); return;
544     case 'g': queuepoly(V1, cgi.shMiniGhost, darken_a(cp.color2)); return;
545     case 'B': queuepoly(V1, GDIM == 3 ? cgi.shAnimatedEagle[wingphase(100)] : cgi.shEagle, darken_a(cp.color2)); return;
546     case 'F': queuepoly(V1, cgi.shShark, darken_a(cp.color2)); return;
547     case 'G': queuepoly(V1, cgi.shGhost, darken_a(cp.color2)); return;
548     }
549   }
550 
551 map<pair<edgeinfo*, int>, int> drawn_edges;
552 
553 map<pair<cell*, cell*>, transmatrix> relmatrices;
554 
memo_relative_matrix(cell * c1,cell * c2)555 transmatrix& memo_relative_matrix(cell *c1, cell *c2) {
556   auto& p = relmatrices[make_pair(c1, c2)];
557   if(p[2][2] == 0) {
558     forCellIdEx(c3, i, c2) if(c3 == c1)
559       return p = currentmap->adj(c2, i);
560     p = calc_relative_matrix(c1, c2,  C0);
561     }
562   return p;
563   }
564 
queue_prec(const shiftmatrix & V,edgeinfo * & ei,color_t col)565 void queue_prec(const shiftmatrix& V, edgeinfo*& ei, color_t col) {
566   if(!fat_edges)
567     queuetable(V, ei->prec, isize(ei->prec), col, 0, PPR::STRUCT0);
568   #if MAXMDIM >= 4
569   else {
570     auto& t = queuetable(V, ei->prec, isize(ei->prec), 0, col | 0x000000FF, PPR::STRUCT0);
571     t.flags |= (1<<22), // poly triangles
572     t.offset_texture = 0,
573     t.tinf = &ei->tinf;
574     t.tinf->texture_id = floor_textures->renderedTexture;
575     }
576   #endif
577   }
578 
579 int brm_limit = 1000;
580 
581 ld labelshift = .2;
582 ld labelscale = .2; // .28 in SVG
583 
drawVertex(const shiftmatrix & V,cell * c,shmup::monster * m)584 bool drawVertex(const shiftmatrix &V, cell *c, shmup::monster *m) {
585   if(m->dead) return true;
586   if(m->type != moRogueviz) return false;
587   int i = m->pid;
588   vertexdata& vd = vdata[i];
589 
590   // bool ghilite = false;
591 
592   // if(vd.special && specialmark) ghilite = true;
593 
594   if(!gmatrix.count(m->base)) printf("base not in gmatrix\n");
595 
596   int lid = shmup::lmousetarget ? shmup::lmousetarget->pid : -2;
597 
598   bool multidraw = quotient;
599 
600   bool use_brm = bounded && isize(currentmap->allcells()) <= brm_limit;
601 
602   if(!lshiftclick) for(int j=0; j<isize(vd.edges); j++) {
603     edgeinfo *ei = vd.edges[j].second;
604     if(multidraw && ei->i != i) continue;
605     vertexdata& vd1 = vdata[ei->i];
606     vertexdata& vd2 = vdata[ei->j];
607 
608     int oi = ei->i, oj = ei->j;
609     bool hilite = false;
610     if(vdata[oi].special && vdata[oj].special && specialmark) hilite = true;
611     else if(svg::in || inHighQual) hilite = false;
612     else if(vd1.m == shmup::mousetarget) hilite = true;
613     else if(vd2.m == shmup::mousetarget) hilite = true;
614     else if(oi == lid || oj == lid) hilite = true;
615 
616     if(ei->weight < (hilite ? ei->type->visible_from_hi : ei->type->visible_from)) continue;
617 
618     // if(hilite) ghilite = true;
619 
620     if(ei->lastdraw < frameid || multidraw) {
621       ei->lastdraw = frameid;
622 
623       color_t col = (hilite ? ei->type->color_hi : ei->type->color);
624       auto& alpha = part(col, 0);
625 
626       if(vizflags & RV_AUTO_MAXWEIGHT) {
627         if(ei->weight2 > maxweight) maxweight = ei->weight2;
628         alpha *= pow(ei->weight2 / maxweight, ggamma);
629         }
630       // if(hilite || hiliteclick) alpha = (alpha + 256) / 2;
631 
632       if(svg::in && alpha < 16) continue;
633 
634       if(ISWEB) {
635         if(alpha >= 128) alpha |= 15;
636         else if(alpha >= 64) alpha |= 7;
637         else if(alpha >= 32) alpha |= 3;
638         else if(alpha >= 16) alpha |= 1;
639         }
640 
641       alpha >>= darken;
642 
643       shiftmatrix gm1, gm2;
644 
645       if(use_brm) {
646         gm1 = V * memo_relative_matrix(vd1.m->base, c);
647         gm2 = gm1 * brm_get(vd1.m->base, tC0(vd1.m->at), vd2.m->base, tC0(vd2.m->at));
648         }
649       else if(multidraw || elliptic) {
650         gm1 = V * memo_relative_matrix(vd1.m->base, c);
651         gm2 = V * memo_relative_matrix(vd2.m->base, c);
652         }
653       else {
654         gm1 = ggmatrix(vd1.m->base);
655         gm2 = ggmatrix(vd2.m->base);
656         }
657 
658       shiftpoint h1 = gm1 * vd1.m->at * C0;
659       shiftpoint h2 = gm2 * vd2.m->at * C0;
660 
661       if(elliptic && hdist(h1, h2) > hdist(h1.h, centralsym * h2.h))
662         h2.h = centralsym * h2.h;
663 
664       if(multidraw) {
665         int code = int(h1[0]) + int(h1[1]) * 12789117 + int(h2[0]) * 126081253 + int(h2[1]) * 126891531;
666         int& lastdraw = drawn_edges[make_pair(ei, code)];
667         if(lastdraw == frameid) continue;
668         lastdraw = frameid;
669         }
670 
671       /* if(hdist0(h1) < .001 || hdist0(h2) < .001) {
672         printf("h1 = %s\n", display(h1));
673         printf("h2 = %s\n", display(h2));
674         display(m->at);
675         display(vd2.m->at);
676         display(V);
677         display(gmatrix[vd2.m->base]);
678         display(shmup::calc_gmatrix(vd2.m->base));
679         } */
680 
681       if((col >> 8) == (DEFAULT_COLOR >> 8)) {
682         col &= 0xFF;
683         col |= (forecolor << 8);
684         }
685 
686       if(callhandlers(false, hooks_alt_edges, ei, false)) ;
687 
688       else if(pmodel && !fat_edges) {
689         queueline(h1, h2, col, 2 + vid.linequality).prio = PPR::STRUCT0;
690         }
691       else {
692 
693         cell *center = multidraw ? c : centerover;
694 
695         if(!multidraw && ei->orig && ei->orig != center && celldistance(ei->orig, center) > 3)
696           ei->orig = NULL;
697         if(!ei->orig) {
698           ei->orig = center; // cwt.at;
699           ei->prec.clear();
700 
701           const shiftmatrix& T = multidraw ? V : ggmatrix(ei->orig);
702 
703           if(callhandlers(false, hooks_alt_edges, ei, true)) ;
704           else if(fat_edges) {
705             ei->tinf.tvertices.clear();
706             shiftmatrix T1 = gm1 * vd1.m->at;
707             hyperpoint goal = inverse_shift(T1, h2);
708             transmatrix S = inverse_shift(T, gm1) * vd1.m->at * rspintox(goal);
709             ld d = hdist0(goal);
710             for(int a=0; a<360; a+=30) {
711               auto store = [&] (ld a, ld b) {
712                 storevertex(ei->prec, S * cpush(0, b) * hr::cspin(1, 2, a * degree) * cpush(1, fat_edges) * C0);
713                 ei->tinf.tvertices.push_back(glhr::makevertex(0,(3+cos(a * degree))/4,0));
714                 };
715               store(a, 0);
716               store(a+30, 0);
717               store(a, d);
718               store(a+30, 0);
719               store(a, d);
720               store(a+30, d);
721               }
722             }
723           else
724             storeline(ei->prec, inverse_shift(T, h1), inverse_shift(T, h2));
725           }
726 
727         const shiftmatrix& T = multidraw ? V : ggmatrix(ei->orig);
728 
729         queue_prec(T, ei, col);
730         if(elliptic) queue_prec(ggmatrix(ei->orig) * centralsym, ei, col);
731         }
732       }
733 /*
734     */
735     }
736 
737   if(!vd.virt) {
738     queuedisk(V * m->at, vd.cp, false, vd.info, i);
739     }
740 
741 
742   if(showlabels) {
743     bool doshow = true;
744     if((vizflags & RV_COMPRESS_LABELS) && i > 0 && !vd.virt) {
745       vertexdata& vdp = vdata[vd.data];
746       shiftpoint h2 = ggmatrix(vdp.m->base) * vdp.m->at * C0;
747       if(hdist(h2, V * m->at * C0) < 0.1) doshow = false;
748       }
749 
750     shiftpoint h = tC0(V * m->at);
751     shiftmatrix V2 = GDIM == 3 ? V * m->at : rgpushxto0(h) * ypush(cgi.scalefactor * labelshift); // todo-variation
752     if(doshow && !behindsphere(V2)) {
753       auto info = vd.info;
754       if(info) queueaction(PPR::MONSTER_HEAD, [info] () { SVG_LINK(*info); });
755       queuestr(V2, labelscale, vd.name, forecolor, (svg::in || ISWEB) ? 0 : 1);
756       if(info) queueaction(PPR::MONSTER_HEAD, [] () { SVG_LINK(""); });
757       }
758     }
759 
760   callhooks(hooks_drawvertex, vd, c, m, i);
761 
762   return true;
763   }
764 
765 vector<int> legend;
766 
767 vector<cell*> named;
768 
769 color_t chosen_legend_color = DEFAULT_COLOR;
770 
rogueviz_hud()771 bool rogueviz_hud() {
772   color_t legend_color = chosen_legend_color == DEFAULT_COLOR ? forecolor : chosen_legend_color;
773   if(cmode & sm::DRAW) return false;
774 
775   int qet = isize(edgetypes);
776   if(qet == 1 || !edge_legend) qet = 0;
777 
778   int legit = qet + isize(legend);
779 
780   if(legit == 0) return true;
781 
782   initquickqueue();
783 
784   int rad = current_display->radius/10;
785   ld x = vid.xres - rad;
786 
787   for(int i=0; i<isize(legend); i++) {
788     int k = legend[i];
789     vertexdata& vd = vdata[k];
790 
791     ld y = (current_display->radius * (i+.5)) / legit * 2 - current_display->radius + vid.yres/2;
792 
793     transmatrix V = atscreenpos(x, y, current_display->radius/4);
794 
795     poly_outline = OUTLINE_NONE;
796     queuedisk(shiftless(V), vd.cp, true, NULL, i);
797     poly_outline = OUTLINE_DEFAULT;
798     queuestr(int(x-rad), int(y), 0, rad*(svg::in?5:3)/4, vd.name, legend_color, 0, 16);
799     }
800 
801   for(int i=0; i<qet; i++) {
802     auto t = edgetypes[i];
803 
804     ld y = (current_display->radius * (i+isize(legend)+.5)) / legit * 2 - current_display->radius + vid.yres/2;
805 
806     transmatrix V = atscreenpos(x, y, current_display->radius/8);
807 
808     poly_outline = t->color | 0xFF;
809     queuepolyat(shiftless(V), cgi.shTriangle, 0, PPR::MONSTER_HEAD);
810 
811     poly_outline = OUTLINE_DEFAULT;
812     queuestr(int(x-rad), int(y), 0, rad*(svg::in?5:3)/4, t->name, legend_color, 0, 16);
813     }
814 
815   quickqueue();
816   return true;
817   }
818 
819 bool rv_ignore_spaces = true;
820 
rv_ignore(char c)821 bool rv_ignore(char c) {
822   if(c == 32 && !rv_ignore_spaces) return true;
823   if(c == 10 || c == 13 || c == 32 || c == 9) return true;
824   return false;
825   }
826 
readcolor(const string & cfname)827 void readcolor(const string& cfname) {
828   FILE *f = fopen(cfname.c_str(), "rt");
829   if(!f) { printf("color file missing\n"); exit(1); }
830   while(true) {
831     string lab = "";
832     while(true) {
833       int c = fgetc(f);
834       if(c == EOF) { fclose(f); return; }
835       else if(c == ',' || c == ';') break;
836       else if(rv_ignore(c)) ;
837       else lab += c;
838       }
839 
840     colorpair x;
841     int c2 = fgetc(f);
842     int known_id = -1;
843 
844     if(callhandlers(false, hooks_readcolor, c2, lab, f)) continue;
845 
846     if(c2 == '#') {
847       while(c2 != 10 && c2 != 13 && c2 != -1) c2 = fgetc(f);
848       continue;
849       }
850     else if(c2 == '=') {
851       string lab2 = "";
852       while(true) {
853         int c = fgetc(f);
854         if(rv_ignore(c) || c == ',' || c == ';' || c == EOF) break;
855         else lab2 += c;
856         }
857       x = vdata[getid(lab2)].cp;
858       }
859     else if(c2 == '@') {
860       legend.push_back(known_id == -1 ? getid(lab) : known_id);
861       continue;
862       }
863     else if(c2 == '/') {
864       char buf[600];
865       int err = fscanf(f, "%500s", buf);
866       if(err > 0)
867         vdata[getid(lab)].info = new string(buf); // replace with std::shared_ptr in C++111
868       continue;
869       }
870     else {
871       ungetc(c2, f);
872       char buf[600];
873       int err = fscanf(f, "%500s", buf);
874       if(err > 0) x = parse(buf);
875       }
876 
877     if(isize(lab) && lab[0] == '*') {
878       lab = lab.substr(1);
879       for(int i=0; i<isize(vdata); i++)
880         if(vdata[i].name.find(lab) != string::npos) {
881           vdata[i].cp = x;
882           }
883       }
884     else if(isize(lab) && lab[0] == '!') {
885       for(int i=0; i<isize(vdata); i++)
886         if(vdata[i].name == lab) {
887           vdata[i].cp = x;
888           }
889       }
890     else {
891       int i = getid(lab);
892       again: vdata[i].cp = x;
893 
894       if(vizflags & RV_COLOR_TREE) {
895         i = vdata[i].data;
896         if(i >= 0) goto again;
897         }
898       }
899     }
900   }
901 
902 void graph_rv_hooks();
903 
init(flagtype _vizflags)904 void init(flagtype _vizflags) {
905 
906   autocheat = true;
907   showstartmenu = false;
908 
909   if(tour::on) {
910     tour::slide_backup(mapeditor::drawplayer);
911     tour::slide_backup(timerghost);
912     }
913 
914 #if !ISWEB
915   mapeditor::drawplayer = false;
916   stop_game();
917   enable_canvas();
918   restart_game(shmup::on ? rg::shmup : rg::nothing);
919 #else
920   stop_game();
921   enable_canvas();
922   restart_game(rg::nothing);
923 #endif
924   autocheat = true;
925   items[itOrbLife] = 0;
926   timerghost = false;
927 
928   gmatrix.clear();
929   calcparam();
930   drawthemap();
931   gmatrix0 = gmatrix;
932 
933   vizflags = _vizflags;
934 
935   graph_rv_hooks();
936   }
937 
938 int search_for = -1;
939 
940 vector<reaction_t> cleanup;
941 
do_cleanup()942 void do_cleanup() {
943   while(!cleanup.empty()) {
944     cleanup.back()();
945     cleanup.pop_back();
946     }
947   }
948 
close()949 void close() {
950   search_for = -1;
951   for(int i=0; i<isize(vdata); i++)
952     if(vdata[i].m) vdata[i].m->dead = true;
953   vdata.clear();
954   labeler.clear();
955   legend.clear();
956   for(int i=0; i<isize(edgeinfos); i++) delete edgeinfos[i];
957   edgeinfos.clear();
958   anygraph::coords.clear();
959   callhooks(hooks_close);
960   edgetypes.clear();
961   do_cleanup();
962   relmatrices.clear();
963   }
964 
965 #if CAP_COMMANDLINE
readArgs()966 int readArgs() {
967   using namespace arg;
968 
969 // options before reading
970   if(0) ;
971   else if(argis("-dftcolor")) {
972     shift(); dftcolor = parse(args());
973     }
974 
975 // graph visualizer
976 //------------------
977 
978 // this visualizes the data from: https://hpi.de/friedrich/research/hyperbolic
979 
980   else if(argis("-graph")) {
981     PHASE(3); shift(); anygraph::read(args());
982     }
983 
984 // graphical parameters
985 //------------------
986 
987   // read the color/legend file
988   else if(argis("-color")) {
989     PHASE(3); shift(); readcolor(args());
990     }
991   else if(argis("-lab")) {
992     showlabels = true;
993     }
994   else if(argis("-lab-off")) {
995     showlabels = false;
996     }
997   else if(argis("-rvspaces")) {
998     rv_ignore_spaces = false;
999     }
1000   else if(argis("-rvlabelshift")) {
1001     shift_arg_formula(labelshift);
1002     }
1003   else if(argis("-rvlabelscale")) {
1004     shift_arg_formula(labelscale);
1005     }
1006   else if(argis("-rog3")) {
1007     rog3 = true;
1008     }
1009   else if(argis("-rvedge")) {
1010     shift(); default_edgetype.color = default_edgetype.color_hi = arghex();
1011     }
1012   else if(argis("-rvedgehi")) {
1013     shift(); default_edgetype.color_hi = arghex();
1014     }
1015   else if(argis("-rvfat")) {
1016     shift();
1017     fat_edges = argf();
1018     }
1019   else if(argis("-lcol")) {
1020     shift();
1021     chosen_legend_color = arghex();
1022     }
1023   else if(argis("-ggamma")) {
1024     shift(); ggamma = argf();
1025     }
1026   else if(argis("-rvwarp")) {
1027     patterns::whichShape = '8';
1028     }
1029   else if(argis("-lq")) {
1030     shift_arg_formula(linequality);
1031     }
1032   else if(argis("-nolegend")) {
1033     legend.clear();
1034     }
1035   else if(argis("-edgelegend")) {
1036     edge_legend = true;
1037     }
1038   else if(argis("-rvshape")) {
1039     shift(); vertex_shape = argi() & 3;
1040     }
1041 
1042 // graphical output
1043 //------------------
1044 
1045   // shmup::turn might be necessary when saving screenshots
1046   else if(argis("-TURN")) {
1047     PHASE(3); shmup::turn(100);
1048     }
1049   else return 1;
1050   return 0;
1051   }
1052 #endif
1053 
configure_edge_display()1054 void configure_edge_display() {
1055   cmode = sm::SIDE | sm::MAYDARK | sm::DIALOG_STRICT_X;
1056   static int mode = 0;
1057   gamescreen(0);
1058   dialog::init(XLAT("rogueviz edges"));
1059   for(int i=0; i<isize(edgetypes); i++) {
1060     auto t = edgetypes[i];
1061     switch(mode) {
1062       case 0:
1063         dialog::addSelItem(t->name, itsh(t->color), 'a' + i);
1064         dialog::lastItem().colorv = t->color >> 8;
1065         dialog::add_action([t] {
1066           dialog::openColorDialog(t->color, NULL);
1067           dialog::dialogflags |= sm::MAYDARK | sm::SIDE;
1068           });
1069         break;
1070       case 1:
1071         if(!(vizflags & RV_INVERSE_WEIGHT)) {
1072           dialog::addSelItem(t->name, fts(t->visible_from), 'a'+i);
1073           dialog::add_action([t] {
1074             dialog::editNumber(t->visible_from, 0.001, 1000, .1, .1, "min weight", "");
1075             dialog::scaleLog();
1076             });
1077           }
1078         else {
1079           dialog::addSelItem(t->name, its(1 / t->visible_from), 'a'+i);
1080           dialog::add_action([t] {
1081             static int i;
1082             i = 1 / t->visible_from;
1083             dialog::editNumber(i, 1, 1000000, 1, 500, weight_label, "");
1084             dialog::reaction = [t] () { t->visible_from = i ? 1. / i : 5; };
1085             dialog::scaleLog(); dialog::ne.step = .2;
1086             });
1087           }
1088         break;
1089       default: break;
1090       }
1091     }
1092   dialog::addBreak(100);
1093   if(vizflags & RV_HAVE_WEIGHT) {
1094     dialog::addBoolItem_choice("color/alpha", mode, 0, '1');
1095     dialog::addBoolItem_choice(weight_label, mode, 1, '2');
1096     }
1097   else mode = 0;
1098 
1099   dialog::addBreak(50);
1100   dialog::addBack();
1101   dialog::display();
1102   }
1103 
search_marker()1104 void search_marker() {
1105   if(search_for >= 0 && search_for < isize(vdata)) {
1106     auto& vd = vdata[search_for];
1107     auto& m = vd.m;
1108     if(!m) return;
1109     shiftpoint H = ggmatrix(m->base) * tC0(m->at);
1110     queuestr(H, 2*vid.fsize, "X", 0x10101 * int(128 + 100 * sin(ticks / 150.)));
1111     addauraspecial(H, iinf[itOrbYendor].color, 0);
1112     }
1113   }
1114 
showVertexSearch()1115 void showVertexSearch() {
1116   cmode = sm::SIDE | sm::MAYDARK | sm::DIALOG_STRICT_X;
1117   gamescreen(0); search_for = -1;
1118 
1119   dialog::init(XLAT("vertex search"));
1120   dialog::v.clear();
1121   if(dialog::infix != "") mouseovers = dialog::infix;
1122 
1123   for(int i=0; i<isize(vdata); i++) if(vdata[i].name != "") dialog::vpush(i, vdata[i].name.c_str());
1124 
1125   for(int i=0; i<9; i++) {
1126     if(i < isize(dialog::v)) {
1127       int id = dialog::v[i].second;
1128       dialog::addItem(dialog::v[i].first, '1'+i);
1129       dialog::add_action([id] () {
1130         search_for = id;
1131         popScreenAll();
1132         });
1133       }
1134     else dialog::addBreak(100);
1135     }
1136 
1137   dialog::addSelItem("matching items", its(isize(dialog::v)), 0);
1138   dialog::display();
1139 
1140   keyhandler = [] (int sym, int uni) {
1141     dialog::handleNavigation(sym, uni);
1142     if(dialog::editInfix(uni)) ;
1143     else if(doexiton(sym, uni)) popScreen();
1144     };
1145 
1146   }
1147 
showMenu()1148 void showMenu() {
1149   if(callhandlers(false, hooks_rvmenu_replace)) return;
1150   cmode = sm::SIDE | sm::MAYDARK | sm::DIALOG_STRICT_X;
1151   gamescreen(0);
1152 
1153   dialog::init(XLAT("rogueviz configuration"));
1154 
1155   dialog::addBoolItem_action(XLAT("show labels"), showlabels, 'l');
1156   dialog::addBoolItem_action(XLAT("mark special vertices"), specialmark, 'x');
1157   dialog::addSelItem(XLAT("background color"), itsh(backcolor), 'b');
1158   dialog::add_action_push([] {backcolor ^= 0xFFFFFF, bordcolor ^= 0xFFFFFF, forecolor ^= 0xFFFFFF; });
1159   if(isize(edgetypes)) {
1160     dialog::addSelItem(XLAT("edge types"), its(isize(edgetypes)), 'g');
1161     dialog::add_action_push(configure_edge_display);
1162     }
1163   dialog::addBoolItem_action(XLAT("vertices in 3D"), rog3, 'v');
1164   dialog::addSelItem(XLAT("vertex shape"), its(vertex_shape), 'w');
1165   dialog::add_action([] {
1166     vertex_shape = (1 + vertex_shape) & 3;
1167     });
1168 
1169   dialog::add_key_action('z', [] {
1170     for(int i=0; i<isize(named)-1; i++) if(named[i] == cwt.at)
1171       swap(named[i], named[i+1]);
1172     if(!isize(named) || named[isize(named)-1] != cwt.at) named.push_back(cwt.at);
1173     printf("named = %d\n", isize(named));
1174     popScreen();
1175     });
1176 
1177   dialog::addItem(XLAT("vertex search"), '/');
1178   dialog::add_action_push(showVertexSearch);
1179 
1180   dialog::addBreak(50);
1181 
1182   callhooks(hooks_rvmenu);
1183 
1184   dialog::addBreak(50);
1185   dialog::addBack();
1186 
1187   dialog::display();
1188   }
1189 
default_help()1190 bool default_help() {
1191 
1192   help =
1193     "This is RogueViz, a visualization engine based on HyperRogue.\n\nUse WASD to move, v for menu.\n\n"
1194     "Read more about RogueViz on : http://roguetemple.com/z/hyper/rogueviz.php\n\n";
1195 
1196   help_extensions.push_back(help_extension{'u', XLAT("RogueViz menu"), [] () { popScreen(); pushScreen(showMenu); }});
1197   return true;
1198   }
1199 
o_key(o_funcs & v)1200 void o_key(o_funcs& v) {
1201   v.push_back(named_dialog(XLAT("RogueViz graph viz settings"), rogueviz::showMenu));
1202   }
1203 
1204 auto hooks  =
1205 #if CAP_COMMANDLINE
1206   addHook(hooks_args, 100, readArgs) +
1207 #endif
1208   addHook(hooks_clearmemory, 0, close) +
1209 
1210   addHook(hooks_markers, 100, search_marker) +
__anond095d0621a02null1211   addHook(hooks_configfile, 100, [] {
1212     param_i(brm_limit, "brm_limit");
1213     }) +
1214  0;
1215 
graph_rv_hooks()1216 void graph_rv_hooks() {
1217   rv_hook(hooks_default_help, 100, default_help);
1218   rv_hook(hooks_o_key, 100, o_key);
1219   rv_hook(hooks_prestats, 100, rogueviz_hud);
1220   rv_hook(shmup::hooks_draw, 100, drawVertex);
1221   rv_hook(shmup::hooks_describe, 100, describe_monster);
1222   rv_hook(shmup::hooks_kill, 100, activate);
1223   rv_hook(hooks_welcome_message, 100, [] () {
1224     addMessage(XLAT("Welcome to RogueViz!"));
1225     return true;
1226     });
1227   }
1228 
1229 }
1230