mirror of
				https://github.com/zenorogue/hyperrogue.git
				synced 2025-10-31 14:02:59 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1665 lines
		
	
	
		
			51 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1665 lines
		
	
	
		
			51 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // Hyperbolic Rogue -- Archimedean Tilings
 | |
| // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
 | |
| 
 | |
| /** \file archimedean.cpp 
 | |
|  *  \brief Archimedean tilings
 | |
|  *
 | |
|  *  These are tilings available in the 'Archimedean' option in Geometry Experiments; simpler Archimedean tilings are defined in other files.
 | |
|  */
 | |
| 
 | |
| #include "hyper.h"
 | |
| namespace hr {
 | |
| 
 | |
| EX namespace arcm {
 | |
| 
 | |
| EX bool in() { return cgflags & qARCHI; }
 | |
| 
 | |
| EX ld euclidean_edge_length = .5;
 | |
| 
 | |
| #if HDR
 | |
| struct hr_archimedean_error : hr_exception {
 | |
|   hr_archimedean_error(string _s) : hr_exception(_s) {}
 | |
|   };
 | |
| 
 | |
| struct archimedean_tiling {
 | |
| 
 | |
|   int coloring;
 | |
| 
 | |
|   string symbol;
 | |
|   
 | |
|   vector<int> faces;
 | |
|   vector<int> adj;
 | |
|   vector<bool> invert;
 | |
|   vector<int> nflags;
 | |
| 
 | |
|   bool have_ph, have_line, have_symmetry;
 | |
|   int real_faces;
 | |
|   int real_face_type;
 | |
| 
 | |
|   int repetition;
 | |
|   int N;
 | |
|   
 | |
|   bool regular;
 | |
| 
 | |
|   ld euclidean_angle_sum;
 | |
| 
 | |
|   vector<int> flags;
 | |
| 
 | |
|   vector<vector<pair<int, int>>> adjacent;
 | |
|   vector<vector<pair<ld, ld>>> triangles;
 | |
|   
 | |
|   void make_match(int a, int i, int b, int j);
 | |
|   void prepare();
 | |
|   void compute_sum();
 | |
|   void compute_geometry();
 | |
|   
 | |
|   void parse();
 | |
|   void parse(string s) { symbol = s; parse(); }
 | |
| 
 | |
|   ld edgelength;
 | |
|   
 | |
|   vector<ld> inradius, circumradius, alphas;
 | |
|   
 | |
|   vector<vector<int>> matches;
 | |
|   vector<int> periods;
 | |
|   vector<int> tilegroup;
 | |
|   vector<int> groupoffset;
 | |
|   int tilegroups;
 | |
| 
 | |
|   int errors;
 | |
|   string errormsg;
 | |
| 
 | |
|   pair<int, int>& get_adj(heptagon *h, int cid);
 | |
|   pair<int, int>& get_adj(heptspin hs) { return get_adj(hs.at, hs.spin); }
 | |
|   pair<ld, ld>& get_triangle(heptagon *h, int cid);
 | |
|   pair<ld, ld>& get_triangle(heptspin hs) { return get_triangle(hs.at, hs.spin); }
 | |
|   pair<ld, ld>& get_triangle(const pair<int, int>& p, int delta = 0);
 | |
|   pair<int, int>& get_adj(const pair<int, int>& p, int delta = 0);
 | |
| 
 | |
|   int support_threecolor();
 | |
|   int support_threecolor_bitruncated();
 | |
|   int support_football();
 | |
|   bool support_chessboard();
 | |
|   void regroup();
 | |
|   string world_size();
 | |
|   void get_nom_denom(int& anom, int& adenom);
 | |
|   
 | |
|   geometryinfo1& get_geometry(ld mul = 1);
 | |
|   eGeometryClass get_class() { return get_geometry().kind; }
 | |
| 
 | |
|   bool get_step_values(int& steps, int& single_step);
 | |
| 
 | |
|   ld dual_tile_area();
 | |
| 
 | |
|   transmatrix adjcell_matrix(heptagon *h, int d);
 | |
|   
 | |
|   ld scale();
 | |
|   };
 | |
| #endif
 | |
| 
 | |
| #if HDR
 | |
| static constexpr int sfPH = 1;
 | |
| static constexpr int sfLINE = 2;
 | |
| static constexpr int sfCHESS = 4;
 | |
| static constexpr int sfTHREE = 8;
 | |
| static constexpr int sfSEMILINE = 16;
 | |
| #endif
 | |
| 
 | |
| #if CAP_ARCM
 | |
| 
 | |
| EX archimedean_tiling current;
 | |
| EX archimedean_tiling fake_current;
 | |
| 
 | |
| EX archimedean_tiling& current_or_fake() {
 | |
|   if(fake::in_ext()) return fake_current;
 | |
|   return current;
 | |
|   }
 | |
| 
 | |
| /** id of vertex in the archimedean tiling
 | |
|  *  odd numbers = reflected tiles
 | |
|  *  0, 2, ..., 2(N-1) = as in the symbol
 | |
|  *  2N = bitruncated tile
 | |
|  */
 | |
| 
 | |
| EX short& id_of(heptagon *h) {
 | |
|   return h->zebraval;
 | |
|   }
 | |
| 
 | |
| /** which index in id_of's neighbor list does h->move(0) have */
 | |
| 
 | |
| EX short& parent_index_of(heptagon *h) {
 | |
|   return h->emeraldval;
 | |
|   }
 | |
| 
 | |
| /** total number of neighbors */
 | |
| 
 | |
| EX int neighbors_of(heptagon *h) {
 | |
|   return isize(current.triangles[id_of(h)]);
 | |
|   }
 | |
| 
 | |
| EX int gcd(int x, int y) { return x ? gcd(y%x, x) : y < 0 ? -y : y; }
 | |
| 
 | |
| void archimedean_tiling::make_match(int a, int i, int b, int j) {
 | |
|   if(isize(adjacent[a]) != isize(adjacent[b])) {
 | |
|     DEBB(DF_GEOM, ("(error here)"));
 | |
|     errormsg = XLAT("polygons match incorrectly");
 | |
|     errors++;
 | |
|     }
 | |
|   if(matches[a][b] == -1)
 | |
|     matches[a][b] = j - i, matches[b][a] = i - j;
 | |
|   else
 | |
|     periods[a] = periods[b] = gcd(matches[a][b] - (j-i), periods[a]);
 | |
|   }
 | |
| 
 | |
| /** mostly to protect the user from entering too large numbers */
 | |
| const int MAX_EDGE_ARCM = FULL_EDGE;
 | |
| 
 | |
| /** compute the edge length of an Archimedean tiling. Each element of facemul is (face sides, multiplicity) */
 | |
| EX ld compute_edgelength(vector<pair<ld, ld>> facemul, ld halftotal IS(M_PI)) {
 | |
|   ld elmin = 0, elmax = hyperbolic ? 10 : sphere ? M_PI : 2 * euclidean_edge_length;
 | |
|   ld edgelength;
 | |
|   for(int p=0; p<100; p++) {
 | |
|     edgelength = (elmin + elmax) / 2;
 | |
|     ld alpha_total = 0;
 | |
|     for(auto fm: facemul) {
 | |
|       if(isinf(fm.first)) {
 | |
|         ld u = sqrt(cosh(edgelength) * 2 - 2);
 | |
|         ld a = atan2(1, u/2);
 | |
|         alpha_total += a * fm.second;
 | |
|         }
 | |
|       else {
 | |
|         ld gamma = M_PI / fm.first;
 | |
|         auto c = asin_auto(sin_auto(edgelength/2) / sin(gamma));
 | |
|         hyperpoint h = lxpush(c) * spin(M_PI - 2*gamma) * lxpush0(c);
 | |
|         ld a = atan2(h);
 | |
|         cyclefix(a, 0);
 | |
|         if(a < 0) a = -a;
 | |
|         alpha_total += a * fm.second;
 | |
|         }
 | |
|       }
 | |
|     if(isnan(alpha_total)) elmax = edgelength;
 | |
|     else if(sphere ^ (alpha_total > halftotal)) elmin = edgelength;
 | |
|     else elmax = edgelength;
 | |
|     }
 | |
|   return edgelength;
 | |
|   }
 | |
| 
 | |
| void archimedean_tiling::compute_sum() {
 | |
|   N = isize(faces);
 | |
|   euclidean_angle_sum = 0;
 | |
|   for(int f: faces) euclidean_angle_sum += (f-2.) / f;
 | |
| 
 | |
|   real_faces = 0, real_face_type = 0;
 | |
|   for(int i=0; i<N; i++) if(faces[i] > 2) real_faces++, real_face_type += faces[i];
 | |
|   real_face_type /= 2;
 | |
|   }
 | |
| 
 | |
| void archimedean_tiling::prepare() {
 | |
| 
 | |
|   compute_sum();
 | |
| 
 | |
|   for(int i: faces) if(i > MAX_EDGE_ARCM) {
 | |
|     errormsg = XLAT("currently no more than %1 edges", its(MAX_EDGE_ARCM));
 | |
|     errors++;
 | |
|     return;
 | |
|     }
 | |
|   if(isize(faces) > MAX_EDGE_ARCM/2) {
 | |
|     errormsg = XLAT("currently no more than %1 faces in vertex", its(MAX_EDGE_ARCM/2));
 | |
|     errors++;
 | |
|     return;
 | |
|     }
 | |
| 
 | |
|   for(int i: faces) if(i < 2) {
 | |
|     errormsg = XLAT("not enough edges");
 | |
|     errors++;
 | |
|     return;
 | |
|     }
 | |
| 
 | |
|   vector<int> nondigonal;
 | |
|   for(int i: faces) if(i > 2) nondigonal.push_back(i);
 | |
| 
 | |
|   if(isize(faces) < 2 || isize(nondigonal) == 1) {
 | |
|     errormsg = XLAT("not enough faces");
 | |
|     errors++;
 | |
|     return;
 | |
|     }
 | |
|     
 | |
|   if(isize(nondigonal) == 2 && faces[0] != faces[1]) {
 | |
|     errormsg = XLAT("invalid dihedron");
 | |
|     errors++;
 | |
|     return;
 | |
|     }
 | |
|   
 | |
|   for(int i=0; i<N; i++) {
 | |
|     bool forward = false;
 | |
|     int f = faces[i];
 | |
|     int i0 = i;
 | |
|     for(int k=0; k<f; k++) {
 | |
|       forward ^= invert[i0];
 | |
|       i0 = adj[i0];
 | |
|       if(forward) { if(faces[i0] != f) { errormsg = XLAT("face mismatch"); errors++; return; } i0++; if(i0 == N) i0 = 0; }
 | |
|       else { if(i0 == 0) i0 = N; i0--; if(faces[i0] != f) { errormsg = XLAT("face mismatch"); errors++; return; } }
 | |
|       }
 | |
|     for(int k=0; k<N; k++) {
 | |
|       f = faces[(i+N-k-1) % N];
 | |
|       if(forward) { if(faces[i0] != f) { errormsg = XLAT("face mismatch II "); errors++; return; } i0++; if(i0 == N) i0 = 0; }
 | |
|       else { if(i0 == 0) i0 = N; i0--; if(faces[i0] != f) { errormsg = XLAT("face mismatch II"); errors++; return; } }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|   if(real_faces) {
 | |
|     for(int i=1; i<isize(faces); i++) if(faces[i] == 2 && faces[i-1] == 2) {
 | |
|       errormsg = XLAT("Not implemented.");
 | |
|       errors++;
 | |
|       return;
 | |
|       }
 | |
|     if(faces[0] == 2 && faces[isize(faces)-1] == 2) {
 | |
|       errormsg = XLAT("Not implemented.");
 | |
|       errors++;
 | |
|       return;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|   if(real_faces == 2) {
 | |
|     for(int i: faces) if(i != real_face_type) {
 | |
|       errormsg = XLAT("polygons match incorrectly");
 | |
|       errors++;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|   errors = 0;
 | |
| 
 | |
|   /* build the 'adjacent' table */
 | |
| 
 | |
|   int M = 2 * N + 2;
 | |
|   adjacent.clear();
 | |
|   adjacent.resize(M);
 | |
| 
 | |
|   have_symmetry = false;
 | |
|   for(int i=0; i<N; i++) if(invert[i]) have_symmetry = true;  
 | |
|   
 | |
|   matches.resize(M);
 | |
|   for(int i=0; i<M; i++) {
 | |
|     matches[i].resize(M);
 | |
|     for(int j=0; j<M; j++) matches[i][j] = i==j ? 0 : -1;
 | |
|     }
 | |
| 
 | |
|   periods.resize(M);
 | |
|   for(int i=0; i<M; i++) periods[i] = i<2*N ? faces[i/2] : N;
 | |
|   
 | |
|   for(int i=0; i<N; i++) {
 | |
|     for(int oi=0; oi<1; oi++) {
 | |
|       int at = (i+oi)%N;
 | |
|       int inv = oi;
 | |
|       DEBB0(DF_GEOM, ("vertex "));
 | |
|       for(int z=0; z<faces[i]; z++) {
 | |
|         DEBB0(DF_GEOM, (hr::format("[%d %d] " , at, inv)));
 | |
|         adjacent[2*i+oi].emplace_back(2*N+int(inv), inv ? (2*at+2*N-2) % (2*N) : 2*at);
 | |
|         if(invert[at]) inv ^= 1;
 | |
|         at = adj[at];
 | |
|         if(inv) at = (at+1) % N;
 | |
|         else at = (at+N-1) % N;
 | |
|         }
 | |
|       if(!inv) make_match(2*i, 0, inv ? (2*at+2*N-1) % 2*N : 2*at, 0);
 | |
|       DEBB(DF_GEOM, (hr::format("-> [%d %d]\n", at, inv)));
 | |
|       }
 | |
|     }
 | |
|   for(int i=0; i<N; i++) {
 | |
|     adjacent[2*N].emplace_back(2*i, 0);
 | |
|     int ai = (i+1) % N;
 | |
|     adjacent[2*N].emplace_back(2*N+int(invert[ai]), (2*adj[ai]+2*N-1) % (2*N));
 | |
|     }
 | |
|   
 | |
|   for(int d=0; d<=2*N; d+=2) {
 | |
|     int s = isize(adjacent[d]);
 | |
|     for(int i=0; i<s; i++) {
 | |
|       auto& orig = adjacent[d][s-1-i];
 | |
|       adjacent[d+1].emplace_back(orig.first ^ 1, orig.second);
 | |
|       }
 | |
|     }
 | |
|   for(int d=0; d<M; d++) {
 | |
|     int s = isize(adjacent[d]);
 | |
|     for(int i=0; i<s; i++) {
 | |
|       auto& orig = adjacent[d][i];
 | |
|       if(orig.first & 1) orig.second = isize(adjacent[orig.first]) - 1 - orig.second;
 | |
|       }
 | |
|     }
 | |
|   
 | |
|   if(debugflags & DF_GEOM) {
 | |
|     for(int i=0; i<M; i++) {
 | |
|       DEBB0(DF_GEOM, ("adjacent ", i, ":"));
 | |
|       for(int j=0; j<isize(adjacent[i]); j++) {
 | |
|         auto p = adjacent[i][j];
 | |
|         DEBB0(DF_GEOM, (" ", p));
 | |
|         }
 | |
|       DEBB(DF_GEOM, ("\n"));
 | |
|       }
 | |
|     }
 | |
| 
 | |
|   for(int i=0; i<M; i++) {
 | |
|     for(int j=0; j<isize(adjacent[i]); j++) {
 | |
|       auto p = adjacent[i][j];
 | |
|       auto q = adjacent[p.first][p.second];
 | |
|       make_match(i, j, q.first, q.second);
 | |
|       }
 | |
|     }
 | |
|     
 | |
|   /* verify all the triangles */
 | |
|   for(int i=0; i<M; i++) {
 | |
|     for(int j=0; j<isize(adjacent[i]); j++) {
 | |
|       int ai = i, aj = j;
 | |
|       DEBB0(DF_GEOM, ("triangle "));
 | |
|       for(int s=0; s<3; s++) {
 | |
|         DEBB0(DF_GEOM, (hr::format("[%d %d] ", ai, aj)));
 | |
|         tie(ai, aj) = adjacent[ai][aj];
 | |
|         aj++; if(aj >= isize(adjacent[ai])) aj = 0;
 | |
|         }
 | |
|       DEBB(DF_GEOM, (hr::format("-> [%d %d]\n", ai, aj)));
 | |
|       make_match(i, j, ai, aj);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|   for(int i=0; i<2*N; i++) {
 | |
|     for(int j=0; j<isize(adjacent[i]); j++) {
 | |
|       auto aa = make_pair(i, j);
 | |
|       aa = get_adj(aa, 1);
 | |
|       aa = get_adj(aa, 2);
 | |
|       aa = get_adj(aa, 1);
 | |
|       aa = get_adj(aa, 2);
 | |
|       make_match(i, j, aa.first, aa.second);
 | |
|       }
 | |
|     }
 | |
|   
 | |
|   regroup();
 | |
|   }
 | |
| 
 | |
| void archimedean_tiling::regroup() {
 | |
|   int M = 2 * N + 2;
 | |
|   for(int i=0; i<M; i++) for(int j=0; j<M; j++) if(matches[i][j] != -1)
 | |
|   for(int l=0; l<M; l++) for(int k=0; k<M; k++) if(matches[j][k] != -1) {
 | |
|     make_match(i, 0, k, matches[i][j] + matches[j][k]);
 | |
|     make_match(i, 0, k, matches[i][j] + matches[j][k] + gcd(periods[i], periods[j]));
 | |
|     }
 | |
|   tilegroup.clear();
 | |
|   tilegroup.resize(M, -1);
 | |
|   groupoffset.resize(M);
 | |
|   tilegroups = 0;
 | |
|   for(int i=0; i<M; i+=(have_symmetry?1:2)) if(tilegroup[i] == -1) {
 | |
|     if(periods[i] < 0) periods[i] = -periods[i];
 | |
|     for(int j=0; j<M; j++) if(matches[i][j] != -1)
 | |
|       tilegroup[j] = tilegroups, groupoffset[j] = matches[i][j] % periods[i];
 | |
|     tilegroups++;
 | |
|     }
 | |
| 
 | |
|   flags.clear();
 | |
|   flags.resize(M, 0);
 | |
|   for(int i=0; i<M; i++)
 | |
|   for(int j=0; j<M; j++) {
 | |
|     if(tilegroup[i] == tilegroup[j]) {
 | |
|       flags[i] |= nflags[j/2];
 | |
|       if(j%2 == 1 && (nflags[j/2] & sfSEMILINE))
 | |
|         flags[i] |= sfLINE;
 | |
|       }
 | |
|     }
 | |
|   
 | |
|   if(!have_ph) {
 | |
|     for(int i=0; i<M; i++) if(tilegroup[i] == 0) flags[i] |= sfPH;
 | |
|     }
 | |
|   
 | |
|   if(debugflags & DF_GEOM) {
 | |
|     for(int i=0; i<M; i+=(have_symmetry?1:2)) {
 | |
|       DEBB(DF_GEOM, (hr::format("tiling group of %2d: [%2d]%2d+Z%2d\n", i, tilegroup[i], groupoffset[i], periods[i])));
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
| geometryinfo1& archimedean_tiling::get_geometry(ld mul) {
 | |
|   if(euclidean_angle_sum * mul < 1.999999) return giSphere2;
 | |
|   else if(euclidean_angle_sum * mul > 2.000001) return giHyperb2;
 | |
|   else return giEuclid2;
 | |
|   }
 | |
| 
 | |
| void archimedean_tiling::compute_geometry() {
 | |
| 
 | |
|   if(embedded_plane && geometry != gArchimedean) return;
 | |
|   if(embedded_plane) return IPF(compute_geometry());
 | |
| 
 | |
|   auto gg = get_geometry();
 | |
| 
 | |
|   for(int a=0; a<2; a++) {
 | |
|     auto& arr = a ? geom3::ginf_backup : ginf;
 | |
|     if(arr.empty()) continue;
 | |
|     if(gg.kind == gcSphere) arr[gArchimedean].g = arr[gSphere].g;
 | |
|     if(gg.kind == gcEuclid) arr[gArchimedean].g = arr[gEuclid].g;
 | |
|     if(gg.kind == gcHyperbolic) arr[gArchimedean].g = arr[gNormal].g;
 | |
|     set_flag(arr[gArchimedean].flags, qCLOSED, gg.kind == gcSphere);
 | |
|     // all spherical Archimedean tilings are small
 | |
|     set_flag(arr[gArchimedean].flags, qSMALL, gg.kind == gcSphere);
 | |
|     }
 | |
| 
 | |
|   DEBB(DF_GEOM, (hr::format("euclidean_angle_sum = %f\n", float(euclidean_angle_sum))));
 | |
| 
 | |
|   bool infake = fake::in_ext();
 | |
|   
 | |
|   dynamicval<eGeometry> dv(geometry, gArchimedean);
 | |
|   
 | |
|   /* compute the geometry */
 | |
|   inradius.resize(N+1); inradius[N] = 0;
 | |
|   circumradius.resize(N+1); circumradius[N] = 0;
 | |
|   alphas.resize(N);
 | |
|   
 | |
|   ld total = M_PI;
 | |
| 
 | |
|   dynamicval<geometryinfo1> dgi(ginf[geometry].g, ginf[geometry].g);
 | |
| 
 | |
|   if(infake) {
 | |
|     total *= N / fake::around;
 | |
|     ginf[geometry].g = get_geometry(fake::around / N);
 | |
|     }
 | |
|   
 | |
|   ld elmin = 0, elmax = hyperbolic ? 10 : sphere ? M_PI : 2 * euclidean_edge_length;
 | |
| 
 | |
|   /* inradius[N] is used in farcorner and nearcorner. Probably a bug */
 | |
| 
 | |
|   bool need_flip = embedded_plane;
 | |
|   if(need_flip) geom3::light_flip(true);
 | |
|   
 | |
|   if(real_faces == 2) {
 | |
|     /* standard methods fail for dihedra, but the answer is easy */
 | |
|     edgelength = TAU / faces[0];
 | |
|     for(int i=0; i<N; i++)
 | |
|       if(faces[i] == 2)
 | |
|         alphas[i] = 0,
 | |
|         circumradius[i] = M_PI / real_face_type,
 | |
|         inradius[i] = 0;
 | |
|       else
 | |
|         alphas[i] = 90._deg,
 | |
|         circumradius[i] = inradius[i] = 90._deg;
 | |
|     }
 | |
|   else if(real_faces == 0) {
 | |
|     // these are called hosohedra
 | |
|     edgelength = M_PI;
 | |
|     for(int i=0; i<N; i++)
 | |
|       alphas[i] = M_PI / N,
 | |
|       circumradius[i] = 90._deg,
 | |
|       inradius[i] = 0;
 | |
|     }
 | |
|   else for(int p=0; p<100; p++) {
 | |
|     /* unfortunately we cannot just use compute_edgelength because we need to set other values */
 | |
|     edgelength = (elmin + elmax) / 2;
 | |
|     
 | |
|     ld alpha_total = 0;
 | |
| 
 | |
|     for(int i=0; i<N; i++) {
 | |
| 
 | |
|       ld gamma = M_PI / faces[i];
 | |
|       
 | |
|       auto& c = circumradius[i];
 | |
| 
 | |
|       c = asin_auto(sin_auto(edgelength/2) / sin(gamma));
 | |
|       inradius[i] = hdist0(mid(xpush0(circumradius[i]), cspin(0, 1, 2*gamma) * xpush0(circumradius[i])));
 | |
|       
 | |
|       hyperpoint h = xpush(c) * cspin(0, 1, M_PI - 2*gamma) * xpush0(c);
 | |
|       ld a = atan2(h);
 | |
|       cyclefix(a, 0);
 | |
|       if(a < 0) a = -a;
 | |
|       alphas[i] = a;
 | |
|       alpha_total += alphas[i];
 | |
|       }
 | |
|     
 | |
|     if(debugflags & DF_GEOM)
 | |
|     if(p < 10 || p == 99)
 | |
|       println(hlog, "edgelength = ", edgelength, " angles = ", alphas, " inradius = ", inradius, " circumradius = ", circumradius);
 | |
|     
 | |
|     if(isnan(alpha_total)) elmax = edgelength;
 | |
|     else if(sphere ^ (alpha_total > total)) elmin = edgelength;
 | |
|     else elmax = edgelength;
 | |
|     if(euclid) break;
 | |
|     }
 | |
| 
 | |
|   if(need_flip) geom3::light_flip(false);
 | |
|   
 | |
|   DEBB(DF_GEOM, (hr::format("computed edgelength = %f\n", float(edgelength))));
 | |
|   
 | |
|   triangles.clear();
 | |
|   triangles.resize(2*N+2);
 | |
|   for(int i=0; i<N; i++) for(int j=0; j<2; j++) 
 | |
|     for(int k=0; k<faces[i]; k++) 
 | |
|       triangles[2*i+j].emplace_back(TAU/faces[i], circumradius[i]);
 | |
|   
 | |
|   for(int k=0; k<N; k++) {
 | |
|     triangles[2*N].emplace_back(alphas[k], circumradius[k]);
 | |
|     triangles[2*N].emplace_back(alphas[(k+1)%N], edgelength);
 | |
|     triangles[2*N+1].emplace_back(alphas[N-1-k], edgelength);
 | |
|     triangles[2*N+1].emplace_back(alphas[N-1-k], circumradius[N-1-k]);
 | |
|     }
 | |
|   
 | |
|   for(auto& ts: triangles) {
 | |
|     ld total = 0;
 | |
|     for(auto& t: ts) tie(t.first, total) = make_pair(total, total + t.first);
 | |
|     }
 | |
| 
 | |
|   if(debugflags & DF_GEOM) for(auto& ts: triangles) {
 | |
|     DEBB0(DF_GEOM, ("T"));
 | |
|     for(auto& t: ts) DEBB0(DF_GEOM, (hr::format(" %f@%f", float(t.first), float(t.second))));
 | |
|     DEBB(DF_GEOM, ());
 | |
|     }
 | |
| 
 | |
|   regular = true;
 | |
|   for(int i: faces) if(i != faces[0]) regular = false;  
 | |
|   }
 | |
| 
 | |
| ld archimedean_tiling::scale() {
 | |
|   if(real_faces == 0 && N == 2) return 90._deg;
 | |
|   if(real_faces == 2) return 90._deg;
 | |
|   if(real_faces == 0) return TAU / N;
 | |
|   if(PURE) {
 | |
|     ld mcr = HUGE_VAL;
 | |
|     for(int i=0; i<N; i++) mcr = min(mcr, circumradius[i]);
 | |
|     return mcr * 3.0308847; // correct for (7,6,6)
 | |
|     }
 | |
|   return edgelength;
 | |
|   }
 | |
| 
 | |
| map<heptagon*, vector<pair<heptagon*, transmatrix> > > altmap;
 | |
| 
 | |
| EX map<heptagon*, pair<heptagon*, transmatrix>> archimedean_gmatrix;
 | |
| 
 | |
| EX hrmap *current_altmap;
 | |
| 
 | |
| heptagon *build_child(heptspin p, pair<int, int> adj);
 | |
| 
 | |
| bool skip_digons(heptspin hs, int step);
 | |
| void connect_digons_too(heptspin h1, heptspin h2);
 | |
| void fixup_matrix(transmatrix& T, const transmatrix& X, ld step);
 | |
| void connectHeptagons(heptspin hi, heptspin hs);
 | |
| 
 | |
| /** @brief should we use gmatrix to compute relative_matrix faster? (not while in fake Archimedean) */
 | |
| EX bool use_gmatrix = true;
 | |
| 
 | |
| /** @brief like adj, but in pure 
 | |
|  *  not used by arcm itself, but used in fake arcm
 | |
|  */
 | |
| 
 | |
| EX geometry_information *alt_cgip[2];
 | |
| 
 | |
| EX geometry_information *find_alt_cgip() {
 | |
|   auto& galt_cgip = alt_cgip[embedded_plane];
 | |
|   if(galt_cgip) return galt_cgip;
 | |
|   check_cgi();
 | |
|   cgi.require_basics();
 | |
|   return galt_cgip = cgip;
 | |
|   }
 | |
| 
 | |
| struct hrmap_archimedean : hrmap {
 | |
|   map<gp::loc, struct cdata> eucdata;
 | |
|   heptagon *origin;
 | |
|   heptagon *getOrigin() override { return origin; }
 | |
| 
 | |
|   hrmap_archimedean() {
 | |
|     dynamicval<hrmap*> curmap(currentmap, this);
 | |
|     int id = DUAL ? current.N * 2 : 0;;
 | |
|     int N0 = isize(current.adjacent[id]);
 | |
|     origin = init_heptagon(N0);
 | |
|     origin->s = hsOrigin;
 | |
| 
 | |
|     parent_index_of(origin) = DUAL ? 1 : 0;
 | |
|     id_of(origin) = id;
 | |
|     origin->c7 = newCell(N0/DUALMUL, origin);
 | |
|     
 | |
|     heptagon *alt = NULL;
 | |
|     
 | |
|     if(hyperbolic) {
 | |
|       bool f = geom3::flipped;
 | |
|       if(f) geom3::light_flip(false);
 | |
|       if(1) {
 | |
|         dynamicval<eGeometry> g(geometry, gNormal);
 | |
|         dynamicval<eVariation> gv(variation, eVariation::pure);
 | |
|         dynamicval<geometry_information*> gi(cgip, find_alt_cgip());
 | |
|         alt = init_heptagon(S7);
 | |
|         alt->s = hsOrigin;
 | |
|         alt->alt = alt;
 | |
|         current_altmap = newAltMap(alt);
 | |
|         }
 | |
|       if(f) geom3::light_flip(true);
 | |
|       }
 | |
| 
 | |
|     bool f = geom3::flipped;
 | |
|     if(f) geom3::light_flip(false);
 | |
|     transmatrix T = lxpush(.01241) * spin(1.4117) * lxpush(0.1241) * Id;
 | |
|     if(f) geom3::light_flip(true);
 | |
|     archimedean_gmatrix[origin] = make_pair(alt, T);
 | |
|     altmap[alt].emplace_back(origin, T);
 | |
|   
 | |
|     if(current.real_faces == 0 && DUAL) {
 | |
|       heptspin hs(origin, 0);
 | |
|       heptagon *hnew = build_child(hs, current.get_adj(hs));
 | |
|       for(int s=1; s<2*current.N; s++)
 | |
|         origin->c.connect(s, hnew, s, false);
 | |
|       }
 | |
|     else if(current.real_faces == 0) {
 | |
|       may_create_step(origin, 0); 
 | |
|       heptagon *o0 = origin->move(0);
 | |
|       may_create_step(origin, 1);
 | |
|       heptagon *o1 = origin->move(1);
 | |
|       for(int s=1; s<2*current.N; s+=2)
 | |
|         o0->c.connect(s, o1, 2*current.N-s, false);
 | |
|       for(int s=2; s<2*current.N; s+=2) {
 | |
|         heptspin hs(o0, s);
 | |
|         heptagon *hnew = build_child(hs, current.get_adj(hs));
 | |
|         // no need to specify archimedean_gmatrix and altmap
 | |
|         hnew->c.connect(1, heptspin(o1, 2*current.N-s));
 | |
|         }
 | |
|       o1->c.connect(1, o0, 2*current.N-1, false);
 | |
|       }
 | |
|     else if(origin->degree() == 2) {
 | |
|       may_create_step(origin, 0);
 | |
|       may_create_step(origin, 1);
 | |
|       origin->move(0)->c.connect(1, origin->move(1), 2*current.N-1, false);
 | |
|       origin->move(1)->c.connect(1, origin->move(0), 2*current.N-1, false);
 | |
|       }
 | |
|     
 | |
|     auto_compute_range(origin->c7);
 | |
|     }
 | |
| 
 | |
|   ~hrmap_archimedean() {
 | |
|     clearfrom(origin);
 | |
|     altmap.clear();
 | |
|     archimedean_gmatrix.clear();
 | |
|     if(current_altmap) {
 | |
|       dynamicval<eGeometry> g(geometry, gNormal);       
 | |
|       dynamicval<eVariation> gv(variation, eVariation::pure);
 | |
|       dynamicval<geometry_information*> gi(cgip, find_alt_cgip());
 | |
|       delete current_altmap;
 | |
|       current_altmap = NULL;
 | |
|       }
 | |
|     }
 | |
|   void verify() override { }
 | |
| 
 | |
|   heptagon *create_step(heptagon *h, int d) override {
 | |
|   
 | |
|     bool f = geom3::flipped;
 | |
|     if(f) {
 | |
|       dynamicval<int> uc(cgip->use_count, cgip->use_count+1);
 | |
|       auto bcgip = cgip;
 | |
|       geom3::light_flip(false);
 | |
|       check_cgi();
 | |
|       cgi.require_basics();
 | |
|       auto h1 = create_step(h, d);
 | |
|       geom3::light_flip(true);
 | |
|       cgip = bcgip;
 | |
|       return h1;
 | |
|       }
 | |
| 
 | |
|     DEBB(DF_GEOM, (heptspin(h,d), " ~ ?"));
 | |
| 
 | |
|     dynamicval<geometryinfo1> gi(ginf[geometry].g, ginf[gArchimedean].g);
 | |
|     
 | |
|     heptspin hi(h, d);
 | |
|     
 | |
|     while(skip_digons(hi, 1)) hi++;
 | |
|     
 | |
|     auto& t1 = current.get_triangle(hi);
 | |
|   
 | |
|     // * spin(-tri[id][pi+i].first) * lxpush(t.second) * pispin * spin(tri[id'][p'+d'].first)
 | |
| 
 | |
|     auto& p1 = archimedean_gmatrix[h];
 | |
|     
 | |
|     heptagon *alt = p1.first;
 | |
|   
 | |
|     transmatrix T = p1.second * spin(-t1.first) * lxpush(t1.second);
 | |
|     transmatrix U = Id;
 | |
| 
 | |
|     if(hyperbolic) {
 | |
|       dynamicval<int> uc(cgip->use_count, cgip->use_count+1);
 | |
|       dynamicval<eGeometry> g(geometry, gNormal);
 | |
|       dynamicval<eVariation> gv(variation, eVariation::pure);
 | |
|       dynamicval<geometry_information*> gi(cgip, find_alt_cgip());
 | |
|       dynamicval<hrmap*> cm(currentmap, current_altmap);
 | |
|       U = T;
 | |
|       current_altmap->virtualRebase(alt, T);
 | |
|       U = U * iso_inverse(T);
 | |
|       }
 | |
|     
 | |
|     if(euclid) {
 | |
|       /* hash the rough coordinates as heptagon* alt */
 | |
|       size_t s = size_t(T[0][LDIM]+.261) * 124101 + size_t(T[1][LDIM]+.261) * 82143;
 | |
|       alt = (heptagon*) s;
 | |
|       }
 | |
|       
 | |
|     DEBB(DF_GEOM, ("look for: ", alt, " / ", kz(T * C0)));
 | |
|   
 | |
|     for(auto& p2: altmap[alt]) if(same_point_may_warn(p2.second * tile_center(), T * tile_center())) {
 | |
|       DEBB(DF_GEOM, ("cell found: ", p2.first));
 | |
|       for(int d2=0; d2<p2.first->degree(); d2++) {
 | |
|         heptspin hs(p2.first, d2);
 | |
|         auto& t2 = current.get_triangle(p2.first, d2);
 | |
|         transmatrix T1 = T * spin(M_PI + t2.first);
 | |
|         DEBB(DF_GEOM, ("compare: ", kz(T1 * lxpush0(1)), ":: ", kz(p2.second * lxpush0(1))));
 | |
|         if(same_point_may_warn(T1 * lxpush0(1), p2.second * lxpush0(1))) {
 | |
|         
 | |
|           // T1 = p2.second
 | |
|           // T * spin(pi+t2.first) == p2.second
 | |
|           // p1.second * spinm(-t1.first) * lxpush(t1.second) * spin(pi+t2.first) == p2.second
 | |
|           
 | |
|           // bring p1 and p2 closer, to prevent floating point errors
 | |
|           if(hyperbolic) {
 | |
|             fixup_matrix(p1.second, U * p2.second * spin(-M_PI - t2.first) * lxpush(-t1.second) * spin(t1.first), 0.25);
 | |
|             fixup_matrix(p2.second, T1, 0.25);
 | |
|             }
 | |
|   
 | |
|           while(skip_digons(hs, -1)) hs--;
 | |
|           connectHeptagons(hi, hs);
 | |
|           connect_digons_too(hi, hs);
 | |
|           return h->move(d);
 | |
|           }
 | |
|         }
 | |
|       DEBB(DF_GEOM, ("but rotation not found"));
 | |
|       }
 | |
|     
 | |
|     auto& t2 = current.get_triangle(current.get_adj(hi));
 | |
|     transmatrix T1 = T * spin(M_PI + t2.first);
 | |
|     fixmatrix(T1);
 | |
|   
 | |
|     heptagon *hnew = build_child(hi, current.get_adj(hi));
 | |
|     altmap[alt].emplace_back(hnew, T1);
 | |
|     archimedean_gmatrix[hnew] = make_pair(alt, T1);
 | |
|     connect_digons_too(hi, heptspin(hnew, 0));
 | |
|     
 | |
|     return hnew;
 | |
|     }
 | |
|   
 | |
|   void draw_at(cell *at, const shiftmatrix& where) override {
 | |
|     dq::clear_all();
 | |
|     dq::enqueue(at->master, where);
 | |
|     
 | |
|     while(!dq::drawqueue.empty()) {
 | |
|       auto& p = dq::drawqueue.front();
 | |
|       heptagon *h = p.first;
 | |
|       shiftmatrix V = p.second;
 | |
|       dq::drawqueue.pop();
 | |
|   
 | |
|       int id = id_of(h);
 | |
|       int S = isize(current.triangles[id]);
 | |
|   
 | |
|       if(id < 2*current.N ? !DUAL : !PURE) {
 | |
|         if(!do_draw(h->c7, V)) continue;
 | |
|         drawcell(h->c7, V);
 | |
|         }
 | |
|   
 | |
|       for(int i=0; i<S; i++) {
 | |
|         if(DUAL && (i&1)) continue;
 | |
|         h->cmove(i);
 | |
|         if(PURE && id >= 2*current.N && h->move(i) && id_of(h->move(i)) >= 2*current.N) continue;
 | |
|         shiftmatrix V1 = V * current.adjcell_matrix(h, i);
 | |
|         optimize_shift(V1);
 | |
|         dq::enqueue(h->move(i), V1);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|   transmatrix adj(cell *c, int dir) override {
 | |
|     return calc_relative_matrix(c->cmove(dir), c, C0);
 | |
|     }
 | |
|   
 | |
|   transmatrix relative_matrixh(heptagon *h2, heptagon *h1, const hyperpoint& hint) override {
 | |
|     if(use_gmatrix && gmatrix0.count(h2->c7) && gmatrix0.count(h1->c7))
 | |
|       return inverse_shift(gmatrix0[h1->c7], gmatrix0[h2->c7]);
 | |
|     transmatrix gm = Id, where = Id;
 | |
|     auto& cof = current_or_fake();
 | |
|     while(h1 != h2) {
 | |
|       for(int i=0; i<neighbors_of(h1); i++) {
 | |
|         if(h1->move(i) == h2) {
 | |
|           return gm * cof.adjcell_matrix(h1, i) * where;
 | |
|           }
 | |
|         }
 | |
|       if(h1->distance > h2->distance) {
 | |
|         gm = gm * cof.adjcell_matrix(h1, 0);
 | |
|         h1 = h1->move(0);
 | |
|         }
 | |
|       else {
 | |
|         where = iso_inverse(cof.adjcell_matrix(h2, 0)) * where;
 | |
|         h2 = h2->move(0);
 | |
|         }
 | |
|       }
 | |
|     return gm * where;
 | |
|     }
 | |
|       
 | |
|   ld spin_angle(cell *c, int d) override {
 | |
|     auto &cof = current_or_fake();
 | |
|     if(PURE) {
 | |
|       auto& t1 = cof.get_triangle(c->master, d-1);
 | |
|       return -(t1.first + M_PI / c->type);
 | |
|       }
 | |
|     else if(DUAL) {
 | |
|       auto& t1 = cof.get_triangle(c->master, 2*d);
 | |
|       return -t1.first;
 | |
|       }
 | |
|     else { /* BITRUNCATED */
 | |
|       auto& t1 = cof.get_triangle(c->master, d);
 | |
|       return -t1.first;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|   void find_cell_connection(cell *c, int d) override {
 | |
|     if(PURE) {
 | |
|       if(arcm::id_of(c->master) < arcm::current.N * 2) {
 | |
|         heptspin hs = heptspin(c->master, d) + wstep + 2 + wstep + 1;
 | |
|         c->c.connect(d, hs.at->c7, hs.spin, hs.mirrored);
 | |
|         }
 | |
|       else c->c.connect(d, c, d, false);
 | |
|       }
 | |
|     else if(DUAL) {
 | |
|       if(arcm::id_of(c->master) >= arcm::current.N * 2) {
 | |
|         heptagon *h2 = createStep(c->master, d*2);
 | |
|         int d1 = c->master->c.spin(d*2);
 | |
|         c->c.connect(d, h2->c7, d1/2, false);
 | |
|         }
 | |
|       else {
 | |
|         printf("bad connection\n");
 | |
|         c->c.connect(d,c,d,false);
 | |
|         }
 | |
|       }
 | |
|     else hrmap::find_cell_connection(c, d);
 | |
|     }
 | |
| 
 | |
|   int shvid(cell *c) override {
 | |
|     auto& ac = arcm::current;
 | |
|     int id = arcm::id_of(c->master);
 | |
|     if(ac.regular && id>=2 && id < 2*ac.N) id &= 1;    
 | |
|     return id;    
 | |
|     }  
 | |
| 
 | |
|   int full_shvid(cell *c) override {
 | |
|     return id_of(c->master) + 20 * parent_index_of(c->master);
 | |
|     }
 | |
| 
 | |
|   hyperpoint get_corner(cell *c, int cid, ld cf) override {
 | |
|     auto &ac = arcm::current_or_fake();
 | |
|     if(PURE) {
 | |
|       if(arcm::id_of(c->master) >= ac.N*2) return C0;
 | |
|       auto& t = ac.get_triangle(c->master, cid-1);
 | |
|       return xspinpush0(-t.first, t.second * 3 / cf * (ac.real_faces == 0 ? 0.999 : 1));
 | |
|       }
 | |
|     if(BITRUNCATED) {
 | |
|       auto& t0 = ac.get_triangle(c->master, cid-1);
 | |
|       auto& t1 = ac.get_triangle(c->master, cid);
 | |
|       hyperpoint h0 = xspinpush0(-t0.first, t0.second * 3 / cf * (ac.real_faces == 0 ? 0.999 : 1));
 | |
|       hyperpoint h1 = xspinpush0(-t1.first, t1.second * 3 / cf * (ac.real_faces == 0 ? 0.999 : 1));
 | |
|       return mid3(tile_center(), h0, h1);
 | |
|       }
 | |
|     if(DUAL) {
 | |
|       auto& t0 = ac.get_triangle(c->master, 2*cid-1);
 | |
|       return xspinpush0(-t0.first, t0.second * 3 / cf * (ac.real_faces == 0 ? 0.999 : 1));
 | |
|       }
 | |
|     return C0;
 | |
|     }
 | |
| 
 | |
|   int pattern_value(cell *c) override {
 | |
|     if(sphere) return c->master->fiftyval;
 | |
|     return hrmap::pattern_value(c);
 | |
|     }
 | |
| 
 | |
|   };
 | |
| 
 | |
| EX hrmap *new_map() { return new hrmap_archimedean; }
 | |
| 
 | |
| heptagon *build_child(heptspin p, pair<int, int> adj) {
 | |
|   indenter ind;
 | |
|   auto h = buildHeptagon1(tailored_alloc<heptagon> (isize(current.adjacent[adj.first])), p.at, p.spin, hstate(1), 0);
 | |
|   DEBB(DF_GEOM, ("NEW ", p, " ~ ", heptspin(h, 0)));
 | |
|   id_of(h) = adj.first;
 | |
|   parent_index_of(h) = adj.second;
 | |
|   int nei = neighbors_of(h);
 | |
|   h->c7 = newCell(nei/DUALMUL, h);
 | |
|   h->distance = p.at->distance + 1;
 | |
|   if(adj.first < 2*current.N && !DUAL) {
 | |
|     int s = 0;
 | |
|     heptspin hs(p);
 | |
|     while(id_of(hs.at->move(0)) >= 2 * current.N) {
 | |
|       s += hs.spin / 2 - 1;
 | |
|       hs = hs - hs.spin + wstep - 1;
 | |
|       }
 | |
|     h->fieldval = hs.at->move(0)->fieldval + s + hs.spin/2;
 | |
|     }
 | |
|   else
 | |
|     h->fieldval = -100;
 | |
|   h->fiftyval = isize(archimedean_gmatrix);
 | |
|   if(p.at->s == hsOrigin)
 | |
|     h->rval1 = 1 + (p.spin % 2);
 | |
|   else {
 | |
|     if(p.spin % 2 == 0)
 | |
|       h->rval1 = p.at->move(0)->rval1;
 | |
|     else
 | |
|       h->rval1 = 3 - p.at->move(0)->rval1 - p.at->rval1;
 | |
|     }
 | |
|   h->rval0 = hrand(256);
 | |
|   heptspin hs(h, 0);
 | |
|   return h;
 | |
|   }
 | |
| 
 | |
| bool skip_digons(heptspin hs, int step) {
 | |
|   return
 | |
|     isize(current.adjacent[current.get_adj(hs).first]) == 2 ||
 | |
|     isize(current.adjacent[current.get_adj(hs+step).first]) == 2;
 | |
|   }
 | |
| 
 | |
| void connect_digons_too(heptspin h1, heptspin h2) {
 | |
|   if(skip_digons(h1, -1)) {
 | |
|     h1--, h2++;
 | |
|     heptagon *hnew = build_child(h1, current.get_adj(h1));
 | |
|     // no need to specify archimedean_gmatrix and altmap
 | |
|     hnew->c.connect(1, h2);
 | |
|     h1--, h2++;
 | |
|     DEBB(DF_GEOM, (hr::format("OL2 %p.%d ~ %p.%d\n", hr::voidp(h1.at), h1.spin, hr::voidp(h2.at), h2.spin)));
 | |
|     h1.at->c.connect(h1.spin, h2);
 | |
|     }
 | |
|   }
 | |
| 
 | |
| void connectHeptagons(heptspin hi, heptspin hs) {
 | |
|   DEBB(DF_GEOM, ("OLD ", hi, " ~ ", hs));
 | |
|   if(hi.at->move(hi.spin) == hs.at && hi.at->c.spin(hi.spin) == hs.spin) {
 | |
|     DEBB(DF_GEOM, (hr::format("WARNING: already connected\n")));
 | |
|     return;
 | |
|     }
 | |
|   if(hi.peek()) {
 | |
|     DEBB(DF_GEOM, (hr::format("ERROR: already connected left\n")));
 | |
|     throw hr_archimedean_error("Archimedean error: already connected left");
 | |
|     }
 | |
|   if(hs.peek()) {
 | |
|     DEBB(DF_GEOM, (hr::format("ERROR: already connected right\n")));
 | |
|     throw hr_archimedean_error("Archimedean error: already connected right");
 | |
|     }
 | |
|   hi.at->c.connect(hi.spin, hs);
 | |
| 
 | |
|   auto p = current.get_adj(hi);
 | |
|   if(current.tilegroup[p.first] != current.tilegroup[id_of(hs.at)]) {
 | |
|     printf("should merge %d %d\n", p.first, id_of(hs.at));
 | |
|     current.make_match(p.first, p.second, id_of(hs.at), hs.spin + parent_index_of(hs.at));
 | |
|     current.regroup();
 | |
|     }
 | |
|   // heptagon *hnew = build_child(h, d, get_adj(h, d).first, get_adj(h, d).second);
 | |
|   }
 | |
| 
 | |
| /** T and X are supposed to be equal -- move T so that it is closer to X */
 | |
| void fixup_matrix(transmatrix& T, const transmatrix& X, ld step) {
 | |
|   for(int i=0; i<MXDIM; i++)
 | |
|   for(int j=0; j<MXDIM; j++)
 | |
|     T[i][j] = (T[i][j] * (1-step) + X[i][j] * step);
 | |
| 
 | |
|   /*
 | |
|   for(int i=0; i<3; i++)
 | |
|   for(int j=0; j<3; j++)
 | |
|     if(T[i][j] - X[i][j] > 1e-3) exit(1);
 | |
|   */
 | |
|   fixmatrix(T);
 | |
|   }
 | |
| 
 | |
| pair<ld, ld>& archimedean_tiling::get_triangle(heptagon *h, int cid) {
 | |
|   return triangles[id_of(h)][gmod(parent_index_of(h) + cid, neighbors_of(h))];
 | |
|   }
 | |
| 
 | |
| pair<int, int>& archimedean_tiling::get_adj(heptagon *h, int cid) {
 | |
|   return adjacent[id_of(h)][gmod(parent_index_of(h) + cid, neighbors_of(h))];
 | |
|   }
 | |
| 
 | |
| pair<int, int>& archimedean_tiling::get_adj(const pair<int, int>& p, int delta) {
 | |
|   return adjacent[p.first][gmod(p.second + delta, isize(adjacent[p.first]))];
 | |
|   }
 | |
| 
 | |
| pair<ld, ld>& archimedean_tiling::get_triangle(const pair<int, int>& p, int delta) {
 | |
|   return triangles[p.first][gmod(p.second + delta, isize(adjacent[p.first]))];
 | |
|   }
 | |
| 
 | |
| transmatrix archimedean_tiling::adjcell_matrix(heptagon *h, int d) {
 | |
|   auto& t1 = get_triangle(h, d);
 | |
| 
 | |
|   heptagon *h2 = h->move(d);
 | |
| 
 | |
|   int d2 = h->c.spin(d);
 | |
|   auto& t2 = get_triangle(h2, d2);
 | |
|   
 | |
|   return spin(-t1.first) * lxpush(t1.second) * spin(M_PI + t2.first);
 | |
|   }
 | |
| 
 | |
| EX int fix(heptagon *h, int spin) {
 | |
|   int type = isize(current.adjacent[id_of(h)]);
 | |
|   spin %= type;
 | |
|   if(spin < 0) spin += type;
 | |
|   return spin;
 | |
|   }
 | |
| 
 | |
| void archimedean_tiling::parse() {
 | |
|   int at = 0;
 | |
|   
 | |
|   auto peek = [&] () { if(at == isize(symbol)) return char(0); else return symbol[at]; };
 | |
|   auto is_number = [&] () { char p = peek(); return p >= '0' && p <= '9'; };
 | |
|   auto read_number = [&] () { int result = 0; while(is_number()) result = 10 * result + peek() - '0', at++; return result; };
 | |
|   
 | |
|   faces.clear(); nflags.clear();
 | |
|   have_line = false;
 | |
|   have_ph = false;
 | |
|   int nflags0 = 0;
 | |
|   auto nfback = [this, &nflags0] () -> int& { if(nflags.empty()) return nflags0; else return nflags.back(); };
 | |
|   while(true) {
 | |
|     if(peek() == ')' || (peek() == '(' && isize(faces)) || peek() == 0) break;
 | |
|     else if((peek() == 'L') && faces.size()) {
 | |
|       if(!nflags.empty()) nfback() |= sfLINE;
 | |
|       have_line = true, at++;
 | |
|       }
 | |
|     else if((peek() == 'l') && faces.size()) {
 | |
|       if(!nflags.empty()) nfback() |= sfSEMILINE;
 | |
|       have_line = true, at++;
 | |
|       }
 | |
|     else if((peek() == 'H' || peek() == 'h') && faces.size()) {
 | |
|       if(!nflags.empty()) nfback() |= sfPH;
 | |
|       have_ph = true, at++;
 | |
|       }
 | |
|     else if(is_number()) faces.push_back(read_number()), nflags.push_back(0);
 | |
|     else if(peek() == '^' && !faces.empty()) {
 | |
|       at++;
 | |
|       int rep = read_number();
 | |
|       if(rep == 0) nflags.pop_back(), faces.pop_back();
 | |
|       for(int i=1; i<rep; i++) nflags.push_back(nfback()), faces.push_back(faces.back());
 | |
|       }
 | |
|     else at++;
 | |
|     }
 | |
|   nflags.push_back(nflags0);
 | |
|   repetition = 1;
 | |
|   N = isize(faces);
 | |
|   invert.clear(); invert.resize(N, true);
 | |
|   adj.clear(); adj.resize(N, 0); for(int i=0; i<N; i++) adj[i] = i;
 | |
|   while(peek() != 0) {
 | |
|     if(peek() == '^') at++, repetition = read_number();
 | |
|     else if(peek() == '(') { 
 | |
|       at++; int a = read_number(); while(!is_number() && !among(peek(), '(', '[', ')',']', 0)) at++;
 | |
|       if(is_number()) { int b = read_number(); adj[a] = b; adj[b] = a; invert[a] = invert[b] = false; }
 | |
|       else { invert[a] = false; }
 | |
|       }
 | |
|     else if(peek() == '[') { 
 | |
|       at++; int a = read_number(); while(!is_number() && !among(peek(), '(', '[', ')',']', 0)) at++;
 | |
|       if(is_number()) { int b = read_number(); adj[a] = b; adj[b] = a; invert[a] = invert[b] = true; }
 | |
|       else { invert[a] = true; }
 | |
|       }
 | |
|     else at++;
 | |
|     }
 | |
|   for(int i=0; i<N * (repetition-1); i++)
 | |
|     faces.push_back(faces[i]),
 | |
|     nflags.push_back(nflags[i]),
 | |
|     invert.push_back(invert[i]),
 | |
|     adj.push_back(adj[i] + N);
 | |
|   N *= repetition;
 | |
|   prepare();  
 | |
|   }
 | |
| 
 | |
| EX bool load_symbol(const string& s, bool switch_geom) {
 | |
|   archimedean_tiling at; at.parse(s);
 | |
|   if(at.errors) {
 | |
|     DEBB(DF_ERROR | DF_GEOM, ("error: ", at.errormsg));
 | |
|     return false;
 | |
|     }
 | |
|   if(!switch_geom && geometry != gArchimedean) return true;
 | |
|   stop_game();
 | |
|   set_geometry(gArchimedean);
 | |
|   current = at;
 | |
|   if(!delayed_start) start_game();
 | |
|   return true;
 | |
|   }
 | |
| 
 | |
| #if CAP_COMMANDLINE  
 | |
| int readArgs() {
 | |
|   using namespace arg;
 | |
|            
 | |
|   if(0) ;
 | |
|   else if(argis("-symbol")) {
 | |
|     PHASEFROM(2);
 | |
|     shift(); load_symbol(args(), true);
 | |
|     showstartmenu = false;
 | |
|     }
 | |
|   else if(argis("-dual")) {
 | |
|     PHASEFROM(2);
 | |
|     if(in())
 | |
|       set_variation(eVariation::dual);
 | |
|     else gp::dual_of_current();
 | |
|     }
 | |
|   else if(argis("-d:arcm")) 
 | |
|     launch_dialog(show);
 | |
|   else return 1;
 | |
|   return 0;
 | |
|   }
 | |
| #endif
 | |
| 
 | |
| #if CAP_COMMANDLINE
 | |
| auto hook = 
 | |
|   addHook(hooks_args, 100, readArgs)
 | |
| + addHook(hooks_gamedata, 0, [] (gamedata* gd) { gd->store(altmap); gd->store(archimedean_gmatrix); gd->store(current_altmap); });
 | |
| 
 | |
| #endif
 | |
| 
 | |
| #if MAXMDIM >= 4
 | |
| auto hooksw = addHook(hooks_swapdim, 100, [] {
 | |
| 
 | |
|   if(!arcm::in()) return;
 | |
| 
 | |
|   dynamicval<eGeometry> g(geometry, gNormal);
 | |
|   dynamicval<eVariation> gv(variation, eVariation::pure);
 | |
| 
 | |
|   alt_cgip[0] = nullptr;
 | |
|   alt_cgip[1] = nullptr;
 | |
| 
 | |
|   dynamicval<geometry_information*> gi(cgip, find_alt_cgip());
 | |
| 
 | |
|   for(auto& p: altmap) for(auto& pp: p.second) swapmatrix(pp.second);
 | |
|   for(auto& p: archimedean_gmatrix) swapmatrix(p.second.second);
 | |
| 
 | |
|   alt_cgip[0] = nullptr;
 | |
|   alt_cgip[1] = nullptr;
 | |
|   });
 | |
| #endif
 | |
| 
 | |
| int archimedean_tiling::support_threecolor() {
 | |
|   return (isize(faces) == 3 && invert[0] && invert[1] && invert[2] && faces[0] % 2 == 0 && faces[1] % 2 == 0 && faces[2] % 2 == 0) ? 2 :
 | |
|     tilegroup[N*2] > 1 ? 1 :
 | |
|     0;
 | |
|   return 2;
 | |
|   }
 | |
| 
 | |
| int archimedean_tiling::support_threecolor_bitruncated() {
 | |
|   for(int i: faces) if(i % 2) return 0;
 | |
|   return 2;
 | |
|   }
 | |
| 
 | |
| int archimedean_tiling::support_football() {
 | |
|   return 
 | |
|     have_ph ? 1 :
 | |
|     (isize(faces) == 3 && invert[0] && invert[1] && invert[2] && faces[1] % 2 == 0 && faces[2] % 2 == 0) ? 2 :
 | |
|     0;
 | |
|   }
 | |
| 
 | |
| bool archimedean_tiling::support_chessboard() {
 | |
|   return N % 2 == 0;
 | |
|   }
 | |
| 
 | |
| EX bool pseudohept(cell *c) {
 | |
|   if(DUAL)
 | |
|     return !(c->master->rval0 & 3);
 | |
|   int id = id_of(c->master);
 | |
|   if(PURE) 
 | |
|     return current.flags[id] & arcm::sfPH;
 | |
|   if(BITRUNCATED)
 | |
|     return id < current.N * 2;
 | |
|   return false;
 | |
|   }
 | |
| 
 | |
| EX bool chessvalue(cell *c) {
 | |
|   if(DUAL)
 | |
|     return c->master->rval1 - 1;
 | |
|   return c->master->fieldval & 1;
 | |
|   }
 | |
| 
 | |
| EX bool linespattern(cell *c) {
 | |
|   return current.flags[id_of(c->master)] & arcm::sfLINE;
 | |
|   }
 | |
| 
 | |
| EX int threecolor(cell *c) {
 | |
|   if(current.have_ph)
 | |
|     return !arcm::pseudohept(c);
 | |
|   else if(PURE)
 | |
|     return current.tilegroup[id_of(c->master)];
 | |
|   else {
 | |
|     int id = id_of(c->master);
 | |
|     if(current.support_threecolor() == 2) return id < current.N * 2 ? (id&1) : 2;
 | |
|     return current.tilegroup[id];
 | |
|     }
 | |
|   }
 | |
| 
 | |
| int cEucRegular = 0x008000;
 | |
| int cEucSemiregular = 0x40C040;
 | |
| int cPlatonic = 0x000080;
 | |
| int cArchimedean = 0x4040C0;
 | |
| int cPrism = 0x40A0A0;
 | |
| int cAntiPrism = 0x80A0A0;
 | |
| int cHyperRegular = 0x800000;
 | |
| int cHyperSemi = 0xC04040;
 | |
| 
 | |
| int cWeird = 0xA000A0;
 | |
| 
 | |
| EX vector<pair<string, int> > samples = {
 | |
|   /* Euclidean */
 | |
|   {"(3,3,3,3,3,3)", cEucRegular},
 | |
|   {"(4,4,4,4)", cEucRegular},
 | |
|   {"(6,6,6)", cEucRegular},
 | |
|   {"(8,8,4)", cEucSemiregular},
 | |
|   {"(4,6,12)", cEucSemiregular},
 | |
|   {"(6,4,3,4)", cEucSemiregular}, 
 | |
|   {"(3,6,3,6)", cEucSemiregular}, 
 | |
|   {"(3,12,12)", cEucSemiregular}, 
 | |
|   {"(4,4,3L,3L,3L) [3,4]", cEucSemiregular},
 | |
|   {"(3,3,3,3,6) (1,2)(0,4)(3)", cEucSemiregular},
 | |
|   {"(3,3,4,3,4) (0,4)(1)(2,3)", cEucSemiregular},
 | |
|   
 | |
|   /* Platonic */
 | |
|   {"(3,3,3)", cPlatonic},
 | |
|   {"(3,3,3,3)", cPlatonic},
 | |
|   {"(3,3,3,3,3)", cPlatonic},
 | |
|   {"(4,4,4)", cPlatonic},
 | |
|   {"(5,5,5)", cPlatonic},
 | |
|   
 | |
|   /* Archimedean solids */
 | |
|   {"(3,6,6)", cArchimedean},
 | |
|   {"(3,4,3,4)", cArchimedean},
 | |
|   {"(3,8,8)", cArchimedean},
 | |
|   {"(4,6,6)", cArchimedean},
 | |
|   {"(3,4,4,4)", cArchimedean},
 | |
|   {"(4,6,8)", cArchimedean}, 
 | |
|   {"(3,3,3,3,4) (1,2)(0,4)(3)", cArchimedean},
 | |
|   {"(3,5,3,5)", cArchimedean},
 | |
|   {"(3,10,10)", cArchimedean},
 | |
|   {"(5,6,6)", cArchimedean},
 | |
|   {"(3,4,5,4)", cArchimedean},
 | |
|   {"(4,6,10)", cArchimedean},
 | |
|   {"(3,3,3,3,5) (1,2)(0,4)(3)", cArchimedean},
 | |
|   
 | |
|   /* prisms */
 | |
|   {"(3,4,4)", cPrism},
 | |
|   {"(5,4,4)", cPrism},
 | |
|   {"(6,4,4)", cPrism},
 | |
|   {"(7,4,4)", cPrism},
 | |
|   
 | |
|   /* sample antiprisms */
 | |
|   {"(3,3,3,4)(1)(2)", cAntiPrism},
 | |
|   {"(3,3,3,5)(1)(2)", cAntiPrism},
 | |
|   {"(3,3,3,6)(1)(2)", cAntiPrism},
 | |
|   {"(3,3,3,7)(1)(2)", cAntiPrism}, 
 | |
|   
 | |
|   /* hyperbolic ones */
 | |
|   {"(3)^7", cHyperRegular},
 | |
|   {"(4)^5", cHyperRegular}, 
 | |
|   {"(4)^6", cHyperRegular}, 
 | |
|   {"(5,5,5,5)", cHyperRegular},
 | |
|   {"(7,7,7)", cHyperRegular},
 | |
|   {"(8,8,8)", cHyperRegular},
 | |
|   {"(7,6^2)", cHyperSemi},
 | |
|   {"(4,6,14)", cHyperSemi},
 | |
|   {"(3,4,7,4)", cHyperSemi},
 | |
|   {"(6,6,4L,4L)", cHyperSemi},
 | |
|   {"(8,8,4L,4L)", cHyperSemi},
 | |
|   {"(3,3,3,3,7) (1,2)(0,4)(3)", cHyperSemi}, 
 | |
|   {"(3H,6,6,6) (1,0)[2](3)", cHyperSemi},
 | |
|   {"(3,6,6,6) (0 1)(2)(3)", cHyperSemi},
 | |
|   {"(3,4,4L,4L,4)", cHyperSemi}, // buggy
 | |
|   {"(3l,4l,4,4,4) (0 1)[2 3](4)", cHyperSemi},
 | |
|   {"(3,4,4,4,4) (0 1)(2)(3)(4)", cHyperSemi},
 | |
|   {"(3,4,4L,4L,4L,4)", cHyperSemi},
 | |
|   {"(6,6,3L,3L,3L) (0 2)(1)(3)(4)", cHyperSemi},
 | |
|   {"(5,3,5,3,3) (0 1)(2 3)(4)", cHyperSemi}, 
 | |
|   {"(4,3,3,3,3,3) (0 1)(2 3)(4 5)", cHyperSemi},
 | |
|   {"(3l,5l,5,5,5,5) (0 1)[2 3](4)(5)", cHyperSemi},
 | |
|   {"(3,5,5,5,5,5) (0 1)(2 4)(3 5)", cHyperSemi}, 
 | |
|   {"(3l,5l,5,5,5,5) (0 1)(2 4)[3 5]", cHyperSemi},
 | |
|   {"(3l,5l,5,5,5,5) (0 1)[2 4](3)(5)", cHyperSemi}, 
 | |
|   {"(3,5,5,5,5,5) (0 1)(2)(3)(4)(5)", cHyperSemi}, 
 | |
|   {"(3,3,4,3,5)(0,4)(1)(2,3)", cHyperSemi},
 | |
|   {"(3,14,14)", cHyperSemi},
 | |
|   {"(3,3,3,3,3,4)[0](1,2)(3,4)[5]", cHyperSemi},
 | |
|   
 | |
|   /* interesting symmetry variants */
 | |
|   {"(3,3,3,3,3,3) (0,1)(2,3)(4,5)", cEucRegular},
 | |
|   {"(3,3H,3,3,3L,3L,3L) (0 4)(1 2)(3)(5)(6)", cHyperRegular},
 | |
|   {"(3,3H,3,3,3L,3L,3L) (0 4)(1 2)(3)[5 6]", cHyperRegular},
 | |
|   {"(3,3H,3,3L,3,3L,3L) [0 4](1 2)[3 5](6)", cHyperRegular},
 | |
| 
 | |
|   /* with digons */
 | |
|   {"(2,3,3,3,3,3) (2,3)(4,5)", cWeird},
 | |
|   {"(6,6)", cWeird},
 | |
|   {"(2,2)", cWeird},
 | |
|   {"(2,2,2,2,2,2)", cWeird},
 | |
|   {"(6,6,2)", cWeird},
 | |
|   {"(6,2,6,2)", cWeird},
 | |
|   };
 | |
| 
 | |
| int lastsample = 0;
 | |
| 
 | |
| vector<archimedean_tiling> tilings;
 | |
| 
 | |
| int spos = 0;
 | |
| 
 | |
| archimedean_tiling edited;
 | |
| 
 | |
| bool symbol_editing;
 | |
| 
 | |
| EX void next_variation() {
 | |
|   set_variation(
 | |
|     PURE ? eVariation::dual :
 | |
|     DUAL ? eVariation::bitruncated : 
 | |
|     eVariation::pure);
 | |
|   start_game();
 | |
|   }
 | |
| 
 | |
| EX void enable(archimedean_tiling& arct) {
 | |
|   stop_game();
 | |
|   if(!in()) set_variation(eVariation::pure);
 | |
|   set_geometry(gArchimedean);
 | |
|   patterns::whichPattern = patterns::PAT_NONE;
 | |
|   current = arct;
 | |
| #if CAP_TEXTURE
 | |
|   if(texture::config.tstate == texture::tsActive && texture::cgroup == cpThree) {
 | |
|     patterns::whichPattern = patterns::PAT_COLORING;
 | |
|     if(geosupport_threecolor() < 2) {
 | |
|       if(arct.support_threecolor() == 2) set_variation(eVariation::pure);
 | |
|       else if(arct.support_threecolor_bitruncated() == 2) set_variation(eVariation::bitruncated);
 | |
|       }
 | |
|     }
 | |
|   if(texture::config.tstate == texture::tsActive && texture::cgroup == cpFootball) {
 | |
|     patterns::whichPattern = patterns::PAT_TYPES, patterns::subpattern_flags = patterns::SPF_FOOTBALL;
 | |
|     if(geosupport_football() < 2) set_variation(eVariation::bitruncated);
 | |
|     }
 | |
|   if(texture::config.tstate == texture::tsActive && texture::cgroup == cpChess) {
 | |
|     patterns::whichPattern = patterns::PAT_CHESS;
 | |
|     if(!geosupport_chessboard()) {
 | |
|       if(arct.support_chessboard()) set_variation(eVariation::pure);
 | |
|       else if(arct.support_threecolor_bitruncated() == 2) set_variation(eVariation::dual);
 | |
|       }
 | |
|     }
 | |
| #endif
 | |
|   start_game();
 | |
|   }
 | |
| 
 | |
| function<void()> setcanvas(ccolor::data& c) {
 | |
|   auto pc = &c;
 | |
|   return [pc] () {
 | |
|     stop_game();
 | |
|     enable_canvas();
 | |
|     ccolor::which = pc;
 | |
|     start_game();
 | |
|     };
 | |
|   }
 | |
| 
 | |
| dialog::string_dialog se;
 | |
| 
 | |
| EX void init_symbol_edit() {
 | |
|   symbol_editing = true;
 | |
|   edited = current;
 | |
|   se.start_editing(edited.symbol);
 | |
|   edited.parse();
 | |
|   }
 | |
| 
 | |
| EX void show() {
 | |
|   if(lastsample < isize(samples)) {
 | |
|     string s = samples[lastsample].first;
 | |
|     int col = samples[lastsample].second;
 | |
|     lastsample++;
 | |
|     archimedean_tiling tested;
 | |
|     tested.parse(s);
 | |
|     if(tested.errors) {
 | |
|       DEBB(DF_GEOM | DF_WARN, ("WARNING: ", tested.errors, " errors on ", s, " '", tested.errormsg, "'"));
 | |
|       }
 | |
|     else {
 | |
|       tested.coloring = col;
 | |
|       tilings.push_back(std::move(tested));
 | |
|       /* sort(tilings.begin(), tilings.end(), [] (archimedean_tiling& s1, archimedean_tiling& s2) {
 | |
|         if(s1.euclidean_angle_sum < s2.euclidean_angle_sum - 1e-6) return true;
 | |
|         if(s2.euclidean_angle_sum < s1.euclidean_angle_sum - 1e-6) return false;
 | |
|         return s1.symbol < s2.symbol;
 | |
|         }); */
 | |
|       }
 | |
|     }
 | |
|   cmode = sm::SIDE | sm::MAYDARK;
 | |
|   gamescreen();
 | |
|   dialog::init(XLAT("Archimedean tilings"));
 | |
|   
 | |
|   if(symbol_editing) {
 | |
|     dialog::addSelItem("edit", se.view_edited_string(), '/');
 | |
|     dialog::add_action([] () { 
 | |
|       symbol_editing = false;
 | |
|       if(!edited.errors) enable(edited);
 | |
|       });
 | |
|     dialog::addBreak(100);
 | |
|     if(edited.errors)
 | |
|       dialog::addInfo(edited.errormsg, 0xFF0000);
 | |
|     else
 | |
|       dialog::addInfo(XLAT("OK"), 0x00FF00);
 | |
|     
 | |
|     dialog::addBreak(100);
 | |
|     dialog::addSelItem(XLAT("full angle"), fts(edited.euclidean_angle_sum * 180) + "°", 0);
 | |
|     dialog::addSelItem(XLAT("size of the world"), edited.world_size(), 0);
 | |
| 
 | |
|     edited.compute_geometry();
 | |
|     dialog::addSelItem(XLAT("edge length"), fts(edited.edgelength) + (edited.get_class() == gcEuclid ? XLAT(" (arbitrary)") : ""), 0);
 | |
|     current.compute_geometry();
 | |
| 
 | |
|     dialog::addBreak(100);
 | |
|     dialog::addKeyboardItem("1234567890");
 | |
|     dialog::addKeyboardItem("()[]lLhH,");
 | |
|     dialog::addKeyboardItem(" \t\b\x1\x2\n");
 | |
|     dialog::addBreak(100);
 | |
|     }
 | |
|   else {
 | |
|     string cs = in() ? current.symbol : XLAT("OFF");
 | |
|     dialog::addSelItem("edit", cs, '/');  
 | |
|     dialog::add_action(init_symbol_edit);
 | |
|     dialog::addBreak(100);
 | |
|     int nextpos = spos;
 | |
|     int shown = 0;
 | |
|     while(nextpos < isize(tilings) && shown < 10) {
 | |
|       auto &ps = tilings[nextpos++];
 | |
|       bool valid = true;
 | |
|       string suffix = "";
 | |
| #if CAP_TEXTURE
 | |
|       if(texture::config.tstate == texture::tsActive && texture::cgroup == cpThree) {
 | |
|         valid = false;
 | |
|         if(ps.support_threecolor() == 2) valid = true, suffix += bitruncnames[int(eVariation::pure)];
 | |
|         if(ps.support_threecolor_bitruncated() == 2) valid = true, suffix += bitruncnames[int(eVariation::bitruncated)];
 | |
|         }
 | |
|       if(texture::config.tstate == texture::tsActive && texture::cgroup == cpFootball) {
 | |
|         if(ps.support_football() == 2) suffix += bitruncnames[int(eVariation::pure)];
 | |
|         suffix += bitruncnames[int(eVariation::bitruncated)];
 | |
|         }
 | |
|       if(texture::config.tstate == texture::tsActive && texture::cgroup == cpChess && !ps.support_chessboard()) {
 | |
|         valid = false;
 | |
|         if(ps.support_chessboard()) valid = true, suffix += bitruncnames[int(eVariation::pure)];
 | |
|         if(ps.support_threecolor_bitruncated() == 2) valid = true, suffix += bitruncnames[int(eVariation::dual)];
 | |
|         }
 | |
| #endif
 | |
|       if(!valid) continue;
 | |
|       if(current_filter == &gf_hyperbolic && ps.get_geometry().kind != gcHyperbolic) continue;
 | |
|       if(current_filter == &gf_spherical && ps.get_geometry().kind != gcSphere) continue;
 | |
|       if(current_filter == &gf_euclidean && ps.get_geometry().kind != gcEuclid) continue;
 | |
|       dialog::addSelItem(ps.symbol, fts(ps.euclidean_angle_sum * 180) + "°" + suffix, 'a' + shown);
 | |
|       dialog::lastItem().color = ps.coloring;
 | |
|       dialog::add_action([&] () { enable(ps); });
 | |
|       shown++;
 | |
|       }
 | |
|     dialog::addSelItem(XLAT("current filter"), current_filter ? XLAT(current_filter->name) : XLAT("none"), 'x');
 | |
|     dialog::add_action([] {
 | |
|       if(current_filter == &gf_hyperbolic) current_filter = &gf_euclidean;
 | |
|       else if(current_filter == &gf_euclidean) current_filter = &gf_spherical;
 | |
|       else if(current_filter == &gf_spherical) current_filter = nullptr;
 | |
|       else current_filter = &gf_hyperbolic;
 | |
|       });
 | |
|     dialog::addItem(XLAT("next page"), '-');
 | |
|     if(shown == 0) nextpos = 0;
 | |
|     dialog::add_action([nextpos] () {
 | |
|       if(nextpos >= isize(tilings))
 | |
|         spos = 0;
 | |
|       else spos = nextpos;
 | |
|       });
 | |
|     dialog::addItem(XLAT("previous page"), '=');
 | |
|     dialog::add_action([] () {
 | |
|       spos -= 10;
 | |
|       if(spos < 0) spos = 0;
 | |
|       });
 | |
|     
 | |
|     if(in()) {
 | |
|       dialog::addSelItem(XLAT("size of the world"), current.world_size(), 'S');
 | |
|       add_size_action();
 | |
| 
 | |
|       dialog::addSelItem(XLAT("edge length"), current.get_class() == gcEuclid ? (fts(current.edgelength) + XLAT(" (arbitrary)")) : fts(current.edgelength), 0);
 | |
| 
 | |
|       dialog::addItem(XLAT("color by symmetries"), 't');
 | |
|       dialog::add_action(setcanvas(ccolor::shape));
 | |
|       dialog::addItem(XLAT("color by symmetries (reversed tiles marked)"), 'r');
 | |
|       dialog::add_action(setcanvas(ccolor::shape_mirror));
 | |
|       }
 | |
|     else {
 | |
|       dialog::addBreak(100);
 | |
|       dialog::addBreak(100);
 | |
|       dialog::addBreak(100);
 | |
|       }
 | |
| 
 | |
|     if(true) {
 | |
|       dialog::addItem(XLAT("color by sides"), 'u');
 | |
|       dialog::add_action(setcanvas(ccolor::sides));
 | |
|       }
 | |
|     
 | |
|     if(geosupport_threecolor() == 2) {
 | |
|       dialog::addItem(XLAT("three colors"), 'w');
 | |
|       dialog::add_action(setcanvas(ccolor::threecolor));
 | |
|       }
 | |
|     else if(geosupport_football() == 2) {
 | |
|       dialog::addItem(XLAT("football"), 'w');
 | |
|       dialog::add_action(setcanvas(ccolor::football));
 | |
|       }
 | |
|     else if(geosupport_chessboard()) {
 | |
|       dialog::addItem(XLAT("chessboard"), 'w');
 | |
|       dialog::add_action(setcanvas(ccolor::chessboard));
 | |
|       }
 | |
|     else dialog::addBreak(100);
 | |
| 
 | |
|     if(in()) {
 | |
|       dialog::addSelItem(XLAT("variations"), gp::operation_name(), 'v');
 | |
|       dialog::add_action(next_variation);
 | |
|       }
 | |
|     else dialog::addBreak(100);
 | |
|     }
 | |
| 
 | |
|   dialog::addHelp();
 | |
|   dialog::addBack();
 | |
|   dialog::display();
 | |
| 
 | |
|   se.handle_textinput();
 | |
|   keyhandler = [] (int sym, int uni) {
 | |
|     if(symbol_editing && sym == SDLK_RETURN) sym = uni = '/';
 | |
|     dialog::handleNavigation(sym, uni);
 | |
|     if(symbol_editing && se.handle_edit_string(sym, uni)) {
 | |
|       edited.parse(edited.symbol);
 | |
|       return;
 | |
|       }
 | |
|     if(doexiton(sym, uni)) popScreen();
 | |
|     };
 | |
|   }
 | |
| 
 | |
| void archimedean_tiling::get_nom_denom(int& anom, int& adenom) {
 | |
|   int nom = 2 - N, denom = 2;
 | |
|   for(int f: faces) {
 | |
|     if(f == 0) {
 | |
|       /* prevent a crash */
 | |
|       anom = 1; adenom = 1; return;
 | |
|       }
 | |
|     int g = gcd(denom, f);
 | |
|     nom = (nom * f + denom) / g;
 | |
|     denom = denom / g * f;
 | |
|     }
 | |
|   anom = 0, adenom = 1;
 | |
|   if(BITRUNCATED || DUAL) anom = 1, adenom = 1;
 | |
|   if(!DUAL) for(int f: faces) {
 | |
|     int g = gcd(adenom, f);
 | |
|     anom = (anom * f + adenom) / g;
 | |
|     adenom = adenom / g * f;
 | |
|     }
 | |
|   anom *= 2 * denom, adenom *= nom;
 | |
|   int g = gcd(anom, adenom);
 | |
|   if(g != 0) {
 | |
|     anom /= g; adenom /= g;
 | |
|     }
 | |
|   if(adenom < 0) anom = -anom, adenom = -adenom;
 | |
|   }
 | |
| 
 | |
| string archimedean_tiling::world_size() {
 | |
|   if(get_class() == gcEuclid) return "∞";
 | |
|   
 | |
|   int anom, adenom;
 | |
|   get_nom_denom(anom, adenom);
 | |
| 
 | |
|   string s;
 | |
|   bool hyp = (anom < 0);
 | |
|   if(hyp) anom = -anom;
 | |
|   if(adenom != 1) 
 | |
|     s += its(anom) + "/" + its(adenom);
 | |
|   else
 | |
|     s += its(anom);
 | |
|   if(hyp) s += " exp(∞)";
 | |
|   return s;
 | |
|   }
 | |
| 
 | |
| EX int degree(heptagon *h) {
 | |
|   return isize(current.adjacent[id_of(h)]);
 | |
|   }
 | |
| 
 | |
| EX bool is_vertex(heptagon *h) {
 | |
|   return id_of(h) >= 2 * current.N;
 | |
|   }
 | |
| 
 | |
| EX int get_graphical_id(cell *c) {
 | |
|   int id = arcm::id_of(c->master);
 | |
|   int tid = arcm::current.tilegroup[id];
 | |
|   int tid2 = arcm::current.tilegroup[id^1];
 | |
|   if(tid2 >= 0) tid = min(tid, tid2);
 | |
|   return tid;
 | |
|   }
 | |
| 
 | |
| ld archimedean_tiling::dual_tile_area() {
 | |
|   /* this will work both in Euclidean and non-Euclidean cases */
 | |
|   /* (note: we cannot just check get_geometry() here because we might be fake) */
 | |
| 
 | |
|   ld total_alpha = 0;
 | |
|   for(auto a: alphas) total_alpha += a;
 | |
| 
 | |
|   if(abs(total_alpha - M_PI) > 1e-6) {
 | |
|     return 2 * abs(M_PI - total_alpha);
 | |
|     }
 | |
| 
 | |
|   ld total = 0;
 | |
|   for(auto r: inradius) total += r;
 | |
|   return total * edgelength / 2;
 | |
|   }
 | |
| 
 | |
| bool archimedean_tiling::get_step_values(int& steps, int& single_step) {
 | |
| 
 | |
|   int nom = -2;
 | |
|   int denom = 1;
 | |
|   
 | |
|   for(int f: arcm::current.faces) {
 | |
|     if(int(denom*f)/f != denom) { steps = 0; single_step = 0; return false; }
 | |
|     int g = gcd(denom, f);
 | |
|     nom = nom * (f/g) + (f-2) * (denom/g);
 | |
|     denom = denom/g * f;
 | |
|     }
 | |
|     
 | |
|   steps = 2 * abs(denom);
 | |
|   single_step = abs(nom);
 | |
|   if(steps/2 != abs(denom)) return false;
 | |
|   return (2 * denom) % nom == 0;  
 | |
|   }
 | |
| 
 | |
| EX int valence() {
 | |
|   if(PURE) return arcm::current.N;
 | |
|   if(BITRUNCATED) return 3;
 | |
|   // in DUAL, usually valence would depend on the vertex.
 | |
|   // 3 is the most interesting, as it allows us to kill hedgehog warriors
 | |
|   int total = 0;
 | |
|   for(int i: current.faces) {
 | |
|     if(i == 3) return 3;
 | |
|     total += i;
 | |
|     }
 | |
|   return total / isize(current.faces);
 | |
|   }
 | |
|  
 | |
| EX map<gp::loc, cdata>& get_cdata() { return ((arcm::hrmap_archimedean*) (currentmap))->eucdata; }
 | |
| 
 | |
| #endif
 | |
| 
 | |
| EX }
 | |
| 
 | |
| }
 | 
