1 // Hyperbolic Rogue -- Arbitrary Tilings
2 // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file arbitrile.cpp
5  *  \brief Arbitrary tilings
6  *
7  *  Arbitrary tilings, defined in .tes files.
8  */
9 
10 #include "hyper.h"
11 namespace hr {
12 
13 EX namespace arb {
14 
15 EX int affine_limit = 200;
16 
17 EX bool legacy; /* angleofs command */
18 
19 #if HDR
20 
21 /** a type used to specify the connections between shapes */
22 struct connection_t {
23   /** the index of the connected shape in the 'shapes' table */
24   int sid;
25   /** the index of the edge in the 'shapes' table */
26   int eid;
27   /** 1 if this connection mirrored, 0 otherwise. do_unmirror() removes all mirrors by doubling shapes */
28   int mirror;
29   };
30 
print(hstream & hs,const connection_t & conn)31 inline void print(hstream& hs, const connection_t& conn) { print(hs, tie(conn.sid, conn.eid, conn.mirror)); }
32 
33 /** \brief each shape of the arb tessellation
34  *  note: the usual HyperRogue convention is: vertex 0, edge 0, vertex 1, edge 1, ...
35  *  note: the tesfile convention is: edge 0, vertex 0, edge 1, vertex 1, ...
36  */
37 
38 struct shape {
39   /** index in the arbi_tiling::shapes */
40   int id;
41   /** flags such as sfLINE and sfPH */
42   int flags;
43   /** list of vertices in the usual convention */
44   vector<hyperpoint> vertices;
45   /** list of vertices in the tesfile convention */
46   vector<ld> angles;
47   /** list of edge lengths */
48   vector<ld> edges;
49   /** list of edge connections */
50   vector<connection_t> connections;
sizehr::arb::shape51   int size() const { return isize(vertices); }
52   void build_from_angles_edges();
53   vector<pair<int, int> > sublines;
54   vector<pair<ld, ld>> stretch_shear;
55   int repeat_value;
56   /** if a tile/edge combination may be connected to edges j1 and j2 of this, j1-j2 must be divisible by cycle_length */
57   int cycle_length;
58   /** list of valences of vertices in the tesfile convention */
59   vector<int> vertex_valence;
60   };
61 
62 struct slider {
63   string name;
64   ld zero;
65   ld current;
66   ld min;
67   ld max;
68   };
69 
70 struct arbi_tiling {
71 
72   int order;
73   bool have_line, have_ph, have_tree;
74   int yendor_backsteps;
75 
76   vector<shape> shapes;
77   string name;
78   string comment;
79 
80   vector<slider> sliders;
81 
82   ld cscale;
83   int range;
84   ld floor_scale;
85   ld boundary_ratio;
86   string filename;
87 
88   int min_valence, max_valence;
89 
90   geometryinfo1& get_geometry();
get_classhr::arb::arbi_tiling91   eGeometryClass get_class() { return get_geometry().kind; }
92 
93   ld scale();
94   };
95 #endif
96 
97 EX arbi_tiling current;
98 
99 EX bool using_slided;
100 
101 EX arbi_tiling slided;
102 
in_slided()103 EX bool in_slided() { return in() && using_slided; }
104 
current_or_slided()105 EX arbi_tiling& current_or_slided() {
106   return using_slided ? slided : current;
107   }
108 
109 /** id of vertex in the arbitrary tiling */
110 
id_of(heptagon * h)111 EX short& id_of(heptagon *h) { return h->zebraval; }
112 
113 #if HDR
114 struct hr_polygon_error : hr_exception {
115   vector<transmatrix> v;
116   eGeometryClass c;
117   int id;
118   transmatrix end;
119   map<string, cld> params;
hr_polygon_errorhr::arb::hr_polygon_error120   hr_polygon_error(const vector<transmatrix>& _v, int _id, transmatrix _e) : v(_v), c(cgclass), id(_id), end(_e) {}
~hr_polygon_errorhr::arb::hr_polygon_error121   ~hr_polygon_error() noexcept(true) {}
122   string generate_error();
123   };
124 #endif
125 
generate_error()126 string hr_polygon_error::generate_error() {
127   cld dist = (hdist0(tC0(end)) / params["distunit"]);
128   bool angle = abs(dist) < 1e-9;
129   if(angle) dist = (atan2(end * xpush0(1)) / params["angleunit"]);
130   return
131     XLAT("Polygon number %1 did not close correctly (%2 %3). Here is the picture to help you understand the issue.\n\n", its(id),
132       angle ? "angle" : "distance",
133       lalign(0, dist)
134       );
135   }
136 
137 struct connection_debug_request : hr_exception {
138   int id;
139   eGeometryClass c;
connection_debug_requesthr::arb::connection_debug_request140   connection_debug_request(int i): id(i), c(cgclass) {}
141   };
142 
ensure_geometry(eGeometryClass c)143 void ensure_geometry(eGeometryClass c) {
144   stop_game();
145   if(c != cgclass) {
146     if(c == gcEuclid) set_geometry(gEuclid);
147     if(c == gcHyperbolic) set_geometry(gNormal);
148     if(c == gcSphere) set_geometry(gSphere);
149     }
150 
151   if(specialland != laCanvas) {
152     canvas_default_wall = waInvisibleFloor;
153     patterns::whichCanvas = 'g';
154     patterns::canvasback = 0xFFFFFF;
155     enable_canvas();
156     }
157   start_game();
158   }
159 
start_poly_debugger(hr_polygon_error & err)160 void start_poly_debugger(hr_polygon_error& err) {
161   #if CAP_EDIT
162   ensure_geometry(err.c);
163 
164   drawthemap();
165 
166   mapeditor::drawing_tool = true;
167   pushScreen(mapeditor::showDrawEditor);
168   mapeditor::initdraw(cwt.at);
169 
170   int n = isize(err.v);
171 
172   mapeditor::dtcolor = 0xFF0000FF;
173   mapeditor::dtwidth = 0.02;
174   for(int i=0; i<n-1; i++)
175     mapeditor::dt_add_line(shiftless(tC0(err.v[i])), shiftless(tC0(err.v[i+1])), 0);
176 
177   mapeditor::dtcolor = 0xFFFFFFFF;
178   for(int i=0; i<n; i++)
179     mapeditor::dt_add_text(shiftless(tC0(err.v[i])), 0.5, its(i));
180   #endif
181   }
182 
build_from_angles_edges()183 void shape::build_from_angles_edges() {
184   transmatrix at = Id;
185   vertices.clear();
186   int n = isize(angles);
187   hyperpoint ctr = Hypc;
188   vector<transmatrix> matrices;
189   if(!legacy) for(auto& a: angles) a += M_PI;
190   for(int i=0; i<n; i++) {
191     matrices.push_back(at);
192     if(debugflags & DF_GEOM) println(hlog, "at = ", at);
193     vertices.push_back(tC0(at));
194     ctr += tC0(at);
195     at = at * xpush(edges[i]) * spin(angles[i]);
196     }
197   matrices.push_back(at);
198   if(!eqmatrix(at, Id)) {
199     throw hr_polygon_error(matrices, id, at);
200     }
201   if(sqhypot_d(3, ctr) < 1e-2) {
202     // this may happen for some spherical tilings
203     // try to move towards the center
204     if(debugflags & DF_GEOM) println(hlog, "special case encountered");
205     for(int i=0; i<n; i++) {
206       ctr += at * xpush(edges[i]) * spin((angles[i]+M_PI)/2) * xpush0(.01);
207       at = at * xpush(edges[i]) * spin(angles[i]);
208       }
209     if(debugflags & DF_GEOM) println(hlog, "ctr = ", ctr);
210     }
211   if(!legacy) for(auto& a: angles) a -= M_PI;
212   ctr = normalize(ctr);
213   for(auto& v: vertices) v = gpushxto0(ctr) * v;
214   }
215 
correct_index(int index,int size)216 EX bool correct_index(int index, int size) { return index >= 0 && index < size; }
correct_index(int index,const T & v)217 template<class T> bool correct_index(int index, const T& v) { return correct_index(index, isize(v)); }
218 
verify_index(int index,const T & v,exp_parser & ep)219 template<class T> void verify_index(int index, const T& v, exp_parser& ep) { if(!correct_index(index, v)) throw hr_parse_exception("bad index: " + its(index) + " at " + ep.where()); }
220 
221 string unnamed = "unnamed";
222 
load_tile(exp_parser & ep,arbi_tiling & c,bool unit)223 EX void load_tile(exp_parser& ep, arbi_tiling& c, bool unit) {
224   c.shapes.emplace_back();
225   auto& cc = c.shapes.back();
226   cc.id = isize(c.shapes) - 1;
227   cc.flags = 0;
228   cc.repeat_value = 1;
229   while(ep.next() != ')') {
230     cld dist = 1;
231     if(!unit) {
232       dist = ep.parse(0);
233       ep.force_eat(",");
234       }
235     cld angle = ep.parse(0);
236     cc.edges.push_back(ep.validate_real(dist * ep.extra_params["distunit"]));
237     cc.angles.push_back(ep.validate_real(angle * ep.extra_params["angleunit"] + ep.extra_params["angleofs"]));
238     if(ep.eat(",")) continue;
239     else if(ep.eat(")")) break;
240     else throw hr_parse_exception("expecting , or )");
241     }
242   try {
243     cc.build_from_angles_edges();
244     }
245   catch(hr_parse_exception& ex) {
246     throw hr_parse_exception(ex.s + ep.where());
247     }
248   catch(hr_polygon_error& poly) {
249     poly.params = ep.extra_params;
250     throw;
251     }
252   cc.connections.resize(cc.size());
253   for(int i=0; i<isize(cc.connections); i++)
254     cc.connections[i] = connection_t{cc.id, i, false};
255   cc.stretch_shear.resize(cc.size(), make_pair(1, 0));
256   }
257 
258 EX bool do_unmirror = true;
259 
260 /** \brief for tessellations which contain mirror rules, remove them by taking the orientable double cover */
unmirror()261 EX void unmirror() {
262   int mirror_rules = 0;
263   for(auto& s: arb::current.shapes)
264     for(auto& t: s.connections)
265       if(t.mirror)
266         mirror_rules++;
267   if(!mirror_rules) return;
268   auto& sh = current.shapes;
269   int s = isize(sh);
270   for(int i=0; i<s; i++)
271     sh.push_back(sh[i]);
272   for(int i=0; i<2*s; i++)
273     sh[i].id = i;
274   for(int i=s; i<s+s; i++) {
275     for(auto& v: sh[i].vertices)
276       v[1] = -v[1];
277     reverse(sh[i].edges.begin(), sh[i].edges.end());
278     reverse(sh[i].vertices.begin()+1, sh[i].vertices.end());
279     reverse(sh[i].angles.begin(), sh[i].angles.end()-1);
280     reverse(sh[i].connections.begin(), sh[i].connections.end());
281     }
282 
283   if(true) for(int i=0; i<s+s; i++) {
284     for(auto& co: sh[i].connections) {
285       bool mirr = co.mirror ^ (i >= s);
286       co.mirror = false;
287       if(mirr) {
288         co.sid += s;
289         co.eid = isize(sh[co.sid].angles) - 1 - co.eid;
290         }
291       }
292     }
293   }
294 
compute_vertex_valence()295 EX void compute_vertex_valence() {
296   auto& ac = arb::current;
297 
298   int tcl = -1;
299 
300   for(auto& sh: ac.shapes)
301     sh.cycle_length = isize(sh.vertices);
302 
303   recompute:
304   while(true) {
305 
306     for(auto& sh: ac.shapes) {
307       int i = sh.id;
308       int n = isize(sh.vertices);
309 
310       for(int k=sh.cycle_length; k<n; k++) {
311         auto co = sh.connections[k];
312         auto co1 = sh.connections[k-sh.cycle_length];
313         if(co.sid != co1.sid) {
314           println(hlog, "ik = ", tie(i,k), " co=", co, "co1=", co1, " cl=", sh.cycle_length);
315           throw hr_parse_exception("connection error #2 in compute_vertex_valence");
316           }
317         ac.shapes[co.sid].cycle_length = abs(gcd(ac.shapes[co.sid].cycle_length, co.eid - co1.eid));
318         }
319 
320       for(int k=0; k<n; k++) {
321         auto co = sh.connections[k];
322         co = ac.shapes[co.sid].connections[co.eid];
323         if(co.sid != i) throw hr_parse_exception("connection error in compute_vertex_valence");
324         sh.cycle_length = abs(gcd(sh.cycle_length, k-co.eid));
325         }
326       if(debugflags & DF_GEOM)
327         println(hlog, "tile ", i, " cycle_length = ", sh.cycle_length, " / ", n);
328       }
329 
330     int new_tcl = 0;
331     for(auto& sh: ac.shapes) {
332       auto& len = sh.cycle_length;
333       if(len < 0) len = -len;
334       new_tcl += len;
335       }
336 
337     if(new_tcl == tcl) break;
338     tcl = new_tcl;
339     }
340 
341   if(cgflags & qAFFINE) return;
342   for(auto& sh: ac.shapes) {
343     int n = sh.size();
344     int i = sh.id;
345     sh.vertex_valence.resize(n);
346     for(int k=0; k<n; k++) {
347       ld total = 0;
348       int qty = 0;
349       connection_t at = {i, k, false};
350       do {
351         ld a = ac.shapes[at.sid].angles[at.eid];
352         while(a < 0) a += 360 * degree;
353         while(a > 360 * degree) a -= 360 * degree;
354         total += a;
355         qty++;
356 
357         at.eid++;
358         if(at.eid == isize(ac.shapes[at.sid].angles)) at.eid = 0;
359 
360         at = ac.shapes[at.sid].connections[at.eid];
361         }
362       while(total < 360*degree - 1e-6);
363       if(total > 360*degree + 1e-6) throw hr_parse_exception("improper total in compute_stats");
364       if(at.sid != i) throw hr_parse_exception("ended at wrong type determining vertex_valence");
365       if((at.eid - k) % ac.shapes[i].cycle_length) {
366         ac.shapes[i].cycle_length = abs(gcd(ac.shapes[i].cycle_length, at.eid - k));
367         goto recompute;
368         }
369       sh.vertex_valence[k] = qty;
370       }
371     if(debugflags & DF_GEOM)
372       println(hlog, "computed vertex_valence of ", i, " as ", ac.shapes[i].vertex_valence);
373     }
374 
375   ac.min_valence = UNKNOWN; ac.max_valence = 0;
376   for(auto& sh: ac.shapes)
377     for(auto& val: sh.vertex_valence) {
378       if(val < ac.min_valence) ac.min_valence = val;
379       if(val > ac.max_valence) ac.max_valence = val;
380       }
381   }
382 
load(const string & fname,bool after_sliding IS (false))383 EX void load(const string& fname, bool after_sliding IS(false)) {
384   fhstream f(fname, "rt");
385   if(!f.f) throw hr_parse_exception("file " + fname + " does not exist");
386   string s;
387   while(true) {
388     int c = fgetc(f.f);
389     if(c < 0) break;
390     s += c;
391     }
392   auto& c = after_sliding ? slided : current;
393   c.order++;
394   c.shapes.clear();
395   c.sliders.clear();
396   c.name = unnamed;
397   c.comment = "";
398   c.filename = fname;
399   c.cscale = 1;
400   c.range = 0;
401   c.boundary_ratio = 1;
402   c.floor_scale = .5;
403   c.have_ph = c.have_line = false;
404   c.have_tree = false;
405   c.yendor_backsteps = 0;
406   exp_parser ep;
407   ep.s = s;
408   ld angleunit = 1, distunit = 1, angleofs = 0;
409   auto addflag = [&] (int f) {
410     int ai;
411     if(ep.next() == ')') ai = isize(c.shapes)-1;
412     else ai = ep.iparse();
413     verify_index(ai, c.shapes, ep);
414     c.shapes[ai].flags |= f;
415     ep.force_eat(")");
416     };
417   while(true) {
418 
419     ep.extra_params["distunit"] = distunit;
420     ep.extra_params["angleunit"] = angleunit;
421     ep.extra_params["angleofs"] = angleofs;
422 
423     ep.skip_white();
424     if(ep.next() == 0) break;
425     if(ep.eat("#")) {
426       bool doubled = ep.eat("#");
427       while(ep.eat(" ")) ;
428       string s = "";
429       while(ep.next() >= 32) s += ep.next(), ep.at++;
430       if(doubled) {
431         if(c.name == unnamed) c.name = s;
432         else {
433           c.comment += s;
434           c.comment += "\n";
435           }
436         }
437       }
438     else if(ep.eat("e2.")) {
439       ginf[gArbitrary].g = giEuclid2;
440       ginf[gArbitrary].sides = 7;
441       set_flag(ginf[gArbitrary].flags, qBOUNDED, false);
442       set_flag(ginf[gArbitrary].flags, qAFFINE, false);
443       geom3::apply_always3();
444       }
445     else if(ep.eat("a2.")) {
446       ginf[gArbitrary].g = giEuclid2;
447       ginf[gArbitrary].sides = 7;
448       set_flag(ginf[gArbitrary].flags, qBOUNDED, false);
449       set_flag(ginf[gArbitrary].flags, qAFFINE, true);
450       affine_limit = 200;
451       geom3::apply_always3();
452       }
453     else if(ep.eat("h2.")) {
454       ginf[gArbitrary].g = giHyperb2;
455       ginf[gArbitrary].sides = 7;
456       set_flag(ginf[gArbitrary].flags, qBOUNDED, false);
457       set_flag(ginf[gArbitrary].flags, qAFFINE, false);
458       geom3::apply_always3();
459       }
460     else if(ep.eat("s2.")) {
461       ginf[gArbitrary].g = giSphere2;
462       ginf[gArbitrary].sides = 5;
463       set_flag(ginf[gArbitrary].flags, qBOUNDED, true);
464       set_flag(ginf[gArbitrary].flags, qAFFINE, false);
465       geom3::apply_always3();
466       }
467     else if(ep.eat("legacysign.")) {
468       if(legacy) angleunit *= -1;
469       }
470     else if(ep.eat("angleunit(")) angleunit = real(ep.parsepar());
471     else if(ep.eat("angleofs(")) {
472       angleofs = real(ep.parsepar());
473       if(!legacy) angleofs = 0;
474       }
475     else if(ep.eat("distunit(")) distunit = real(ep.parsepar());
476     else if(ep.eat("line(")) {
477       addflag(arcm::sfLINE);
478       c.have_line = true;
479       }
480     else if(ep.eat("grave(")) {
481       addflag(arcm::sfPH);
482       c.have_ph = true;
483       }
484     else if(ep.eat("slider(")) {
485       slider sl;
486       sl.name = ep.next_token();
487       ep.force_eat(",");
488       sl.current = sl.zero = ep.rparse();
489       ep.force_eat(",");
490       sl.min = ep.rparse();
491       ep.force_eat(",");
492       sl.max = ep.rparse();
493       ep.force_eat(")");
494       c.sliders.push_back(sl);
495       if(after_sliding)
496         ep.extra_params[sl.name] = current.sliders[isize(c.sliders)-1].current;
497       else
498         ep.extra_params[sl.name] = sl.zero;
499       }
500     else if(ep.eat("let(")) {
501       string tok = ep.next_token();
502       ep.force_eat("=");
503       ep.extra_params[tok] =ep.parsepar();
504       if(debugflags & DF_GEOM)
505         println(hlog, "let ", tok, " = ", ep.extra_params[tok]);
506       }
507     else if(ep.eat("unittile(")) load_tile(ep, c, true);
508     else if(ep.eat("tile(")) load_tile(ep, c, false);
509     else if(ep.eat("affine_limit(")) {
510       affine_limit = ep.iparse();
511       ep.force_eat(")");
512       }
513     else if(ep.eat("cscale(")) {
514       c.cscale = ep.rparse();
515       ep.force_eat(")");
516       }
517     else if(ep.eat("treestate(")) {
518       rulegen::parse_treestate(c, ep);
519       }
520     else if(ep.eat("first_treestate(")) {
521       rulegen::rule_root = ep.iparse();
522       ep.force_eat(")");
523       }
524     else if(ep.eat("yendor_backsteps(")) {
525       c.yendor_backsteps = ep.iparse();
526       ep.force_eat(")");
527       }
528     else if(ep.eat("range(")) {
529       c.range = ep.iparse();
530       ep.force_eat(")");
531       }
532     else if(ep.eat("floor_scale(")) {
533       c.floor_scale = ep.rparse();
534       ep.force_eat(")");
535       }
536     else if(ep.eat("boundary_ratio(")) {
537       c.boundary_ratio = ep.rparse();
538       ep.force_eat(")");
539       }
540     else if(ep.eat("conway(\"")) {
541       string s = "";
542       while(true) {
543         int m = 0;
544         if(ep.eat("(")) m = 0;
545         else if(ep.eat("[")) m = 1;
546         else if(ep.eat("\"")) break;
547         else throw hr_parse_exception("cannot parse Conway notation, " + ep.where());
548 
549         int ai = 0;
550         int as = ep.iparse();
551         while(ep.eat("'")) ai++;
552         if(ep.eat("@")) ai = ep.iparse();
553         int bi = 0, bs = 0;
554         if(ep.eat(")") || ep.eat("]")) bs = as, bi = ai;
555         else {
556           bs = ep.iparse();
557           while(ep.eat("'")) bi++;
558           if(ep.eat("@")) bi = ep.iparse();
559           }
560         if(ep.eat(")") || ep.eat("]")) {}
561         verify_index(ai, c.shapes, ep);
562         verify_index(as, c.shapes[ai], ep);
563         verify_index(bi, c.shapes, ep);
564         verify_index(bs, c.shapes[bi], ep);
565         c.shapes[ai].connections[as] = connection_t{bi, bs, m};
566         c.shapes[bi].connections[bs] = connection_t{ai, as, m};
567         }
568       ep.force_eat(")");
569       }
570     else if(ep.eat("c(")) {
571       int ai = ep.iparse(); verify_index(ai, c.shapes, ep); ep.force_eat(",");
572       int as = ep.iparse(); verify_index(as, c.shapes[ai], ep); ep.force_eat(",");
573       int bi = ep.iparse(); verify_index(bi, c.shapes, ep); ep.force_eat(",");
574       int bs = ep.iparse(); verify_index(bs, c.shapes[bi], ep); ep.force_eat(",");
575       int m = ep.iparse(); ep.force_eat(")");
576       c.shapes[ai].connections[as] = connection_t{bi, bs, m};
577       c.shapes[bi].connections[bs] = connection_t{ai, as, m};
578       }
579     else if(ep.eat("subline(")) {
580       int ai = ep.iparse(); verify_index(ai, c.shapes, ep); ep.force_eat(",");
581       int as = ep.iparse(); verify_index(as, c.shapes[ai], ep); ep.force_eat(",");
582       int bs = ep.iparse(); verify_index(bs, c.shapes[ai], ep); ep.force_eat(")");
583       c.shapes[ai].sublines.emplace_back(as, bs);
584       }
585     else if(ep.eat("sublines(")) {
586       ld d = ep.rparse() * distunit;
587       ld eps = 1e-4;
588       if(ep.eat(",")) eps = ep.rparse() * distunit;
589       ep.force_eat(")");
590       for(auto& sh: c.shapes) {
591         for(int i=0; i<isize(sh.vertices); i++)
592         for(int j=0; j<i; j++)
593           if(j != i+1 && i != j+1 && !(i==0 && j == isize(sh.vertices)-1) && !(j==0 && i == isize(sh.vertices)-1) && i != j) {
594             ld dist = hdist(sh.vertices[i], sh.vertices[j]);
595             if(abs(dist - d) < eps) {
596               sh.sublines.emplace_back(i, j);
597               if(debugflags & DF_GEOM) println(hlog, "add subline ", i, "-", j);
598               }
599             }
600         }
601       }
602     else if(ep.eat("repeat(")) {
603       int i = ep.iparse(0);
604       verify_index(i, c.shapes, ep);
605       ep.force_eat(",");
606       int rep = ep.iparse(0);
607       ep.force_eat(")");
608       auto& sh = c.shapes[i];
609       int N = isize(sh.angles);
610       if(N % rep)
611         throw hr_parse_exception("repeat value should be a factor of the number of vertices, " + ep.where());
612       sh.repeat_value = rep;
613 
614       int d = N / rep;
615       for(int i=d; i<N; i++)
616         sh.connections[i] = sh.connections[i-d];
617       }
618     else if(ep.eat("debug(")) {
619       int i = ep.iparse(0);
620       verify_index(i, c.shapes, ep);
621       ep.force_eat(")");
622       throw connection_debug_request(i);
623       }
624     else if(ep.eat("stretch_shear(")) {
625       ld stretch = ep.rparse(0);
626       ep.force_eat(",");
627       ld shear = ep.rparse(0);
628       ep.force_eat(",");
629       int i = ep.iparse(0);
630       verify_index(i, c.shapes, ep);
631       ep.force_eat(",");
632       int j = ep.iparse(0);
633       verify_index(j, c.shapes[i], ep);
634       ep.force_eat(")");
635       auto& sh = c.shapes[i];
636       sh.stretch_shear[j] = {stretch, shear};
637       auto& co = sh.connections[j];
638       auto& xsh = c.shapes[co.sid];
639       ld scale = sh.edges[j] / xsh.edges[co.eid];
640       println(hlog, "scale = ", scale);
641       xsh.stretch_shear[co.eid] = {1/stretch, shear * (co.mirror ? 1 : -1) * stretch };
642       }
643     else throw hr_parse_exception("expecting command, " + ep.where());
644     }
645   if(!(cgflags & qAFFINE)) {
646     for(int i=0; i<isize(c.shapes); i++) {
647       auto& sh = c.shapes[i];
648       for(int j=0; j<isize(sh.edges); j++) {
649         ld d1 = sh.edges[j];
650         auto con = sh.connections[j];
651         auto& xsh = c.shapes[con.sid];
652         ld d2 = xsh.edges[con.eid];
653         if(abs(d1 - d2) > 1e-6)
654           throw hr_parse_exception(lalign(0, "connecting ", make_pair(i,j), " to ", con, " of different lengths only possible in a2"));
655         }
656       }
657     }
658 
659   if(do_unmirror) {
660     unmirror();
661     }
662   if(!c.have_tree) compute_vertex_valence();
663 
664   if(c.have_tree) rulegen::verify_parsed_treestates();
665 
666   if(!after_sliding) slided = current;
667   }
668 
669 arbi_tiling debugged;
670 vector<pair<transmatrix, int> > debug_polys;
671 
primes(int i)672 string primes(int i) {
673   string res;
674   while(i--) res += "'";
675   return res;
676   }
677 
connection_debugger()678 void connection_debugger() {
679   cmode = sm::SIDE | sm::DIALOG_STRICT_X;
680   gamescreen(0);
681 
682   auto& last = debug_polys.back();
683 
684   initquickqueue();
685   for(auto& p: debug_polys) {
686     int id = p.second;
687 
688     shiftmatrix V = gmatrix[cwt.at] * p.first;
689 
690     auto& sh = debugged.shapes[id].vertices;
691 
692     for(auto& v: sh)
693       curvepoint(v);
694 
695     curvepoint(sh[0]);
696 
697     color_t col = colortables['A'][id];
698     col = darkena(col, 0, 0xFF);
699 
700     if(&p == &last) {
701       vid.linewidth *= 2;
702       queuecurve(V, 0xFFFF00FF, col, PPR::LINE);
703       vid.linewidth /= 2;
704       for(int i=0; i<isize(sh); i++)
705         queuestr(V * sh[i], vid.fsize, its(i), 0xFFFFFFFF);
706       }
707     else
708       queuecurve(V, 0xFFFFFFFF, col, PPR::LINE);
709     }
710   quickqueue();
711 
712   dialog::init(XLAT("connection debugger"));
713 
714   dialog::addInfo(debugged.name);
715   dialog::addHelp(debugged.comment);
716 
717   dialog::addBreak(50);
718 
719   dialog::addInfo("face index " + its(last.second));
720 
721   dialog::addBreak(50);
722 
723   auto& sh = debugged.shapes[last.second];
724   int N = isize(sh.edges);
725   for(int k=0; k<N; k++) {
726     auto con = sh.connections[k];
727     string cap = its(k) + primes(last.second) + " -> " + its(con.eid) + primes(con.sid) + (con.mirror ? " (m) " : "");
728     dialog::addSelItem(cap, "go", '0' + k);
729 
730     dialog::add_action([k, last, con] {
731       if(euclid) cgflags |= qAFFINE;
732       debug_polys.emplace_back(last.first * get_adj(debugged, last.second, k, -1, -1), con.sid);
733       if(euclid) cgflags &= ~qAFFINE;
734       });
735 
736     }
737 
738   dialog::addItem("undo", 'u');
739   dialog::add_action([] {
740     if(isize(debug_polys) > 1)
741       debug_polys.pop_back();
742     });
743 
744   dialog::addBack();
745   dialog::display();
746 
747   keyhandler = [] (int sym, int uni) {
748     handlePanning(sym, uni);
749     dialog::handleNavigation(sym, uni);
750     if(doexiton(sym, uni)) popScreen();
751     };
752   }
753 
get_geometry()754 geometryinfo1& arbi_tiling::get_geometry() {
755   return ginf[gEuclid].g;
756   }
757 
758 map<heptagon*, vector<pair<heptagon*, transmatrix> > > altmap;
759 
760 EX map<heptagon*, pair<heptagon*, transmatrix>> arbi_matrix;
761 
762 EX hrmap *current_altmap;
763 
764 heptagon *build_child(heptspin p, pair<int, int> adj);
765 
get_adj(arbi_tiling & c,int t,int dl,int t1,int xdl)766 EX transmatrix get_adj(arbi_tiling& c, int t, int dl, int t1, int xdl) {
767 
768   auto& sh = c.shapes[t];
769 
770   int dr = gmod(dl+1, sh.size());
771 
772   auto& co = sh.connections[dl];
773   if(xdl == -1) xdl = co.eid;
774 
775   if(t1 == -1) t1 = co.sid;
776 
777   auto& xsh = c.shapes[t1];
778   int xdr = gmod(xdl+1, xsh.size());
779 
780   hyperpoint vl = sh.vertices[dl];
781   hyperpoint vr = sh.vertices[dr];
782   hyperpoint vm = mid(vl, vr);
783 
784   transmatrix rm = gpushxto0(vm);
785 
786   hyperpoint xvl = xsh.vertices[xdl];
787   hyperpoint xvr = xsh.vertices[xdr];
788   hyperpoint xvm = mid(xvl, xvr);
789 
790   transmatrix xrm = gpushxto0(xvm);
791 
792   transmatrix Res = rgpushxto0(vm) * rspintox(rm*vr);
793 
794   if(cgflags & qAFFINE) {
795     ld sca = hdist(vl, vr) / hdist(xvl, xvr);
796     transmatrix Tsca = Id;
797     Tsca[0][0] = Tsca[1][1] = sca;
798 
799     auto& ss = sh.stretch_shear[dl];
800     Tsca[0][1] = ss.first * ss.second * sca;
801     Tsca[1][1] *= ss.first;
802 
803     Res = Res * Tsca;
804     }
805 
806   if(co.mirror) Res = Res * MirrorX;
807   Res = Res * spintox(xrm*xvl) * xrm;
808 
809   if(co.mirror) swap(vl, vr);
810 
811   if(hdist(vl, Res*xvr) + hdist(vr, Res*xvl) > .1) {
812     println(hlog, "s1 = ", kz(spintox(rm*vr)), " s2 = ", kz(rspintox(xrm*xvr)));
813     println(hlog, tie(t, dl), " = ", kz(Res));
814     println(hlog, hdist(vl, Res * xvr), " # ", hdist(vr, Res * xvl));
815     exit(3);
816     }
817 
818   return Res;
819   }
820 
821 struct hrmap_arbi : hrmap {
822   heptagon *origin;
getOriginhr::arb::hrmap_arbi823   heptagon *getOrigin() override { return origin; }
824 
hrmap_arbihr::arb::hrmap_arbi825   hrmap_arbi() {
826     dynamicval<hrmap*> curmap(currentmap, this);
827     origin = init_heptagon(current.shapes[0].size());
828     origin->s = hsOrigin;
829     origin->c7 = newCell(origin->type, origin);
830 
831     heptagon *alt = NULL;
832 
833     if(hyperbolic) {
834       dynamicval<eGeometry> g(geometry, gNormal);
835       alt = init_heptagon(S7);
836       alt->s = hsOrigin;
837       alt->alt = alt;
838       current_altmap = newAltMap(alt);
839       }
840 
841     transmatrix T = xpush(.01241) * spin(1.4117) * xpush(0.1241) * Id;
842     arbi_matrix[origin] = make_pair(alt, T);
843     altmap[alt].emplace_back(origin, T);
844 
845     if(!current.range)
846       current.range = auto_compute_range(origin->c7);
847     }
848 
~hrmap_arbihr::arb::hrmap_arbi849   ~hrmap_arbi() {
850     clearfrom(origin);
851     altmap.clear();
852     arbi_matrix.clear();
853     if(current_altmap) {
854       dynamicval<eGeometry> g(geometry, gNormal);
855       delete current_altmap;
856       current_altmap = NULL;
857       }
858     }
verifyhr::arb::hrmap_arbi859   void verify() override { }
860 
adjhr::arb::hrmap_arbi861   transmatrix adj(heptagon *h, int dl) override {
862     return get_adj(current_or_slided(), id_of(h), dl, -1, h->c.move(dl) ? h->c.spin(dl) : -1);
863     }
864 
create_stephr::arb::hrmap_arbi865   heptagon *create_step(heptagon *h, int d) override {
866 
867     dynamicval<bool> sl(using_slided, false);
868     int t = id_of(h);
869 
870     auto& sh = current.shapes[t];
871 
872     auto& co = sh.connections[d];
873 
874     auto& xsh = current.shapes[co.sid];
875 
876     if(cgflags & qAFFINE) {
877       set<heptagon*> visited;
878 
879       vector<pair<heptagon*, transmatrix> > v;
880 
881       visited.insert(h);
882       v.emplace_back(h, Id);
883 
884       transmatrix goal = adj(h, d);
885 
886       for(int i=0; i<affine_limit && i < isize(v); i++) {
887         transmatrix T = v[i].second;
888         heptagon *h2 = v[i].first;
889         if(eqmatrix(T, goal)) {
890           h->c.connect(d, h2, co.eid, co.mirror);
891           return h2;
892           }
893         for(int i=0; i<h2->type; i++) {
894           heptagon *h3 = h2->move(i);
895           if(!h3) continue;
896           if(visited.count(h3)) continue;
897           visited.insert(h3);
898           v.emplace_back(h3, T * adj(h2, i));
899           }
900         }
901 
902       auto h1 = init_heptagon(current.shapes[co.sid].size());
903       h1->distance = h->distance + 1;
904       h1->zebraval = co.sid;
905       h1->c7 = newCell(h1->type, h1);
906       h1->emeraldval = h->emeraldval ^ co.mirror;
907       h->c.connect(d, h1, co.eid, co.mirror);
908 
909       return h1;
910       }
911 
912     const auto& p = arbi_matrix[h];
913 
914     heptagon *alt = p.first;
915 
916     transmatrix T = p.second * adj(h, d);
917 
918     if(hyperbolic) {
919       dynamicval<eGeometry> g(geometry, gNormal);
920       dynamicval<hrmap*> cm(currentmap, current_altmap);
921       // transmatrix U = T;
922       current_altmap->virtualRebase(alt, T);
923       // U = U * inverse(T);
924       }
925     fixmatrix(T);
926 
927     if(euclid) {
928       /* hash the rough coordinates as heptagon* alt */
929       size_t s = size_t(T[0][LDIM]+.261) * 124101 + size_t(T[1][LDIM]+.261) * 82143;
930       alt = (heptagon*) s;
931       }
932 
933     for(auto& p2: altmap[alt]) if(id_of(p2.first) == co.sid && hdist(tC0(p2.second), tC0(T)) < 1e-2) {
934       for(int oth=0; oth < p2.first->type; oth++) {
935         ld err = hdist(p2.second * xsh.vertices[oth], T * xsh.vertices[co.eid]);
936         if(err < 1e-2) {
937           static ld max_err = 0;
938           if(err > max_err) {
939             println(hlog, "err = ", err);
940             max_err = err;
941             }
942           h->c.connect(d, p2.first, oth%p2.first->type, co.mirror);
943           return p2.first;
944           }
945         }
946       }
947 
948     auto h1 = init_heptagon(current.shapes[co.sid].size());
949     h1->distance = h->distance + 1;
950     h1->zebraval = co.sid;
951     h1->c7 = newCell(h1->type, h1);
952     h1->emeraldval = h->emeraldval ^ co.mirror;
953     h->c.connect(d, h1, co.eid, co.mirror);
954 
955     arbi_matrix[h1] = make_pair(alt, T);
956     altmap[alt].emplace_back(h1, T);
957     return h1;
958     }
959 
relative_matrixhhr::arb::hrmap_arbi960   transmatrix relative_matrixh(heptagon *h2, heptagon *h1, const hyperpoint& hint) override {
961     return relative_matrix_recursive(h2, h1);
962     }
963 
adjhr::arb::hrmap_arbi964   transmatrix adj(cell *c, int dir) override { return adj(c->master, dir); }
965 
spin_anglehr::arb::hrmap_arbi966   ld spin_angle(cell *c, int d) override { return SPIN_NOT_AVAILABLE; }
967 
shvidhr::arb::hrmap_arbi968   int shvid(cell *c) override {
969     return id_of(c->master);
970     }
971 
get_cornerhr::arb::hrmap_arbi972   hyperpoint get_corner(cell *c, int cid, ld cf) override {
973     auto& sh = arb::current_or_slided().shapes[arb::id_of(c->master)];
974     return normalize(C0 + (sh.vertices[gmod(cid, c->type)] - C0) * 3 / cf);
975     }
976 
977   };
978 
new_map()979 EX hrmap *new_map() { return new hrmap_arbi; }
980 
run(string fname)981 EX void run(string fname) {
982   stop_game();
983   eGeometry g = geometry;
984   arbi_tiling t = current;
985   auto v = variation;
986   set_geometry(gArbitrary);
987   try {
988      load(fname);
989      ginf[gArbitrary].tiling_name = current.name;
990      }
991    catch(hr_polygon_error& poly) {
992      set_geometry(g);
993      set_variation(v);
994      current = t;
995      start_poly_debugger(poly);
996      string help = poly.generate_error();
997      showstartmenu = false;
998      for(auto& p: poly.params)
999        help += lalign(-1, p.first, " = ", p.second, "\n");
1000      gotoHelp(help);
1001      }
1002    catch(hr_parse_exception& ex) {
1003      println(hlog, "failed: ", ex.s);
1004      set_geometry(g);
1005      current = t;
1006      start_game();
1007      addMessage("failed: " + ex.s);
1008      }
1009   catch(connection_debug_request& cr) {
1010     set_geometry(g);
1011     debugged = current;
1012     current = t;
1013     ensure_geometry(cr.c);
1014     debug_polys.clear();
1015     debug_polys.emplace_back(Id, cr.id);
1016     pushScreen(connection_debugger);
1017     }
1018   start_game();
1019   }
1020 
1021 string slider_error;
1022 
sliders_changed()1023 EX void sliders_changed() {
1024   try {
1025     load(current.filename, true);
1026     using_slided = true;
1027     slider_error = "OK";
1028     #if CAP_TEXTURE
1029     texture::config.remap();
1030     #endif
1031     }
1032   catch(hr_parse_exception& ex) {
1033     using_slided = false;
1034     slider_error = ex.s;
1035     }
1036   catch(hr_polygon_error& poly) {
1037     using_slided = false;
1038     slider_error = poly.generate_error();
1039     }
1040   }
1041 
set_sliders()1042 EX void set_sliders() {
1043   cmode = sm::SIDE | sm::MAYDARK;
1044   gamescreen(1);
1045   dialog::init(XLAT("tessellation sliders"));
1046   char ch = 'A';
1047   for(auto& sl: current.sliders) {
1048     dialog::addSelItem(sl.name, fts(sl.current), ch++);
1049     dialog::add_action([&] {
1050       dialog::editNumber(sl.current, sl.min, sl.max, 1, sl.zero, sl.name, sl.name);
1051       dialog::reaction = sliders_changed;
1052       });
1053     }
1054   dialog::addInfo(slider_error);
1055   dialog::addBack();
1056   dialog::display();
1057   }
1058 
1059 /** convert a tessellation (e.g. Archimedean, regular, etc.) to the arb::current internal representation */
1060 EX namespace convert {
1061 
1062 EX eGeometry base_geometry;
1063 EX eVariation base_variation;
1064 
1065 struct id_record {
1066   int target;   /* master of this id type */
1067   int shift;    /* sample direction 0 == our direction shift */
1068   int modval;   /* this master type is the same as itself rotated by modval */
1069   cell *sample; /* sample of the master type */
1070   };
1071 
print(hstream & hs,const id_record & i)1072 inline void print(hstream& hs, const id_record& i) { print(hs, "[", i.target, " shift=", i.shift, " mod=", i.modval, "]"); }
1073 
1074 map<int, id_record> identification;
1075 
get_identification(int s,cell * c)1076 id_record& get_identification(int s, cell *c) {
1077   if(!identification.count(s)) {
1078     auto &id = identification[s];
1079     id.target = s;
1080     id.shift = 0;
1081     id.modval = c->type;
1082     id.sample = c;
1083     }
1084   return identification[s];
1085   }
1086 
get_identification(cell * c)1087 id_record& get_identification(cell *c) {
1088   auto id = currentmap->full_shvid(c);
1089   return get_identification(id, c);
1090   }
1091 
1092 int changes;
1093 
be_identified(cellwalker cw1,cellwalker cw2)1094 void be_identified(cellwalker cw1, cellwalker cw2) {
1095   auto& id1 = get_identification(cw1.at);
1096   auto& id2 = get_identification(cw2.at);
1097 
1098   indenter ind(2);
1099 
1100   int t = cw2.at->type;
1101 
1102   if(cw1.at->type != t) {
1103     println(hlog, cw1.at->type, " vs ", t);
1104     throw hr_exception("numbers disagree");
1105     }
1106 
1107   int d2 = gmod(-cw2.to_spin(id2.shift), id2.modval);
1108   int d1 = gmod(-cw1.to_spin(id1.shift), id1.modval);
1109 
1110   indenter ind1(2);
1111 
1112   if(id2.target != id1.target) {
1113     auto oid2 = id2;
1114     id1.modval = gcd(id1.modval, id2.modval);
1115     if(id1.modval < 6 && t == 6) throw hr_exception("reducing hex");
1116     for(auto& p: identification) {
1117       auto& idr = p.second;
1118       if(idr.target == oid2.target) {
1119         idr.target = id1.target;
1120         idr.modval = id1.modval;
1121         idr.shift = gmod(idr.shift + (d2-d1), idr.modval);
1122         idr.sample = id1.sample;
1123         }
1124       }
1125     changes++;
1126     println(hlog, identification);
1127     return;
1128     }
1129   if(d2 != d1) {
1130     auto oid2 = id2;
1131     id2.modval = gcd(id2.modval, abs(d2-d1));
1132     if(id1.modval == 1 && t == 6) throw hr_exception("reducing hex");
1133     for(auto& p: identification)
1134       if(p.second.target == oid2.target) p.second.modval = id2.modval;
1135     changes++;
1136     println(hlog, identification);
1137     return;
1138     }
1139   }
1140 
convert()1141 EX void convert() {
1142   start_game();
1143   identification.clear(); changes = 0;
1144 
1145   manual_celllister cl;
1146   cl.add(currentmap->gamestart());
1147 
1148   int chg = -1;
1149   while(changes > chg) {
1150     changes = chg;
1151 
1152     set<int> masters_analyzed;
1153 
1154     for(int i=0; i<isize(cl.lst); i++) {
1155       auto c = cl.lst[i];
1156       auto& id = get_identification(c);
1157 
1158       if(masters_analyzed.count(id.target)) continue;
1159       masters_analyzed.insert(id.target);
1160 
1161       cellwalker cw0(c, id.shift);
1162       cellwalker cws(id.sample, 0);
1163 
1164       for(int i=0; i<c->type; i++) {
1165         if(1) {
1166           indenter ind(2);
1167           be_identified(cw0 + i + wstep, cws + i + wstep);
1168           be_identified(cw0 + i + wstep, cw0 + i + id.modval + wstep);
1169           }
1170 
1171         if(1) {
1172           indenter ind(2);
1173           auto cwx = cw0 + i + wstep;
1174 
1175           auto idx = get_identification(cwx.at);
1176           cellwalker xsample(idx.sample, cwx.spin);
1177           xsample -= idx.shift;
1178 
1179           be_identified(cwx + wstep, xsample + wstep);
1180 
1181           cl.add((cw0 + i).cpeek());
1182           }
1183         }
1184       }
1185     }
1186 
1187   vector<int> old_shvids;
1188   map<int, int> old_to_new;
1189   for(auto id: identification)
1190     if(id.first == id.second.target) {
1191       old_to_new[id.first] = isize(old_shvids);
1192       old_shvids.push_back(id.first);
1193       }
1194 
1195   auto& ac = arb::current;
1196   ac.order++;
1197   ac.comment = ac.filename = "converted from: " + full_geometry_name();
1198   ac.cscale = cgi.scalefactor;
1199   ac.boundary_ratio = 1;
1200   ac.floor_scale = cgi.hexvdist / cgi.scalefactor;
1201   ac.range = cgi.base_distlimit;
1202   int N = isize(old_shvids);
1203   ac.shapes.clear();
1204   ac.shapes.resize(N);
1205 
1206   ginf[gArbitrary].g = cginf.g;
1207   ginf[gArbitrary].flags = cgflags & qBOUNDED;
1208 
1209   for(int i=0; i<N; i++) {
1210     auto id = identification[old_shvids[i]];
1211     cell *s = id.sample;
1212     auto& sh = ac.shapes[i];
1213     sh.id = i;
1214     int t = s->type;
1215     sh.vertices.clear();
1216     sh.connections.clear();
1217     sh.repeat_value = id.modval;
1218     for(int j=0; j<t; j++) {
1219       auto co = currentmap->get_corner(s, j);
1220       sh.vertices.push_back(co);
1221       cellwalker cw(s, j);
1222       cw += wstep;
1223       auto idx = get_identification(cw.at);
1224       cellwalker xsample(idx.sample, cw.spin);
1225       xsample -= idx.shift;
1226       sh.connections.emplace_back(arb::connection_t{old_to_new.at(idx.target), xsample.spin, false});
1227       }
1228     sh.stretch_shear.resize(t, make_pair(1, 0));
1229     sh.edges.clear();
1230     for(int j=0; j<t-1; j++)
1231       sh.edges.push_back(hdist(sh.vertices[j], sh.vertices[j+1]));
1232     sh.edges.push_back(hdist(sh.vertices[t-1], sh.vertices[0]));
1233 
1234     sh.angles.clear();
1235     for(int j=0; j<t; j++) {
1236       hyperpoint v0 = sh.vertices[j];
1237       hyperpoint v1 = sh.vertices[(j+1) % t];
1238       hyperpoint v2 = sh.vertices[(j+2) % t];
1239       transmatrix T = gpushxto0(v1);
1240       v0 = T * v0;
1241       v2 = T * v2;
1242       ld alpha = atan2(v0) - atan2(v2);
1243       while(alpha > M_PI) alpha -= 360*degree;
1244       while(alpha < -M_PI) alpha += 360*degree;
1245       sh.angles.push_back(alpha);
1246       }
1247     if(debugflags & DF_GEOM) {
1248       println(hlog, "shape ", i, ":");
1249       indenter indp(2);
1250       println(hlog, "vertices=", sh.vertices);
1251       println(hlog, "connections=", sh.connections);
1252       println(hlog, "edges=", sh.edges);
1253       println(hlog, "angles=", sh.angles);
1254       }
1255     }
1256 
1257   arb::compute_vertex_valence();
1258   }
1259 
in()1260 EX bool in() {
1261   return arb::in() && base_geometry != gArbitrary;
1262   }
1263 
1264 /** activate the converted tessellation */
activate()1265 EX void activate() {
1266   if(geometry != gArbitrary) {
1267     base_geometry = geometry;
1268     base_variation = variation;
1269     stop_game();
1270     geometry = gArbitrary;
1271     variation = eVariation::pure;
1272     }
1273   }
1274 
1275 EX }
1276 
1277 #if CAP_COMMANDLINE
readArgs()1278 int readArgs() {
1279   using namespace arg;
1280 
1281   if(0) ;
1282   else if(argis("-tes") || argis("-arbi")) {
1283     PHASEFROM(2);
1284     shift();
1285     run(args());
1286     }
1287   else if(argis("-arb-legacy")) {
1288     legacy = true;
1289     }
1290   else if(argis("-arb-convert")) {
1291     try {
1292       convert::convert();
1293       set_geometry(gArbitrary);
1294       }
1295     catch(hr_exception& e) {
1296       println(hlog, "failed to convert: ", e.what());
1297       }
1298     }
1299   else if(argis("-arb-slider")) {
1300     PHASEFROM(2);
1301     shift();
1302     string slider = args();
1303     bool found = true;
1304     for(auto& sl: current.sliders)
1305       if(sl.name == slider) {
1306         shift_arg_formula(sl.current, sliders_changed);
1307         found = true;
1308         }
1309     if(!found) {
1310       println(hlog, "warning: no slider named ", slider, " found");
1311       shift();
1312       }
1313     }
1314   else return 1;
1315   return 0;
1316   }
1317 
1318 auto hook = addHook(hooks_args, 100, readArgs);
1319 #endif
1320 
in()1321 EX bool in() { return geometry == gArbitrary; }
1322 
1323 EX string tes = "tessellations/sample/marjorie-rice.tes";
1324 
linespattern(cell * c)1325 EX bool linespattern(cell *c) {
1326   return current.shapes[id_of(c->master)].flags & arcm::sfLINE;
1327   }
1328 
pseudohept(cell * c)1329 EX bool pseudohept(cell *c) {
1330   if(!current.have_ph) return true;
1331   return current.shapes[id_of(c->master)].flags & arcm::sfPH;
1332   }
1333 
choose()1334 EX void choose() {
1335   dialog::openFileDialog(tes, XLAT("open a tiling"), ".tes",
1336   [] () {
1337     run(tes);
1338     return true;
1339     });
1340   }
1341 
1342 #if MAXMDIM >= 4
__anone2326bbb0702null1343 auto hooksw = addHook(hooks_swapdim, 100, [] {
1344   for(auto& p: {&current, &slided})
1345     for(auto& s: p->shapes)
1346       for(auto& v: s.vertices)
1347         swapmatrix(v);
1348   for(auto& p: altmap) for(auto& pp: p.second) swapmatrix(pp.second);
1349   for(auto& p: arbi_matrix) swapmatrix(p.second.second);
1350   });
1351 #endif
1352 
1353 EX }
1354 }
1355