mirror of
				https://github.com/zenorogue/hyperrogue.git
				synced 2025-10-29 04:47:41 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			2204 lines
		
	
	
		
			65 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			2204 lines
		
	
	
		
			65 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // Hyperbolic Rogue -- Arbitrary Tilings
 | |
| // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
 | |
| 
 | |
| /** \file arbitrile.cpp 
 | |
|  *  \brief Arbitrary tilings
 | |
|  *
 | |
|  *  Arbitrary tilings, defined in .tes files.
 | |
|  */
 | |
| 
 | |
| #include "hyper.h"
 | |
| namespace hr {
 | |
| 
 | |
| EX namespace arb {
 | |
| 
 | |
| EX int affine_limit = 200;
 | |
| 
 | |
| #if HDR
 | |
| 
 | |
| /** a type used to specify the connections between shapes */
 | |
| struct connection_t {
 | |
|   /** the index of the connected shape in the 'shapes' table */
 | |
|   int sid;
 | |
|   /** the index of the edge in the 'shapes' table */
 | |
|   int eid;
 | |
|   /** 1 if this connection mirrored, 0 otherwise. do_unmirror() removes all mirrors by doubling shapes */
 | |
|   int mirror;
 | |
|   bool operator == (const arb::connection_t& b) const { return tie(sid, eid, mirror) == tie(b.sid, b.eid, b.mirror); }
 | |
|   bool operator < (const arb::connection_t& b) const { return tie(sid, eid, mirror) < tie(b.sid, b.eid, b.mirror); }
 | |
|   };
 | |
| 
 | |
| inline void print(hstream& hs, const connection_t& conn) { print(hs, tie(conn.sid, conn.eid, conn.mirror)); }
 | |
| 
 | |
| /** \brief each shape of the arb tessellation
 | |
|  *  note: the usual HyperRogue convention is: vertex 0, edge 0, vertex 1, edge 1, ... 
 | |
|  *  note: the tesfile convention is: edge 0, vertex 0, edge 1, vertex 1, ...
 | |
|  */
 | |
| 
 | |
| /** edge with infinite end on the left */
 | |
| constexpr ld INFINITE_LEFT = -1;
 | |
| /** edge with infinite end on the right */
 | |
| constexpr ld INFINITE_RIGHT = -2;
 | |
| /** edge with two infinite ends */
 | |
| constexpr ld INFINITE_BOTH = -3;
 | |
| 
 | |
| struct shape {
 | |
|   /** index in the arbi_tiling::shapes */
 | |
|   int id;
 | |
|   /** index in the original file */
 | |
|   int orig_id;
 | |
|   /** flags such as sfLINE and sfPH */
 | |
|   int flags;
 | |
|   /** list of vertices in the usual convention */
 | |
|   vector<hyperpoint> vertices;
 | |
|   /** list of angles in the tesfile convention */
 | |
|   vector<ld> angles;
 | |
|   /** list of edge lengths */
 | |
|   vector<ld> edges;
 | |
|   /** list of input edges */
 | |
|   vector<ld> in_edges;
 | |
|   /** list of input angles */
 | |
|   vector<ld> in_angles;
 | |
|   /** (ultra)ideal markers */
 | |
|   vector<bool> ideal_markers;
 | |
|   /** list of edge connections */
 | |
|   vector<connection_t> connections;
 | |
|   int size() const { return isize(vertices); }
 | |
|   void build_from_angles_edges(bool is_comb);
 | |
|   vector<pair<int, int> > sublines;
 | |
|   vector<pair<ld, ld>> stretch_shear;
 | |
|   /** '*inf' was applied to represent an apeirogon/pseudogon */
 | |
|   bool apeirogonal;
 | |
|   /** connections repeat `repeat_value` times */
 | |
|   int repeat_value;
 | |
|   /** 0 if the no mirror symmetries are declared; otherwise, edge i is the mirror of edge gmod(symmetric_value-i, size()). Make sure symmetric_value != 0, e.g., by adding size() */
 | |
|   int symmetric_value;
 | |
|   /** if a tile/edge combination may be connected to edges j1 and j2 of this, j1-j2 must be divisible by cycle_length */
 | |
|   int cycle_length;
 | |
|   /** list of valences of vertices in the tesfile convention */
 | |
|   vector<int> vertex_valence;
 | |
|   /** list of periods of vertices in the tesfile convention */
 | |
|   vector<int> vertex_period;
 | |
|   /** list of angles at vertices in the tesfile convention */
 | |
|   vector<vector<ld>> vertex_angles;
 | |
|   /** football types */
 | |
|   int football_type;
 | |
|   /** is it a mirrored version of an original tile */
 | |
|   bool is_mirrored;
 | |
|   /** auxiliary function for symmetric_value: is the edge index reflectable? */
 | |
|   bool reflectable(int id) {
 | |
|     if(!symmetric_value) return false;
 | |
|     if(apeirogonal && gmod(id, size()) >= size() - 2) return false;
 | |
|     return true;
 | |
|     }
 | |
|   /** reflect a reflectable reflect index */
 | |
|   int reflect(int id) {
 | |
|     return gmod(symmetric_value - id, size() - (apeirogonal ? 2 : 0));
 | |
|     }
 | |
|   };
 | |
| 
 | |
| struct slider {
 | |
|   string name;
 | |
|   ld zero;
 | |
|   ld current;
 | |
|   ld min;
 | |
|   ld max;
 | |
|   };
 | |
| 
 | |
| struct intslider {
 | |
|   string name;
 | |
|   int zero;
 | |
|   int current;
 | |
|   int min;
 | |
|   int max;
 | |
|   };
 | |
| 
 | |
| struct arbi_tiling {
 | |
| 
 | |
|   int order;
 | |
|   /* line flags have been marked for tiles */
 | |
|   bool have_line;
 | |
|   /* pseudohept flags have been marked for tiles (1), or the tiling is football-colorable (2), or neither (0) */
 | |
|   int have_ph;
 | |
|   /* is the tree structure given in the tes file */
 | |
|   bool have_tree;
 | |
|   /* is the valence data reliable */
 | |
|   bool have_valence;
 | |
|   /* use "star." if the tessellation includs star polygons */
 | |
|   bool is_star;
 | |
|   /* use "combinatorial." for combinatorial tessellations; vertex valences computed based on their angles. Currently only rulegen works for combinatorial tessellations */
 | |
|   bool is_combinatorial;
 | |
|   /* reserved for future flags */
 | |
|   bool res0, res1, res2, res3;
 | |
| 
 | |
|   int yendor_backsteps;
 | |
| 
 | |
|   vector<shape> shapes;
 | |
|   string name;
 | |
|   string comment;
 | |
|   
 | |
|   vector<slider> sliders;
 | |
|   vector<intslider> intsliders;
 | |
|   
 | |
|   ld cscale;
 | |
|   int range;
 | |
|   ld floor_scale;
 | |
|   ld boundary_ratio;
 | |
|   string filename;
 | |
|   int mirror_rules;
 | |
|   
 | |
|   vector<string> options;
 | |
| 
 | |
|   int min_valence, max_valence;
 | |
|   bool is_football_colorable;
 | |
|   bool was_unmirrored;
 | |
|   bool was_split_for_football;
 | |
| 
 | |
|   geometryinfo1& get_geometry();
 | |
|   eGeometryClass get_class() { return get_geometry().kind; }
 | |
| 
 | |
|   ld scale();
 | |
|   };
 | |
| #endif
 | |
| 
 | |
| /** currently loaded tiling */
 | |
| EX arbi_tiling current;
 | |
| 
 | |
| /** is the currently displayed map current or slided */
 | |
| EX bool using_slided;
 | |
| 
 | |
| /** for real-valued sliders, current is the tiling used by the map, while slided is the tiling used for the display */
 | |
| EX arbi_tiling slided;
 | |
| 
 | |
| EX bool in_slided() { return in() && using_slided; }
 | |
| 
 | |
| EX arbi_tiling& current_or_slided() {
 | |
|   return using_slided ? slided : current;
 | |
|   }
 | |
| 
 | |
| /** id of vertex in the arbitrary tiling */
 | |
| 
 | |
| EX short& id_of(heptagon *h) { return h->zebraval; }
 | |
| 
 | |
| #if HDR
 | |
| struct hr_polygon_error : hr_exception {
 | |
|   vector<transmatrix> v;
 | |
|   eGeometryClass c;
 | |
|   int id;
 | |
|   transmatrix end;
 | |
|   map<string, cld> params;
 | |
|   hr_polygon_error(const vector<transmatrix>& _v, int _id, transmatrix _e) : v(_v), c(cgclass), id(_id), end(_e) {}
 | |
|   ~hr_polygon_error() noexcept(true) {}
 | |
|   string generate_error();
 | |
|   };
 | |
| #endif
 | |
| 
 | |
| string hr_polygon_error::generate_error() {
 | |
|   cld dist = (hdist0(tC0(end)) / params["distunit"]);
 | |
|   bool angle = abs(dist) < 1e-9;
 | |
|   if(angle) dist = (atan2(end * lxpush0(1)) / params["angleunit"]);
 | |
|   return
 | |
|     XLAT("Polygon number %1 did not close correctly (%2 %3). Here is the picture to help you understand the issue.\n\n", its(id), 
 | |
|       angle ? "angle" : "distance",
 | |
|       lalign(0, dist)
 | |
|       );
 | |
|   }
 | |
| 
 | |
| struct connection_debug_request : hr_exception {
 | |
|   int id;
 | |
|   eGeometryClass c;
 | |
|   connection_debug_request(int i): id(i), c(cgclass) {}
 | |
|   };
 | |
| 
 | |
| void ensure_geometry(eGeometryClass c) {
 | |
|   stop_game();
 | |
|   if(c != cgclass) {
 | |
|     if(c == gcEuclid) set_geometry(gEuclid);
 | |
|     if(c == gcHyperbolic) set_geometry(gNormal);
 | |
|     if(c == gcSphere) set_geometry(gSphere);
 | |
|     }
 | |
| 
 | |
|   if(specialland != laCanvas) {   
 | |
|     canvas_default_wall = waInvisibleFloor;
 | |
|     ccolor::set_plain_nowall(0xFFFFFF);
 | |
|     enable_canvas();
 | |
|     }
 | |
|   start_game();
 | |
|   }
 | |
| 
 | |
| void start_poly_debugger(hr_polygon_error& err) {
 | |
|   #if CAP_EDIT
 | |
|   ensure_geometry(err.c);
 | |
| 
 | |
|   drawthemap();
 | |
| 
 | |
|   mapeditor::drawing_tool = true;
 | |
|   pushScreen(mapeditor::showDrawEditor);
 | |
|   mapeditor::initdraw(cwt.at);
 | |
|   
 | |
|   int n = isize(err.v);
 | |
|   
 | |
|   mapeditor::dtcolor = 0xFF0000FF;
 | |
|   mapeditor::dtwidth = 0.02;
 | |
|   for(int i=0; i<n-1; i++)
 | |
|     mapeditor::dt_add_line(shiftless(tC0(err.v[i])), shiftless(tC0(err.v[i+1])), 0);
 | |
|   
 | |
|   mapeditor::dtcolor = 0xFFFFFFFF;
 | |
|   for(int i=0; i<n; i++)
 | |
|     mapeditor::dt_add_text(shiftless(tC0(err.v[i])), 0.5, its(i));
 | |
|   #endif
 | |
|   }
 | |
| 
 | |
| void shape::build_from_angles_edges(bool is_comb) {
 | |
|   transmatrix at = Id;
 | |
|   int n = isize(in_angles);
 | |
|   hyperpoint ctr = Hypc;
 | |
|   vector<transmatrix> matrices;
 | |
|   for(int i=0; i<n; i++) {
 | |
|     matrices.push_back(at);
 | |
|     if(debugflags & DF_GEOM) println(hlog, "at = ", at);
 | |
|     ctr += tC0(at);
 | |
|     at = at * lxpush(in_edges[i]) * spin(in_angles[i]+M_PI);
 | |
|     }
 | |
|   matrices.push_back(at);
 | |
|   if(is_comb) return;
 | |
|   if(!eqmatrix(at, Id) && !apeirogonal) {
 | |
|     throw hr_polygon_error(matrices, id, at);
 | |
|     }
 | |
|   if(sqhypot_d(3, ctr) < 1e-2) {
 | |
|     // this may happen for some spherical tilings
 | |
|     // try to move towards the center
 | |
|     if(debugflags & DF_GEOM) println(hlog, "special case encountered");
 | |
|     for(int i=0; i<n; i++) {
 | |
|       ctr += at * lxpush(in_edges[i]) * spin((in_angles[i]+M_PI)/2) * lxpush0(.01);
 | |
|       at = at * lxpush(in_edges[i]) * spin(in_angles[i]);
 | |
|       }
 | |
|     if(debugflags & DF_GEOM) println(hlog, "ctr = ", ctr);
 | |
|     }
 | |
|   hyperpoint inf_point;
 | |
|   if(apeirogonal) {
 | |
|     transmatrix U = at;
 | |
|     for(int i=0; i<3; i++) for(int j=0; j<3; j++) U[i][j] -= Id[i][j];
 | |
|     hyperpoint v;
 | |
|     ld det = U[0][1] * U[1][0] - U[1][1] * U[0][0];
 | |
|     v[1] = (U[1][2] * U[0][0] - U[0][2] * U[1][0]) / det;
 | |
|     v[0] = (U[0][2] * U[1][1] - U[1][2] * U[0][1]) / det;
 | |
|     v[2] = 1;
 | |
|     inf_point = v;
 | |
|     ctr = mid(C0, tC0(at));
 | |
|     ctr = towards_inf(ctr, inf_point);
 | |
|     }
 | |
|   ctr = normalize(ctr);
 | |
|   vertices.clear();
 | |
|   angles.clear();
 | |
|   for(int i=0; i<n; i++) {
 | |
|     edges.push_back(in_edges[i]);
 | |
|     if(!ideal_markers[i]) {
 | |
|       vertices.push_back(tC0(gpushxto0(ctr) * matrices[i]));
 | |
|       angles.push_back(in_angles[i]);
 | |
|       }
 | |
|     else {
 | |
|       angles.push_back(0);
 | |
|       hyperpoint a1 = tC0(matrices[i]);
 | |
|       hyperpoint t1 = get_column(matrices[i], 0);
 | |
|       hyperpoint a2 = tC0(matrices[i+2]);
 | |
|       hyperpoint t2 = get_column(matrices[i+2], 0);
 | |
| 
 | |
|       a1 /= a1[2];
 | |
|       a2 /= a2[2];
 | |
| 
 | |
|       t1 -= a1 * t1[2];
 | |
|       t2 -= a2 * t2[2];
 | |
| 
 | |
|       ld c1 = a2[0] - a1[0], c2 = a2[1] - a1[1];
 | |
|       ld v1 = t1[0], v2 = t1[1];
 | |
|       ld u1 = t2[0], u2 = t2[1];
 | |
| 
 | |
|       ld r = (u2 * c1 - c2 * u1) / (v1 * u2 - v2 * u1);
 | |
|       // ld s = (v2 * c1 - c2 * v1) / (v1 * u2 - v2 * u1);
 | |
| 
 | |
|       hyperpoint v = a1 + r * t1;
 | |
|       // also v == a2 + s * t2;
 | |
|       v[2] = 1;
 | |
|       v = gpushxto0(ctr) * v;
 | |
|       v /= v[2];
 | |
|       vertices.push_back(v);
 | |
|       i++;
 | |
|       }
 | |
|     }
 | |
|   if(apeirogonal) {
 | |
|     vertices.push_back(gpushxto0(ctr) * tC0(at));
 | |
|     hyperpoint v = gpushxto0(ctr) * inf_point;
 | |
|     v /= v[2];
 | |
|     vertices.push_back(v);
 | |
|     auto b = angles.back() / 2;
 | |
|     angles.back() = b;
 | |
|     angles.push_back(0);
 | |
|     angles.push_back(b);
 | |
|     edges.push_back(0);
 | |
|     edges.push_back(0);
 | |
|     }
 | |
|   n = isize(angles);
 | |
|   for(int i=0; i<n; i++) {
 | |
|     bool left = angles[i] == 0;
 | |
|     bool right = angles[gmod(i-1, isize(vertices))] == 0;
 | |
|     if(left && right) edges[i] = INFINITE_BOTH;
 | |
|     else if(left) edges[i] = INFINITE_LEFT;
 | |
|     else if(right) edges[i] = INFINITE_RIGHT;
 | |
|     }
 | |
|   }
 | |
| 
 | |
| EX bool correct_index(int index, int size) { return index >= 0 && index < size; }
 | |
| template<class T> bool correct_index(int index, const T& v) { return correct_index(index, isize(v)); }
 | |
| 
 | |
| template<class T> void verify_index(int index, const T& v, exp_parser& ep) { if(!correct_index(index, v)) throw hr_parse_exception("bad index: " + its(index) + " at " + ep.where()); }
 | |
| 
 | |
| string unnamed = "unnamed";
 | |
| 
 | |
| EX void load_tile(exp_parser& ep, arbi_tiling& c, bool unit) {
 | |
|   c.shapes.emplace_back();
 | |
|   auto& cc = c.shapes.back();
 | |
|   cc.id = isize(c.shapes) - 1;
 | |
|   cc.orig_id = cc.id;
 | |
|   cc.is_mirrored = false;
 | |
|   cc.symmetric_value = 0;
 | |
|   cc.flags = 0;
 | |
|   cc.repeat_value = 1;
 | |
|   cc.apeirogonal = false;
 | |
|   bool is_symmetric = false;
 | |
|   while(ep.next() != ')') {
 | |
|     cld dist = 1;
 | |
|     ep.skip_white();
 | |
|     if(ep.eat("|")) {
 | |
|       cc.symmetric_value = ep.iparse();
 | |
|       is_symmetric = true;
 | |
|       ep.force_eat(")");
 | |
|       break;
 | |
|       }
 | |
|     if(ep.eat("*")) {
 | |
|       ld frep = ep.rparse(0);
 | |
|       if(isinf(frep)) {
 | |
|         cc.apeirogonal = true;
 | |
|         set_flag(ginf[gArbitrary].flags, qIDEAL, true);
 | |
|         if(ep.eat(",") && ep.eat("|")) {
 | |
|           is_symmetric = true;
 | |
|           if(isize(cc.in_edges) == 1 && ep.eat(")")) break;
 | |
|           cc.symmetric_value = ep.iparse();
 | |
|           }
 | |
|         ep.force_eat(")");
 | |
|         break;
 | |
|         }
 | |
|       int rep = int(frep+.5);
 | |
|       int repeat_from = 0;
 | |
|       int repeat_to = cc.in_edges.size();
 | |
|       if(rep == 0) {
 | |
|         cc.in_edges.resize(repeat_from);
 | |
|         cc.in_angles.resize(repeat_from);
 | |
|         cc.ideal_markers.resize(repeat_from);
 | |
|         }
 | |
|       else if(rep < 0) throw hr_parse_exception("don't know how to use a negative repeat in tile definition");
 | |
|       for(int i=1; i<rep; i++)
 | |
|       for(int j=repeat_from; j<repeat_to; j++) {
 | |
|         cc.in_edges.push_back(cc.in_edges[j]);
 | |
|         cc.in_angles.push_back(cc.in_angles[j]);
 | |
|         cc.ideal_markers.push_back(cc.ideal_markers[j]);
 | |
|         }
 | |
|       ep.skip_white();
 | |
|       if(ep.eat(",")) {
 | |
|         ep.force_eat("|");
 | |
|         is_symmetric = true;
 | |
|         if(repeat_to == 1 && ep.eat(")")) goto skip;
 | |
|         cc.symmetric_value = ep.iparse();
 | |
|         }
 | |
|       if(ep.eat(")")) {
 | |
|         skip:
 | |
|         if(repeat_from == 0) cc.repeat_value = rep;
 | |
|         break;
 | |
|         }
 | |
|       else throw hr_parse_exception("expecting ) after repeat");
 | |
|       }
 | |
|     if(!unit) {
 | |
|       dist = ep.parse(0);
 | |
|       ep.force_eat(",");
 | |
|       }
 | |
|     cld angle;
 | |
|     ep.skip_white();
 | |
|     if(ep.eat("[")) {
 | |
|       cc.in_edges.push_back(ep.validate_real(dist * ep.extra_params["distunit"]));
 | |
|       angle = ep.parse(0); ep.force_eat(",");
 | |
|       cc.in_angles.push_back(ep.validate_real(angle * ep.extra_params["angleunit"]));
 | |
|       cc.ideal_markers.push_back(true);
 | |
|       dist = ep.parse(0); ep.force_eat(",");
 | |
|       angle = ep.parse(0); ep.force_eat("]");
 | |
|       set_flag(ginf[gArbitrary].flags, qIDEAL, true);
 | |
|       }
 | |
|     else
 | |
|       angle = ep.parse(0);
 | |
|     cc.in_edges.push_back(ep.validate_real(dist * ep.extra_params["distunit"]));
 | |
|     cc.in_angles.push_back(ep.validate_real(angle * ep.extra_params["angleunit"]));
 | |
|     cc.ideal_markers.push_back(false);
 | |
|     if(ep.eat(",")) continue;
 | |
|     else if(ep.eat(")")) break;
 | |
|     else throw hr_parse_exception("expecting , or )");
 | |
|     }
 | |
|   try {
 | |
|     cc.build_from_angles_edges(c.is_combinatorial);
 | |
|     }
 | |
|   catch(hr_parse_exception& ex) {
 | |
|     throw hr_parse_exception(ex.s + ep.where());
 | |
|     }
 | |
|   catch(hr_polygon_error& poly) {
 | |
|     poly.params = ep.extra_params;
 | |
|     throw;
 | |
|     }
 | |
|   int n = cc.size();
 | |
|   if(is_symmetric && !cc.symmetric_value) cc.symmetric_value += n - (cc.apeirogonal ? 2 : 0);
 | |
|   cc.connections.resize(n);
 | |
|   for(int i=0; i<isize(cc.connections); i++)
 | |
|     cc.connections[i] = connection_t{cc.id, i, false};
 | |
|   if(cc.apeirogonal) {
 | |
|     cc.connections[n-2].eid = n-1;
 | |
|     cc.connections[n-1].eid = n-2;
 | |
|     }
 | |
|   cc.stretch_shear.resize(n, make_pair(1, 0));
 | |
|   }
 | |
| 
 | |
| EX bool do_unmirror = true;
 | |
| 
 | |
| template<class T> void cycle(vector<T>& t) {
 | |
|   std::rotate(t.begin(), t.begin() + 2, t.end());
 | |
|   }
 | |
| 
 | |
| /** \brief for tessellations which contain mirror rules, remove them by taking the orientable double cover */
 | |
| EX void unmirror(arbi_tiling& c) {
 | |
|   if(cgflags & qAFFINE) return;
 | |
|   auto& mirror_rules = c.mirror_rules;
 | |
|   mirror_rules = 0;
 | |
|   for(auto& s: c.shapes)
 | |
|     for(auto& t: s.connections)
 | |
|       if(t.mirror)
 | |
|         mirror_rules++;
 | |
|   if(!mirror_rules) return;
 | |
|   auto& sh = c.shapes;
 | |
|   int s = isize(sh);
 | |
|   vector<int> mirrored_id(s, -1);
 | |
|   for(int i=0; i<s; i++)
 | |
|     if(!sh[i].symmetric_value) {
 | |
|       mirrored_id[i] = isize(sh);
 | |
|       sh.push_back(sh[i]);
 | |
|       }
 | |
|   int ss = isize(sh);
 | |
|   for(int i=0; i<ss; i++) {
 | |
|     sh[i].id = i;
 | |
|     if(i >= s) sh[i].is_mirrored = true;
 | |
|     }
 | |
|   for(int i=s; i<ss; i++) {
 | |
|     for(auto& v: sh[i].vertices) 
 | |
|       v[1] = -v[1];
 | |
|     reverse(sh[i].edges.begin(), sh[i].edges.end());
 | |
|     for(auto& e: sh[i].edges) {
 | |
|       if(e == INFINITE_LEFT) e = INFINITE_RIGHT;
 | |
|       else if(e == INFINITE_RIGHT) e = INFINITE_LEFT;
 | |
|       }
 | |
|     reverse(sh[i].vertices.begin()+1, sh[i].vertices.end());
 | |
|     reverse(sh[i].angles.begin(), sh[i].angles.end()-1);
 | |
|     reverse(sh[i].connections.begin(), sh[i].connections.end());
 | |
|     if(sh[i].apeirogonal) {
 | |
|       cycle(sh[i].edges);
 | |
|       cycle(sh[i].vertices);
 | |
|       if(debugflags & DF_GEOM) println(hlog, "angles before = ", sh[i].angles);
 | |
|       cycle(sh[i].angles);
 | |
|       if(debugflags & DF_GEOM) println(hlog, "angles now = ", sh[i].angles);
 | |
|       cycle(sh[i].connections);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|   if(true) for(int i=0; i<ss; i++) {
 | |
|     for(auto& co: sh[i].connections) {
 | |
|       bool mirr = co.mirror ^ (i >= s);
 | |
|       co.mirror = false;
 | |
|       if(mirr && mirrored_id[co.sid] == -1) {
 | |
|         if(sh[co.sid].reflectable(co.eid)) {
 | |
|           co.eid = sh[co.sid].reflect(co.eid);
 | |
|           }
 | |
|         }
 | |
|       else if(mirr) {
 | |
|         co.sid = mirrored_id[co.sid];
 | |
|         co.eid = isize(sh[co.sid].angles) - 1 - co.eid;
 | |
|         if(sh[co.sid].apeirogonal)
 | |
|           co.eid = gmod(co.eid - 2, isize(sh[co.sid].angles));
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|   c.was_unmirrored = true;
 | |
|   }
 | |
| 
 | |
| static void reduce_gcd(int& a, int b) {
 | |
|   a = abs(gcd(a, b));
 | |
|   }
 | |
| 
 | |
| EX void mirror_connection(arb::arbi_tiling& ac, connection_t& co) {
 | |
|   if(co.mirror && ac.shapes[co.sid].reflectable(co.eid)) {
 | |
|     co.eid = ac.shapes[co.sid].reflect(co.eid);
 | |
|     co.mirror = !co.mirror;
 | |
|     }
 | |
|   }
 | |
| 
 | |
| EX void compute_vertex_valence_prepare(arb::arbi_tiling& ac) {
 | |
| 
 | |
|   int tcl = -1;
 | |
| 
 | |
|   while(true) {
 | |
| 
 | |
|     for(auto& sh: ac.shapes) {
 | |
|       int i = sh.id;
 | |
|       int n = isize(sh.vertices);
 | |
|       
 | |
|       for(int k=sh.cycle_length; k<n; k++) {
 | |
|         auto co = sh.connections[k];
 | |
|         auto co1 = sh.connections[k-sh.cycle_length];
 | |
|         if(co.sid != co1.sid) {
 | |
|           println(hlog, "ik = ", tie(i,k), " co=", co, " co1=", co1, " cl=", sh.cycle_length);
 | |
|           throw hr_parse_exception("connection error #2 in compute_vertex_valence");
 | |
|           }
 | |
|         mirror_connection(ac, co);
 | |
|         mirror_connection(ac, co1);
 | |
|         reduce_gcd(ac.shapes[co.sid].cycle_length, co.eid - co1.eid);
 | |
|         }
 | |
| 
 | |
|       for(int k=0; k<n; k++) {
 | |
|         auto co = sh.connections[k];
 | |
|         auto co0 = co;
 | |
|         co = ac.shapes[co.sid].connections[co.eid];
 | |
|         if(co.sid != i) throw hr_parse_exception("connection error in compute_vertex_valence");
 | |
|         co.mirror ^= co0.mirror;
 | |
|         mirror_connection(ac, co);
 | |
|         reduce_gcd(sh.cycle_length, k-co.eid);
 | |
|         }
 | |
|       if(debugflags & DF_GEOM) 
 | |
|         println(hlog, "tile ", i, " cycle_length = ", sh.cycle_length, " / ", n);
 | |
|       }
 | |
|     
 | |
|     int new_tcl = 0;
 | |
|     for(auto& sh: ac.shapes) {
 | |
|       auto& len = sh.cycle_length;
 | |
|       if(len < 0) len = -len;
 | |
|       new_tcl += len;
 | |
|       }
 | |
| 
 | |
|     if(new_tcl == tcl) break;
 | |
|     tcl = new_tcl;
 | |
|     }
 | |
|   }
 | |
| 
 | |
| /** returns true if we need to recompute */
 | |
| EX bool compute_vertex_valence_flat(arb::arbi_tiling& ac) {
 | |
|   for(auto& sh: ac.shapes) {
 | |
|     int n = sh.size();
 | |
|     int i = sh.id;
 | |
|     sh.vertex_valence.resize(n);
 | |
|     sh.vertex_period.resize(n);
 | |
|     sh.vertex_angles.resize(n);
 | |
|     for(int k=0; k<n; k++) {
 | |
|       ld total = 0;
 | |
|       int qty = 0, pqty = 0;
 | |
|       connection_t at = {i, k, false};
 | |
|       connection_t at1 = at;
 | |
|       vector<ld> anglelist;
 | |
|       do {
 | |
|         if(at.sid == at1.sid && (at.eid-at1.eid) % ac.shapes[at.sid].cycle_length == 0) pqty = 0;
 | |
|         if(qty && pqty == 0 && !total) break;
 | |
|         ld a = ac.shapes[at.sid].angles[at.eid];
 | |
|         while(a < 0) a += TAU;
 | |
|         while(a > TAU) a -= TAU;
 | |
|         total += a;
 | |
|         anglelist.push_back(a);
 | |
|         qty++;
 | |
|         pqty++;
 | |
| 
 | |
|         at.eid++;
 | |
|         if(at.eid == isize(ac.shapes[at.sid].angles)) at.eid = 0;
 | |
| 
 | |
|         at = ac.shapes[at.sid].connections[at.eid];
 | |
|         }
 | |
|       while(total < TAU - 1e-6);
 | |
|       if(total == 0) qty = OINF;
 | |
|       if(total > TAU + 1e-6) throw hr_parse_exception("improper total in compute_stats");
 | |
|       if(at.sid != i) throw hr_parse_exception("ended at wrong type determining vertex_valence");
 | |
|       if((at.eid - k) % ac.shapes[i].cycle_length) {
 | |
|         reduce_gcd(ac.shapes[i].cycle_length, at.eid - k);
 | |
|         return true;
 | |
|         }
 | |
|       sh.vertex_valence[k] = qty;
 | |
|       sh.vertex_period[k] = pqty;
 | |
|       sh.vertex_angles[k] = std::move(anglelist);
 | |
|       }
 | |
|     if(debugflags & DF_GEOM) 
 | |
|       println(hlog, "computed vertex_valence of ", i, " as ", ac.shapes[i].vertex_valence);
 | |
|     }
 | |
|   return false;
 | |
|   }
 | |
| 
 | |
| /** returns true if we need to recompute */
 | |
| EX bool compute_vertex_valence_generic(arb::arbi_tiling& ac) {
 | |
|   for(auto& sh: ac.shapes) {
 | |
|     int n = sh.size();
 | |
|     int i = sh.id;
 | |
|     sh.vertex_valence.resize(n);
 | |
|     for(int k=0; k<n; k++) {
 | |
|       connection_t at = {i, k, false};
 | |
|       transmatrix T = Id;
 | |
|       int qty = 0;
 | |
|       do {
 | |
|         if(qty && at.sid == i) {
 | |
|           auto co1 = at;
 | |
|           bool found = find_connection(T, Id, co1);
 | |
|           if(found) {
 | |
|             mirror_connection(ac, co1);
 | |
|             if((co1.eid - k) % ac.shapes[i].cycle_length) {
 | |
|               reduce_gcd(ac.shapes[i].cycle_length, co1.eid - k);
 | |
|               return true;
 | |
|               }
 | |
|             break;
 | |
|             }
 | |
|           }
 | |
| 
 | |
|         if(at.mirror) {
 | |
|           if(at.eid == 0) at.eid = isize(ac.shapes[at.sid].angles);
 | |
|           at.eid--;
 | |
|           }
 | |
|         else {
 | |
|           at.eid++;
 | |
|           if(at.eid == isize(ac.shapes[at.sid].angles)) at.eid = 0;
 | |
|           }
 | |
| 
 | |
|         auto at0 = at;
 | |
|         at = ac.shapes[at.sid].connections[at.eid];
 | |
|         T = T * get_adj(ac, at0.sid, at0.eid, at.sid, at.eid, at.mirror);
 | |
|         at.mirror ^= at0.mirror;
 | |
|         qty++;
 | |
|         }
 | |
|       while(qty < OINF);
 | |
|       sh.vertex_valence[k] = qty;
 | |
|       }
 | |
|     if(debugflags & DF_GEOM)
 | |
|       println(hlog, "computed vertex_valence of ", i, " as ", ac.shapes[i].vertex_valence);
 | |
|     }
 | |
|   return false;
 | |
|   }
 | |
| 
 | |
| EX void compute_vertex_valence(arb::arbi_tiling& ac) {
 | |
| 
 | |
|   for(auto& sh: ac.shapes)
 | |
|     sh.cycle_length = isize(sh.vertices) / sh.repeat_value;
 | |
| 
 | |
|   bool generic = false;
 | |
|   
 | |
|   if(!ac.was_unmirrored) for(auto& sh: ac.shapes) if(sh.symmetric_value) generic = true;
 | |
|   for(auto& sh: ac.shapes) for(auto& co: sh.connections) if(co.mirror) generic = true;
 | |
| 
 | |
|   if(cgflags & qAFFINE) generic = true;
 | |
|   if(ac.is_star) generic = true;
 | |
| 
 | |
|   recompute:
 | |
|   compute_vertex_valence_prepare(ac);
 | |
| 
 | |
|   if(generic ? compute_vertex_valence_generic(ac) : compute_vertex_valence_flat(ac)) goto recompute;
 | |
|   ac.have_valence = true;
 | |
| 
 | |
|   ac.min_valence = UNKNOWN; ac.max_valence = 0;
 | |
|   for(auto& sh: ac.shapes) 
 | |
|     for(auto& val: sh.vertex_valence) {
 | |
|       if(val < ac.min_valence) ac.min_valence = val;
 | |
|       if(val > ac.max_valence) ac.max_valence = val;
 | |
|       }      
 | |
|   }
 | |
| 
 | |
| EX bool extended_football = true;
 | |
| 
 | |
| EX void check_football_colorability(arbi_tiling& c) {
 | |
|   if(!c.have_valence) return;
 | |
|   for(auto&sh: c.shapes) for(auto v: sh.vertex_valence)
 | |
|     if(v % 3) return;
 | |
| 
 | |
|   for(int i=0; i<3; i++) {
 | |
|     for(auto&sh: c.shapes) sh.football_type = 3;
 | |
| 
 | |
|     vector<int> aqueue;
 | |
| 
 | |
|     c.shapes[0].football_type = i;
 | |
|     aqueue = {0};
 | |
|     bool bad = false;
 | |
|     for(int qi=0; qi<isize(aqueue); qi++) {
 | |
|       int sid = aqueue[qi];
 | |
| 
 | |
|       auto& sh = c.shapes[sid];
 | |
| 
 | |
|       for(int j=0; j<sh.size(); j++) {
 | |
|         auto &co = sh.connections[j];
 | |
|         auto t = sh.football_type;
 | |
|         if(c.have_ph && ((sh.flags & arcm::sfPH) != (t==2))) bad = true;
 | |
|         if(sh.apeirogonal && t < 2 && (isize(sh) & 1)) bad = true;
 | |
| 
 | |
|         auto assign = [&] (int tt) {
 | |
|           auto& t1 = c.shapes[co.sid].football_type;
 | |
|           if(t1 == 3) {
 | |
|             t1 = tt;
 | |
|             aqueue.push_back(co.sid);
 | |
|             }
 | |
|           else {
 | |
|             if(t1 != tt) bad = true;
 | |
|             }
 | |
|           };
 | |
| 
 | |
|         if(t < 2) {
 | |
|           if((j & 1) == t) assign(2); else assign((co.eid & 1) ? 0 : 1);
 | |
|           }
 | |
|         else {
 | |
|           assign((co.eid & 1) ? 1 : 0);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     if(!bad) {
 | |
|       c.have_ph = 2;
 | |
|       for(auto& sh: c.shapes) if(sh.football_type == 2) sh.flags |= arcm::sfPH;
 | |
|       return;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|   if(extended_football && !c.have_tree) {
 | |
|     for(auto&sh: c.shapes)
 | |
|       sh.football_type = 0;
 | |
| 
 | |
|     for(int i=0; i<3*isize(c.shapes); i++) {
 | |
|       for(auto&sh: c.shapes) {
 | |
|         int &res = sh.football_type;
 | |
|         int siz = sh.size();
 | |
|         if(sh.apeirogonal) siz -= 2;
 | |
|         else if(siz & 1) res |= 3;
 | |
|         if((sh.cycle_length & 1) && !sh.apeirogonal) {
 | |
|           if(res & 3) res |= 3;
 | |
|           }
 | |
|         if(sh.apeirogonal && (siz & 1)) {
 | |
|           if(res & 3) res |= 3;
 | |
|           }
 | |
|         if(sh.flags & arcm::sfPH) res |= 3;
 | |
|         for(int i=0; i<sh.size(); i++) {
 | |
|           auto co = sh.connections[i];
 | |
|           co.eid %= c.shapes[co.sid].cycle_length;
 | |
|           if(res & 1) {
 | |
|             if(i&1) {
 | |
|               if(co.eid & 1)
 | |
|                 c.shapes[co.sid].football_type |= 1;
 | |
|               else
 | |
|                 c.shapes[co.sid].football_type |= 2;
 | |
|               }
 | |
|             else
 | |
|               c.shapes[co.sid].football_type |= 4;
 | |
|             }
 | |
|           if(res & 2) {
 | |
|             if(!(i&1)) {
 | |
|               if(co.eid & 1)
 | |
|                 c.shapes[co.sid].football_type |= 1;
 | |
|               else
 | |
|                 c.shapes[co.sid].football_type |= 2;
 | |
|               }
 | |
|             else
 | |
|               c.shapes[co.sid].football_type |= 4;
 | |
|             }
 | |
|           if(res & 4) {
 | |
|             if(co.eid & 1)
 | |
|               c.shapes[co.sid].football_type |= 2;
 | |
|             else
 | |
|               c.shapes[co.sid].football_type |= 1;
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|       }
 | |
| 
 | |
|     c.is_football_colorable = true;
 | |
|     c.was_split_for_football = true;
 | |
|     for(auto&sh: c.shapes)
 | |
|      if(sh.football_type == 7)
 | |
|        c.is_football_colorable = false;
 | |
| 
 | |
|     if(c.is_football_colorable) {
 | |
|       vector<array<int, 3> > new_indices(isize(c.shapes), make_array(-1, -1, -1));
 | |
|       auto oldshapes = c.shapes;
 | |
|       c.shapes.clear();
 | |
|       for(int i=0; i<isize(oldshapes); i++)
 | |
|         for(int t=0; t<3; t++)
 | |
|           if(!(oldshapes[i].football_type & (1<<t))) {
 | |
|             if(t == 1 && (oldshapes[i].cycle_length & 1) && !oldshapes[i].apeirogonal) continue;
 | |
|             new_indices[i][t] = isize(c.shapes);
 | |
|             c.shapes.push_back(oldshapes[i]);
 | |
|             c.shapes.back().football_type = t;
 | |
|             if(t == 2) c.shapes.back().flags |= arcm::sfPH;
 | |
|             }
 | |
| 
 | |
|       for(int i=0; i<isize(oldshapes); i++)
 | |
|         for(int t=0; t<3; t++) {
 | |
|           int ni = new_indices[i][t];
 | |
|           if(ni == -1) continue;
 | |
|           auto& sh = c.shapes[ni];
 | |
|           sh.id = ni;
 | |
|           for(int j=0; j<isize(sh); j++) {
 | |
|             auto &co = sh.connections[j];
 | |
|             auto assign = [&] (int tt) {
 | |
|               auto ni1 = new_indices[co.sid][tt];
 | |
|               if(ni1 == -1 && tt == 1) {
 | |
|                 ni1 = new_indices[co.sid][0];
 | |
|                 co.eid += oldshapes[co.sid].cycle_length;
 | |
|                 co.eid %= isize(oldshapes[co.sid]);
 | |
|                 }
 | |
|               co.sid = ni1;
 | |
|               };
 | |
| 
 | |
|             if(sh.apeirogonal && j >= isize(sh)-2) {
 | |
|               co.sid = ni;
 | |
|               if(t < 2 && (isize(sh) & 1)) co.sid = new_indices[i][t^1];
 | |
|               continue;
 | |
|               }
 | |
| 
 | |
|             co.eid %= oldshapes[co.sid].cycle_length;
 | |
|             if(t < 2) {
 | |
|               if((j & 1) == t) assign(2); else assign((co.eid & 1) ? 0 : 1);
 | |
|               }
 | |
|             else {
 | |
|               assign((co.eid & 1) ? 1 : 0);
 | |
|               }
 | |
|             }
 | |
| 
 | |
|           if((sh.cycle_length&1) && (t < 2) && !sh.apeirogonal) sh.cycle_length *= 2;
 | |
|           if(debugflags & DF_GEOM)
 | |
|             println(hlog, tie(i,t), " becomes ", ni, " with connections ", sh.connections, " and cycle length = ", sh.cycle_length);
 | |
|           }
 | |
| 
 | |
|       c.have_ph = 2;
 | |
|       return;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|   for(auto&sh: c.shapes) sh.football_type = 3;
 | |
|   }
 | |
| 
 | |
| EX void add_connection_sub(arbi_tiling& c, int ai, int as, int bi, int bs, int m) {
 | |
|   int as0 = as, bs0 = bs;
 | |
|   auto& ash = c.shapes[ai];
 | |
|   auto& bsh = c.shapes[bi];
 | |
|   do {
 | |
|     ash.connections[as] = connection_t{bi, bs, m};
 | |
|     as = gmod(as + ash.size() / ash.repeat_value, ash.size());
 | |
|     }
 | |
|   while(as != as0);
 | |
|   do {
 | |
|     c.shapes[bi].connections[bs] = connection_t{ai, as, m};
 | |
|     bs = gmod(bs + bsh.size() / bsh.repeat_value, bsh.size());
 | |
|     }
 | |
|   while(bs != bs0);
 | |
|   }
 | |
| 
 | |
| EX void add_connection(arbi_tiling& c, int ai, int as, int bi, int bs, int m) {
 | |
|   auto& ash = c.shapes[ai];
 | |
|   auto& bsh = c.shapes[bi];
 | |
|   add_connection_sub(c, ai, as, bi, bs, m);
 | |
|   int as1, bs1;
 | |
|   if(ash.symmetric_value) {
 | |
|     as1 = ash.reflect(as);
 | |
|     add_connection_sub(c, ai, as1, bi, bs, !m);
 | |
|     }
 | |
|   if(bsh.symmetric_value) {
 | |
|     bs1 = bsh.reflect(bs);
 | |
|     add_connection_sub(c, ai, as, bi, bs1, !m);
 | |
|     }
 | |
|   if(ash.symmetric_value && bsh.symmetric_value)
 | |
|     add_connection_sub(c, ai, as1, bi, bs1, m);
 | |
|   }
 | |
| 
 | |
| EX void set_defaults(arb::arbi_tiling& c, bool keep_sliders, string fname) {
 | |
|   c.order++;
 | |
|   c.name = unnamed;
 | |
|   c.comment = "";
 | |
|   c.filename = fname;
 | |
|   c.cscale = 1;
 | |
|   c.range = 0;
 | |
|   c.boundary_ratio = 1;
 | |
|   c.floor_scale = .5;
 | |
|   c.have_ph = 0;
 | |
|   c.have_line = false;
 | |
|   c.is_football_colorable = false;
 | |
|   c.have_tree = false;
 | |
|   c.have_valence = false;
 | |
|   c.yendor_backsteps = 0;
 | |
|   c.is_star = false;
 | |
|   c.is_combinatorial = false;
 | |
|   c.was_unmirrored = false;
 | |
|   c.was_split_for_football = false;
 | |
|   c.shapes.clear();
 | |
|   if(!keep_sliders) {
 | |
|     c.sliders.clear();
 | |
|     c.intsliders.clear();
 | |
|     }
 | |
|   }
 | |
| 
 | |
| EX void load(const string& fname, bool load_as_slided IS(false), bool keep_sliders IS(false)) {
 | |
|   fhstream f(fname, "rt");
 | |
|   if(!f.f) throw hr_parse_exception("file " + fname + " does not exist");
 | |
|   string s;
 | |
|   while(true) {
 | |
|     int c = fgetc(f.f);
 | |
|     if(c < 0) break;
 | |
|     s += c;
 | |
|     }
 | |
|   auto& c = load_as_slided ? slided : current;
 | |
|   set_defaults(c, keep_sliders, fname);
 | |
|   int qsliders = 0, qintsliders = 0;
 | |
|   exp_parser ep;
 | |
|   ep.s = s;
 | |
|   ld angleunit = 1, distunit = 1;
 | |
|   auto addflag = [&] (int f) {
 | |
|     int ai;
 | |
|     if(ep.next() == ')') ai = isize(c.shapes)-1;
 | |
|     else ai = ep.iparse();
 | |
|     verify_index(ai, c.shapes, ep); 
 | |
|     c.shapes[ai].flags |= f;
 | |
|     ep.force_eat(")");
 | |
|     };
 | |
|   while(true) {
 | |
| 
 | |
|     ep.extra_params["distunit"] = distunit;
 | |
|     ep.extra_params["angleunit"] = angleunit;
 | |
| 
 | |
|     ep.skip_white();
 | |
|     if(ep.next() == 0) break;
 | |
|     if(ep.eat("#")) {
 | |
|       bool doubled = ep.eat("#");
 | |
|       while(ep.eat(" ")) ;
 | |
|       string s = "";
 | |
|       while(ep.next() >= 32) s += ep.next(), ep.at++;
 | |
|       if(doubled) {
 | |
|         if(c.name == unnamed) c.name = s;
 | |
|         else {
 | |
|           c.comment += s; 
 | |
|           c.comment += "\n";
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     else if(ep.eat("c2(")) {
 | |
|       ld curv = ep.rparse(0);
 | |
|       ep.force_eat(")");
 | |
|       ginf[gArbitrary].g = curv > 0 ? giSphere2 : curv < 0 ? giHyperb2 : giEuclid2;
 | |
|       ginf[gArbitrary].sides = 7;
 | |
|       set_flag(ginf[gArbitrary].flags, qCLOSED, curv > 0);
 | |
|       set_flag(ginf[gArbitrary].flags, qAFFINE, false);
 | |
|       geom3::apply_always3();
 | |
|       }
 | |
|     else if(ep.eat("e2.")) {
 | |
|       ginf[gArbitrary].g = giEuclid2;
 | |
|       ginf[gArbitrary].sides = 7;
 | |
|       set_flag(ginf[gArbitrary].flags, qCLOSED, false);
 | |
|       set_flag(ginf[gArbitrary].flags, qAFFINE, false);
 | |
|       geom3::apply_always3();
 | |
|       }
 | |
|     else if(ep.eat("a2.")) {
 | |
|       ginf[gArbitrary].g = giEuclid2;
 | |
|       ginf[gArbitrary].sides = 7;
 | |
|       set_flag(ginf[gArbitrary].flags, qCLOSED, false);
 | |
|       set_flag(ginf[gArbitrary].flags, qAFFINE, true);
 | |
|       affine_limit = 200;
 | |
|       geom3::apply_always3();
 | |
|       }
 | |
|     else if(ep.eat("h2.")) {
 | |
|       ginf[gArbitrary].g = giHyperb2;
 | |
|       ginf[gArbitrary].sides = 7;
 | |
|       set_flag(ginf[gArbitrary].flags, qCLOSED, false);
 | |
|       set_flag(ginf[gArbitrary].flags, qAFFINE, false);
 | |
|       geom3::apply_always3();
 | |
|       }
 | |
|     else if(ep.eat("s2.")) {
 | |
|       ginf[gArbitrary].g = giSphere2;
 | |
|       ginf[gArbitrary].sides = 5;
 | |
|       set_flag(ginf[gArbitrary].flags, qCLOSED, true);
 | |
|       set_flag(ginf[gArbitrary].flags, qAFFINE, false);
 | |
|       geom3::apply_always3();
 | |
|       }
 | |
|     else if(ep.eat("star.")) {
 | |
|       c.is_star = true;
 | |
|       }
 | |
|     else if(ep.eat("combinatorial.")) {
 | |
|       c.is_combinatorial = true;
 | |
|       }
 | |
|     else if(ep.eat("option(\"")) {
 | |
|       next:
 | |
|       string s = "";
 | |
|       while(ep.next() != '"') s += ep.eatchar();
 | |
|       ep.force_eat("\"");
 | |
|       c.options.push_back(s);
 | |
|       ep.skip_white();
 | |
|       if(ep.eat(",")) { ep.skip_white(); ep.force_eat("\""); goto next; }
 | |
|       ep.force_eat(")");
 | |
|       }
 | |
|     else if(ep.eat("angleunit(")) angleunit = real(ep.parsepar());
 | |
|     else if(ep.eat("distunit(")) distunit = real(ep.parsepar());
 | |
|     else if(ep.eat("line(")) {
 | |
|       addflag(arcm::sfLINE);
 | |
|       c.have_line = true;
 | |
|       }
 | |
|     else if(ep.eat("grave(")) {
 | |
|       addflag(arcm::sfPH);
 | |
|       c.have_ph = true;
 | |
|       }
 | |
|     else if(ep.eat("slider(")) {
 | |
|       slider sl;
 | |
|       sl.name = ep.next_token();
 | |
|       ep.force_eat(",");
 | |
|       sl.current = sl.zero = ep.rparse();
 | |
|       ep.force_eat(",");
 | |
|       sl.min = ep.rparse();
 | |
|       ep.force_eat(",");
 | |
|       sl.max = ep.rparse();
 | |
|       ep.force_eat(")");
 | |
|       if(load_as_slided || !keep_sliders)
 | |
|         c.sliders.push_back(sl);
 | |
|       if(load_as_slided || keep_sliders)
 | |
|         ep.extra_params[sl.name] = current.sliders[qsliders++].current;
 | |
|       else
 | |
|         ep.extra_params[sl.name] = sl.zero;
 | |
|       }
 | |
|     else if(ep.eat("intslider(")) {
 | |
|       intslider sl;
 | |
|       sl.name = ep.next_token();
 | |
|       ep.force_eat(",");
 | |
|       sl.current = sl.zero = ep.iparse();
 | |
|       ep.force_eat(",");
 | |
|       sl.min = ep.iparse();
 | |
|       ep.force_eat(",");
 | |
|       sl.max = ep.iparse();
 | |
|       ep.force_eat(")");
 | |
|       if(load_as_slided || !keep_sliders)
 | |
|         c.intsliders.push_back(sl);
 | |
|       if(load_as_slided || keep_sliders)
 | |
|         ep.extra_params[sl.name] = current.intsliders[qintsliders++].current;
 | |
|       else
 | |
|         ep.extra_params[sl.name] = sl.zero;
 | |
|       }
 | |
|     else if(ep.eat("let(")) {
 | |
|       string tok = ep.next_token();
 | |
|       ep.force_eat("=");
 | |
|       ep.extra_params[tok] =ep.parsepar();
 | |
|       if(debugflags & DF_GEOM)
 | |
|         println(hlog, "let ", tok, " = ", ep.extra_params[tok]);
 | |
|       }
 | |
|     else if(ep.eat("unittile(")) load_tile(ep, c, true);
 | |
|     else if(ep.eat("tile(")) load_tile(ep, c, false);
 | |
|     else if(ep.eat("affine_limit(")) {
 | |
|       affine_limit = ep.iparse();
 | |
|       ep.force_eat(")");
 | |
|       }
 | |
|     else if(ep.eat("cscale(")) {
 | |
|       c.cscale = ep.rparse();
 | |
|       ep.force_eat(")");
 | |
|       }
 | |
|     else if(ep.eat("treestate(")) {
 | |
|       rulegen::parse_treestate(c, ep);
 | |
|       }
 | |
|     else if(ep.eat("first_treestate(")) {
 | |
|       rulegen::rule_root = ep.iparse();
 | |
|       ep.force_eat(")");
 | |
|       }
 | |
|     else if(ep.eat("yendor_backsteps(")) {
 | |
|       c.yendor_backsteps = ep.iparse();
 | |
|       ep.force_eat(")");
 | |
|       }
 | |
|     else if(ep.eat("range(")) {
 | |
|       c.range = ep.iparse();
 | |
|       ep.force_eat(")");
 | |
|       }
 | |
|     else if(ep.eat("floor_scale(")) {
 | |
|       c.floor_scale = ep.rparse();
 | |
|       ep.force_eat(")");
 | |
|       }
 | |
|     else if(ep.eat("boundary_ratio(")) {
 | |
|       c.boundary_ratio = ep.rparse();
 | |
|       ep.force_eat(")");
 | |
|       }
 | |
|     else if(ep.eat("conway(\"")) {
 | |
|       string s = "";
 | |
|       while(true) {
 | |
|         int m = 0;
 | |
|         if(ep.eat("(")) m = 0;
 | |
|         else if(ep.eat("[")) m = 1;
 | |
|         else if(ep.eat("\"")) break;
 | |
|         else throw hr_parse_exception("cannot parse Conway notation, " + ep.where());
 | |
| 
 | |
|         int ai = 0;
 | |
|         int as = ep.iparse();
 | |
|         while(ep.eat("'")) ai++;
 | |
|         if(ep.eat("@")) ai = ep.iparse();
 | |
|         int bi = 0, bs = 0;
 | |
|         if(ep.eat(")") || ep.eat("]")) bs = as, bi = ai;
 | |
|         else {
 | |
|           bs = ep.iparse();
 | |
|           while(ep.eat("'")) bi++;
 | |
|           if(ep.eat("@")) bi = ep.iparse();
 | |
|           }          
 | |
|         if(ep.eat(")") || ep.eat("]")) {}
 | |
|         verify_index(ai, c.shapes, ep);
 | |
|         verify_index(as, c.shapes[ai], ep);
 | |
|         verify_index(bi, c.shapes, ep);
 | |
|         verify_index(bs, c.shapes[bi], ep);
 | |
|         add_connection(c, ai, as, bi, bs, m);
 | |
|         }
 | |
|       ep.force_eat(")");
 | |
|       }
 | |
|     else if(ep.eat("c(")) {
 | |
|       int ai = ep.iparse(); verify_index(ai, c.shapes, ep); ep.force_eat(",");
 | |
|       int as = ep.iparse(); verify_index(as, c.shapes[ai], ep); ep.force_eat(",");
 | |
|       int bi = ep.iparse(); verify_index(bi, c.shapes, ep); ep.force_eat(",");
 | |
|       int bs = ep.iparse(); verify_index(bs, c.shapes[bi], ep); ep.force_eat(",");
 | |
|       int m = ep.iparse(); ep.force_eat(")");
 | |
|       add_connection(c, ai, as, bi, bs, m);
 | |
|       }
 | |
|     else if(ep.eat("subline(")) {
 | |
|       int ai = ep.iparse(); verify_index(ai, c.shapes, ep); ep.force_eat(",");
 | |
|       int as = ep.iparse(); verify_index(as, c.shapes[ai], ep); ep.force_eat(",");
 | |
|       int bs = ep.iparse(); verify_index(bs, c.shapes[ai], ep); ep.force_eat(")");
 | |
|       c.shapes[ai].sublines.emplace_back(as, bs);
 | |
|       }
 | |
|     else if(ep.eat("sublines(")) {
 | |
|       ld d = ep.rparse() * distunit; 
 | |
|       ld eps = 1e-4;
 | |
|       if(ep.eat(",")) eps = ep.rparse() * distunit;
 | |
|       ep.force_eat(")");
 | |
|       for(auto& sh: c.shapes) {
 | |
|         for(int i=0; i<isize(sh.vertices); i++)
 | |
|         for(int j=0; j<i; j++)
 | |
|           if(j != i+1 && i != j+1 && !(i==0 && j == isize(sh.vertices)-1) && !(j==0 && i == isize(sh.vertices)-1) && i != j) {
 | |
|             ld dist = hdist(sh.vertices[i], sh.vertices[j]);
 | |
|             if(abs(dist - d) < eps) {
 | |
|               sh.sublines.emplace_back(i, j);
 | |
|               if(debugflags & DF_GEOM) println(hlog, "add subline ", i, "-", j);
 | |
|               }
 | |
|             }
 | |
|         }
 | |
|       }
 | |
|     else if(ep.eat("repeat(")) {
 | |
|       int i = ep.iparse(0);
 | |
|       verify_index(i, c.shapes, ep);
 | |
|       ep.force_eat(",");
 | |
|       int rep = ep.iparse(0);
 | |
|       ep.force_eat(")");
 | |
|       auto& sh = c.shapes[i];
 | |
|       int N = isize(sh.angles);
 | |
|       if(N % rep) 
 | |
|         throw hr_parse_exception("repeat value should be a factor of the number of vertices, " + ep.where());
 | |
|       sh.repeat_value = rep;
 | |
|       
 | |
|       int d = N / rep;
 | |
|       for(int i=d; i<N; i++)
 | |
|         sh.connections[i] = sh.connections[i-d];
 | |
|       }
 | |
|     else if(ep.eat("debug(")) {
 | |
|       int i = ep.iparse(0);
 | |
|       verify_index(i, c.shapes, ep);
 | |
|       ep.force_eat(")");
 | |
|       throw connection_debug_request(i);
 | |
|       }
 | |
|     else if(ep.eat("stretch_shear(")) {
 | |
|       ld stretch = ep.rparse(0);
 | |
|       ep.force_eat(",");
 | |
|       ld shear = ep.rparse(0);
 | |
|       ep.force_eat(",");
 | |
|       int i = ep.iparse(0);
 | |
|       verify_index(i, c.shapes, ep);
 | |
|       ep.force_eat(",");
 | |
|       int j = ep.iparse(0);
 | |
|       verify_index(j, c.shapes[i], ep);
 | |
|       ep.force_eat(")");
 | |
|       auto& sh = c.shapes[i];
 | |
|       sh.stretch_shear[j] = {stretch, shear};
 | |
|       auto& co = sh.connections[j];
 | |
|       auto& xsh = c.shapes[co.sid];
 | |
|       ld scale = sh.edges[j] / xsh.edges[co.eid];
 | |
|       println(hlog, "scale = ", scale);
 | |
|       xsh.stretch_shear[co.eid] = {1/stretch, shear * (co.mirror ? 1 : -1) * stretch };
 | |
|       }
 | |
|     else throw hr_parse_exception("expecting command, " + ep.where());
 | |
|     }
 | |
|   if(!(cgflags & qAFFINE)) {
 | |
|     for(int i=0; i<isize(c.shapes); i++) {
 | |
|       auto& sh = c.shapes[i];
 | |
|       for(int j=0; j<isize(sh.edges); j++) {
 | |
|         ld d1 = sh.edges[j];
 | |
|         auto con = sh.connections[j];
 | |
|         auto& xsh = c.shapes[con.sid];
 | |
|         ld d2 = xsh.edges[con.eid];
 | |
|         if(d1 == INFINITE_LEFT) d1 = INFINITE_RIGHT;
 | |
|         else if(d1 == INFINITE_RIGHT) d1 = INFINITE_LEFT;
 | |
|         if(abs(d1 - d2) > 1e-6)
 | |
|           throw hr_parse_exception(lalign(0, "connecting ", make_pair(i,j), " to ", con, " of different lengths only possible in a2"));
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|   if(do_unmirror) {
 | |
|     unmirror(c);
 | |
|     }
 | |
|   if(!c.have_tree) compute_vertex_valence(c);
 | |
| 
 | |
|   check_football_colorability(c);
 | |
| 
 | |
|   if(c.have_tree) rulegen::verify_parsed_treestates(c);
 | |
|   
 | |
|   if(!load_as_slided) slided = current;
 | |
|   }
 | |
| 
 | |
| arbi_tiling debugged;
 | |
| vector<pair<transmatrix, int> > debug_polys;
 | |
| 
 | |
| string primes(int i) {
 | |
|   string res;
 | |
|   while(i--) res += "'";
 | |
|   return res;
 | |
|   }
 | |
| 
 | |
| void connection_debugger() {
 | |
|   cmode = sm::SIDE | sm::DIALOG_STRICT_X;
 | |
|   gamescreen();
 | |
|   
 | |
|   auto& last = debug_polys.back();
 | |
|   
 | |
|   initquickqueue();
 | |
|   for(auto& p: debug_polys) {
 | |
|     int id = p.second;
 | |
|     
 | |
|     shiftmatrix V = gmatrix[cwt.at] * p.first;
 | |
|     
 | |
|     auto& sh = debugged.shapes[id].vertices;
 | |
|     
 | |
|     for(auto& v: sh)
 | |
|       curvepoint(v);
 | |
| 
 | |
|     curvepoint(sh[0]);
 | |
|     
 | |
|     color_t col = ccolor::shape.ctab[id];
 | |
|     col = darkena(col, 0, 0xFF);
 | |
|     
 | |
|     if(&p == &last) {
 | |
|       vid.linewidth *= 2;
 | |
|       queuecurve(V, 0xFFFF00FF, col, PPR::LINE);
 | |
|       vid.linewidth /= 2;
 | |
|       for(int i=0; i<isize(sh); i++)
 | |
|         queuestr(V * sh[i], vid.fsize, its(i), 0xFFFFFFFF);
 | |
|       }
 | |
|     else
 | |
|       queuecurve(V, 0xFFFFFFFF, col, PPR::LINE);
 | |
|     }
 | |
|   quickqueue();
 | |
| 
 | |
|   dialog::init(XLAT("connection debugger"));
 | |
|   
 | |
|   dialog::addInfo(debugged.name);
 | |
|   dialog::addHelp(debugged.comment);
 | |
|   
 | |
|   dialog::addBreak(50);
 | |
| 
 | |
|   dialog::addInfo("face index " + its(last.second));
 | |
| 
 | |
|   dialog::addBreak(50);  
 | |
|   
 | |
|   auto& sh = debugged.shapes[last.second];
 | |
|   int N = isize(sh.edges);
 | |
|   for(int k=0; k<N; k++) {
 | |
|     auto con = sh.connections[k];
 | |
|     string cap = its(k) + primes(last.second) + " -> " + its(con.eid) + primes(con.sid) + (con.mirror ? " (m) " : "");
 | |
|     dialog::addSelItem(cap, "go", '0' + k);
 | |
|     
 | |
|     dialog::add_action([k, last, con] {
 | |
|       if(euclid) cgflags |= qAFFINE;
 | |
|       debug_polys.emplace_back(last.first * get_adj(debugged, last.second, k), con.sid);
 | |
|       if(euclid) cgflags &= ~qAFFINE;
 | |
|       });
 | |
|     
 | |
|     }    
 | |
| 
 | |
|   dialog::addItem("undo", 'u');
 | |
|   dialog::add_action([] {
 | |
|     if(isize(debug_polys) > 1)
 | |
|       debug_polys.pop_back();
 | |
|     });
 | |
|   
 | |
|   dialog::addBack();
 | |
|   dialog::display();
 | |
| 
 | |
|   keyhandler = [] (int sym, int uni) {
 | |
|     handlePanning(sym, uni);
 | |
|     dialog::handleNavigation(sym, uni);
 | |
|     if(doexiton(sym, uni)) popScreen();
 | |
|     };
 | |
|   }
 | |
| 
 | |
| geometryinfo1& arbi_tiling::get_geometry() {
 | |
|   return ginf[gEuclid].g;
 | |
|   }
 | |
| 
 | |
| map<heptagon*, vector<pair<heptagon*, transmatrix> > > altmap;
 | |
| 
 | |
| EX map<heptagon*, pair<heptagon*, transmatrix>> arbi_matrix;
 | |
| 
 | |
| EX hrmap *current_altmap;
 | |
| 
 | |
| heptagon *build_child(heptspin p, pair<int, int> adj);
 | |
| 
 | |
| /** get the midedge of lr; it takes infinite vertices into account */
 | |
| EX hyperpoint get_midedge(ld len, const hyperpoint &l, const hyperpoint &r) {
 | |
|   if(len == INFINITE_BOTH) {
 | |
|     return normalize(closest_to_zero(l, r));
 | |
|     }
 | |
|   else if(len == INFINITE_RIGHT) {
 | |
|     return towards_inf(r, l);
 | |
|     }
 | |
|   else if(len == INFINITE_LEFT) {
 | |
|     return towards_inf(l, r);
 | |
|     }
 | |
|   else return mid(l, r);
 | |
|   }
 | |
| 
 | |
| EX bool is_apeirogonal(cell *c) {
 | |
|   if(!in()) return false;
 | |
|   return current_or_slided().shapes[id_of(c->master)].apeirogonal;
 | |
|   }
 | |
| 
 | |
| EX bool is_apeirogonal() {
 | |
|   if(!in()) return false;
 | |
|   for(auto& sh: current_or_slided().shapes)
 | |
|     if(sh.apeirogonal) return true;
 | |
|   return false;
 | |
|   }
 | |
| 
 | |
| EX bool apeirogon_consistent_coloring = true;
 | |
| EX bool apeirogon_hide_grid_edges = true;
 | |
| EX bool apeirogon_simplified_display = false;
 | |
| 
 | |
| /** get the adj matrix corresponding to the connection of (t,dl) to connection_t{t1, xdl, xmirror} */
 | |
| EX transmatrix get_adj(arbi_tiling& c, int t, int dl, int t1, int xdl, bool xmirror) {
 | |
| 
 | |
|   auto& sh = c.shapes[t];
 | |
|   
 | |
|   int dr = gmod(dl+1, sh.size());
 | |
| 
 | |
|   auto& xsh = c.shapes[t1];
 | |
|   int xdr = gmod(xdl+1, xsh.size());
 | |
| 
 | |
|   hyperpoint vl = sh.vertices[dl];
 | |
|   hyperpoint vr = sh.vertices[dr];
 | |
|   hyperpoint xvl = xsh.vertices[xdl];
 | |
|   hyperpoint xvr = xsh.vertices[xdr];
 | |
| 
 | |
|   bool emb = embedded_plane;
 | |
|   if(emb) {
 | |
|     vl = cgi.emb->actual_to_base(vl);
 | |
|     vr = cgi.emb->actual_to_base(vr);
 | |
|     xvl = cgi.emb->actual_to_base(xvl);
 | |
|     xvr = cgi.emb->actual_to_base(xvr);
 | |
|     geom3::light_flip(true);
 | |
|     }
 | |
| 
 | |
|   hyperpoint vm = get_midedge(sh.edges[dl], vl, vr);
 | |
|       
 | |
|   transmatrix rm = gpushxto0(vm);
 | |
|   
 | |
|   hyperpoint xvm = get_midedge(xsh.edges[xdl], xvl, xvr);
 | |
|   
 | |
|   transmatrix xrm = gpushxto0(xvm);
 | |
|   
 | |
|   transmatrix Res = rgpushxto0(vm) * rspintox(rm*vr);
 | |
|     
 | |
|   if(cgflags & qAFFINE) {
 | |
|     ld sca = hdist(vl, vr) / hdist(xvl, xvr);
 | |
|     transmatrix Tsca = Id;
 | |
|     Tsca[0][0] = Tsca[1][1] = sca;
 | |
| 
 | |
|     auto& ss = sh.stretch_shear[dl];
 | |
|     Tsca[0][1] = ss.first * ss.second * sca;
 | |
|     Tsca[1][1] *= ss.first;
 | |
|     
 | |
|     Res = Res * Tsca;
 | |
|     }
 | |
| 
 | |
|   if(xmirror) Res = Res * MirrorX;
 | |
|   Res = Res * spintox(xrm*xvl) * xrm;
 | |
|   
 | |
|   if(xmirror) swap(vl, vr);
 | |
|   
 | |
|   if(hdist(vl, Res*xvr) + hdist(vr, Res*xvl) > .1 && !c.is_combinatorial) {
 | |
|     println(hlog, "s1 = ", kz(spintox(rm*vr)), " s2 = ", kz(rspintox(xrm*xvr)));    
 | |
|     println(hlog, tie(t, dl), " = ", kz(Res));    
 | |
|     println(hlog, hdist(vl, Res * xvr), " # ", hdist(vr, Res * xvl));
 | |
|     throw hr_exception("error in arb::get_adj");
 | |
|     }
 | |
| 
 | |
|   if(emb) {
 | |
|     Res = cgi.emb->base_to_actual(Res);
 | |
|     geom3::light_flip(false);
 | |
|     }
 | |
| 
 | |
|   return Res;
 | |
|   }
 | |
| 
 | |
| /** get the adj matrix corresponding to the connection of (t,dl) -- note: it may be incorrect for rotated/symmetric connections */
 | |
| EX transmatrix get_adj(arbi_tiling& c, int t, int dl) {
 | |
|   auto& sh = c.shapes[t];
 | |
|   auto& co = sh.connections[dl];
 | |
|   return get_adj(c, t, dl, co.sid, co.eid, co.mirror);
 | |
|   }
 | |
| 
 | |
| /** Returns if F describes the same tile as T, taking possible symmetries into account. Paramater co is the expected edge (co.sid tells us the tile type); if yes, co may be adjusted */
 | |
| EX bool find_connection(const transmatrix& T, const transmatrix& F, connection_t& co) {
 | |
| 
 | |
|   if(!same_point_may_warn(tC0(F), tC0(T))) return false;
 | |
| 
 | |
|   auto& xsh = current.shapes[co.sid];
 | |
|   int n = isize(xsh.connections);
 | |
|   for(int oth = 0; oth < n; oth++) {
 | |
|     int oth1 = gmod(oth+1, n);
 | |
|     int eid1 = gmod(co.eid+1, n);
 | |
|     if(same_point_may_warn(F * xsh.vertices[oth], T * xsh.vertices[co.eid]) && same_point_may_warn(F * xsh.vertices[oth1], T * xsh.vertices[eid1])) {
 | |
|       co.eid = oth;
 | |
|       return true;
 | |
|       }
 | |
|     if(same_point_may_warn(F * xsh.vertices[oth], T * xsh.vertices[eid1]) && same_point_may_warn(F * xsh.vertices[oth1], T * xsh.vertices[co.eid])) {
 | |
|       co.eid = oth; co.mirror = !co.mirror;
 | |
|       return true;
 | |
|       }
 | |
|     }
 | |
|   return false;
 | |
|   }
 | |
| 
 | |
| struct hrmap_arbi : hrmap {
 | |
|   heptagon *origin;
 | |
|   heptagon *getOrigin() override { return origin; }
 | |
| 
 | |
|   hrmap_arbi() {
 | |
|     dynamicval<hrmap*> curmap(currentmap, this);
 | |
|     origin = init_heptagon(current.shapes[0].size());
 | |
|     origin->s = hsOrigin;
 | |
|     origin->c7 = newCell(origin->type, origin);
 | |
| 
 | |
|     heptagon *alt = NULL;
 | |
|     
 | |
|     if(mhyperbolic) {
 | |
|       dynamicval<eGeometry> g(geometry, gNormal); 
 | |
|       alt = init_heptagon(S7);
 | |
|       alt->s = hsOrigin;
 | |
|       alt->alt = alt;
 | |
|       current_altmap = newAltMap(alt); 
 | |
|       }
 | |
|     
 | |
|     transmatrix T = lxpush(.01241) * spin(1.4117) * lxpush(0.1241) * Id;
 | |
|     arbi_matrix[origin] = make_pair(alt, T);
 | |
|     altmap[alt].emplace_back(origin, T);
 | |
|     
 | |
|     if(!current.range)
 | |
|       current.range = auto_compute_range(origin->c7);
 | |
|     }
 | |
| 
 | |
|   ~hrmap_arbi() {
 | |
|     clearfrom(origin);
 | |
|     altmap.clear();
 | |
|     arbi_matrix.clear();
 | |
|     if(current_altmap) {
 | |
|       dynamicval<eGeometry> g(geometry, gNormal);       
 | |
|       delete current_altmap;
 | |
|       current_altmap = NULL;
 | |
|       }
 | |
|     }
 | |
|   void verify() override { }
 | |
| 
 | |
|   transmatrix adj(heptagon *h, int dl) override { 
 | |
|     if(h->c.move(dl))
 | |
|       return get_adj(current_or_slided(), id_of(h), dl, id_of(h->c.move(dl)), h->c.spin(dl), h->c.mirror(dl));
 | |
|     else
 | |
|       return get_adj(current_or_slided(), id_of(h), dl);
 | |
|     }
 | |
| 
 | |
|   heptagon *create_step(heptagon *h, int d) override {
 | |
|   
 | |
|     if(geom3::flipped) return geom3::in_not_flipped([&] { return create_step(h, d); });
 | |
|     dynamicval<bool> sl(using_slided, false);
 | |
|     int t = id_of(h);
 | |
|   
 | |
|     auto& sh = current.shapes[t];
 | |
|     
 | |
|     auto& co = sh.connections[d];
 | |
|     
 | |
|     if(cgflags & qAFFINE) {
 | |
|       set<heptagon*> visited;
 | |
|       
 | |
|       vector<pair<heptagon*, transmatrix> > v;
 | |
|       
 | |
|       visited.insert(h);
 | |
|       v.emplace_back(h, Id);
 | |
|       
 | |
|       transmatrix goal = adj(h, d);
 | |
|       
 | |
|       for(int i=0; i<affine_limit && i < isize(v); i++) {
 | |
|         transmatrix T = v[i].second;
 | |
|         heptagon *h2 = v[i].first;
 | |
|         if(eqmatrix(T, goal)) {
 | |
|           h->c.connect(d, h2, co.eid, co.mirror);
 | |
|           return h2;
 | |
|           }
 | |
|         for(int i=0; i<h2->type; i++) {
 | |
|           heptagon *h3 = h2->move(i);
 | |
|           if(!h3) continue;
 | |
|           if(visited.count(h3)) continue;
 | |
|           visited.insert(h3);
 | |
|           v.emplace_back(h3, T * adj(h2, i));
 | |
|           }
 | |
|         }
 | |
|       
 | |
|       auto h1 = init_heptagon(current.shapes[co.sid].size());
 | |
|       h1->distance = h->distance + 1;
 | |
|       h1->zebraval = co.sid;
 | |
|       h1->c7 = newCell(h1->type, h1);
 | |
|       h1->emeraldval = h->emeraldval ^ co.mirror;
 | |
|       h->c.connect(d, h1, co.eid, co.mirror);
 | |
|       
 | |
|       return h1;
 | |
|       }
 | |
| 
 | |
|     const auto& p = arbi_matrix[h];
 | |
|     
 | |
|     heptagon *alt = p.first;
 | |
|     
 | |
|     transmatrix T = p.second * adj(h, d);
 | |
|     
 | |
|     if(mhyperbolic) {
 | |
|       dynamicval<eGeometry> g(geometry, gNormal); 
 | |
|       dynamicval<hrmap*> cm(currentmap, current_altmap);
 | |
|       // transmatrix U = T;
 | |
|       current_altmap->virtualRebase(alt, T);
 | |
|       // U = U * inverse(T);
 | |
|       }
 | |
|     fixmatrix(T);
 | |
| 
 | |
|     if(meuclid) {
 | |
|       /* 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;
 | |
|       }
 | |
| 
 | |
|     for(auto& p2: altmap[alt]) if(id_of(p2.first) == co.sid) {
 | |
|       connection_t co1 = co;
 | |
|       if(find_connection(T, p2.second, co1)) {
 | |
|         if(p2.first->move(co1.eid)) {
 | |
|           throw hr_exception("already connected!");
 | |
|           }
 | |
|         h->c.connect(d, p2.first, co1.eid, co1.mirror);
 | |
|         return p2.first;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|     auto h1 = init_heptagon(current.shapes[co.sid].size());
 | |
|     h1->distance = h->distance + 1;
 | |
|     h1->zebraval = co.sid;
 | |
|     h1->c7 = newCell(h1->type, h1);
 | |
|     h1->emeraldval = h->emeraldval ^ co.mirror;
 | |
|     h->c.connect(d, h1, co.eid, co.mirror);
 | |
|     
 | |
|     arbi_matrix[h1] = make_pair(alt, T);
 | |
|     altmap[alt].emplace_back(h1, T);    
 | |
|     return h1;
 | |
|     }
 | |
|   
 | |
|   transmatrix relative_matrixh(heptagon *h2, heptagon *h1, const hyperpoint& hint) override {
 | |
|     return relative_matrix_recursive(h2, h1);
 | |
|     }
 | |
| 
 | |
|   transmatrix adj(cell *c, int dir) override { return adj(c->master, dir); }
 | |
|   
 | |
|   ld spin_angle(cell *c, int d) override { return SPIN_NOT_AVAILABLE; }
 | |
| 
 | |
|   int shvid(cell *c) override {
 | |
|     return id_of(c->master);
 | |
|     }
 | |
| 
 | |
|   int pattern_value(cell *c) override { return id_of(c->master); }
 | |
| 
 | |
|   hyperpoint get_corner(cell *c, int cid, ld cf) override {
 | |
|     auto& sh = arb::current_or_slided().shapes[arb::id_of(c->master)];
 | |
|     int id = gmod(cid, c->type);
 | |
|     if(sh.angles[gmod(id-1, c->type)] <= 0)
 | |
|       return sh.vertices[id];
 | |
|     return normalize(C0 + (sh.vertices[id] - C0) * 3 / cf);
 | |
|     }
 | |
| 
 | |
|   };
 | |
| 
 | |
| EX hrmap *new_map() { return new hrmap_arbi; }
 | |
| 
 | |
| EX void run_raw(string fname) {
 | |
|   stop_game();
 | |
|   set_geometry(gArbitrary);
 | |
|   load(fname);
 | |
|   ginf[gArbitrary].tiling_name = current.name;
 | |
|   tes = fname;
 | |
|   convert::base_geometry = gArbitrary;
 | |
|   }
 | |
| 
 | |
| EX void run(string fname) {
 | |
|   eGeometry g = geometry;
 | |
|   arbi_tiling t = current;
 | |
|   auto v = variation;
 | |
|   try {
 | |
|      run_raw(fname);
 | |
|      }
 | |
|    catch(hr_polygon_error& poly) {
 | |
|      set_geometry(g);
 | |
|      set_variation(v);
 | |
|      current = t;
 | |
|      start_poly_debugger(poly);     
 | |
|      string help = poly.generate_error();
 | |
|      showstartmenu = false;
 | |
|      for(auto& p: poly.params)
 | |
|        help += lalign(-1, p.first, " = ", p.second, "\n");
 | |
|      gotoHelp(help);
 | |
|      }
 | |
|    catch(hr_parse_exception& ex) {
 | |
|      println(hlog, "failed: ", ex.s);
 | |
|      set_geometry(g);
 | |
|      current = t;
 | |
|      start_game();
 | |
|      addMessage("failed: " + ex.s);
 | |
|      }
 | |
|   catch(connection_debug_request& cr) {
 | |
|     set_geometry(g);     
 | |
|     debugged = current;
 | |
|     current = t;
 | |
|     ensure_geometry(cr.c);
 | |
|     debug_polys.clear();
 | |
|     debug_polys.emplace_back(Id, cr.id);
 | |
|     pushScreen(connection_debugger);
 | |
|     }
 | |
|   start_game();
 | |
|   }
 | |
| 
 | |
| string slider_error;
 | |
| 
 | |
| EX void sliders_changed(bool need_restart, bool need_start) {
 | |
|   if(need_restart) stop_game();
 | |
|   auto& c = current_or_slided();
 | |
|   arbi_tiling backup = c;
 | |
|   try {
 | |
|     load(current.filename, !need_restart, need_restart);
 | |
|     using_slided = !need_restart;
 | |
|     slider_error = "OK";
 | |
|     #if CAP_TEXTURE
 | |
|     texture::config.remap();
 | |
|     #endif
 | |
|     }
 | |
|   catch(hr_parse_exception& ex) {
 | |
|     c = backup;
 | |
|     slider_error = ex.s;
 | |
|     }
 | |
|   catch(hr_polygon_error& poly) {
 | |
|     c = backup;
 | |
|     slider_error = poly.generate_error();
 | |
|     }
 | |
|   if(need_restart && need_start) start_game();
 | |
|   }
 | |
| 
 | |
| EX void set_sliders() {
 | |
|   cmode = sm::SIDE | sm::MAYDARK;
 | |
|   gamescreen();
 | |
|   dialog::init(XLAT("tessellation sliders"));
 | |
|   dialog::addHelp(current.comment);
 | |
|   char ch = 'A';
 | |
|   for(auto& sl: current.sliders) {
 | |
|     dialog::addSelItem(sl.name, fts(sl.current), ch++);
 | |
|     dialog::add_action([&] {
 | |
|       dialog::editNumber(sl.current, sl.min, sl.max, 1, sl.zero, sl.name, sl.name);
 | |
|       dialog::get_di().reaction = [] { sliders_changed(false, false); };
 | |
|       });
 | |
|     }
 | |
|   if(isize(current.intsliders))
 | |
|     dialog::addInfo(XLAT("the following sliders will restart the game"));
 | |
|   for(auto& sl: current.intsliders) {
 | |
|     dialog::addSelItem(sl.name, its(sl.current), ch++);
 | |
|     dialog::add_action([&] {
 | |
|       dialog::editNumber(sl.current, sl.min, sl.max, 1, sl.zero, sl.name, sl.name);
 | |
|       dialog::get_di().reaction = [] { sliders_changed(true, true); };
 | |
|       });
 | |
|     }
 | |
|   dialog::addInfo(slider_error);
 | |
|   dialog::addBack();
 | |
|   dialog::display();
 | |
|   }
 | |
| 
 | |
| /** convert a tessellation (e.g. Archimedean, regular, etc.) to the arb::current internal representation */
 | |
| EX namespace convert {
 | |
| 
 | |
| EX eGeometry base_geometry = gArbitrary;
 | |
| EX eVariation base_variation;
 | |
| 
 | |
| struct id_record {
 | |
|   int target;   /* master of this id type */
 | |
|   int shift;    /* sample direction 0 == our direction shift */
 | |
|   int modval;   /* this master type is the same as itself rotated by modval */
 | |
|   cell *sample; /* sample of the master type */
 | |
|   };
 | |
| 
 | |
| inline void print(hstream& hs, const id_record& i) { print(hs, "[", i.target, " shift=", i.shift, " mod=", i.modval, "]"); }
 | |
| 
 | |
| map<int, id_record> identification;
 | |
| 
 | |
| id_record& get_identification(int s, cell *c) {  
 | |
|   if(!identification.count(s)) {
 | |
|     auto &id = identification[s];
 | |
|     id.target = s;
 | |
|     id.shift = 0;
 | |
|     id.modval = c->type;
 | |
|     id.sample = c;
 | |
|     }
 | |
|   return identification[s];
 | |
|   }
 | |
| 
 | |
| id_record& get_identification(cell *c) {  
 | |
|   auto id = currentmap->full_shvid(c);
 | |
|   return get_identification(id, c);
 | |
|   }
 | |
| 
 | |
| int changes;
 | |
| 
 | |
| void be_identified(cellwalker cw1, cellwalker cw2) {
 | |
|   auto& id1 = get_identification(cw1.at);
 | |
|   auto& id2 = get_identification(cw2.at);
 | |
|   
 | |
|   indenter ind(2);
 | |
|   
 | |
|   int t = cw2.at->type;
 | |
| 
 | |
|   if(cw1.at->type != t) {
 | |
|     println(hlog, cw1.at->type, " vs ", t);
 | |
|     throw hr_exception("numbers disagree");
 | |
|     }
 | |
|     
 | |
|   int d2 = gmod(-cw2.to_spin(id2.shift), id2.modval);
 | |
|   int d1 = gmod(-cw1.to_spin(id1.shift), id1.modval);
 | |
| 
 | |
|   indenter ind1(2);
 | |
| 
 | |
|   if(id2.target != id1.target) {
 | |
|     auto oid2 = id2;
 | |
|     id1.modval = gcd(id1.modval, id2.modval);
 | |
|     for(auto& p: identification) {
 | |
|       auto& idr = p.second;
 | |
|       if(idr.target == oid2.target) {
 | |
|         idr.target = id1.target;
 | |
|         idr.modval = id1.modval;
 | |
|         idr.shift = gmod(idr.shift + (d2-d1), idr.modval);        
 | |
|         idr.sample = id1.sample;
 | |
|         }
 | |
|       }
 | |
|     changes++;
 | |
|     println(hlog, identification);
 | |
|     return;
 | |
|     }
 | |
|   if(d2 != d1) {
 | |
|     auto oid2 = id2;
 | |
|     id2.modval = gcd(id2.modval, abs(d2-d1));
 | |
|     for(auto& p: identification) 
 | |
|       if(p.second.target == oid2.target) p.second.modval = id2.modval;
 | |
|     changes++;
 | |
|     println(hlog, identification);
 | |
|     return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
| EX bool reverse_order;
 | |
| EX bool minimize_on_convert;
 | |
| 
 | |
| EX void convert_max() {
 | |
|   identification.clear(); changes = 0;
 | |
| 
 | |
|   manual_celllister cl;
 | |
|   cl.add(currentmap->gamestart());
 | |
|   
 | |
|   int more_tests = 1000;
 | |
|   pointer_indices.clear();
 | |
| 
 | |
|   int chg = -1;
 | |
|   while(changes > chg) {
 | |
|     changes = chg;
 | |
|     
 | |
|     set<int> masters_analyzed;
 | |
| 
 | |
|     for(int i=0; i<isize(cl.lst); i++) {
 | |
|       auto c = cl.lst[i];
 | |
|       auto& id = get_identification(c);
 | |
|       
 | |
|       if(masters_analyzed.count(id.target)) {
 | |
|         more_tests--;
 | |
|         if(more_tests < 0) continue;
 | |
|         }
 | |
|       masters_analyzed.insert(id.target);
 | |
|       
 | |
|       cellwalker cw0(c, id.shift);
 | |
|       cellwalker cws(id.sample, 0);
 | |
|       
 | |
|       for(int i=0; i<c->type; i++) {
 | |
|         if(1) {
 | |
|           indenter ind(2);
 | |
|           be_identified(cw0 + i + wstep, cws + i + wstep);
 | |
|           be_identified(cw0 + i + wstep, cw0 + i + id.modval + wstep);
 | |
|           }
 | |
|         
 | |
|         if(1) {
 | |
|           indenter ind(2);
 | |
|           auto cwx = cw0 + i + wstep;
 | |
|           
 | |
|           auto idx = get_identification(cwx.at);
 | |
|           cellwalker xsample(idx.sample, cwx.spin);
 | |
|           xsample -= idx.shift;
 | |
|           
 | |
|           be_identified(cwx + wstep, xsample + wstep);
 | |
|           
 | |
|           cl.add((cw0 + i).cpeek());
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
| EX void convert_minimize(int N, vector<int>& old_shvids, map<int, int>& old_to_new) {
 | |
|   vector<pair<int, int>> address;
 | |
|   vector<int> address_start;
 | |
| 
 | |
|   for(int i=0; i<N; i++) {
 | |
|     int q = identification[old_shvids[i]].modval;
 | |
|     int c = isize(address);
 | |
|     address_start.push_back(c);
 | |
|     for(int j=0; j<q; j++) {
 | |
|       address.emplace_back(i, j);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|   int K = isize(address);  
 | |
|   vector<int> next(K), step(K);
 | |
| 
 | |
|   for(int k=0; k<K; k++) {
 | |
|     auto i = address[k].first;
 | |
|     auto j = address[k].second;
 | |
| 
 | |
|     auto& id = identification[old_shvids[i]];
 | |
|     next[k] = address_start[i] + (j+1) % id.modval;
 | |
| 
 | |
|     cell *s = id.sample;
 | |
|     cellwalker cw(s, j);
 | |
|     cw += wstep;
 | |
|     auto idx = get_identification(cw.at);
 | |
|     step[k] = address_start[old_to_new.at(idx.target)] + gmod(cw.spin - idx.shift, idx.modval);
 | |
|     }
 | |
| 
 | |
|   vector<array<ld, 3> > dists(K);
 | |
|   for(int i=0; i<K; i++) {
 | |
|     auto pi = address[i];
 | |
|     auto si = identification[old_shvids[pi.first]];
 | |
|     pi.second += si.shift;
 | |
|     array<hyperpoint, 3> pcorner;
 | |
|     array<ld, 3> pdists;
 | |
| 
 | |
|     for(int j=0; j<3; j++)
 | |
|       pcorner[j] = currentmap->get_corner(si.sample, gmod(pi.second+j, si.sample->type));
 | |
| 
 | |
|     for(int j=0; j<3; j++)
 | |
|       pdists[j] = hdist(pcorner[j], pcorner[(j+1)%3]);
 | |
| 
 | |
|     dists[i] = pdists;
 | |
|     }
 | |
| 
 | |
|   // this is O(K^3) and also possibly could get confused on convex/concave,
 | |
|   // but should be good enough, hopefully
 | |
|   
 | |
|   vector<vector<int>> equal(K);
 | |
|   for(int i=0; i<K; i++) equal[i].resize(K, 0);
 | |
|   for(int i=0; i<K; i++)
 | |
|   for(int j=0; j<K; j++) {
 | |
| 
 | |
|     equal[i][j] = true;
 | |
|     for(int s=0; s<3; s++)
 | |
|       equal[i][j] = equal[i][j] && abs(dists[i][s] - dists[j][s]) < 1e-6;
 | |
|     }
 | |
|   
 | |
|   int chg = 1;
 | |
|   while(chg) {
 | |
|     if(debugflags & DF_GEOM) {
 | |
|       println(hlog, "current table of equals:");
 | |
|       int eqid = 0;
 | |
|       for(auto& eq: equal) {
 | |
|         println(hlog, eq, " for ", eqid, ": ", address[eqid], " next= ", next[eqid], " step= ", step[eqid]);
 | |
|         eqid++;
 | |
|         }
 | |
|       }
 | |
|     chg = 0;
 | |
|     for(int i=0; i<K; i++)
 | |
|     for(int j=0; j<K; j++)
 | |
|       if(equal[i][j] && (!equal[next[i]][next[j]] || !equal[step[i]][step[j]])) {
 | |
|         equal[i][j] = false;
 | |
|         chg++;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|   for(int i=0; i<K; i++)
 | |
|   for(int j=0; j<K; j++) if(i!=j && equal[i][j]) {
 | |
|     auto pi = address[i];
 | |
|     auto si = identification[old_shvids[pi.first]];
 | |
|     cellwalker cwi(si.sample, si.shift + pi.second);
 | |
| 
 | |
|     auto pj = address[j];
 | |
|     auto sj = identification[old_shvids[pj.first]];
 | |
|     cellwalker cwj(sj.sample, sj.shift + pj.second);
 | |
| 
 | |
|     be_identified(cwi, cwj);
 | |
|     }
 | |
|   }
 | |
| 
 | |
| EX void convert() {
 | |
|   start_game();
 | |
|   convert_max();
 | |
|   bool minimize = minimize_on_convert;
 | |
|   reidentify:
 | |
|   vector<int> old_shvids;
 | |
|   map<int, int> old_to_new;
 | |
|   for(auto id: identification)
 | |
|     if(id.first == id.second.target) {
 | |
|       old_to_new[id.first] = isize(old_shvids);
 | |
|       old_shvids.push_back(id.first);
 | |
|       }
 | |
|   
 | |
|   int N = isize(old_shvids);
 | |
|   println(hlog, "N = ", N);
 | |
|   if(minimize) {
 | |
|     convert_minimize(N, old_shvids, old_to_new);
 | |
|     minimize = false;
 | |
|     goto reidentify;
 | |
|     }
 | |
| 
 | |
|   if(reverse_order) {
 | |
|     reverse(old_shvids.begin(), old_shvids.end());
 | |
|     for(int i=0; i<isize(old_shvids); i++)
 | |
|       old_to_new[old_shvids[i]] = i;
 | |
|     }
 | |
| 
 | |
|   auto& ac = arb::current;
 | |
|   ac.order++; 
 | |
|   ac.filename = full_geometry_name();
 | |
|   ac.comment = "converted from: " + ac.filename;
 | |
|   ac.cscale = cgi.scalefactor;
 | |
|   ac.boundary_ratio = 1;
 | |
|   ac.floor_scale = cgi.hexvdist / cgi.scalefactor;
 | |
|   ac.range = cgi.base_distlimit;
 | |
|   ac.shapes.clear();
 | |
|   ac.shapes.resize(N);
 | |
| 
 | |
|   ginf[gArbitrary].g = cginf.g;
 | |
|   ginf[gArbitrary].flags = cgflags & qCLOSED;
 | |
|   ginf[gArbitrary].tiling_name = full_geometry_name();
 | |
|   
 | |
|   for(int i=0; i<N; i++) {
 | |
|     auto id = identification[old_shvids[i]];
 | |
|     cell *s = id.sample;
 | |
|     auto& sh = ac.shapes[i];
 | |
|     sh.id = i;
 | |
|     int t = s->type;
 | |
|     sh.vertices.clear();
 | |
|     sh.connections.clear();
 | |
|     sh.cycle_length = id.modval;
 | |
|     if(arcm::in())
 | |
|       sh.orig_id = arcm::get_graphical_id(s);
 | |
|     else
 | |
|       sh.orig_id = shvid(s);
 | |
|     sh.repeat_value = t / id.modval;
 | |
|     sh.flags = hr::pseudohept(s) ? arcm::sfPH : 0;
 | |
|     #if CAP_ARCM
 | |
|     if(arcm::in() && arcm::linespattern(s)) { sh.flags |= arcm::sfLINE; ac.have_line = true; }
 | |
|     #endif
 | |
|     for(int j=0; j<t; j++) {
 | |
|       auto co = currentmap->get_corner(s, j);
 | |
|       sh.vertices.push_back(co);
 | |
|       cellwalker cw(s, j);
 | |
|       cw += wstep;
 | |
|       auto idx = get_identification(cw.at);
 | |
|       cellwalker xsample(idx.sample, cw.spin);
 | |
|       xsample -= idx.shift;
 | |
|       sh.connections.emplace_back(arb::connection_t{old_to_new.at(idx.target), xsample.spin, false});
 | |
|       }
 | |
|     sh.stretch_shear.resize(t, make_pair(1, 0));    
 | |
|     sh.edges.clear();
 | |
|     for(int j=0; j<t-1; j++)
 | |
|       sh.edges.push_back(hdist(sh.vertices[j], sh.vertices[j+1]));
 | |
|     sh.edges.push_back(hdist(sh.vertices[t-1], sh.vertices[0]));
 | |
| 
 | |
|     sh.angles.clear();
 | |
|     for(int j=0; j<t; j++) {
 | |
|       hyperpoint v0 = sh.vertices[j];
 | |
|       hyperpoint v1 = sh.vertices[(j+1) % t];
 | |
|       hyperpoint v2 = sh.vertices[(j+2) % t];
 | |
|       transmatrix T = gpushxto0(v1);
 | |
|       v0 = T * v0;
 | |
|       v2 = T * v2;
 | |
|       ld alpha = atan2(v0) - atan2(v2);
 | |
|       cyclefix(alpha, 0);
 | |
|       sh.angles.push_back(alpha);
 | |
|       }
 | |
|     if(debugflags & DF_GEOM) {
 | |
|       println(hlog, "shape ", i, ":");
 | |
|       indenter indp(2);
 | |
|       println(hlog, "vertices=", sh.vertices);
 | |
|       println(hlog, "connections=", sh.connections);
 | |
|       println(hlog, "edges=", sh.edges);
 | |
|       println(hlog, "angles=", sh.angles);
 | |
|       }
 | |
|     }
 | |
|   
 | |
|   arb::compute_vertex_valence(ac);
 | |
| 
 | |
|   ac.have_ph = geosupport_football() ? 1 : 0;
 | |
|   arb::check_football_colorability(ac);
 | |
|   }
 | |
| 
 | |
| EX bool in() {
 | |
|   return arb::in() && base_geometry != gArbitrary;
 | |
|   }
 | |
| 
 | |
| /** activate the converted tessellation */
 | |
| EX void activate() {
 | |
|   if(geometry != gArbitrary) {
 | |
|     base_geometry = geometry;
 | |
|     base_variation = variation;
 | |
|     stop_game();
 | |
|     geometry = gArbitrary;
 | |
|     variation = eVariation::pure;
 | |
|     }
 | |
|   }
 | |
| 
 | |
| EX }
 | |
| 
 | |
| #if CAP_COMMANDLINE  
 | |
| int readArgs() {
 | |
|   using namespace arg;
 | |
|            
 | |
|   if(0) ;
 | |
|   else if(argis("-tes") || argis("-arbi")) {
 | |
|     PHASEFROM(2);
 | |
|     shift(); 
 | |
|     run_raw(args());
 | |
|     }
 | |
|   else if(argis("-tes-opt")) {
 | |
|      arg::run_arguments(current.options);
 | |
|     }
 | |
|   else if(argis("-arb-convert")) {
 | |
|     try {
 | |
|       convert::convert();
 | |
|       set_geometry(gArbitrary);      
 | |
|       }
 | |
|     catch(hr_exception& e) {
 | |
|       println(hlog, "failed to convert: ", e.what());
 | |
|       }
 | |
|     }
 | |
|   else if(argis("-arb-unmirror")) {
 | |
|     shift(); do_unmirror = argi();
 | |
|     }
 | |
|   else if(argis("-arb-football")) {
 | |
|     shift(); extended_football = argi();
 | |
|     }
 | |
|   else if(argis("-arb-slider")) {
 | |
|     PHASEFROM(2);
 | |
|     shift();
 | |
|     string slider = args();
 | |
|     bool found = true;
 | |
|     for(auto& sl: current.sliders)
 | |
|       if(sl.name == slider) {
 | |
|         shift_arg_formula(sl.current, [] { sliders_changed(false, false); });
 | |
|         found = true;
 | |
|         }
 | |
|     for(auto& sl: current.intsliders)
 | |
|       if(sl.name == slider) {
 | |
|         shift(); sl.current = argi();
 | |
|         stop_game();
 | |
|         sliders_changed(true, false);
 | |
|         found = true;
 | |
|         }
 | |
|     if(!found) {
 | |
|       println(hlog, "warning: no slider named ", slider, " found");
 | |
|       shift();
 | |
|       }
 | |
|     }
 | |
|   else return 1;
 | |
|   return 0;
 | |
|   }
 | |
| 
 | |
| auto hook = addHook(hooks_args, 100, readArgs);
 | |
| #endif
 | |
| 
 | |
| EX bool in() { return geometry == gArbitrary; }
 | |
| 
 | |
| EX string tes = find_file("tessellations/sample/marjorie-rice.tes");
 | |
| 
 | |
| EX bool linespattern(cell *c) {
 | |
|   return current.shapes[id_of(c->master)].flags & arcm::sfLINE;
 | |
|   }
 | |
| 
 | |
| EX bool pseudohept(cell *c) {
 | |
|   if(!current.have_ph) return true;
 | |
|   return current.shapes[id_of(c->master)].flags & arcm::sfPH;
 | |
|   }
 | |
| 
 | |
| EX void choose() {
 | |
|   dialog::openFileDialog(tes, XLAT("open a tiling"), ".tes", 
 | |
|   [] () {
 | |
|     run(tes);
 | |
|     #if CAP_COMMANDLINE
 | |
|     if(!current.options.empty())
 | |
|       dialog::push_confirm_dialog([] { arg::run_arguments(current.options); start_game(); }, "load the settings defined in this file?");
 | |
|     #endif
 | |
|     return true;
 | |
|     });
 | |
|   }
 | |
| 
 | |
| EX pair<ld, ld> rep_ideal(ld e, ld u IS(1)) {
 | |
|   ld alpha = TAU / e;
 | |
|   hyperpoint h1 = point3(cos(alpha)*u, -sin(alpha)*u, 1);
 | |
|   hyperpoint h2 = point3(u, 0, 1);
 | |
|   hyperpoint h3 = point3(cos(alpha)*u, sin(alpha)*u, 1);
 | |
|   hyperpoint h12 = mid(h1, h2);
 | |
|   hyperpoint h23 = mid(h2, h3);
 | |
|   ld len = hdist(h12, h23);
 | |
|   transmatrix T = gpushxto0(h12);
 | |
|   auto T0 = T * C0;
 | |
|   auto Th23 = T * h23;
 | |
|   ld beta = atan2(T0);
 | |
|   ld gamma = atan2(Th23);
 | |
|   return {len, 90._deg - (gamma - beta)};
 | |
|   }
 | |
| 
 | |
| EX void swap_vertices() {
 | |
|   for(auto& p: {¤t, &slided}) 
 | |
|     for(auto& s: p->shapes)
 | |
|       for(auto& v: s.vertices)
 | |
|         swappoint(v);
 | |
|   }
 | |
| 
 | |
| #if MAXMDIM >= 4
 | |
| auto hooksw = addHook(hooks_swapdim, 100, [] {
 | |
|   swap_vertices();
 | |
|   for(auto& p: altmap) for(auto& pp: p.second) swapmatrix(pp.second);
 | |
|   for(auto& p: arbi_matrix) swapmatrix(p.second.second);
 | |
|   });
 | |
| #endif
 | |
| 
 | |
| EX }
 | |
| }
 | 
