1 #ifdef NOTKNOT
2 #include "../hyper.cpp"
3 #endif
4 
5 #include "rogueviz.h"
6 
7 #if CAP_RAY
8 
9 /**
10 
11 * mg 2 in old video
12 
13 Blocky Knot Portal:
14 
15 compile with: mymake rogueviz/notknot
16 
17 the video has been created with the following options:
18 
19 older Euclidean
20 
21 https://youtu.be/1TMY2U4_9Qg
22 nk_margin=2 -noplayer -canvas-random 20 -geo notknot -sight3 0.5 -ray-cells 600000 smooth_scrolling=1 camspd=10 panini_alpha=1 fov=150 -shot-hd ray_exp_decay_poly=30 ray_fixed_map=1
23 
24 better Euclidean
25 
26 https://youtu.be/eb2DhCcGH7U
27 nk_margin=4 -noplayer -canvas-random 20 -geo notknot -sight3 0.5 -ray-cells 600000 smooth_scrolling=1 camspd=10 panini_alpha=1 fov=150 -shot-hd ray_exp_decay_poly=30 ray_fixed_map=1 -ray-iter 100 ray_reflect_val=0.30
28 
29 self-hiding Euclidean
30 
31 https://youtu.be/vFLZ2NGtuGw
32 selfhide=1 nk_loop=4 nk_margin=4 -noplayer -canvas-random 20 -geo notknot -sight3 0.5 -ray-cells 600000 smooth_scrolling=1 camspd=10 panini_alpha=1 fov=150 -shot-hd ray_exp_decay_poly=30 ray_fixed_map=1 -ray-iter 100 ray_reflect_val=0.30
33 
34 penrose staircase in Nil
35 
36 -noplayer nk_loop=6 nk_secondary=0 nk_terminate=9999999 -geo Nil -nilperiod 8 8 8 -nilwidth .25 -canvas-random 20 -nkbase -geo notknot ray_fixed_map=1 -ray-cells 600000 -sight3 1 -ray-range 1 3 -ray-random 1 -shot-1000
37 
38 self-hiding knotted portal in S3
39 
40 selfhide=1 -run nk_secondary=0 nk_terminate=9999999 nk_loop=4 -canvas-random 10 -load spherknot.lev -nkbase -nkbasemap spherknot.lev -PM nativ -fov 270 panini_alpha=1 -nk-volumetric -noplayer ray_fixed_map=1 -ray-cells 600000 -ray-iter 600
41 
42 basic portal in S3:
43 
44 nk_secondary=0 nk_terminate=99999 nk_loop=0 -nk-unloop 60 7 -canvas-random 10 -load spherring.lev -nkbase -nkbasemap spherring.lev -PM nativ -fov 270 panini_alpha=1 -nk-volumetric -noplayer
45 
46 
47 
48 The algorithm here is as follows:
49 
50 * create the map without portals (this is just the cube with the trifoil knot in it)
51 
52   This is done with a program (function create_trifoil_knot).
53   The general algorithm should work with any map, and it would be possible to make it so that the user can create the map using HyperRogue map editor.
54 
55 * the universal cover can be seen as the set of all paths, where two paths are identified if they are homotopic. So we generate all paths
56   from the chosen starting point; when we generate extensions from path ...A, we check if we have a situation where we could go ...A->B->D or ...A->C->D --
57   if so, the two paths are homotopic, so we identify them (that is, all the references to one of the paths are replaced with the references to the other path,
58   possibly propagating to all known extensions of these paths)
59 
60 * since the universal cover of the trifoil knot complement is infinite, we also identify two paths if they differ by three loops around the knot (around any point of the knot).
61   (If we did not do that, the algorithm would never terminate, or we could terminate it at some point (terminate_at), but then we easily run into ugly unfinished areas.)
62 
63 * this result in a relatively standard HyperRogue map representation (cubes connected via glued faces) which is rendered using HyperRogue's raycaster.
64 
65 */
66 
67 namespace hr {
68 
69 namespace notknot {
70 
71 void create_notknot();
72 
73 /* how many times we need to loop around the portal frame to get back to the same space */
74 /* the number of the worlds is: 1 (loop=1), 6 (loop=2), 24, 96, 600, infinity (loop>5) */
75 int loop = 3;
76 
77 /* any loop repeated loop_any times */
78 int loop_any = 0;
79 
80 /* extra space around the knot */
81 int margin = 4;
82 
83 /* the scale factor for the knot */
84 int knotsize = 3;
85 
86 int secondary_percentage = 10;
87 
88 int terminate_at = 500000000;
89 
90 string base_map = "";
91 
92 /* make a self-hiding knot */
93 bool self_hiding = false;
94 
95 eGeometry gNotKnot(eGeometry(-1));
96 
97 /** It was easier to generate a program to design the trifoil knot than to generate it manually.
98  *  This function does generate the instructions for trifoil knot, although they have been adjusted manually
99  *  The generated instructions is the weird array in create_trifoil_knot()
100  */
101 
102 vector<vector<int> > to_unloop;
103 
gen_trifoil()104 void gen_trifoil() {
105   for(int len=2; len<=12; len+=2) {
106     println(hlog, "trying len = ", len);
107     int pos = 1;
108     for(int l=0; l<len; l++) pos *= 6;
109 
110     for(int p=0; p<pos; p++) {
111 
112       vector<int> lst;
113       int a = p;
114       int bal = 0;
115       for(int p=0; p<len; p++) {
116         if(a%6 < 3) bal++; else bal--;
117         lst.push_back(a%6);
118         a /= 6;
119         }
120       if(bal) continue;
121 
122       array<int, 3> start = {0, 0, 0};
123 
124       array<int, 3> where = start;
125 
126       set<array<int, 3> > ful;
127       map<array<int, 2>, int> proj;
128 
129       int steps = 0;
130       vector<pair<int, int> > crosses;
131 
132       for(int i=0; i<3; i++) {
133         for(auto d: lst) {
134           if(ful.count(where)) goto next;
135           ful.insert(where);
136           auto pco = array<int,2>{{where[0]-where[2], where[1]-where[2]}};
137           if(proj.count(pco)) crosses.emplace_back(proj[pco], steps);
138           else proj[pco] = steps;
139           where[(d+i)%3] += (d<3?1:-1);
140           steps++;
141           }
142         }
143 
144       if(where != start) { println(hlog, "bad loop"); continue; }
145       if(isize(ful) != 3*len) continue;
146 
147       if(isize(proj) != 3*len-3) continue;
148 
149       println(hlog, "len=", len, " ful=", isize(ful), " proj=", isize(proj), " for ", lst);
150 
151       println(hlog, "crosses = ", crosses);
152 
153       if(1) {
154         set<int> crosval;
155         for(auto c: crosses) crosval.insert(c.first), crosval.insert(c.second);
156         vector<int> cvs;
157         for(auto s: crosval) cvs.push_back(s);
158 
159         bool wrong = false;
160         for(auto c: crosses) if(c.first == cvs[0] && c.second == cvs[1]) wrong = true;
161         for(auto c: crosses) if(c.first == cvs[1] && c.second == cvs[2]) wrong = true;
162 
163         if(wrong) continue;
164         }
165 
166       println(hlog, "result: ", lst);
167 
168       exit(3);
169 
170       next: ;
171       }
172     }
173 
174   exit(2);
175   }
176 
177 eGeometry base = gCubeTiling;
178 
179 const int arrsize = 12;
180 
181 struct hrmap_notknot : hrmap {
182 
183   /* represents a path (may be partially identified) */
184   struct ucover {
185     /* the heptagon of the underlying map */
186     heptagon *where;
187     /* the heptagon of the result map */
188     heptagon *result;
189     /* what is has been merged into */
190     ucover *merged_into;
191     /* connections in every direction */
192     ucover *ptr[arrsize];
193     /* used for painting walls in a single color */
194     ucover *wall_merge;
195     color_t wallcolor, wallcolor2;
196     /* 0 = live, 1 = wall, 2 = merged, 4 = overflow, 8 = to hide */
197     char state;
198     /* direction to the parent */
199     char parentdir;
200     /* index in the table `all` */
201     int index;
ucoverhr::notknot::hrmap_notknot::ucover202     ucover(heptagon *w, int s) { where = w; for(int i=0; i<arrsize; i++) ptr[i] = nullptr; state = s; merged_into = nullptr; result = nullptr; wall_merge = this; }
iswallhr::notknot::hrmap_notknot::ucover203     bool iswall() { return state & 1; }
nowallhr::notknot::hrmap_notknot::ucover204     bool nowall() { return !iswall(); }
ismergedhr::notknot::hrmap_notknot::ucover205     bool ismerged() { return state & 2; }
isoverhr::notknot::hrmap_notknot::ucover206     bool isover() { return state & 4; }
tohidehr::notknot::hrmap_notknot::ucover207     bool tohide() { return state & 8; }
208     };
209 
210   /* find-union algorithm for wall_merge */
ufindhr::notknot::hrmap_notknot211   ucover *ufind(ucover *at) {
212     if(at->wall_merge == at) return at;
213     return at->wall_merge = ufind(at->wall_merge);
214     }
215 
funionhr::notknot::hrmap_notknot216   void funion(ucover *a, ucover *b) {
217     ufind(b)->wall_merge = ufind(a);
218     }
219 
220   /* the vector of all paths */
221   vector<ucover*> all;
222 
223   /* the stack of known unifications */
224   vector<pair<ucover*, ucover*>> unify;
225 
226   /* the underlying map */
227   hrmap *euc;
228 
getOriginhr::notknot::hrmap_notknot229   heptagon *getOrigin() override {
230     // return hepts[0];
231     return all[0]->result;
232     }
233 
athr::notknot::hrmap_notknot234   heptagon* at(int x, int y, int z) {
235     dynamicval<eGeometry> g(geometry, base);
236     dynamicval<hrmap*> m(currentmap, euc);
237     euc::coord co = euc::basic_canonicalize({x, y, z});
238 
239     return euc::get_at(co);
240     }
241 
242   /* make sure that where->move(d) and where->c.spin(d) are known */
cmovhr::notknot::hrmap_notknot243   void cmov(heptagon *where, int d) {
244     if(where->move(d)) return;
245     dynamicval<eGeometry> g(geometry, base);
246     dynamicval<hrmap*> m(currentmap, euc);
247     createStep(where, d);
248     }
249 
create_trifoil_knothr::notknot::hrmap_notknot250   heptagon *create_trifoil_knot() {
251     euc::coord cmin{99,99,99}, cmax{-99,-99,-99}, cat{0,0,0};
252     heptagon *h = at(0, 0, 0);
253 
254     vector<heptagon*> trifoil;
255 
256     int step = knotsize;
257 
258     for(int i=0; i<3; i++) {
259       for(auto m: {3, 3, 3, 3, 5, 5, 1, 0, 0, 0, 0, 0}) for(int rep=0; rep<step; rep++) {
260         trifoil.push_back(h);
261         int d = (i+m)%3 + (m<3?0:3);
262         cmov(h, d);
263         h = h->move(d);
264         if(m<3) cat[(i+m)%3]++; else cat[(i+m)%3]--;
265         for(int k=0; k<3; k++) cmin[k] = min(cmin[k], cat[k]), cmax[k] = max(cmax[k], cat[k]);
266         }
267       }
268 
269     int& mg = margin;
270 
271     for(int i=cmin[0]-mg; i<=cmax[0]+mg; i++)
272     for(int j=cmin[1]-mg; j<=cmax[1]+mg; j++)
273     for(int k=cmin[2]-mg; k<=cmax[2]+mg; k++)
274       if(among(i,cmin[0]-mg,cmax[0]+mg) || among(j,cmin[1]-mg,cmax[1]+mg) || among(k,cmin[2]-mg,cmax[2]+mg))
275         at(i,j,k)->zebraval = 1;
276       else
277         at(i,j,k)->zebraval = 0;
278 
279     for(auto h: trifoil)
280         h->zebraval = 9;
281 
282     return at(cmax[0], cmax[1], cmax[2]);
283     }
284 
create_nil_knothr::notknot::hrmap_notknot285   heptagon *create_nil_knot() {
286     dynamicval<eGeometry> g(geometry, base);
287     dynamicval<hrmap*> m(currentmap, euc);
288     auto ac = currentmap->allcells();
289     for(cell *c: ac) c->master->zebraval = 0;
290 
291     auto hept = [&] (int x, int y, int z) {
292       x = zgmod(x, nilv::nilperiod[0]);
293       y = zgmod(y, nilv::nilperiod[1]);
294       z = zgmod(z, nilv::nilperiod[2]);
295       return nilv::get_heptagon_at(nilv::mvec(x,y,z));
296       };
297 
298     hept(-3, -3, -4)->zebraval |= 16;
299 
300     heptagon* h0 = hept(-2, -2, 0);
301     auto h = h0;
302 
303     for(int d: {4, 3, 1, 0})
304       for(int i=0; i<4; i++) {
305         h->zebraval |= 1;
306         h = h->cmove(d);
307         h->zebraval |= 1;
308         h = h->cmove(5);
309         }
310 
311     if(h != h0) { println(hlog, "not looped"); exit(1); }
312 
313     hept(0, 0, 2)->zebraval |= 1;
314     hept(1, 0, 2)->zebraval |= 1;
315     hept(-1, 0, 2)->zebraval |= 1;
316 
317     for(int z=0; z<8; z++)
318     for(int x=-3; x<4; x++)
319     for(int y=-3; y<4; y++)
320       if(!(x>=-1 && x<=1 && y>=-1 && y<=1))
321         if(!(hept(x,y,z)->zebraval & 1))
322         hept(x, y, z)->zebraval |= 32;
323 
324     return ac[0]->master;
325     }
326 
327   #if CAP_SOLV
create_solv_knothr::notknot::hrmap_notknot328   heptagon *create_solv_knot() {
329     dynamicval<eGeometry> g(geometry, base);
330     dynamicval<hrmap*> m(currentmap, euc);
331     auto ac = currentmap->allcells();
332     for(cell *c: ac) c->master->zebraval = 0;
333 
334     auto hept = [&] (int x, int y, int z) {
335       asonov::coord co(x, y, z);
336       return asonov::get_at(co);
337       };
338 
339     auto h = hept(0, 0, 0);
340 
341     h->zebraval |= 16;
342 
343     int top = asonov::period_xy;
344     top += 2 - margin;
345 
346     for(int x=1; x<top-1; x++)
347     for(int y=1; y<top-1; y++)
348       if(x==1 || y==1 || x==top-2 || y==top-2)
349         hept(x, y, 0)->zebraval |= 9;
350 
351     hept(0, 0, (asonov::period_z+1)/2)->c7->wall = waFloorA;
352 
353     hept(2, 2, 0)->zebraval |= 128;
354 
355     // if(top > 4) hept(3, 3, 1)->zebraval |= 9;
356 
357     return ac[0]->master;
358     }
359   #endif
360 
interpret_basemaphr::notknot::hrmap_notknot361   heptagon *interpret_basemap() {
362     dynamicval<eGeometry> g(geometry, base);
363     dynamicval<hrmap*> m(currentmap, euc);
364     auto ac = currentmap->allcells();
365 
366     for(cell *c: ac) {
367       auto& m = c->master->zebraval;
368       m = 0;
369       if(c->wall == waPlatform)
370         m |= 9;
371       }
372 
373     return ac[0]->master;
374     }
375 
create_underhr::notknot::hrmap_notknot376   heptagon *create_under() {
377     if(base_map != "")
378       return interpret_basemap();
379     if(base == gCubeTiling)
380       return create_trifoil_knot();
381     else if(base == gNil)
382       return create_nil_knot();
383     #if CAP_SOLV
384     else if(base == gArnoldCat)
385       return create_solv_knot();
386     #endif
387     throw hr_exception();
388     }
389 
390   bool remove_marked_walls;
391 
gen_adjhr::notknot::hrmap_notknot392   ucover *gen_adj(ucover *u, int d) {
393     if(u->ptr[d]) return u->ptr[d];
394     cmov(u->where, d);
395     auto x = u->where->move(d);
396     auto d1 = u->where->c.spin(d);
397     auto z = x->zebraval;
398     if(z & 6) {
399       throw hr_exception("zebraval failure!");
400       exit(3);
401       x->zebraval = 0;
402       }
403     if(remove_marked_walls && (z & 8))
404       z &=~ 9;
405     u->ptr[d] = new ucover(x, z & 15);
406     u->ptr[d]->ptr[d1] = u;
407     u->ptr[d]->index = isize(all);
408     u->ptr[d]->parentdir = d1;
409     all.push_back(u->ptr[d]);
410     return u->ptr[d];
411     };
412 
add_to_unifyhr::notknot::hrmap_notknot413   void add_to_unify(ucover *a, ucover *b) {
414     if(a->where != b->where)
415       throw hr_exception("unification error");
416     unify.emplace_back(a, b);
417     };
418 
419   map<heptagon*, vector<vector<int> > > uloops;
420 
collapse_loophr::notknot::hrmap_notknot421   bool collapse_loop(ucover* u, int loopcount, const vector<int>& looplist) {
422     auto ux = u;
423     for(int iter=0; iter<loopcount; iter++)
424       for(int w: looplist) {
425         ux = gen_adj(ux, w);
426         if(ux->iswall()) return false;
427         }
428     add_to_unify(u, ux);
429     return true;
430     };
431 
verify_loophr::notknot::hrmap_notknot432   bool verify_loop(ucover* u, int loopcount, const vector<int>& looplist) {
433     auto ux = u;
434     for(int iter=0; iter<loopcount; iter++)
435       for(int w: looplist) {
436         ux = gen_adj(ux, w);
437         if(ux->iswall()) return false;
438         }
439     return true;
440     }
441 
record_loophr::notknot::hrmap_notknot442   void record_loop(ucover* u, int loopcount, const vector<int>& looplist) {
443     vector<int> repeated;
444     for(int l=0; l<loopcount; l++)
445       for(auto v: looplist) repeated.push_back(v);
446     uloops[u->where].push_back(repeated);
447     };
448 
record_loop_if_nowallhr::notknot::hrmap_notknot449   void record_loop_if_nowall(ucover* u, int loopcount, const vector<int>& looplist) {
450     if(verify_loop(u, loopcount, looplist))
451       record_loop(u, loopcount, looplist);
452     }
453 
record_loop_verifyhr::notknot::hrmap_notknot454   void record_loop_verify(ucover* u, int loopcount, const vector<int>& looplist, const hr_exception& ex) {
455     if(!verify_loop(u, loopcount, looplist)) throw ex;
456     record_loop(u, loopcount, looplist);
457     }
458 
adjhr::notknot::hrmap_notknot459   transmatrix adj(ucover *u, int k) {
460     dynamicval<eGeometry> g(geometry, base);
461     dynamicval<hrmap*> m(currentmap, euc);
462     return currentmap->adj(u->where, k);
463     }
464 
adjacent_matrixhr::notknot::hrmap_notknot465   bool adjacent_matrix(const transmatrix& Tk, const transmatrix& Tl) {
466     for(auto vk: cgi.heptshape->vertices_only)
467     for(auto vl: cgi.heptshape->vertices_only)
468       if(hdist(Tk * vk, Tl * vl) < .01)
469         return true;
470 
471     return false;
472     }
473 
adjacent_facehr::notknot::hrmap_notknot474   bool adjacent_face(ucover *u, int k, int l) {
475     if(base == gCubeTiling) return abs(k-l) != 3;
476     if(base == gNil) return abs(k-l) != 3;
477     return adjacent_matrix(adj(u, k), adj(u, l));
478     }
479 
unify_homotopieshr::notknot::hrmap_notknot480   void unify_homotopies(ucover *u) {
481     /* unify homotopies */
482     if(base == gNil) {
483       collapse_loop(u, 1, {0, 2, 3, 5});
484       collapse_loop(u, 1, {1, 2, 4, 5});
485       collapse_loop(u, 1, {0, 1, 3, 4, 2});
486       return;
487       }
488     if(base == gCubeTiling) {
489       collapse_loop(u, 1, {0, 1, 3, 4});
490       collapse_loop(u, 1, {0, 2, 3, 5});
491       collapse_loop(u, 1, {1, 2, 4, 5});
492       return;
493       }
494 
495     for(auto& v: cgi.heptshape->vertices_only) {
496       map<heptagon*, ucover*> visited;
497       vector<pair<ucover *, transmatrix>> q;
498 
499       auto visit = [&] (ucover *u, const transmatrix& T) {
500         if(visited.count(u->where)) {
501           add_to_unify(u, visited[u->where]);
502           return;
503           }
504         visited[u->where] = u;
505         q.emplace_back(u, T);
506         };
507 
508       hyperpoint h = v;
509       visit(u, Id);
510       for(int i=0; i<isize(q); i++) {
511         auto u1 = q[i].first;
512         transmatrix T0 = q[i].second;
513         for(int i=0; i<u1->where->type; i++) {
514           auto u2 = gen_adj(u1, i);
515           if(u2->state != 0) continue;
516           auto T1 = T0 * adj(u1, i);
517           bool adjacent = false;
518           for(auto& v2: cgi.heptshape->vertices_only)
519             if(hdist(T1 * v2, h) < 1e-5)
520               adjacent = true;
521           if(adjacent)
522             visit(u2, T1);
523           }
524         }
525       }
526     }
527 
unify_loops_generalhr::notknot::hrmap_notknot528   void unify_loops_general(ucover *u) {
529     int t = u->where->type;
530     for(int i=0; i<t; i++) {
531       auto u1 = gen_adj(u, i);
532       if(u1->nowall()) continue;
533       if(u1->where->zebraval != 9) continue;
534       transmatrix M = adj(u, i);
535 
536       map<heptagon*, int> camefrom;
537       vector<pair<ucover*, transmatrix>> visited;
538 
539       auto visit = [&] (ucover *from, ucover *at, int ldir, const transmatrix& T) {
540         // println(hlog, from ? from->where : (heptagon*)nullptr, " -> ", at->where, " (", i, ")", " (reverse ", at->where->c.spin(i), ")");
541         if(camefrom.count(at->where)) {
542           vector<int> path;
543           vector<int> rpath;
544 
545           while(at->where != u->where) {
546             int d = camefrom[at->where];
547             // println(hlog, "from ", at->where, " going back ", d);
548             rpath.push_back(at->where->c.spin(d));
549             at = gen_adj(at, d);
550             }
551           while(!rpath.empty()) { path.push_back(rpath.back()); rpath.pop_back(); }
552           path.push_back(ldir);
553 
554           int st = 0;
555 
556           while(from->where != u->where) {
557             int d = camefrom[from->where];
558             // println(hlog, "from ", from->where, " going ", d);
559             st++; if(st == 10) exit(1);
560             path.push_back(d);
561             from = gen_adj(from, d);
562             }
563 
564           if(false) {
565             println(hlog, "path = ", path);
566             }
567 
568           record_loop_verify(u, loop, path, hr_exception("wall in loops_general"));
569           }
570         else {
571           camefrom[at->where] = ldir;
572           visited.emplace_back(at, T);
573           }
574         };
575 
576       visit(nullptr, u, -1, Id);
577       for(int vi=0; vi<isize(visited); vi++) {
578         auto at = visited[vi].first;
579         auto Ti = visited[vi].second;
580 
581         for(int j=0; j<at->where->type; j++) {
582           if(j == camefrom[at->where]) continue;
583           auto u2 = gen_adj(at, j);
584           if(u2->iswall()) continue;
585           transmatrix Tj = Ti * adj(at, j);
586           bool adj = false;
587           for(auto v: cgi.heptshape->vertices_only) for(auto v1: cgi.heptshape->vertices_only)
588             if(hdist(M * v1, Tj * v) < 1e-3) adj = true;
589 
590           if(adj) visit(at, u2, at->where->c.spin(j), Tj);
591           }
592         }
593       }
594     }
595 
unify_loopshr::notknot::hrmap_notknot596   void unify_loops(ucover *u) {
597     /* try to make it finite */
598     bool special = false;
599 
600     if(base_map == "" && base == gNil) {
601       special = true;
602       record_loop_if_nowall(u, loop, {0, 0, 2, 2, 2, 3, 3, 5, 5, 5});
603 
604       if(u->where->zebraval & 16) {
605         for(int dir: {0,1,2})
606           record_loop_verify(u, nilv::nilperiod[dir], {dir}, hr_exception("16 failed"));
607         }
608       }
609 
610     #if CAP_SOLV
611     if(base_map == "" && base == gArnoldCat) {
612 
613       if(u->where->zebraval & 16) {
614         for(int dir: {0,4,5}) {
615           int steps = dir ? asonov::period_xy : asonov::period_z;
616           if(dir) steps *= 2;
617           record_loop_verify(u, steps, {dir}, hr_exception("16 failed"));
618           }
619         }
620 
621       if(u->where->zebraval & 128) {
622         for(int a=0; a<2; a++) {
623           vector<int> myloop;
624           myloop.push_back(0);
625 
626           auto add_shift = [&] (int x, int y, int lev) {
627             if(a) swap(x, y);
628             while(lev>0) lev--, tie(x,y) = make_pair(x*2-y, y-x);
629             while(lev<0) lev++, tie(x,y) = make_pair(x+y, x+2*y);
630             while(x>0) x--, myloop.push_back(4);
631             while(y>0) y--, myloop.push_back(5);
632             while(x<0) x++, myloop.push_back(10);
633             while(y<0) y++, myloop.push_back(11);
634             };
635 
636           auto p = asonov::period_xy;
637 
638           add_shift(p-2, 0, 1);
639           myloop.push_back(6);
640           myloop.push_back(6);
641           add_shift(2, 0, -1);
642           myloop.push_back(0);
643           myloop.push_back(0);
644           add_shift(-2, 0, 1);
645           myloop.push_back(6);
646           myloop.push_back(6);
647           add_shift(2-p, 0, -1);
648           myloop.push_back(0);
649 
650           record_loop_verify(u, 1, myloop, hr_exception("128 failed"));
651           }
652         }
653       }
654     #endif
655 
656     if(base == gCubeTiling) {
657       special = true;
658       record_loop_if_nowall(u, loop, {0, 0, 1, 1, 3, 3, 4, 4});
659       }
660 
661     if(base == gCell120) {
662       special = true;
663       int t = u->where->type;
664       for(int i=0; i<t; i++)
665       for(int j=0; j<i; j++) {
666         auto u1 = u->ptr[i];
667         auto u2 = u->ptr[j];
668         if(u1->nowall()) continue;
669         if(u2->nowall()) continue;
670         auto ucur = u;
671         auto ulast = (ucover*) nullptr;
672 
673         vector<int> myloop;
674 
675         for(int step=0; step<5; step++) {
676           for(int i=0; i<t; i++) {
677             auto ucand = ucur->ptr[i];
678             if(ucand && isNeighbor(ucand->where->c7, u1->where->c7) && isNeighbor(ucand->where->c7, u2->where->c7) && ucand != ulast) {
679               myloop.push_back(i);
680               ulast = ucur, ucur = ucand;
681               goto next_step;
682               }
683             }
684           goto fail;
685           next_step: ;
686           }
687 
688         record_loop(u, loop, myloop);
689         fail: ;
690         }
691       }
692 
693     if(loop && !special)
694       unify_loops_general(u);
695 
696     if(u->where == all[0]->where)
697       for(auto& lo: to_unloop) {
698         record_loop_verify(u, 1, lo, hr_exception("loop-to-unloop goes through a wall"));
699         }
700     }
701 
702   map<heptagon*, int> indices;
703 
hrmap_notknothr::notknot::hrmap_notknot704   hrmap_notknot() {
705 
706     try {
707     if(base_map != "") {
708       dynamicval<eGeometry> dg(geometry, geometry);
709       mapstream::loadMap(base_map);
710       base = geometry;
711       create_notknot();
712       euc = currentmap;
713       for(hrmap*& m: allmaps) if(m == euc) m = NULL;
714       }
715     else {
716       dynamicval<eGeometry> dg(geometry, base);
717       initcells(); euc = currentmap;
718       for(hrmap*& m: allmaps) if(m == euc) m = NULL;
719       }
720 
721     int i = 0;
722 
723     all.emplace_back(new ucover(create_under(), 0));
724     all[0]->index = 0;
725     all[0]->parentdir = -1;
726 
727     if(all[0]->where->zebraval & 1)
728       throw hr_exception("error: starting inside a wall");
729 
730     remove_marked_walls = false;
731     bool first = true;
732 
733     back:
734 
735     while(true) {
736 
737       /* handle all known unifications */
738       if(!unify.empty()) {
739         ucover *uf, *ut;
740         tie(uf, ut) = unify.back();
741         unify.pop_back();
742         while(uf->merged_into) uf = uf->merged_into;
743         while(ut->merged_into) ut = ut->merged_into;
744         if(uf == ut) continue;
745         if(!uf || !ut) println(hlog, "null unified");
746         /* we always keep the one with the lower index */
747         if(uf->index < ut->index) swap(uf, ut);
748 
749         /* if a knot is removed, remove the other copy */
750         if(uf->iswall() && ut->nowall())
751           uf->state &=~ 1;
752 
753         uf->state |= 2; uf->merged_into = ut;
754         if(uf->where != ut->where)
755           throw hr_exception("where confusion");
756         for(int d=0; d<uf->where->type; d++) {
757           cmov(uf->where, d);
758           auto d1 = uf->where->c.spin(d);
759           if(uf->ptr[d]) {
760             if(!ut->ptr[d]) {
761               /* was a known connection in uf, but not in ut -- reconnect it to ut */
762               uf->ptr[d]->ptr[d1] = ut;
763               ut->ptr[d] = uf->ptr[d];
764               uf->ptr[d] = nullptr;
765               }
766             else {
767               /* in some direction, connections for both uf and ut are already known, so unify them too */
768               add_to_unify(uf->ptr[d], ut->ptr[d]);
769               uf->ptr[d]->ptr[d1] = nullptr;
770               uf->ptr[d] = nullptr;
771               }
772             }
773           }
774         continue;
775         }
776 
777       /* handle creation and loops */
778 
779       if(i >= isize(all)) break;
780       auto u = all[i++];
781       if(u->state != 0) continue;
782       if(i > terminate_at) { u->state |= 4; continue; }
783 
784       for(int k=0; k<u->where->type; k++) gen_adj(u, k);
785 
786       unify_homotopies(u);
787 
788       if(!uloops.count(u->where)) {
789         uloops[u->where] = {};
790         unify_loops(u);
791         // println(hlog, "loops recorded for ", u->where, ": ", isize(uloops[u->where]));
792         }
793 
794       for(auto& myloop: uloops[u->where])
795         if(!collapse_loop(u, 1, myloop))
796           throw hr_exception("invalid loop recorded");
797 
798       if(u->where == all[0]->where) {
799         vector<int> pathback;
800         auto uc = u;
801         while(uc->parentdir != -1) {
802           pathback.push_back(uc->parentdir);
803           uc = uc->ptr[(int) uc->parentdir];
804           }
805         // println(hlog, "pathback = ", pathback);
806 
807         if(loop_any) {
808           auto us = all[0];
809           for(int it=1; it<loop_any; it++) {
810             uc = u;
811             while(uc->parentdir != -1) {
812               us = gen_adj(us, uc->parentdir);
813               uc = uc->ptr[(int) uc->parentdir];
814               }
815             }
816           add_to_unify(us, u);
817           }
818         }
819       }
820 
821     /* make the walls single-colored */
822 
823     println(hlog, "single-colored");
824 
825     for(int i=0; i<isize(all); i++) {
826       auto u = all[i];
827       if(u->state != 0) continue;
828 
829       if(u->where->zebraval & 32) {
830         for(int k=0; k<u->where->type; k++) {
831           auto uk = gen_adj(u, k);
832           if(uk->state != 0) continue;
833           if(uk->where->zebraval & 32)
834             funion(u, uk);
835           }
836         }
837 
838       /* convex corners */
839       for(int k=0; k<u->where->type; k++)
840       for(int l=0; l<u->where->type; l++) {
841         auto uk = gen_adj(u, k);
842         if(uk->state != 0) continue;
843         auto ul = gen_adj(u, l);
844         if(ul->state != 0) continue;
845         if(base == gCubeTiling || base == gNil) {
846           auto ukl = gen_adj(uk, l);
847           auto ulk = gen_adj(ul, k);
848           if(ukl->where == ulk->where && ukl->state != 0)
849             funion(ukl, ulk);
850           }
851         else {
852           for(int k1=0; k1<u->where->type; k1++)
853           for(int l1=0; l1<u->where->type; l1++) {
854             auto ukl = gen_adj(uk, l1);
855             auto ulk = gen_adj(ul, k1);
856             if(ukl->where == ulk->where && ukl->state != 0 &&
857               eqmatrix(adj(u, k) * adj(uk, l1), adj(u, l) * adj(ul, k1))
858               )
859               funion(ukl, ulk);
860             if(base == gCell600 && isNeighbor(ukl->where->c7, ulk->where->c7) && ukl->state != 0 && ulk->state != 0)
861               funion(ukl, ulk);
862 
863             if(base == gCell600 && ulk->nowall() && ukl->iswall()) {
864               for(int m1=0; m1<u->where->type; m1++) {
865                 auto ulkm = gen_adj(ulk, m1);
866                 if(ulkm->where == ukl->where) funion(ulkm, ukl);
867                 }
868               }
869             }
870           }
871         }
872 
873       /* flat areas */
874       #if CAP_SOLV
875       if(!asonov::in())
876       #endif
877       for(int k=0; k<u->where->type; k++)
878       for(int l=0; l<u->where->type; l++) {
879         auto uk = gen_adj(u, k);
880         if(uk->state != 0) continue;
881         auto ul = gen_adj(u, l);
882         if(ul->nowall()) continue;
883         auto ukl = gen_adj(uk, l);
884         if(ukl->nowall()) continue;
885         funion(ul, ukl);
886         }
887 
888       /* concave corners */
889       for(int k=0; k<u->where->type; k++)
890       for(int l=0; l<u->where->type; l++)
891       if(adjacent_face(u, k, l)) {
892         auto uk = gen_adj(u, k);
893         if(uk->nowall()) continue;
894         auto ul = gen_adj(u, l);
895         if(ul->nowall()) continue;
896         funion(ul, uk);
897         }
898 
899       if(base == gCell600)
900       for(int k=0; k<u->where->type; k++)
901       for(int l=0; l<u->where->type; l++)
902       if(adjacent_face(u, k, l)) {
903         auto uk = gen_adj(u, k);
904         if(uk->nowall()) continue;
905         auto ul = gen_adj(u, l);
906         if(ul->iswall()) continue;
907 
908         for(int m=0; m<u->where->type; m++)
909           if(adjacent_matrix(adj(u, k), adj(u, l) * adj(ul, m))) {
910           auto um = gen_adj(ul, m);
911           if(um->nowall()) continue;
912           funion(um, uk);
913           }
914         }
915       }
916 
917     /* statistics */
918     int lives = 0, walls = 0, overflow = 0, merged = 0;
919 
920     for(auto v: all) {
921       if(v->state == 0) lives++;
922       if(v->iswall()) walls++;
923       if(v->ismerged()) merged++;
924       if(v->isover()) overflow++;
925       }
926 
927     set<heptagon*> wheres;
928 
929     println(hlog, "lives = ", lives);
930     println(hlog, "walls = ", walls);
931     println(hlog, "merged = ", merged);
932     println(hlog, "overflow = ", overflow);
933     println(hlog, "total = ", isize(all));
934 
935     /* create the result map */
936     for(int i=0; i<isize(all); i++) {
937       auto u = all[i];
938       if(u->ismerged()) continue;
939       if(u->state == 0) wheres.insert(all[i]->where);
940       u->result = tailored_alloc<heptagon> (S7);
941       u->result->c7 = newCell(S7, u->result);
942       indices[u->result] = i;
943       }
944 
945     println(hlog, "wheres = ", isize(wheres), " : ", lives * 1. / isize(wheres));
946 
947     for(int i=0; i<isize(all); i++) {
948       auto u = all[i];
949       if(u->ismerged()) continue;
950 
951       for(int d=0; d<S7; d++) {
952         cmov(u->where, d);
953         auto d1 = u->where->c.spin(d);
954         if(u->ptr[d] && u->ptr[d]->result == nullptr)
955           throw hr_exception(lalign(0, "connection to null in state ", u->ptr[d]->state, " from state ", u->state, " i=", i, " .. ", u->ptr[d]->index));
956         if(u->ptr[d] && u->ptr[d]->ptr[d1] != u)
957           throw hr_exception("wrong connection");
958         if(u->ptr[d])
959           u->result->c.connect(d, u->ptr[d]->result, d1, false);
960         else
961           u->result->c.connect(d, u->result, d, false);
962         }
963       }
964 
965     for(int k=0; k<23; k++) hrand(5);
966 
967     int colors_used = 0;
968 
969     for(int i=0; i<isize(all); i++)
970       all[i]->wallcolor = 0;
971 
972     for(int i=0; i<isize(all); i++)
973       if(all[i]->iswall() && !all[i]->ismerged())
974         ufind(all[i])->wallcolor++;
975 
976     map<int, int> sizes;
977 
978     for(int i=0; i<isize(all); i++)
979       if(all[i]->iswall() && ufind(all[i]) == all[i] && all[i]->wallcolor)
980         colors_used++,
981         sizes[all[i]->wallcolor]++;
982 
983     for(auto p: sizes)
984       println(hlog, "size = ", p.first, " times ", p.second);
985 
986     println(hlog, "colors_used = ", colors_used);
987 
988     if(first && self_hiding) {
989       ucover *what = nullptr;
990       for(int i=0; i<isize(all); i++)
991         if(all[i]->iswall() && all[i]->tohide() && !all[i]->ismerged())
992           what = ufind(all[i]);
993 
994       for(int i=0; i<isize(all); i++)
995         if(all[i]->iswall() && ufind(all[i]) == what)
996           all[i]->state &=~ 9;
997 
998       println(hlog, "removed one knot!");
999 
1000       first = false; i = 0; remove_marked_walls = true;
1001       goto back;
1002       }
1003 
1004     for(int i=0; i<isize(all); i++)
1005       if(all[i]->iswall() && ufind(all[i]) == all[i] && all[i]->wallcolor) {
1006         all[i]->wallcolor = hrand(0x1000000) | 0x404040,
1007         all[i]->wallcolor2 = hrand(0x1000000) | 0x404040;
1008         }
1009 
1010     for(int i=0; i<isize(all); i++)
1011       if((all[i]->where->zebraval & 32) && ufind(all[i]) == all[i]) {
1012         auto& w = all[i]->wallcolor;
1013         all[i]->wallcolor = (hrand(0x1000000) << 8) | 0x01;
1014         switch(hrand(6)) {
1015           case 0: w |= 0xFF000000; break;
1016           case 1: w |= 0x00FF0000; break;
1017           case 2: w |= 0x0000FF00; break;
1018           case 3: w |= 0xC0C00000; break;
1019           case 4: w |= 0x00C0C000; break;
1020           case 5: w |= 0xC000C000; break;
1021           }
1022         }
1023 
1024     for(int i=0; i<isize(all); i++) {
1025       auto u = all[i];
1026       if(!u->result) continue;
1027       cell *c = u->result->c7;
1028       setdist(c, 7, c);
1029       c->land = laCanvas;
1030       if(u->iswall()) {
1031         c->wall = waWaxWall;
1032         c->landparam = hrand(100) < secondary_percentage ? ufind(u)->wallcolor2 : ufind(u)->wallcolor;
1033         if(ufind(u)->nowall()) println(hlog, "connected to state ", ufind(u)->state);
1034         // if(!(c->landparam & 0x404040)) println(hlog, "color found ", c->landparam);
1035         }
1036       else if(u->isover())
1037         c->wall = waBigTree;
1038       else
1039         c->wall = waNone;
1040 
1041       if(all[i]->where->zebraval & 32)
1042         ray::volumetric::vmap[c] = ufind(u)->wallcolor ^ ((hrand(0x1000000) & 0x3F3F3F) << 8);
1043       else
1044         ray::volumetric::vmap[c] = 0x00000001;
1045       }
1046 
1047     } catch(const hr_exception& s) {
1048       println(hlog, "exception: ", s.what());
1049       throw;
1050       }
1051     }
1052 
add_foghr::notknot::hrmap_notknot1053   void add_fog() {
1054     vector<color_t> cols = {0xFF000001, 0xC0C00001, 0x00FF0001, 0x00C0C001, 0x0000FF01, 0xC000C001};
1055     int id = 0;
1056     map<cell*, pair<int, int> > dist;
1057     vector<cell*> lst;
1058 
1059     auto color = [&] (cell *c, color_t col, int d) {
1060       if(!dist.count(c)) dist[c] = {d, 0};
1061       auto& p = dist[c];
1062       if(p.first == d) {
1063         if(!p.second) lst.push_back(c);
1064         p.second++;
1065         auto& vm = ray::volumetric::vmap[c];
1066         if(p.second == 1) vm = col;
1067         else vm = gradient(vm, col, 0, 1, p.second);
1068         }
1069       };
1070 
1071     int qty = 0;
1072     for(int i=0; i<isize(all); i++)
1073       if(all[i]->result)
1074       if(all[i]->where->c7->wall == waFloorA)
1075         qty++;
1076 
1077     for(int i=0; i<isize(all); i++)
1078       if(all[i]->result)
1079       if(all[i]->where->c7->wall == waFloorA) {
1080         cell *c = all[i]->result->c7;
1081         if(dist.count(c)) continue;
1082         int idd = (id++) % isize(cols);
1083         color_t col = rainbow_color(1, idd * 1. / qty);
1084         col <<= 8; col |= 1;
1085 
1086         color(c, cols[idd], 0);
1087         }
1088 
1089     for(int i=0; i<isize(lst); i++) {
1090       auto c = lst[i];
1091       auto col = ray::volumetric::vmap[c];
1092       int d = dist[c].first;
1093       forCellCM(c1, c)
1094         if(c1->wall == waNone)
1095           color(c1, col, d+1);
1096       }
1097 
1098     for(int i=0; i<isize(lst); i++) {
1099       auto c = lst[i];
1100       ray::volumetric::vmap[c] ^= ((hrand(0x1000000) & 0x3F3F3F) << 8);
1101       }
1102 
1103     ray::volumetric::enable();
1104     }
1105 
relative_matrixhhr::notknot::hrmap_notknot1106   transmatrix relative_matrixh(heptagon *h2, heptagon *h1, const hyperpoint& hint) override {
1107     return Id;
1108     }
1109 
adjhr::notknot::hrmap_notknot1110   transmatrix adj(heptagon *h, int i) override {
1111     return adj(all[indices[h]], i);
1112     }
1113 
adjhr::notknot::hrmap_notknot1114   transmatrix adj(cell *c, int i) override {
1115     return adj(c->master, i);
1116     }
1117 
~hrmap_notknothr::notknot::hrmap_notknot1118   ~hrmap_notknot() {
1119     for(auto uc: all) {
1120       if(uc && uc->result) {
1121         tailored_delete(uc->result->c7);
1122         tailored_delete(uc->result);
1123         }
1124       if(uc) delete uc;
1125       }
1126     delete euc;
1127     }
1128 
1129   };
1130 
__anon07a57b340702null1131 auto h = addHook(hooks_newmap, 0, [] {
1132   // gen_trifoil();
1133   if(geometry == gNotKnot) {
1134     return (hrmap*) new hrmap_notknot;
1135     }
1136   return (hrmap*) nullptr;
1137   });
1138 
create_notknot()1139 void create_notknot() {
1140   if(true) {
1141     dynamicval<eGeometry> b(geometry, base);
1142     check_cgi();
1143     cgi.require_basics();
1144     cgi.require_shapes();
1145     cgi.require_usershapes();
1146     }
1147   if(gNotKnot == eGeometry(-1)) {
1148     ginf.push_back(ginf[base]);
1149     gNotKnot = eGeometry(isize(ginf) - 1);
1150     }
1151   else ginf[gNotKnot] = ginf[base];
1152   auto& gi = ginf.back();
1153   gi.flags |= qANYQ | qBOUNDED | qEXPERIMENTAL | qPORTALSPACE;
1154   gi.quotient_name = "notknot";
1155   gi.shortname = "notknot";
1156   gi.menu_displayed_name = "notknot";
1157   }
1158 
regenerate()1159 void regenerate() {
1160   if(geometry == gNotKnot && game_active) {
1161     stop_game();
1162     start_game();
1163     }
1164   }
1165 
1166 bool show_selfhiding = true;
1167 
launch_euc()1168 void launch_euc() {
1169   stop_game();
1170   set_geometry(gCubeTiling);
1171   base = gCubeTiling;
1172   base_map = "";
1173   to_unloop.clear();
1174   create_notknot();
1175   loop = 3;
1176   secondary_percentage = 10;
1177   show_selfhiding = true;
1178   set_geometry(gNotKnot);
1179   start_game();
1180   ray::reset_raycaster();
1181   ray::volumetric::on = false;
1182   ray::exp_decay_poly = 30;
1183   pmodel = mdPerspective;
1184   }
1185 
launch_nil()1186 void launch_nil() {
1187   stop_game();
1188   set_geometry(gNil);
1189   base_map = "";
1190   base = geometry;
1191   to_unloop.clear();
1192   secondary_percentage = 0;
1193   nilv::nilwidth = .25;
1194   nilv::nilperiod = make_array(8, 8, 8);
1195   nilv::set_flags();
1196   create_notknot();
1197   show_selfhiding = false;
1198   loop = 6;
1199   set_geometry(gNotKnot);
1200   start_game();
1201   ray::reset_raycaster();
1202   ray::volumetric::on = true;
1203   ray::exp_decay_poly = 3;
1204   camera_speed = 1;
1205   pmodel = mdGeodesic;
1206   }
1207 
launch_sphere()1208 void launch_sphere() {
1209   stop_game();
1210   set_geometry(gCell120);
1211   base_map = "spherring.lev";
1212   to_unloop.clear();
1213   vector<int> v; for(int i=0; i<60; i++) v.push_back(7);
1214   to_unloop.emplace_back(v);
1215   show_selfhiding = true;
1216   secondary_percentage = 0;
1217   loop = 0;
1218   set_geometry(gNotKnot);
1219   start_game();
1220   ray::reset_raycaster();
1221   ray::volumetric::on = false;
1222   ray::exp_decay_poly = 10;
1223   camera_speed = 1;
1224   mapeditor::drawplayer = false;
1225   pmodel = mdPerspective;
1226   ((hrmap_notknot*)currentmap)->add_fog();
1227   }
1228 
launch_sphereknot()1229 void launch_sphereknot() {
1230   stop_game();
1231   set_geometry(gCell600);
1232   base_map = "spherknot.lev";
1233   to_unloop.clear();
1234   secondary_percentage = 0;
1235   show_selfhiding = true;
1236   loop = 3;
1237   set_geometry(gNotKnot);
1238   start_game();
1239   ray::reset_raycaster();
1240   ray::volumetric::on = false;
1241   ray::exp_decay_poly = 10;
1242   camera_speed = 1;
1243   mapeditor::drawplayer = false;
1244   pmodel = mdPerspective;
1245   ((hrmap_notknot*)currentmap)->add_fog();
1246   }
1247 
1248  #if CAP_SOLV
launch_solv()1249 void launch_solv() {
1250   stop_game();
1251   set_geometry(gArnoldCat);
1252   base_map = "";
1253   base = geometry;
1254   to_unloop.clear();
1255   secondary_percentage = 0;
1256   vid.binary_width = .25;
1257   margin = 3;
1258   asonov::period_xy = 8;
1259   asonov::period_z = 3;
1260   loop = 6;
1261   asonov::set_flags();
1262   create_notknot();
1263   show_selfhiding = false;
1264   set_geometry(gNotKnot);
1265   start_game();
1266   ray::reset_raycaster();
1267   ray::exp_decay_poly = 10;
1268   camera_speed = 1;
1269   pmodel = mdGeodesic;
1270   ((hrmap_notknot*)currentmap)->add_fog();
1271   }
1272   #endif
1273 
show()1274 void show() {
1275   cmode = sm::SIDE | sm::MAYDARK;
1276   gamescreen(0);
1277   dialog::init(XLAT("notknot"), 0xFFFFFFFF, 150, 0);
1278 
1279   dialog::addItem("available scenes", 'a');
1280   dialog::add_action_push([] {
1281     cmode = sm::SIDE | sm::MAYDARK;
1282     gamescreen(0);
1283 
1284     dialog::init(XLAT("notknot scenes"), 0xFFFFFFFF, 150, 0);
1285 
1286     dialog::addItem("knot portal in Euclidean geometry", 'a');
1287     dialog::add_action(launch_euc);
1288 
1289     dialog::addItem("Penrose staircase portal in Nil geometry", 'b');
1290     dialog::add_action(launch_nil);
1291 
1292     dialog::addItem("great circle portal in spherical geometry", 'c');
1293     dialog::add_action(launch_sphere);
1294 
1295     dialog::addItem("knotted portal in spherical geometry", 'd');
1296     dialog::add_action(launch_sphereknot);
1297 
1298     #if CAP_SOLV
1299     dialog::addItem("a portal in Solv geometry", 'e');
1300     dialog::add_action(launch_solv);
1301     #endif
1302 
1303     dialog::display();
1304     });
1305 
1306   if(loop) add_edit(loop);
1307   if(base == gCubeTiling && base_map == "") {
1308     add_edit(margin);
1309     add_edit(knotsize);
1310     }
1311   if(show_selfhiding) add_edit(self_hiding);
1312 
1313   if(nil) menuitem_nilwidth('w');
1314   if(sol) menuitem_binary_width('w');
1315 
1316   if(base != gCubeTiling) {
1317     dialog::addBoolItem("fog enabled", ray::volumetric::on, 'f');
1318     dialog::add_action([] {
1319       ray::volumetric::on = !ray::volumetric::on;
1320       ray::reset_raycaster();
1321       if(sphere && ray::volumetric::on)
1322         ((hrmap_notknot*)currentmap)->add_fog();
1323       });
1324     }
1325 
1326   dialog::addBreak(100);
1327 
1328   dialog::addItem(XLAT("configure raycasting"), 'A');
1329   dialog::add_action_push(ray::configure);
1330 
1331   add_edit_fov('f');
1332 
1333   #if CAP_VR
1334   dialog::addBoolItem(XLAT("VR settings"), vrhr::active(), 'v');
1335   dialog::add_action_push(vrhr::show_vr_settings);
1336   #endif
1337 
1338   dialog::addBack();
1339   dialog::display();
1340   }
1341 
o_key(o_funcs & v)1342 void o_key(o_funcs& v) {
1343   if(geometry == gNotKnot) v.push_back(named_dialog("notknot", show));
1344   }
1345 
1346 bool do_check_cycle;
1347 cell *startcell, *current;
1348 vector<int> dirs;
1349 
check_cycle()1350 void check_cycle() {
1351   if(!do_check_cycle) return;
1352   if(!current) {
1353     auto s = currentmap->allcells()[0];
1354     println(hlog, "starting the cycle, ", cwt.at == s);
1355     startcell = current = cwt.at = s;
1356     }
1357   if(cwt.at != current) {
1358     forCellIdEx(c1, i, current)
1359       if(c1 == cwt.at) {
1360         dirs.push_back(i);
1361         current = cwt.at;
1362         startcell->item = itGold;
1363         println(hlog, "dirs = ", dirs, " finished = ", startcell == current);
1364         string dirstr;
1365         for(int d: dirs)
1366           if(d < 10)
1367             dirstr += char('0' + d);
1368           else
1369             dirstr += char('a' + d-10);
1370         addMessage("this loop can be identified with identity using: -nk-unloop 1 " + dirstr);
1371         }
1372     }
1373   }
1374 
gen_knot()1375 void gen_knot() {
1376   for(cell *c: currentmap->allcells())
1377     c->wall = waNone;
1378 
1379   cell *last = nullptr;
1380 
1381   for(int i=0; i<3600; i++) {
1382     ld alpha = i * degree / 10;
1383     ld q = sqrt(2)/2;
1384     hyperpoint h = hyperpoint(q*cos(alpha*2), q*sin(alpha*2), q*cos(alpha*3), q*sin(alpha*3));
1385     cell *b = currentmap->gamestart();
1386     virtualRebase(b, h);
1387     b->wall = waPlatform;
1388     if(b != last) {
1389       if(!last) println(hlog, "start at ", b);
1390       if(last) println(hlog, "i=", i, ": to ", b, " isN = ", isNeighbor(last, b));
1391       last = b;
1392       }
1393     }
1394   }
1395 
nk_launch()1396 void nk_launch() {
1397   margin = 4;
1398   mapeditor::drawplayer = false;
1399   stop_game();
1400   firstland = specialland = laCanvas;
1401   set_geometry(gNotKnot);
1402   sightranges[geometry] = .5;
1403   ray::max_cells = 600000;
1404   smooth_scrolling = 1;
1405   camera_speed = 10;
1406   // panini_alpha = 1;
1407   // fov = 150;
1408   ray::exp_decay_poly = 30;
1409   ray::fixed_map = true;
1410   ray::max_iter_iso = 80;
1411   showstartmenu = false;
1412   #if CAP_VR
1413   vrhr::hsm = vrhr::eHeadset::holonomy;
1414   vrhr::eyes = vrhr::eEyes::truesim;
1415   vrhr::cscr = vrhr::eCompScreen::eyes;
1416   vrhr::absolute_unit_in_meters = 0.2;
1417   #endif
1418   }
1419 
1420 auto shot_hooks = addHook(hooks_initialize, 100, create_notknot)
__anon07a57b340a02null1421   + addHook(hooks_welcome_message, 100, [] {
1422     if(geometry == gNotKnot) {
1423       addMessage("Welcome to Notknot! Press 'o' for options");
1424       return true;
1425       }
1426     return false;
1427     })
__anon07a57b340b02null1428   + addHook(hooks_args, 100, [] {
1429     using namespace arg;
1430 
1431     if(0) ;
1432     else if(argis("-nkbase")) {
1433       base = geometry;
1434       create_notknot();
1435       }
1436     else if(argis("-nkbasemap")) {
1437       shift(); base_map = args();
1438       set_geometry(gNotKnot);
1439       }
1440     else if(argis("-nk-volumetric")) {
1441       start_game();
1442       ((hrmap_notknot*)currentmap)->add_fog();
1443       }
1444     else if(argis("-nk-genknot")) {
1445       start_game();
1446       gen_knot();
1447       }
1448     else if(argis("-nk-findloop")) {
1449       do_check_cycle = true;
1450       }
1451     else if(argis("-nk-unloop")) {
1452       shift();
1453       int copies = argi();
1454       shift();
1455       vector<int> v;
1456       for(int i=0; i<copies; i++)
1457       for(char c: args())
1458         if(c >= '0' && c <= '9') v.push_back(c - '0');
1459         else v.push_back(c - 'a' + 10);
1460       to_unloop.push_back(v);
1461       println(hlog, "pushed to to_unloop: ", v);
1462       }
1463     else if(argis("-nk-launch"))
1464       nk_launch();
1465     else return 1;
1466     return 0;
1467     })
1468 
1469   + addHook(hooks_o_key, 80, o_key)
1470   + addHook(hooks_frame, 100, check_cycle)
__anon07a57b340c02(string& s) 1471   + addHook(hooks_cgi_string, 100, [] (string& s) {
1472     if(geometry == gNotKnot) {
1473       s += " base: ";
1474       dynamicval<eGeometry> b(geometry, base);
1475       s += cgi_string();
1476       }
1477     })
__anon07a57b340d02null1478   + addHook(hooks_configfile, 100, [] {
1479     param_i(loop, "nk_loop")
1480     ->editable(1, 5, 1, "notknot order", "How many times do we need to go around the knot to get back.", 'o')
1481     ->set_sets([] { dialog::bound_low(1); dialog::bound_up(5); })
1482     ->set_reaction(regenerate);
1483     param_i(margin, "nk_margin")
1484     ->editable(0, 10, 1, "notknot margins", "Empty space close to the walls.", 'm')
1485     ->set_sets([] { dialog::bound_low(0); dialog::bound_up(10); })
1486     ->set_reaction(regenerate);
1487     param_i(knotsize, "nk_knotsize")
1488     ->editable(0, 10, 1, "notknot size", "Size of the knot.", 's')
1489     ->set_sets([] { dialog::bound_low(2); dialog::bound_up(5); })
1490     ->set_reaction(regenerate);
1491     param_i(terminate_at, "nk_terminate")->set_reaction(regenerate);
1492     param_i(secondary_percentage, "nk_secondary");
1493     param_b(self_hiding, "selfhide")
1494     ->editable("self-hiding knot", 'h')
1495     ->set_reaction(regenerate);
1496     param_i(loop_any, "nk_loopany");
1497     })
1498 #ifndef NOTKNOT
__anon07a57b341102(string s, vector<tour::slide>& v) 1499   + addHook_rvslides(180, [] (string s, vector<tour::slide>& v) {
1500       if(s != "mixed") return;
1501       v.push_back(tour::slide{
1502         "weird portals", 10, tour::LEGAL::NONE | tour::QUICKSKIP,
1503         "Some experiments with weird portals. Press '5' to change between available experiments.\n"
1504         ,
1505         [] (tour::presmode mode) {
1506           slide_url(mode, 'k', "knotted portal (YouTube)", "https://www.youtube.com/watch?v=eb2DhCcGH7U");
1507           slide_url(mode, 'h', "self-hiding knot portal (YouTube)", "https://www.youtube.com/watch?v=vFLZ2NGtuGw");
1508           slide_url(mode, 'n', "non-Euclidean portal in Nil (YouTube)", "https://www.youtube.com/watch?v=2K-v8tK68AE");
1509           slide_url(mode, 's', "spherical portal (YouTube)", "https://www.youtube.com/watch?v=PerPeQFu5gw");
1510           slide_url(mode, 'c', "Cat Portal in Solv (YouTube)", "https://www.youtube.com/watch?v=CGiSxC9B6i0");
1511           setCanvas(mode, '0');
1512           using namespace tour;
1513           if(mode == pmStart) {
1514             slide_backup(margin);
1515             slide_backup(mapeditor::drawplayer);
1516             slide_backup(firstland);
1517             slide_backup(specialland);
1518             slide_backup(ray::max_cells);
1519             slide_backup(smooth_scrolling);
1520             slide_backup(camera_speed);
1521             slide_backup(ray::exp_decay_poly);
1522             slide_backup(ray::fixed_map);
1523             slide_backup(ray::max_iter_iso);
1524             #if CAP_VR
1525             slide_backup(vrhr::hsm);
1526             slide_backup(vrhr::eyes);
1527             slide_backup(vrhr::cscr);
1528             slide_backup(vrhr::absolute_unit_in_meters);
1529             #endif
1530 
1531             on_restore([] { nilv::set_flags(); asonov::set_flags(); });
1532 
1533             slide_backup(nilv::nilwidth);
1534             slide_backup(nilv::nilperiod);
1535 
1536             slide_backup(vid.binary_width);
1537             slide_backup(asonov::period_xy);
1538             slide_backup(asonov::period_z);
1539 
1540             nk_launch();
1541             start_game();
1542             loop = 2;
1543             }
1544           if(mode == tour::pmKey) {
1545             pushScreen(show);
1546             }
1547           }
1548         });
1549       })
1550 #endif
1551       ;
1552 
1553 #ifdef NOTKNOT
1554 auto hook1=
__anon07a57b341402null1555     addHook(hooks_config, 100, [] {
1556       if(arg::curphase == 1)
1557         conffile = "notknot.ini";
1558       if(arg::curphase == 2)
1559         nk_launch();
1560       });
1561 #endif
1562 
1563 }
1564 
1565 }
1566 #endif
1567