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