1 // Hyperbolic Rogue -- Archimedean Tilings
2 // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file archimedean.cpp
5  *  \brief Archimedean tilings
6  *
7  *  These are tilings available in the 'Archimedean' option in Geometry Experiments; simpler Archimedean tilings are defined in other files.
8  */
9 
10 #include "hyper.h"
11 namespace hr {
12 
13 EX namespace arcm {
14 
in()15 EX bool in() { return cgflags & qARCHI; }
16 
17 EX ld euclidean_edge_length = .5;
18 
19 #if HDR
20 struct hr_archimedean_error : hr_exception {
hr_archimedean_errorhr::arcm::hr_archimedean_error21   hr_archimedean_error(string _s) : hr_exception(_s) {}
22   };
23 
24 struct archimedean_tiling {
25 
26   int coloring;
27 
28   string symbol;
29 
30   vector<int> faces;
31   vector<int> adj;
32   vector<bool> invert;
33   vector<int> nflags;
34 
35   bool have_ph, have_line, have_symmetry;
36   int real_faces;
37   int real_face_type;
38 
39   int repetition;
40   int N;
41 
42   bool regular;
43 
44   ld euclidean_angle_sum;
45 
46   vector<int> flags;
47 
48   vector<vector<pair<int, int>>> adjacent;
49   vector<vector<pair<ld, ld>>> triangles;
50 
51   void make_match(int a, int i, int b, int j);
52   void prepare();
53   void compute_sum();
54   void compute_geometry();
55 
56   void parse();
parsehr::arcm::archimedean_tiling57   void parse(string s) { symbol = s; parse(); }
58 
59   ld edgelength;
60 
61   vector<ld> inradius, circumradius, alphas;
62 
63   vector<vector<int>> matches;
64   vector<int> periods;
65   vector<int> tilegroup;
66   vector<int> groupoffset;
67   int tilegroups;
68 
69   int errors;
70   string errormsg;
71 
72   pair<int, int>& get_adj(heptagon *h, int cid);
get_adjhr::arcm::archimedean_tiling73   pair<int, int>& get_adj(heptspin hs) { return get_adj(hs.at, hs.spin); }
74   pair<ld, ld>& get_triangle(heptagon *h, int cid);
get_trianglehr::arcm::archimedean_tiling75   pair<ld, ld>& get_triangle(heptspin hs) { return get_triangle(hs.at, hs.spin); }
76   pair<ld, ld>& get_triangle(const pair<int, int>& p, int delta = 0);
77   pair<int, int>& get_adj(const pair<int, int>& p, int delta = 0);
78 
79   int support_threecolor();
80   int support_threecolor_bitruncated();
81   int support_football();
82   bool support_chessboard();
83   void regroup();
84   string world_size();
85   void get_nom_denom(int& anom, int& adenom);
86 
87   geometryinfo1& get_geometry(ld mul = 1);
get_classhr::arcm::archimedean_tiling88   eGeometryClass get_class() { return get_geometry().kind; }
89 
90   bool get_step_values(int& steps, int& single_step);
91 
92   transmatrix adjcell_matrix(heptagon *h, int d);
93 
94   ld scale();
95   };
96 #endif
97 
98 #if HDR
99 static const int sfPH = 1;
100 static const int sfLINE = 2;
101 static const int sfCHESS = 4;
102 static const int sfTHREE = 8;
103 static const int sfSEMILINE = 16;
104 #endif
105 
106 #if CAP_ARCM
107 
108 EX archimedean_tiling current;
109 EX archimedean_tiling fake_current;
110 
current_or_fake()111 EX archimedean_tiling& current_or_fake() {
112   if(fake::in()) return fake_current;
113   return current;
114   }
115 
116 /** id of vertex in the archimedean tiling
117  *  odd numbers = reflected tiles
118  *  0, 2, ..., 2(N-1) = as in the symbol
119  *  2N = bitruncated tile
120  */
121 
id_of(heptagon * h)122 EX short& id_of(heptagon *h) {
123   return h->zebraval;
124   }
125 
126 /** which index in id_of's neighbor list does h->move(0) have */
127 
parent_index_of(heptagon * h)128 EX short& parent_index_of(heptagon *h) {
129   return h->emeraldval;
130   }
131 
132 /** total number of neighbors */
133 
neighbors_of(heptagon * h)134 EX int neighbors_of(heptagon *h) {
135   return isize(current.triangles[id_of(h)]);
136   }
137 
gcd(int x,int y)138 EX int gcd(int x, int y) { return x ? gcd(y%x, x) : y < 0 ? -y : y; }
139 
make_match(int a,int i,int b,int j)140 void archimedean_tiling::make_match(int a, int i, int b, int j) {
141   if(isize(adjacent[a]) != isize(adjacent[b])) {
142     DEBB(DF_GEOM, ("(error here)"));
143     errormsg = XLAT("polygons match incorrectly");
144     errors++;
145     }
146   if(matches[a][b] == -1)
147     matches[a][b] = j - i, matches[b][a] = i - j;
148   else
149     periods[a] = periods[b] = gcd(matches[a][b] - (j-i), periods[a]);
150   }
151 
152 /** mostly to protect the user from entering too large numbers */
153 const int MAX_EDGE_ARCM = FULL_EDGE;
154 
compute_sum()155 void archimedean_tiling::compute_sum() {
156   N = isize(faces);
157   euclidean_angle_sum = 0;
158   for(int f: faces) euclidean_angle_sum += (f-2.) / f;
159 
160   real_faces = 0, real_face_type = 0;
161   for(int i=0; i<N; i++) if(faces[i] > 2) real_faces++, real_face_type += faces[i];
162   real_face_type /= 2;
163   }
164 
prepare()165 void archimedean_tiling::prepare() {
166 
167   compute_sum();
168 
169   for(int i: faces) if(i > MAX_EDGE_ARCM) {
170     errormsg = XLAT("currently no more than %1 edges", its(MAX_EDGE_ARCM));
171     errors++;
172     return;
173     }
174   if(isize(faces) > MAX_EDGE_ARCM/2) {
175     errormsg = XLAT("currently no more than %1 faces in vertex", its(MAX_EDGE_ARCM/2));
176     errors++;
177     return;
178     }
179 
180   for(int i: faces) if(i < 2) {
181     errormsg = XLAT("not enough edges");
182     errors++;
183     return;
184     }
185 
186   vector<int> nondigonal;
187   for(int i: faces) if(i > 2) nondigonal.push_back(i);
188 
189   if(isize(faces) < 2 || isize(nondigonal) == 1) {
190     errormsg = XLAT("not enough faces");
191     errors++;
192     return;
193     }
194 
195   if(isize(nondigonal) == 2 && faces[0] != faces[1]) {
196     errormsg = XLAT("invalid dihedron");
197     errors++;
198     return;
199     }
200 
201   for(int i=0; i<N; i++) {
202     bool forward = false;
203     int f = faces[i];
204     int i0 = i;
205     for(int k=0; k<f; k++) {
206       forward ^= invert[i0];
207       i0 = adj[i0];
208       if(forward) { if(faces[i0] != f) { errormsg = XLAT("face mismatch"); errors++; return; } i0++; if(i0 == N) i0 = 0; }
209       else { if(i0 == 0) i0 = N; i0--; if(faces[i0] != f) { errormsg = XLAT("face mismatch"); errors++; return; } }
210       }
211     for(int k=0; k<N; k++) {
212       f = faces[(i+N-k-1) % N];
213       if(forward) { if(faces[i0] != f) { errormsg = XLAT("face mismatch II "); errors++; return; } i0++; if(i0 == N) i0 = 0; }
214       else { if(i0 == 0) i0 = N; i0--; if(faces[i0] != f) { errormsg = XLAT("face mismatch II"); errors++; return; } }
215       }
216     }
217 
218   if(real_faces) {
219     for(int i=1; i<isize(faces); i++) if(faces[i] == 2 && faces[i-1] == 2) {
220       errormsg = XLAT("Not implemented.");
221       errors++;
222       return;
223       }
224     if(faces[0] == 2 && faces[isize(faces)-1] == 2) {
225       errormsg = XLAT("Not implemented.");
226       errors++;
227       return;
228       }
229     }
230 
231   if(real_faces == 2) {
232     for(int i: faces) if(i != real_face_type) {
233       errormsg = XLAT("polygons match incorrectly");
234       errors++;
235       }
236     }
237 
238   errors = 0;
239 
240   /* build the 'adjacent' table */
241 
242   int M = 2 * N + 2;
243   adjacent.clear();
244   adjacent.resize(M);
245 
246   have_symmetry = false;
247   for(int i=0; i<N; i++) if(invert[i]) have_symmetry = true;
248 
249   matches.resize(M);
250   for(int i=0; i<M; i++) {
251     matches[i].resize(M);
252     for(int j=0; j<M; j++) matches[i][j] = i==j ? 0 : -1;
253     }
254 
255   periods.resize(M);
256   for(int i=0; i<M; i++) periods[i] = i<2*N ? faces[i/2] : N;
257 
258   for(int i=0; i<N; i++) {
259     for(int oi=0; oi<1; oi++) {
260       int at = (i+oi)%N;
261       int inv = oi;
262       DEBB0(DF_GEOM, ("vertex "));
263       for(int z=0; z<faces[i]; z++) {
264         DEBB0(DF_GEOM, (format("[%d %d] " , at, inv)));
265         adjacent[2*i+oi].emplace_back(2*N+int(inv), inv ? (2*at+2*N-2) % (2*N) : 2*at);
266         if(invert[at]) inv ^= 1;
267         at = adj[at];
268         if(inv) at = (at+1) % N;
269         else at = (at+N-1) % N;
270         }
271       if(!inv) make_match(2*i, 0, inv ? (2*at+2*N-1) % 2*N : 2*at, 0);
272       DEBB(DF_GEOM, (format("-> [%d %d]\n", at, inv)));
273       }
274     }
275   for(int i=0; i<N; i++) {
276     adjacent[2*N].emplace_back(2*i, 0);
277     int ai = (i+1) % N;
278     adjacent[2*N].emplace_back(2*N+int(invert[ai]), (2*adj[ai]+2*N-1) % (2*N));
279     }
280 
281   for(int d=0; d<=2*N; d+=2) {
282     int s = isize(adjacent[d]);
283     for(int i=0; i<s; i++) {
284       auto& orig = adjacent[d][s-1-i];
285       adjacent[d+1].emplace_back(orig.first ^ 1, orig.second);
286       }
287     }
288   for(int d=0; d<M; d++) {
289     int s = isize(adjacent[d]);
290     for(int i=0; i<s; i++) {
291       auto& orig = adjacent[d][i];
292       if(orig.first & 1) orig.second = isize(adjacent[orig.first]) - 1 - orig.second;
293       }
294     }
295 
296   if(debugflags & DF_GEOM) {
297     for(int i=0; i<M; i++) {
298       DEBB0(DF_GEOM, ("adjacent ", i, ":"));
299       for(int j=0; j<isize(adjacent[i]); j++) {
300         auto p = adjacent[i][j];
301         DEBB0(DF_GEOM, (" ", p));
302         }
303       DEBB(DF_GEOM, ("\n"));
304       }
305     }
306 
307   for(int i=0; i<M; i++) {
308     for(int j=0; j<isize(adjacent[i]); j++) {
309       auto p = adjacent[i][j];
310       auto q = adjacent[p.first][p.second];
311       make_match(i, j, q.first, q.second);
312       }
313     }
314 
315   /* verify all the triangles */
316   for(int i=0; i<M; i++) {
317     for(int j=0; j<isize(adjacent[i]); j++) {
318       int ai = i, aj = j;
319       DEBB0(DF_GEOM, ("triangle "));
320       for(int s=0; s<3; s++) {
321         DEBB0(DF_GEOM, (format("[%d %d] ", ai, aj)));
322         tie(ai, aj) = adjacent[ai][aj];
323         aj++; if(aj >= isize(adjacent[ai])) aj = 0;
324         }
325       DEBB(DF_GEOM, (format("-> [%d %d]\n", ai, aj)));
326       make_match(i, j, ai, aj);
327       }
328     }
329 
330   for(int i=0; i<2*N; i++) {
331     for(int j=0; j<isize(adjacent[i]); j++) {
332       auto aa = make_pair(i, j);
333       aa = get_adj(aa, 1);
334       aa = get_adj(aa, 2);
335       aa = get_adj(aa, 1);
336       aa = get_adj(aa, 2);
337       make_match(i, j, aa.first, aa.second);
338       }
339     }
340 
341   regroup();
342   }
343 
regroup()344 void archimedean_tiling::regroup() {
345   int M = 2 * N + 2;
346   for(int i=0; i<M; i++) for(int j=0; j<M; j++) if(matches[i][j] != -1)
347   for(int l=0; l<M; l++) for(int k=0; k<M; k++) if(matches[j][k] != -1) {
348     make_match(i, 0, k, matches[i][j] + matches[j][k]);
349     make_match(i, 0, k, matches[i][j] + matches[j][k] + gcd(periods[i], periods[j]));
350     }
351   tilegroup.clear();
352   tilegroup.resize(M, -1);
353   groupoffset.resize(M);
354   tilegroups = 0;
355   for(int i=0; i<M; i+=(have_symmetry?1:2)) if(tilegroup[i] == -1) {
356     if(periods[i] < 0) periods[i] = -periods[i];
357     for(int j=0; j<M; j++) if(matches[i][j] != -1)
358       tilegroup[j] = tilegroups, groupoffset[j] = matches[i][j] % periods[i];
359     tilegroups++;
360     }
361 
362   flags.clear();
363   flags.resize(M, 0);
364   for(int i=0; i<M; i++)
365   for(int j=0; j<M; j++) {
366     if(tilegroup[i] == tilegroup[j]) {
367       flags[i] |= nflags[j/2];
368       if(j%2 == 1 && (nflags[j/2] & sfSEMILINE))
369         flags[i] |= sfLINE;
370       }
371     }
372 
373   if(!have_ph) {
374     for(int i=0; i<M; i++) if(tilegroup[i] == 0) flags[i] |= sfPH;
375     }
376 
377   if(debugflags & DF_GEOM) {
378     for(int i=0; i<M; i+=(have_symmetry?1:2)) {
379       DEBB(DF_GEOM, (format("tiling group of %2d: [%2d]%2d+Z%2d\n", i, tilegroup[i], groupoffset[i], periods[i])));
380       }
381     }
382   }
383 
get_geometry(ld mul)384 geometryinfo1& archimedean_tiling::get_geometry(ld mul) {
385   if(euclidean_angle_sum * mul < 1.999999) return ginf[gSphere].g;
386   else if(euclidean_angle_sum * mul > 2.000001) return ginf[gNormal].g;
387   else return ginf[gEuclid].g;
388   }
389 
compute_geometry()390 void archimedean_tiling::compute_geometry() {
391   ginf[gArchimedean].g = get_geometry();
392   set_flag(ginf[gArchimedean].flags, qBOUNDED, get_class() == gcSphere);
393 
394   DEBB(DF_GEOM, (format("euclidean_angle_sum = %f\n", float(euclidean_angle_sum))));
395 
396   bool infake = fake::in();
397 
398   dynamicval<eGeometry> dv(geometry, gArchimedean);
399 
400   /* compute the geometry */
401   inradius.resize(N+1); inradius[N] = 0;
402   circumradius.resize(N+1); circumradius[N] = 0;
403   alphas.resize(N);
404 
405   ld total = M_PI;
406 
407   dynamicval<geometryinfo1> dgi(ginf[geometry].g, ginf[geometry].g);
408 
409   if(infake) {
410     total *= N / fake::around;
411     ginf[geometry].g = get_geometry(fake::around / N);
412     }
413 
414   ld elmin = 0, elmax = hyperbolic ? 10 : sphere ? M_PI : 2 * euclidean_edge_length;
415 
416   /* inradius[N] is used in farcorner and nearcorner. Probably a bug */
417 
418   if(real_faces == 2) {
419     /* standard methods fail for dihedra, but the answer is easy */
420     edgelength = 2 * M_PI / faces[0];
421     for(int i=0; i<N; i++)
422       if(faces[i] == 2)
423         alphas[i] = 0,
424         circumradius[i] = M_PI / real_face_type,
425         inradius[i] = 0;
426       else
427         alphas[i] = M_PI/2,
428         circumradius[i] = inradius[i] = M_PI/2;
429     }
430   else if(real_faces == 0) {
431     // these are called hosohedra
432     edgelength = M_PI;
433     for(int i=0; i<N; i++)
434       alphas[i] = M_PI / N,
435       circumradius[i] = M_PI/2,
436       inradius[i] = 0;
437     }
438   else for(int p=0; p<100; p++) {
439     edgelength = (elmin + elmax) / 2;
440 
441     ld alpha_total = 0;
442 
443     for(int i=0; i<N; i++) {
444 
445       ld gamma = M_PI / faces[i];
446 
447       auto& c = circumradius[i];
448 
449       c = asin_auto(sin_auto(edgelength/2) / sin(gamma));
450       inradius[i] = hdist0(mid(xpush0(circumradius[i]), xspinpush0(2*gamma, circumradius[i])));
451 
452       hyperpoint h = xpush(c) * spin(M_PI - 2*gamma) * xpush0(c);
453       ld a = atan2(h);
454       cyclefix(a, 0);
455       if(a < 0) a = -a;
456       alphas[i] = a;
457       alpha_total += alphas[i];
458       }
459 
460     if(debugflags & DF_GEOM)
461     if(p < 10 || p == 99)
462       println(hlog, "edgelength = ", edgelength, " angles = ", alphas, " inradius = ", inradius, " circumradius = ", circumradius);
463 
464     if(isnan(alpha_total)) elmax = edgelength;
465     else if(sphere ^ (alpha_total > total)) elmin = edgelength;
466     else elmax = edgelength;
467     if(euclid) break;
468     }
469 
470   DEBB(DF_GEOM, (format("computed edgelength = %f\n", float(edgelength))));
471 
472   triangles.clear();
473   triangles.resize(2*N+2);
474   for(int i=0; i<N; i++) for(int j=0; j<2; j++)
475     for(int k=0; k<faces[i]; k++)
476       triangles[2*i+j].emplace_back(2*M_PI/faces[i], circumradius[i]);
477 
478   for(int k=0; k<N; k++) {
479     triangles[2*N].emplace_back(alphas[k], circumradius[k]);
480     triangles[2*N].emplace_back(alphas[(k+1)%N], edgelength);
481     triangles[2*N+1].emplace_back(alphas[N-1-k], edgelength);
482     triangles[2*N+1].emplace_back(alphas[N-1-k], circumradius[N-1-k]);
483     }
484 
485   for(auto& ts: triangles) {
486     ld total = 0;
487     for(auto& t: ts) tie(t.first, total) = make_pair(total, total + t.first);
488     }
489 
490   if(debugflags & DF_GEOM) for(auto& ts: triangles) {
491     DEBB0(DF_GEOM, ("T"));
492     for(auto& t: ts) DEBB0(DF_GEOM, (format(" %f@%f", float(t.first), float(t.second))));
493     DEBB(DF_GEOM, ());
494     }
495 
496   regular = true;
497   for(int i: faces) if(i != faces[0]) regular = false;
498   }
499 
scale()500 ld archimedean_tiling::scale() {
501   if(real_faces == 0 && N == 2) return M_PI / 2;
502   if(real_faces == 2) return M_PI / 2;
503   if(real_faces == 0) return 2 * M_PI / N;
504   return edgelength;
505   }
506 
507 map<heptagon*, vector<pair<heptagon*, transmatrix> > > altmap;
508 
509 EX map<heptagon*, pair<heptagon*, transmatrix>> archimedean_gmatrix;
510 
511 EX hrmap *current_altmap;
512 
513 heptagon *build_child(heptspin p, pair<int, int> adj);
514 
515 bool skip_digons(heptspin hs, int step);
516 void connect_digons_too(heptspin h1, heptspin h2);
517 void fixup_matrix(transmatrix& T, const transmatrix& X, ld step);
518 void connectHeptagons(heptspin hi, heptspin hs);
519 
520 /** @brief should we use gmatrix to compute relative_matrix faster? (not while in fake Archimedean) */
521 EX bool use_gmatrix = true;
522 
523 /** @brief like adj, but in pure
524  *  not used by arcm itself, but used in fake arcm
525  */
526 
527 EX geometry_information *alt_cgip;
528 
find_alt_cgip()529 EX geometry_information *find_alt_cgip() {
530   if(alt_cgip) return alt_cgip;
531   check_cgi();
532   cgi.require_basics();
533   return alt_cgip = cgip;
534   }
535 
536 struct hrmap_archimedean : hrmap {
537   map<gp::loc, struct cdata> eucdata;
538   heptagon *origin;
getOriginhr::arcm::hrmap_archimedean539   heptagon *getOrigin() override { return origin; }
540 
hrmap_archimedeanhr::arcm::hrmap_archimedean541   hrmap_archimedean() {
542     dynamicval<hrmap*> curmap(currentmap, this);
543     int id = DUAL ? current.N * 2 : 0;;
544     int N0 = isize(current.adjacent[id]);
545     origin = init_heptagon(N0);
546     origin->s = hsOrigin;
547 
548     parent_index_of(origin) = DUAL ? 1 : 0;
549     id_of(origin) = id;
550     origin->c7 = newCell(N0/DUALMUL, origin);
551 
552     heptagon *alt = NULL;
553 
554     if(hyperbolic) {
555       dynamicval<eGeometry> g(geometry, gNormal);
556       dynamicval<eVariation> gv(variation, eVariation::pure);
557       dynamicval<geometry_information*> gi(cgip, find_alt_cgip());
558       alt = init_heptagon(S7);
559       alt->s = hsOrigin;
560       alt->alt = alt;
561       current_altmap = newAltMap(alt);
562       }
563 
564     transmatrix T = xpush(.01241) * spin(1.4117) * xpush(0.1241) * Id;
565     archimedean_gmatrix[origin] = make_pair(alt, T);
566     altmap[alt].emplace_back(origin, T);
567 
568     if(current.real_faces == 0 && DUAL) {
569       heptspin hs(origin, 0);
570       heptagon *hnew = build_child(hs, current.get_adj(hs));
571       for(int s=1; s<2*current.N; s++)
572         origin->c.connect(s, hnew, s, false);
573       }
574     else if(current.real_faces == 0) {
575       may_create_step(origin, 0);
576       heptagon *o0 = origin->move(0);
577       may_create_step(origin, 1);
578       heptagon *o1 = origin->move(1);
579       for(int s=1; s<2*current.N; s+=2)
580         o0->c.connect(s, o1, 2*current.N-s, false);
581       for(int s=2; s<2*current.N; s+=2) {
582         heptspin hs(o0, s);
583         heptagon *hnew = build_child(hs, current.get_adj(hs));
584         // no need to specify archimedean_gmatrix and altmap
585         hnew->c.connect(1, heptspin(o1, 2*current.N-s));
586         }
587       o1->c.connect(1, o0, 2*current.N-1, false);
588       }
589     else if(origin->degree() == 2) {
590       may_create_step(origin, 0);
591       may_create_step(origin, 1);
592       origin->move(0)->c.connect(1, origin->move(1), 2*current.N-1, false);
593       origin->move(1)->c.connect(1, origin->move(0), 2*current.N-1, false);
594       }
595 
596     auto_compute_range(origin->c7);
597     }
598 
~hrmap_archimedeanhr::arcm::hrmap_archimedean599   ~hrmap_archimedean() {
600     clearfrom(origin);
601     altmap.clear();
602     archimedean_gmatrix.clear();
603     if(current_altmap) {
604       dynamicval<eGeometry> g(geometry, gNormal);
605       dynamicval<eVariation> gv(variation, eVariation::pure);
606       dynamicval<geometry_information*> gi(cgip, find_alt_cgip());
607       delete current_altmap;
608       current_altmap = NULL;
609       }
610     }
verifyhr::arcm::hrmap_archimedean611   void verify() override { }
612 
create_stephr::arcm::hrmap_archimedean613   heptagon *create_step(heptagon *h, int d) override {
614 
615     DEBB(DF_GEOM, (heptspin(h,d), " ~ ?"));
616 
617     dynamicval<geometryinfo1> gi(ginf[geometry].g, ginf[gArchimedean].g);
618 
619     heptspin hi(h, d);
620 
621     while(skip_digons(hi, 1)) hi++;
622 
623     auto& t1 = current.get_triangle(hi);
624 
625     // * spin(-tri[id][pi+i].first) * xpush(t.second) * pispin * spin(tri[id'][p'+d'].first)
626 
627     auto& p1 = archimedean_gmatrix[h];
628 
629     heptagon *alt = p1.first;
630 
631     transmatrix T = p1.second * spin(-t1.first) * xpush(t1.second);
632     transmatrix U = Id;
633 
634     if(hyperbolic) {
635       dynamicval<eGeometry> g(geometry, gNormal);
636       dynamicval<eVariation> gv(variation, eVariation::pure);
637       dynamicval<geometry_information*> gi(cgip, find_alt_cgip());
638       dynamicval<hrmap*> cm(currentmap, current_altmap);
639       U = T;
640       current_altmap->virtualRebase(alt, T);
641       U = U * iso_inverse(T);
642       }
643 
644     if(euclid) {
645       /* hash the rough coordinates as heptagon* alt */
646       size_t s = size_t(T[0][LDIM]+.261) * 124101 + size_t(T[1][LDIM]+.261) * 82143;
647       alt = (heptagon*) s;
648       }
649 
650     DEBB(DF_GEOM, ("look for: ", alt, " / ", T * C0));
651 
652     for(auto& p2: altmap[alt]) if(intval(p2.second * C0, T * C0) < 1e-4) {
653       DEBB(DF_GEOM, ("cell found: ", p2.first));
654       for(int d2=0; d2<p2.first->degree(); d2++) {
655         heptspin hs(p2.first, d2);
656         auto& t2 = current.get_triangle(p2.first, d2);
657         transmatrix T1 = T * spin(M_PI + t2.first);
658         DEBB(DF_GEOM, ("compare: ", T1 * xpush0(1), ":: ", p2.second * xpush0(1)));
659         if(intval(T1 * xpush0(1), p2.second * xpush0(1)) < 1e-4) {
660 
661           // T1 = p2.second
662           // T * spin(pi+t2.first) == p2.second
663           // p1.second * spinm(-t1.first) * xpush(t1.second) * spin(pi+t2.first) == p2.second
664 
665           // bring p1 and p2 closer, to prevent floating point errors
666           if(hyperbolic) {
667             fixup_matrix(p1.second, U * p2.second * spin(-M_PI - t2.first) * xpush(-t1.second) * spin(t1.first), 0.25);
668             fixup_matrix(p2.second, T1, 0.25);
669             }
670 
671           while(skip_digons(hs, -1)) hs--;
672           connectHeptagons(hi, hs);
673           connect_digons_too(hi, hs);
674           return h->move(d);
675           }
676         }
677       DEBB(DF_GEOM, ("but rotation not found"));
678       }
679 
680     auto& t2 = current.get_triangle(current.get_adj(hi));
681     transmatrix T1 = T * spin(M_PI + t2.first);
682     fixmatrix(T1);
683 
684     heptagon *hnew = build_child(hi, current.get_adj(hi));
685     altmap[alt].emplace_back(hnew, T1);
686     archimedean_gmatrix[hnew] = make_pair(alt, T1);
687     connect_digons_too(hi, heptspin(hnew, 0));
688 
689     return hnew;
690     }
691 
draw_athr::arcm::hrmap_archimedean692   void draw_at(cell *at, const shiftmatrix& where) override {
693     dq::clear_all();
694     dq::enqueue(at->master, where);
695 
696     while(!dq::drawqueue.empty()) {
697       auto& p = dq::drawqueue.front();
698       heptagon *h = p.first;
699       shiftmatrix V = p.second;
700       dq::drawqueue.pop();
701 
702       int id = id_of(h);
703       int S = isize(current.triangles[id]);
704 
705       if(id < 2*current.N ? !DUAL : !PURE) {
706         if(!do_draw(h->c7, V)) continue;
707         drawcell(h->c7, V);
708         }
709 
710       for(int i=0; i<S; i++) {
711         if(DUAL && (i&1)) continue;
712         h->cmove(i);
713         if(PURE && id >= 2*current.N && h->move(i) && id_of(h->move(i)) >= 2*current.N) continue;
714         shiftmatrix V1 = V * current.adjcell_matrix(h, i);
715         optimize_shift(V1);
716         dq::enqueue(h->move(i), V1);
717         }
718       }
719     }
720 
adjhr::arcm::hrmap_archimedean721   transmatrix adj(cell *c, int dir) override {
722     return calc_relative_matrix(c->cmove(dir), c, C0);
723     }
724 
relative_matrixhhr::arcm::hrmap_archimedean725   transmatrix relative_matrixh(heptagon *h2, heptagon *h1, const hyperpoint& hint) override {
726     if(use_gmatrix && gmatrix0.count(h2->c7) && gmatrix0.count(h1->c7))
727       return inverse_shift(gmatrix0[h1->c7], gmatrix0[h2->c7]);
728     transmatrix gm = Id, where = Id;
729     auto& cof = current_or_fake();
730     while(h1 != h2) {
731       for(int i=0; i<neighbors_of(h1); i++) {
732         if(h1->move(i) == h2) {
733           return gm * cof.adjcell_matrix(h1, i) * where;
734           }
735         }
736       if(h1->distance > h2->distance) {
737         gm = gm * cof.adjcell_matrix(h1, 0);
738         h1 = h1->move(0);
739         }
740       else {
741         where = iso_inverse(cof.adjcell_matrix(h2, 0)) * where;
742         h2 = h2->move(0);
743         }
744       }
745     return gm * where;
746     }
747 
spin_anglehr::arcm::hrmap_archimedean748   ld spin_angle(cell *c, int d) override {
749     auto &cof = current_or_fake();
750     if(PURE) {
751       auto& t1 = cof.get_triangle(c->master, d-1);
752       return -(t1.first + M_PI / c->type);
753       }
754     else if(DUAL) {
755       auto& t1 = cof.get_triangle(c->master, 2*d);
756       return -t1.first;
757       }
758     else { /* BITRUNCATED */
759       auto& t1 = cof.get_triangle(c->master, d);
760       return -t1.first;
761       }
762     }
763 
find_cell_connectionhr::arcm::hrmap_archimedean764   void find_cell_connection(cell *c, int d) override {
765     if(PURE) {
766       if(arcm::id_of(c->master) < arcm::current.N * 2) {
767         heptspin hs = heptspin(c->master, d) + wstep + 2 + wstep + 1;
768         c->c.connect(d, hs.at->c7, hs.spin, hs.mirrored);
769         }
770       else c->c.connect(d, c, d, false);
771       }
772     else if(DUAL) {
773       if(arcm::id_of(c->master) >= arcm::current.N * 2) {
774         heptagon *h2 = createStep(c->master, d*2);
775         int d1 = c->master->c.spin(d*2);
776         c->c.connect(d, h2->c7, d1/2, false);
777         }
778       else {
779         printf("bad connection\n");
780         c->c.connect(d,c,d,false);
781         }
782       }
783     else hrmap::find_cell_connection(c, d);
784     }
785 
shvidhr::arcm::hrmap_archimedean786   int shvid(cell *c) override {
787     auto& ac = arcm::current;
788     int id = arcm::id_of(c->master);
789     if(ac.regular && id>=2 && id < 2*ac.N) id &= 1;
790     return id;
791     }
792 
full_shvidhr::arcm::hrmap_archimedean793   int full_shvid(cell *c) override {
794     return id_of(c->master) + 20 * parent_index_of(c->master);
795     }
796 
get_cornerhr::arcm::hrmap_archimedean797   hyperpoint get_corner(cell *c, int cid, ld cf) override {
798     auto &ac = arcm::current_or_fake();
799     if(PURE) {
800       if(arcm::id_of(c->master) >= ac.N*2) return C0;
801       auto& t = ac.get_triangle(c->master, cid-1);
802       return xspinpush0(-t.first, t.second * 3 / cf * (ac.real_faces == 0 ? 0.999 : 1));
803       }
804     if(BITRUNCATED) {
805       auto& t0 = ac.get_triangle(c->master, cid-1);
806       auto& t1 = ac.get_triangle(c->master, cid);
807       hyperpoint h0 = xspinpush0(-t0.first, t0.second * 3 / cf * (ac.real_faces == 0 ? 0.999 : 1));
808       hyperpoint h1 = xspinpush0(-t1.first, t1.second * 3 / cf * (ac.real_faces == 0 ? 0.999 : 1));
809       return mid3(C0, h0, h1);
810       }
811     if(DUAL) {
812       auto& t0 = ac.get_triangle(c->master, 2*cid-1);
813       return xspinpush0(-t0.first, t0.second * 3 / cf * (ac.real_faces == 0 ? 0.999 : 1));
814       }
815     return C0;
816     }
817 
818   };
819 
new_map()820 EX hrmap *new_map() { return new hrmap_archimedean; }
821 
build_child(heptspin p,pair<int,int> adj)822 heptagon *build_child(heptspin p, pair<int, int> adj) {
823   indenter ind;
824   auto h = buildHeptagon1(tailored_alloc<heptagon> (isize(current.adjacent[adj.first])), p.at, p.spin, hstate(1), 0);
825   DEBB(DF_GEOM, ("NEW ", p, " ~ ", heptspin(h, 0)));
826   id_of(h) = adj.first;
827   parent_index_of(h) = adj.second;
828   int nei = neighbors_of(h);
829   h->c7 = newCell(nei/DUALMUL, h);
830   h->distance = p.at->distance + 1;
831   if(adj.first < 2*current.N && !DUAL) {
832     int s = 0;
833     heptspin hs(p);
834     while(id_of(hs.at->move(0)) >= 2 * current.N) {
835       s += hs.spin / 2 - 1;
836       hs = hs - hs.spin + wstep - 1;
837       }
838     h->fieldval = hs.at->move(0)->fieldval + s + hs.spin/2;
839     }
840   else
841     h->fieldval = -100;
842   h->fiftyval = isize(archimedean_gmatrix);
843   if(p.at->s == hsOrigin)
844     h->rval1 = 1 + (p.spin % 2);
845   else {
846     if(p.spin % 2 == 0)
847       h->rval1 = p.at->move(0)->rval1;
848     else
849       h->rval1 = 3 - p.at->move(0)->rval1 - p.at->rval1;
850     }
851   h->rval0 = hrand(256);
852   heptspin hs(h, 0);
853   return h;
854   }
855 
skip_digons(heptspin hs,int step)856 bool skip_digons(heptspin hs, int step) {
857   return
858     isize(current.adjacent[current.get_adj(hs).first]) == 2 ||
859     isize(current.adjacent[current.get_adj(hs+step).first]) == 2;
860   }
861 
connect_digons_too(heptspin h1,heptspin h2)862 void connect_digons_too(heptspin h1, heptspin h2) {
863   if(skip_digons(h1, -1)) {
864     h1--, h2++;
865     heptagon *hnew = build_child(h1, current.get_adj(h1));
866     // no need to specify archimedean_gmatrix and altmap
867     hnew->c.connect(1, h2);
868     h1--, h2++;
869     DEBB(DF_GEOM, (format("OL2 %p.%d ~ %p.%d\n", hr::voidp(h1.at), h1.spin, hr::voidp(h2.at), h2.spin)));
870     h1.at->c.connect(h1.spin, h2);
871     }
872   }
873 
connectHeptagons(heptspin hi,heptspin hs)874 void connectHeptagons(heptspin hi, heptspin hs) {
875   DEBB(DF_GEOM, ("OLD ", hi, " ~ ", hs));
876   if(hi.at->move(hi.spin) == hs.at && hi.at->c.spin(hi.spin) == hs.spin) {
877     DEBB(DF_GEOM, (format("WARNING: already connected\n")));
878     return;
879     }
880   if(hi.peek()) {
881     DEBB(DF_GEOM, (format("ERROR: already connected left\n")));
882     throw hr_archimedean_error("Archimedean error: already connected left");
883     }
884   if(hs.peek()) {
885     DEBB(DF_GEOM, (format("ERROR: already connected right\n")));
886     throw hr_archimedean_error("Archimedean error: already connected right");
887     }
888   hi.at->c.connect(hi.spin, hs);
889 
890   auto p = current.get_adj(hi);
891   if(current.tilegroup[p.first] != current.tilegroup[id_of(hs.at)]) {
892     printf("should merge %d %d\n", p.first, id_of(hs.at));
893     current.make_match(p.first, p.second, id_of(hs.at), hs.spin + parent_index_of(hs.at));
894     current.regroup();
895     }
896   // heptagon *hnew = build_child(h, d, get_adj(h, d).first, get_adj(h, d).second);
897   }
898 
899 /** T and X are supposed to be equal -- move T so that it is closer to X */
fixup_matrix(transmatrix & T,const transmatrix & X,ld step)900 void fixup_matrix(transmatrix& T, const transmatrix& X, ld step) {
901   for(int i=0; i<MXDIM; i++)
902   for(int j=0; j<MXDIM; j++)
903     T[i][j] = (T[i][j] * (1-step) + X[i][j] * step);
904 
905   /*
906   for(int i=0; i<3; i++)
907   for(int j=0; j<3; j++)
908     if(T[i][j] - X[i][j] > 1e-3) exit(1);
909   */
910   fixmatrix(T);
911   }
912 
get_triangle(heptagon * h,int cid)913 pair<ld, ld>& archimedean_tiling::get_triangle(heptagon *h, int cid) {
914   return triangles[id_of(h)][gmod(parent_index_of(h) + cid, neighbors_of(h))];
915   }
916 
get_adj(heptagon * h,int cid)917 pair<int, int>& archimedean_tiling::get_adj(heptagon *h, int cid) {
918   return adjacent[id_of(h)][gmod(parent_index_of(h) + cid, neighbors_of(h))];
919   }
920 
get_adj(const pair<int,int> & p,int delta)921 pair<int, int>& archimedean_tiling::get_adj(const pair<int, int>& p, int delta) {
922   return adjacent[p.first][gmod(p.second + delta, isize(adjacent[p.first]))];
923   }
924 
get_triangle(const pair<int,int> & p,int delta)925 pair<ld, ld>& archimedean_tiling::get_triangle(const pair<int, int>& p, int delta) {
926   return triangles[p.first][gmod(p.second + delta, isize(adjacent[p.first]))];
927   }
928 
adjcell_matrix(heptagon * h,int d)929 transmatrix archimedean_tiling::adjcell_matrix(heptagon *h, int d) {
930   auto& t1 = get_triangle(h, d);
931 
932   heptagon *h2 = h->move(d);
933 
934   int d2 = h->c.spin(d);
935   auto& t2 = get_triangle(h2, d2);
936 
937   return spin(-t1.first) * xpush(t1.second) * spin(M_PI + t2.first);
938   }
939 
fix(heptagon * h,int spin)940 EX int fix(heptagon *h, int spin) {
941   int type = isize(current.adjacent[id_of(h)]);
942   spin %= type;
943   if(spin < 0) spin += type;
944   return spin;
945   }
946 
parse()947 void archimedean_tiling::parse() {
948   int at = 0;
949 
950   auto peek = [&] () { if(at == isize(symbol)) return char(0); else return symbol[at]; };
951   auto is_number = [&] () { char p = peek(); return p >= '0' && p <= '9'; };
952   auto read_number = [&] () { int result = 0; while(is_number()) result = 10 * result + peek() - '0', at++; return result; };
953 
954   faces.clear(); nflags.clear();
955   have_line = false;
956   have_ph = false;
957   int nflags0 = 0;
958   auto nfback = [this, &nflags0] () -> int& { if(nflags.empty()) return nflags0; else return nflags.back(); };
959   while(true) {
960     if(peek() == ')' || (peek() == '(' && isize(faces)) || peek() == 0) break;
961     else if((peek() == 'L') && faces.size()) {
962       if(!nflags.empty()) nfback() |= sfLINE;
963       have_line = true, at++;
964       }
965     else if((peek() == 'l') && faces.size()) {
966       if(!nflags.empty()) nfback() |= sfSEMILINE;
967       have_line = true, at++;
968       }
969     else if((peek() == 'H' || peek() == 'h') && faces.size()) {
970       if(!nflags.empty()) nfback() |= sfPH;
971       have_ph = true, at++;
972       }
973     else if(is_number()) faces.push_back(read_number()), nflags.push_back(0);
974     else if(peek() == '^' && !faces.empty()) {
975       at++;
976       int rep = read_number();
977       if(rep == 0) nflags.pop_back(), faces.pop_back();
978       for(int i=1; i<rep; i++) nflags.push_back(nfback()), faces.push_back(faces.back());
979       }
980     else at++;
981     }
982   nflags.push_back(nflags0);
983   repetition = 1;
984   N = isize(faces);
985   invert.clear(); invert.resize(N, true);
986   adj.clear(); adj.resize(N, 0); for(int i=0; i<N; i++) adj[i] = i;
987   while(peek() != 0) {
988     if(peek() == '^') at++, repetition = read_number();
989     else if(peek() == '(') {
990       at++; int a = read_number(); while(!is_number() && !among(peek(), '(', '[', ')',']', 0)) at++;
991       if(is_number()) { int b = read_number(); adj[a] = b; adj[b] = a; invert[a] = invert[b] = false; }
992       else { invert[a] = false; }
993       }
994     else if(peek() == '[') {
995       at++; int a = read_number(); while(!is_number() && !among(peek(), '(', '[', ')',']', 0)) at++;
996       if(is_number()) { int b = read_number(); adj[a] = b; adj[b] = a; invert[a] = invert[b] = true; }
997       else { invert[a] = true; }
998       }
999     else at++;
1000     }
1001   for(int i=0; i<N * (repetition-1); i++)
1002     faces.push_back(faces[i]),
1003     nflags.push_back(nflags[i]),
1004     invert.push_back(invert[i]),
1005     adj.push_back(adj[i] + N);
1006   N *= repetition;
1007   prepare();
1008   }
1009 
1010 #if CAP_COMMANDLINE
readArgs()1011 int readArgs() {
1012   using namespace arg;
1013 
1014   if(0) ;
1015   else if(argis("-symbol")) {
1016     PHASEFROM(2);
1017     archimedean_tiling at;
1018     shift(); at.parse(args());
1019     if(at.errors) {
1020       DEBB(DF_ERROR | DF_GEOM, ("error: ", at.errormsg));
1021       }
1022     else {
1023       set_geometry(gArchimedean);
1024       current = at;
1025       showstartmenu = false;
1026       }
1027     }
1028   else if(argis("-dual")) { PHASEFROM(2); set_variation(eVariation::dual); }
1029   else if(argis("-d:arcm"))
1030     launch_dialog(show);
1031   else return 1;
1032   return 0;
1033   }
1034 #endif
1035 
1036 #if CAP_COMMANDLINE
1037 auto hook =
1038   addHook(hooks_args, 100, readArgs)
__anoncb50b44e0502(gamedata* gd) 1039 + addHook(hooks_gamedata, 0, [] (gamedata* gd) { gd->store(altmap); gd->store(archimedean_gmatrix); gd->store(current_altmap); });
1040 
1041 #endif
1042 
1043 #if MAXMDIM >= 4
__anoncb50b44e0602null1044 auto hooksw = addHook(hooks_swapdim, 100, [] {
1045 
1046   dynamicval<eGeometry> g(geometry, gNormal);
1047   dynamicval<eVariation> gv(variation, eVariation::pure);
1048   dynamicval<geometry_information*> gi(cgip, find_alt_cgip());
1049 
1050   for(auto& p: altmap) for(auto& pp: p.second) swapmatrix(pp.second);
1051   for(auto& p: archimedean_gmatrix) swapmatrix(p.second.second);
1052 
1053   alt_cgip = nullptr;
1054   });
1055 #endif
1056 
support_threecolor()1057 int archimedean_tiling::support_threecolor() {
1058   return (isize(faces) == 3 && invert[0] && invert[1] && invert[2] && faces[0] % 2 == 0 && faces[1] % 2 == 0 && faces[2] % 2 == 0) ? 2 :
1059     tilegroup[N*2] > 1 ? 1 :
1060     0;
1061   return 2;
1062   }
1063 
support_threecolor_bitruncated()1064 int archimedean_tiling::support_threecolor_bitruncated() {
1065   for(int i: faces) if(i % 2) return 0;
1066   return 2;
1067   }
1068 
support_football()1069 int archimedean_tiling::support_football() {
1070   return
1071     have_ph ? 1 :
1072     (isize(faces) == 3 && invert[0] && invert[1] && invert[2] && faces[1] % 2 == 0 && faces[2] % 2 == 0) ? 2 :
1073     0;
1074   }
1075 
support_chessboard()1076 bool archimedean_tiling::support_chessboard() {
1077   return N % 2 == 0;
1078   }
1079 
pseudohept(cell * c)1080 EX bool pseudohept(cell *c) {
1081   if(DUAL)
1082     return !(c->master->rval0 & 3);
1083   int id = id_of(c->master);
1084   if(PURE)
1085     return current.flags[id] & arcm::sfPH;
1086   if(BITRUNCATED)
1087     return id < current.N * 2;
1088   return false;
1089   }
1090 
chessvalue(cell * c)1091 EX bool chessvalue(cell *c) {
1092   if(DUAL)
1093     return c->master->rval1 - 1;
1094   return c->master->fieldval & 1;
1095   }
1096 
linespattern(cell * c)1097 EX bool linespattern(cell *c) {
1098   return current.flags[id_of(c->master)] & arcm::sfLINE;
1099   }
1100 
threecolor(cell * c)1101 EX int threecolor(cell *c) {
1102   if(current.have_ph)
1103     return !arcm::pseudohept(c);
1104   else if(PURE)
1105     return current.tilegroup[id_of(c->master)];
1106   else {
1107     int id = id_of(c->master);
1108     if(current.support_threecolor() == 2) return id < current.N * 2 ? (id&1) : 2;
1109     return current.tilegroup[id];
1110     }
1111   }
1112 
1113 int cEucRegular = 0x008000;
1114 int cEucSemiregular = 0x40C040;
1115 int cPlatonic = 0x000080;
1116 int cArchimedean = 0x4040C0;
1117 int cPrism = 0x40A0A0;
1118 int cAntiPrism = 0x80A0A0;
1119 int cHyperRegular = 0x800000;
1120 int cHyperSemi = 0xC04040;
1121 
1122 int cWeird = 0xA000A0;
1123 
1124 EX vector<pair<string, int> > samples = {
1125   /* Euclidean */
1126   {"(3,3,3,3,3,3)", cEucRegular},
1127   {"(4,4,4,4)", cEucRegular},
1128   {"(6,6,6)", cEucRegular},
1129   {"(8,8,4)", cEucSemiregular},
1130   {"(4,6,12)", cEucSemiregular},
1131   {"(6,4,3,4)", cEucSemiregular},
1132   {"(3,6,3,6)", cEucSemiregular},
1133   {"(3,12,12)", cEucSemiregular},
1134   {"(4,4,3L,3L,3L) [3,4]", cEucSemiregular},
1135   {"(3,3,3,3,6) (1,2)(0,4)(3)", cEucSemiregular},
1136   {"(3,3,4,3,4) (0,4)(1)(2,3)", cEucSemiregular},
1137 
1138   /* Platonic */
1139   {"(3,3,3)", cPlatonic},
1140   {"(3,3,3,3)", cPlatonic},
1141   {"(3,3,3,3,3)", cPlatonic},
1142   {"(4,4,4)", cPlatonic},
1143   {"(5,5,5)", cPlatonic},
1144 
1145   /* Archimedean solids */
1146   {"(3,6,6)", cArchimedean},
1147   {"(3,4,3,4)", cArchimedean},
1148   {"(3,8,8)", cArchimedean},
1149   {"(4,6,6)", cArchimedean},
1150   {"(3,4,4,4)", cArchimedean},
1151   {"(4,6,8)", cArchimedean},
1152   {"(3,3,3,3,4) (1,2)(0,4)(3)", cArchimedean},
1153   {"(3,5,3,5)", cArchimedean},
1154   {"(3,10,10)", cArchimedean},
1155   {"(5,6,6)", cArchimedean},
1156   {"(3,4,5,4)", cArchimedean},
1157   {"(4,6,10)", cArchimedean},
1158   {"(3,3,3,3,5) (1,2)(0,4)(3)", cArchimedean},
1159 
1160   /* prisms */
1161   {"(3,4,4)", cPrism},
1162   {"(5,4,4)", cPrism},
1163   {"(6,4,4)", cPrism},
1164   {"(7,4,4)", cPrism},
1165 
1166   /* sample antiprisms */
1167   {"(3,3,3,4)(1)(2)", cAntiPrism},
1168   {"(3,3,3,5)(1)(2)", cAntiPrism},
1169   {"(3,3,3,6)(1)(2)", cAntiPrism},
1170   {"(3,3,3,7)(1)(2)", cAntiPrism},
1171 
1172   /* hyperbolic ones */
1173   {"(3)^7", cHyperRegular},
1174   {"(4)^5", cHyperRegular},
1175   {"(4)^6", cHyperRegular},
1176   {"(5,5,5,5)", cHyperRegular},
1177   {"(7,7,7)", cHyperRegular},
1178   {"(8,8,8)", cHyperRegular},
1179   {"(7,6^2)", cHyperSemi},
1180   {"(4,6,14)", cHyperSemi},
1181   {"(3,4,7,4)", cHyperSemi},
1182   {"(6,6,4L,4L)", cHyperSemi},
1183   {"(8,8,4L,4L)", cHyperSemi},
1184   {"(3,3,3,3,7) (1,2)(0,4)(3)", cHyperSemi},
1185   {"(3H,6,6,6) (1,0)[2](3)", cHyperSemi},
1186   {"(3,6,6,6) (0 1)(2)(3)", cHyperSemi},
1187   {"(3,4,4L,4L,4)", cHyperSemi}, // buggy
1188   {"(3l,4l,4,4,4) (0 1)[2 3](4)", cHyperSemi},
1189   {"(3,4,4,4,4) (0 1)(2)(3)(4)", cHyperSemi},
1190   {"(3,4,4L,4L,4L,4)", cHyperSemi},
1191   {"(6,6,3L,3L,3L) (0 2)(1)(3)(4)", cHyperSemi},
1192   {"(5,3,5,3,3) (0 1)(2 3)(4)", cHyperSemi},
1193   {"(4,3,3,3,3,3) (0 1)(2 3)(4 5)", cHyperSemi},
1194   {"(3l,5l,5,5,5,5) (0 1)[2 3](4)(5)", cHyperSemi},
1195   {"(3,5,5,5,5,5) (0 1)(2 4)(3 5)", cHyperSemi},
1196   {"(3l,5l,5,5,5,5) (0 1)(2 4)[3 5]", cHyperSemi},
1197   {"(3l,5l,5,5,5,5) (0 1)[2 4](3)(5)", cHyperSemi},
1198   {"(3,5,5,5,5,5) (0 1)(2)(3)(4)(5)", cHyperSemi},
1199   {"(3,3,4,3,5)(0,4)(1)(2,3)", cHyperSemi},
1200   {"(3,14,14)", cHyperSemi},
1201   {"(3,3,3,3,3,4)[0](1,2)(3,4)[5]", cHyperSemi},
1202 
1203   /* interesting symmetry variants */
1204   {"(3,3,3,3,3,3) (0,1)(2,3)(4,5)", cEucRegular},
1205   {"(3,3H,3,3,3L,3L,3L) (0 4)(1 2)(3)(5)(6)", cHyperRegular},
1206   {"(3,3H,3,3,3L,3L,3L) (0 4)(1 2)(3)[5 6]", cHyperRegular},
1207   {"(3,3H,3,3L,3,3L,3L) [0 4](1 2)[3 5](6)", cHyperRegular},
1208 
1209   /* with digons */
1210   {"(2,3,3,3,3,3) (2,3)(4,5)", cWeird},
1211   {"(6,6)", cWeird},
1212   {"(2,2)", cWeird},
1213   {"(2,2,2,2,2,2)", cWeird},
1214   {"(6,6,2)", cWeird},
1215   {"(6,2,6,2)", cWeird},
1216   };
1217 
1218 int lastsample = 0;
1219 
1220 vector<archimedean_tiling> tilings;
1221 
1222 int spos = 0;
1223 
1224 archimedean_tiling edited;
1225 
1226 bool symbol_editing;
1227 
next_variation()1228 EX void next_variation() {
1229   set_variation(
1230     PURE ? eVariation::dual :
1231     DUAL ? eVariation::bitruncated :
1232     eVariation::pure);
1233   start_game();
1234   }
1235 
enable(archimedean_tiling & arct)1236 EX void enable(archimedean_tiling& arct) {
1237   stop_game();
1238   if(!in()) set_variation(eVariation::pure);
1239   set_geometry(gArchimedean);
1240   patterns::whichPattern = patterns::PAT_NONE;
1241   current = arct;
1242 #if CAP_TEXTURE
1243   if(texture::config.tstate == texture::tsActive && texture::cgroup == cpThree) {
1244     patterns::whichPattern = patterns::PAT_COLORING;
1245     if(geosupport_threecolor() < 2) {
1246       if(arct.support_threecolor() == 2) set_variation(eVariation::pure);
1247       else if(arct.support_threecolor_bitruncated() == 2) set_variation(eVariation::bitruncated);
1248       }
1249     }
1250   if(texture::config.tstate == texture::tsActive && texture::cgroup == cpFootball) {
1251     patterns::whichPattern = patterns::PAT_TYPES, patterns::subpattern_flags = patterns::SPF_FOOTBALL;
1252     if(geosupport_football() < 2) set_variation(eVariation::bitruncated);
1253     }
1254   if(texture::config.tstate == texture::tsActive && texture::cgroup == cpChess) {
1255     patterns::whichPattern = patterns::PAT_CHESS;
1256     if(!geosupport_chessboard()) {
1257       if(arct.support_chessboard()) set_variation(eVariation::pure);
1258       else if(arct.support_threecolor_bitruncated() == 2) set_variation(eVariation::dual);
1259       }
1260     }
1261 #endif
1262   start_game();
1263   }
1264 
setcanvas(char c)1265 function<void()> setcanvas(char c) {
1266   return [c] () {
1267     stop_game();
1268     enable_canvas();
1269     patterns::whichCanvas = c;
1270     start_game();
1271     };
1272   }
1273 
show()1274 EX void show() {
1275   if(lastsample < isize(samples)) {
1276     string s = samples[lastsample].first;
1277     int col = samples[lastsample].second;
1278     lastsample++;
1279     archimedean_tiling tested;
1280     tested.parse(s);
1281     if(tested.errors) {
1282       DEBB(DF_GEOM | DF_WARN, ("WARNING: ", tested.errors, " errors on ", s, " '", tested.errormsg, "'"));
1283       }
1284     else {
1285       tested.coloring = col;
1286       tilings.push_back(move(tested));
1287       /* sort(tilings.begin(), tilings.end(), [] (archimedean_tiling& s1, archimedean_tiling& s2) {
1288         if(s1.euclidean_angle_sum < s2.euclidean_angle_sum - 1e-6) return true;
1289         if(s2.euclidean_angle_sum < s1.euclidean_angle_sum - 1e-6) return false;
1290         return s1.symbol < s2.symbol;
1291         }); */
1292       }
1293     }
1294   cmode = sm::SIDE | sm::MAYDARK;
1295   gamescreen(0);
1296   dialog::init(XLAT("Archimedean tilings"));
1297 
1298   if(symbol_editing) {
1299     dialog::addSelItem("edit", dialog::view_edited_string(), '/');
1300     dialog::add_action([] () {
1301       symbol_editing = false;
1302       if(!edited.errors) enable(edited);
1303       });
1304     dialog::addBreak(100);
1305     if(edited.errors)
1306       dialog::addInfo(edited.errormsg, 0xFF0000);
1307     else
1308       dialog::addInfo(XLAT("OK"), 0x00FF00);
1309 
1310     dialog::addBreak(100);
1311     dialog::addSelItem(XLAT("full angle"), fts(edited.euclidean_angle_sum * 180) + "°", 0);
1312     dialog::addSelItem(XLAT("size of the world"), edited.world_size(), 0);
1313 
1314     edited.compute_geometry();
1315     dialog::addSelItem(XLAT("edge length"), fts(edited.edgelength) + (edited.get_class() == gcEuclid ? XLAT(" (arbitrary)") : ""), 0);
1316     current.compute_geometry();
1317 
1318     dialog::addBreak(100);
1319     dialog::addKeyboardItem("1234567890");
1320     dialog::addKeyboardItem("()[]lLhH,");
1321     dialog::addKeyboardItem(" \t\b\x1\x2\n");
1322     dialog::addBreak(100);
1323     }
1324   else {
1325     string cs = in() ? current.symbol : XLAT("OFF");
1326     dialog::addSelItem("edit", cs, '/');
1327     dialog::add_action([] () {
1328       symbol_editing = true;
1329       edited = current;
1330       dialog::start_editing(edited.symbol);
1331       edited.parse();
1332       });
1333     dialog::addBreak(100);
1334     int nextpos = spos;
1335     int shown = 0;
1336     while(nextpos < isize(tilings) && shown < 10) {
1337       auto &ps = tilings[nextpos++];
1338       bool valid = true;
1339       string suffix = "";
1340 #if CAP_TEXTURE
1341       if(texture::config.tstate == texture::tsActive && texture::cgroup == cpThree) {
1342         valid = false;
1343         if(ps.support_threecolor() == 2) valid = true, suffix += bitruncnames[int(eVariation::pure)];
1344         if(ps.support_threecolor_bitruncated() == 2) valid = true, suffix += bitruncnames[int(eVariation::bitruncated)];
1345         }
1346       if(texture::config.tstate == texture::tsActive && texture::cgroup == cpFootball) {
1347         if(ps.support_football() == 2) suffix += bitruncnames[int(eVariation::pure)];
1348         suffix += bitruncnames[int(eVariation::bitruncated)];
1349         }
1350       if(texture::config.tstate == texture::tsActive && texture::cgroup == cpChess && !ps.support_chessboard()) {
1351         valid = false;
1352         if(ps.support_chessboard()) valid = true, suffix += bitruncnames[int(eVariation::pure)];
1353         if(ps.support_threecolor_bitruncated() == 2) valid = true, suffix += bitruncnames[int(eVariation::dual)];
1354         }
1355 #endif
1356       if(!valid) continue;
1357       if(current_filter == &gf_hyperbolic && ps.get_geometry().kind != gcHyperbolic) continue;
1358       if(current_filter == &gf_spherical && ps.get_geometry().kind != gcSphere) continue;
1359       if(current_filter == &gf_euclidean && ps.get_geometry().kind != gcEuclid) continue;
1360       dialog::addSelItem(ps.symbol, fts(ps.euclidean_angle_sum * 180) + "°" + suffix, 'a' + shown);
1361       dialog::lastItem().color = ps.coloring;
1362       dialog::add_action([&] () { enable(ps); });
1363       shown++;
1364       }
1365     dialog::addSelItem(XLAT("current filter"), current_filter ? XLAT(current_filter->name) : XLAT("none"), 'x');
1366     dialog::add_action([] {
1367       if(current_filter == &gf_hyperbolic) current_filter = &gf_euclidean;
1368       else if(current_filter == &gf_euclidean) current_filter = &gf_spherical;
1369       else if(current_filter == &gf_spherical) current_filter = nullptr;
1370       else current_filter = &gf_hyperbolic;
1371       });
1372     dialog::addItem(XLAT("next page"), '-');
1373     if(shown == 0) nextpos = 0;
1374     dialog::add_action([nextpos] () {
1375       if(nextpos >= isize(tilings))
1376         spos = 0;
1377       else spos = nextpos;
1378       });
1379     dialog::addItem(XLAT("previous page"), '=');
1380     dialog::add_action([] () {
1381       spos -= 10;
1382       if(spos < 0) spos = 0;
1383       });
1384 
1385     if(in()) {
1386       dialog::addSelItem(XLAT("size of the world"), current.world_size(), 0);
1387       dialog::addSelItem(XLAT("edge length"), current.get_class() == gcEuclid ? (fts(current.edgelength) + XLAT(" (arbitrary)")) : fts(current.edgelength), 0);
1388 
1389       dialog::addItem(XLAT("color by symmetries"), 't');
1390       dialog::add_action(setcanvas('A'));
1391       }
1392     else {
1393       dialog::addBreak(100);
1394       dialog::addBreak(100);
1395       dialog::addBreak(100);
1396       }
1397 
1398     if(true) {
1399       dialog::addItem(XLAT("color by sides"), 'u');
1400       dialog::add_action(setcanvas('B'));
1401       }
1402 
1403     if(geosupport_threecolor() == 2) {
1404       dialog::addItem(XLAT("three colors"), 'w');
1405       dialog::add_action(setcanvas('T'));
1406       }
1407     else if(geosupport_football() == 2) {
1408       dialog::addItem(XLAT("football"), 'w');
1409       dialog::add_action(setcanvas('F'));
1410       }
1411     else if(geosupport_chessboard()) {
1412       dialog::addItem(XLAT("chessboard"), 'w');
1413       dialog::add_action(setcanvas('c'));
1414       }
1415     else dialog::addBreak(100);
1416 
1417     if(in()) {
1418       dialog::addSelItem(XLAT("variations"), gp::operation_name(), 'v');
1419       dialog::add_action(next_variation);
1420       }
1421     else dialog::addBreak(100);
1422     }
1423 
1424   dialog::addHelp();
1425   dialog::addBack();
1426   dialog::display();
1427 
1428   keyhandler = [] (int sym, int uni) {
1429     if(symbol_editing && sym == SDLK_RETURN) sym = uni = '/';
1430     dialog::handleNavigation(sym, uni);
1431     if(symbol_editing && dialog::handle_edit_string(sym, uni)) {
1432       edited.parse(edited.symbol);
1433       return;
1434       }
1435     if(doexiton(sym, uni)) popScreen();
1436     };
1437   }
1438 
get_nom_denom(int & anom,int & adenom)1439 void archimedean_tiling::get_nom_denom(int& anom, int& adenom) {
1440   int nom = 2 - N, denom = 2;
1441   for(int f: faces) {
1442     int g = gcd(denom, f);
1443     nom = (nom * f + denom) / g;
1444     denom = denom / g * f;
1445     }
1446   anom = 0, adenom = 1;
1447   if(BITRUNCATED || DUAL) anom = 1, adenom = 1;
1448   if(!DUAL) for(int f: faces) {
1449     int g = gcd(adenom, f);
1450     anom = (anom * f + adenom) / g;
1451     adenom = adenom / g * f;
1452     }
1453   anom *= 2 * denom, adenom *= nom;
1454   int g = gcd(anom, adenom);
1455   if(g != 0) {
1456     anom /= g; adenom /= g;
1457     }
1458   if(adenom < 0) anom = -anom, adenom = -adenom;
1459   }
1460 
world_size()1461 string archimedean_tiling::world_size() {
1462   if(get_class() == gcEuclid) return "∞";
1463 
1464   int anom, adenom;
1465   get_nom_denom(anom, adenom);
1466 
1467   string s;
1468   bool hyp = (anom < 0);
1469   if(hyp) anom = -anom;
1470   if(adenom != 1)
1471     s += its(anom) + "/" + its(adenom);
1472   else
1473     s += its(anom);
1474   if(hyp) s += " exp(∞)";
1475   return s;
1476   }
1477 
degree(heptagon * h)1478 EX int degree(heptagon *h) {
1479   return isize(current.adjacent[id_of(h)]);
1480   }
1481 
is_vertex(heptagon * h)1482 EX bool is_vertex(heptagon *h) {
1483   return id_of(h) >= 2 * current.N;
1484   }
1485 
get_step_values(int & steps,int & single_step)1486 bool archimedean_tiling::get_step_values(int& steps, int& single_step) {
1487 
1488   int nom = -2;
1489   int denom = 1;
1490 
1491   for(int f: arcm::current.faces) {
1492     if(int(denom*f)/f != denom) { steps = 0; single_step = 0; return false; }
1493     int g = gcd(denom, f);
1494     nom = nom * (f/g) + (f-2) * (denom/g);
1495     denom = denom/g * f;
1496     }
1497 
1498   steps = 2 * abs(denom);
1499   single_step = abs(nom);
1500   if(steps/2 != abs(denom)) return false;
1501   return (2 * denom) % nom == 0;
1502   }
1503 
valence()1504 EX int valence() {
1505   if(PURE) return arcm::current.N;
1506   if(BITRUNCATED) return 3;
1507   // in DUAL, usually valence would depend on the vertex.
1508   // 3 is the most interesting, as it allows us to kill hedgehog warriors
1509   int total = 0;
1510   for(int i: current.faces) {
1511     if(i == 3) return 3;
1512     total += i;
1513     }
1514   return total / isize(current.faces);
1515   }
1516 
get_cdata()1517 EX map<gp::loc, cdata>& get_cdata() { return ((arcm::hrmap_archimedean*) (currentmap))->eucdata; }
1518 
1519 #endif
1520 
1521 EX }
1522 
1523 }
1524