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