namespace hr { namespace irr { bool on; ld density = 2; ld quality = .2; int place_attempts = 10; int rearrange_max_attempts = 50; int rearrange_less = 10; int cellcount; struct cellinfo { cell *owner; map relmatrices; vector jpoints; hyperpoint p; transmatrix pusher, rpusher; vector neid; vector spin; vector vertices; int localindex; bool is_pseudohept; int patterndir; }; map cellindex; vector cells; ld inner(hyperpoint h1, hyperpoint h2) { return hyperbolic ? h1[2] * h2[2] - h1[0] * h2[0] - h1[1] * h2[1] : h1[2] * h2[2] + h1[0] * h2[0] + h1[1] * h2[1]; } hyperpoint circumscribe(hyperpoint a, hyperpoint b, hyperpoint c) { using namespace hyperpoint_vec; hyperpoint h = C0; b = b - a; c = c - a; if(inner(b,b) < 0) { b = b / sqrt(-inner(b, b)); c = c + b * inner(c, b); h = h + b * inner(h, b); } else { b = b / sqrt(inner(b, b)); c = c - b * inner(c, b); h = h - b * inner(h, b); } if(inner(c,c) < 0) { c = c / sqrt(-inner(c, c)); h = h + c * inner(h, c); } else { c = c / sqrt(inner(c, c)); h = h - c * inner(h, c); } if(h[2] < 0) h[0] = -h[0], h[1] = -h[1], h[2] = -h[2]; ld i = inner(h, h); if(i > 0) h /= sqrt(i); else h /= -sqrt(-i); return h; } bool clockwise(hyperpoint h1, hyperpoint h2) { return h1[0] * h2[1] > h1[1] * h2[0]; } map > cells_of_heptagon; int runlevel; vector edgelens, distlens; void make_cells_of_heptagon() { cells_of_heptagon.clear(); for(int i=0; imaster]; p1.localindex = isize(vc); vc.push_back(i); } } string status[5]; hrmap *base; bool gridmaking; int rearrange_index; bool cell_sorting; int bitruncations_requested = 1, bitruncations_performed = 0; int black_adjacent, white_three; void set_relmatrices(cellinfo& ci) { auto& all = base->allcells(); ci.relmatrices.clear(); for(auto c0: all) ci.relmatrices[c0] = calc_relative_matrix(c0, ci.owner, ci.p); } void rebase(cellinfo& ci) { cell *cx = ci.owner; virtualRebase(ci.owner, ci.p, false); if(ci.owner != cx) { printf("rebased %p to %p\n", cx, ci.owner); set_relmatrices(ci); } } void compute_jpoints() { for(int i=0; i, int> bitruncated_id; for(int i=0; i newnei; for(int j=0; jallcells(); auto t = SDL_GetTicks(); while(SDL_GetTicks() < t + 250) switch(runlevel) { case 0: { cells.clear(); cells_of_heptagon.clear(); cellindex.clear(); if(0) if(cellcount <= isize(all) * 2) { for(auto h: all) { cells.emplace_back(); cellinfo& s = cells.back(); s.patterndir = -1; s.owner = h, s.p = spin(hrand(1000)) * xpush(.01) * C0; set_relmatrices(s); } } runlevel++; break; } case 1: { while(isize(cells) < cellcount) { if(SDL_GetTicks() > t + 250) { make_cells_of_heptagon(); status[0] = its(isize(cells)) + " cells"; return false; } cells.emplace_back(); cellinfo& s = cells.back(); s.patterndir = -1; ld bestval = 0; for(int j=0; j relmatrices; hyperpoint h = randomPointIn(c->type); for(auto c0: all) relmatrices[c0] = calc_relative_matrix(c0, c, h); ld mindist = 1e6; for(auto p: cells) { if(!relmatrices.count(p.owner)) continue; ld val = hdist(h, relmatrices[p.owner] * p.p); if(val < mindist) mindist = val; } if(mindist > bestval) bestval = mindist, s.owner = c, s.p = h, s.relmatrices = move(relmatrices); } // printf("%lf %p %s\n", bestval, s.owner, display(s.p)); } make_cells_of_heptagon(); cell_sorting = true; bitruncations_performed = 0; runlevel++; status[0] = "all " + its(isize(cells)) + " cells"; break; } case 2: { if(cell_sorting) sort(cells.begin(), cells.end(), [] (const cellinfo &s1, const cellinfo &s2) { return hdist0(s1.p) < hdist0(s2.p); }); make_cells_of_heptagon(); edgelens.clear(); distlens.clear(); int stats[16]; for(int k=0; k<16; k++) stats[k] = 0; compute_jpoints(); for(int i=0; i 8 || v< 3) { if(v < 3 || v >= 15) errors++; else toobig++; cells[i] = cells.back(); i--; cells.pop_back(); } } if(errors > 0) status[1] = XLAT("bad cells: %1", its(errors)); else status[1] = " "; if(toobig > 0) status[2] = XLAT("too many edges: %1", its(toobig)); else status[2] = " "; if(isize(cells) < cellcount*3/4) runlevel = 0; else if(isize(cells) < cellcount) runlevel = 1; else { rearrange_index = 0; runlevel++; } break; } case 4: { ld median = edgelens[isize(edgelens) / 2]; ld minedge = median * quality; status[3] = XLAT("median edge: %1 minimum: %2", fts4(median), fts4(edgelens[0])); if(!bitruncations_performed && edgelens[0] < minedge) { if(rearrange_index >= rearrange_max_attempts) { runlevel = 0; break; } int tooshort = rearrange(rearrange_index < rearrange_less, minedge); status[3] += XLAT(" (edges too short: %1)", its(tooshort)); runlevel = 2; rearrange_index++; break; } runlevel++; break; } case 5: { if(bitruncations_performed < bitruncations_requested) bitruncate(); else runlevel = 6; break; } case 6: { int notfound = 0; for(int i=0; i= isize(cells)) { runlevel = 0; return false; } bool found = false; for(int k=0; k < isize(cells[i1].vertices); k++) if(cells[i1].neid[k] == i) found = true, p1.spin[j] = k; if(!found) notfound++; } } if(notfound) { status[4] = XLAT("cells badly paired: %1", its(notfound)); runlevel = 0; break; } int heptas = 0; for(auto p: cells_of_heptagon) { printf("%p: %d\n", p.first, isize(p.second)); heptas++; } if(heptas != isize(all)) { status[4] = XLAT("cells not covered: %1", its(isize(all) - heptas)); printf("heptas = %d\n", heptas); runlevel = 0; break; } int faredge = 0; for(int i=0; imove(i), s.owner, s.p) * C0); // spin(2 * M_PI * i / S7) * xpush(hcrossf) * C0); if(dists[i] < dist) d = i, dist = dists[i]; } if(d != -1 && dists[(d+1) % S7] > dists[(d+S7-1) % S7]) d = (d + S7 - 1) % S7; s.patterndir = d; } break; } case 10: return false; } return false; } ld scale; void compute_geometry() { if(irr::on) { scale = sqrt(isize(cells_of_heptagon) * 1. / isize(cells)); crossf *= scale; hepvdist *= scale; rhexf *= scale; base_distlimit = (base_distlimit + log(scale) / log(2.618)) / scale; if(base_distlimit > 25) base_distlimit = 25; } else scale = 1; } bool draw_cell_schematics(cell *c, transmatrix V) { if(gridmaking) { heptagon *h = c->master; for(int i: cells_of_heptagon[h]) { auto& p = cells[i]; if(p.owner == c) { queuestr(V * rgpushxto0(p.p), .1, its(i), isize(p.vertices) > 8 ? 0xFF0000 : 0xFFFFFF); int N = isize(p.vertices); for(int j=0; jmaster->move(p.patterndir)->c7, c, p.p) * C0, 0x00FF00FF); } } } return false; } struct heptinfo { heptspin base; vector subcells; vector celldists[2]; }; map periodmap; void link_to_base(heptagon *h, heptspin base) { // printf("linking %p to %p/%d\n", h, base.at, base.spin); auto &hi = periodmap[h]; hi.base = base; for(int k: cells_of_heptagon[base.at]) { cell *c = newCell(isize(cells[k].vertices), h); hi.subcells.push_back(c); cellindex[c] = k; } h->c7 = hi.subcells[0]; } void clear_links(heptagon *h) { auto& hi = periodmap[h]; for(cell *c: hi.subcells) { for(int i=0; itype; i++) if(c->move(i)) c->move(i)->move(c->c.spin(i)) = NULL; cellindex.erase(c); delete c; } h->c7 = NULL; periodmap.erase(h); } void link_start(heptagon *h) { link_to_base(h, heptspin(cells[0].owner->master, 0)); } void link_next(heptagon *parent, int d) { if(!periodmap.count(parent)) link_to_base(parent, heptspin(cells[0].owner->master, 0)); // printf("linking next: %p direction %d [s%d]\n", parent, d, parent->c.spin(d)); auto *h = parent->move(d); heptspin hs = periodmap[parent].base + d + wstep - parent->c.spin(d); link_to_base(h, hs); } void may_link_next(heptagon *parent, int d) { if(!periodmap.count(parent->move(d))) link_next(parent, d); } void link_cell(cell *c, int d) { // printf("linking cell: %p direction %d\n", c, d); int ci = cellindex[c]; auto& sc = cells[ci]; int ci2 = sc.neid[d]; auto& sc2 = cells[ci2]; heptagon *master2 = NULL; if(sc2.owner == sc.owner) { master2 = c->master; // printf("local\n"); } else { int dirs = 0; int os = periodmap[c->master].base.spin; for(int d=0; dmaster == sc.owner->master->modmove(os+d)) { heptspin hss(c->master, d); hss += wstep; master2 = hss.at; // printf("master2 is %p; base = %p; should be = %p\n", master2, periodmap[master2].base.at, sc2.owner->master); dirs++; } if(dirs != 1) { printf("dirs error\n"); exit(1); } } cell *c2 = periodmap[master2].subcells[sc2.localindex]; c->c.connect(d, c2, sc.spin[d], false); } int hdist(heptagon *h1, heptagon *h2) { if(h1 == h2) return 0; for(int i=0; imove(i) == h2) return 1; return 2; } // compute celldist or celldistalt for all the subcells of h. // We use the following algorithm: // - assume that everything is computed for all the adjacent heptagons of h which are closer to the origin // - consider h and its two neighbors which are in the same distance to the origin ('siblings') // - compute celldists for all the cells in these three heptagons, by bfs, based on the 'parent' heptagons adjacent to h // - record the computed distances for h, but not for its siblings static const int NODISTANCE = 2000000000; map last_on_horocycle; void compute_horocycle(heptagon *); void compute_distances(heptagon *h, bool alts) { /* if(alts) printf("[%p] compute_distances %p\n", h->alt->alt, h); printf("neighbors:"); for(int i=0; ialt->alt]) last_on_horocycle[h->alt->alt] = h; if(h->alt->alt->s != hsOrigin) while(h->alt->distance <= last_on_horocycle[h->alt->alt]->alt->distance) compute_horocycle(h->alt->alt); } auto dm4 = [alts, h] (heptagon *h1) -> unsigned { if(!alts) return h1->dm4; if(alts && !h1->alt) return 100; // error if(alts && h1->alt->alt != h->alt->alt) return 100; // error return h1->alt->dm4; }; unsigned cdm = dm4(h), pdm = (cdm-1)&3; vector hs; hs.push_back(h); for(int i=0; imove(i)); vector*> to_clear; for(auto hx: hs) { auto &hi = periodmap[hx]; int ct = isize(hi.subcells); auto& cd = hi.celldists[alts]; if(cd.empty() && hx != h) to_clear.push_back(&cd); cd.resize(ct, NODISTANCE); if(h == hx && (alts ? h->alt->s == hsOrigin : h->s == hsOrigin)) cd[0] = 0; } while(true) { bool changed = false; for(auto hx: hs) { auto& hi = periodmap[hx]; auto& cd = hi.celldists[alts]; for(int i=0; imaster), cdm, pdm) && hdist(h, c2->master) < 2) { int d = irr::celldist(c2, alts) + 1; if(d < cd[i]) cd[i] = d, changed = true; } } if(!changed) break; } /* for(auto hx: hs) { auto& hi = periodmap[hx]; auto& cd = hi.celldists[alts]; // for(int i: cd) if(i == NODISTANCE) printf("distances not computed\n"); } */ for(auto x: to_clear) x->clear(); // for(int i: cd) printf(" %d", i); printf("\n"); } void erase_alt(heptagon *alt) { last_on_horocycle.erase(alt); } void compute_horocycle(heptagon *alt) { heptagon *master = last_on_horocycle[alt]; // printf("computing horocycle, master distance = %d [M=%p, A=%p]\n", master->alt->distance, master, alt); static const int LOOKUP = 16; set hs[LOOKUP]; hs[0].insert(master); set region; for(int i=0; imove(j)->alt->alt != master->alt->alt) continue; region.insert(h->move(j)); if(h->move(j)->alt->distance < h->alt->distance) hs[i+1].insert(h->move(j)); } } if(hs[i+1].empty()) { printf("error: hs[%d] not found\n", i+1); exit(1); } } /* printf("[%p] compute_horocycle "); for(int i=0; i ", isize(hs[i])); printf("%p\n", isize(hs[LOOKUP-1])); */ map xdist; vector xdqueue; cell *orig = periodmap[*(hs[LOOKUP-1].begin())].subcells[0]; xdist[orig] = 0; xdqueue.push_back(orig); for(int i=0; imaster)) { xdist[c1] = xdist[xdqueue[i]] + 1; xdqueue.push_back(c1); } } int delta = NODISTANCE; for(int i=0; imove(i); if(h->alt->alt != master->alt->alt) continue; heptinfo& hi = periodmap[h]; if(!isize(hi.celldists[1])) continue; for(int c=0; calt->distance - xdist[periodmap[master].subcells[0]]; // printf("delta not found, set to %d\n", delta); } // printf("using delta = %d\n", delta); for(int i=0; imove(j)]; hi.celldists[1].resize(isize(hi.subcells)); for(int c=0; cmaster; auto &hi = periodmap[master]; /* if(alts && master->alt->alt->s != hsOrigin && isize(hi.celldists[alts]) == 0) { int doalts = 0; for(int i=0; imove(i)->alt == master->alt->move[0]) { doalts = 1; if(periodmap[master->move(i)].celldists[true].empty()) { compute_horocycle(master); doalts = 2; } } if(doalts == 0) { generateAlts(master); for(int i=0; imove(i)->alt == master->alt->move[0] && periodmap[master->move(i)].celldists[true].empty()) compute_horocycle(master); } } */ if(isize(hi.celldists[alts]) == 0) compute_distances(master, alts); return hi.celldists[alts][cells[cellindex[c]].localindex]; } eGeometry orig_geometry; void start_game_on_created_map() { #if CAP_TEXTURE auto old_tstate = texture::config.tstate; auto old_tstate_max = texture::config.tstate_max; #endif popScreen(); for(hrmap *& hm : allmaps) if(hm == base) hm = NULL; stop_game(); geometry = orig_geometry; irr::on = true; nonbitrunc = true; gp::on = false; need_reset_geometry = true; gridmaking = false; start_game(); #if CAP_TEXTURE texture::config.remap(old_tstate, old_tstate_max); #endif } bool save_map(const string& fname) { FILE *f = fopen(fname.c_str(), "wt"); if(!f) return false; auto& all = base->allcells(); fprintf(f, "%d %d %d\n", geometry, isize(all), isize(cells)); for(auto h: all) { fprintf(f, "%d\n", isize(cells_of_heptagon[h->master])); for(auto i: cells_of_heptagon[h->master]) { auto &ci = cells[i]; fprintf(f, "%lf %lf %lf\n", double(ci.p[0]), double(ci.p[1]), double(ci.p[2])); } } fclose(f); return true; } bool load_map(const string &fname) { FILE *f = fopen(fname.c_str(), "rt"); if(!f) return false; auto& all = base->allcells(); int g, sa; ignore(fscanf(f, "%d %d %d\n", &g, &sa, &cellcount)); if(sa != isize(all) || g != geometry) { printf("bad parameters\n"); addMessage(XLAT("bad format or bad map geometry")); return false; } density = cellcount * 1. / isize(all); cells.clear(); for(auto h: all) { int q = 0; ignore(fscanf(f, "%d\n", &q)); if(q < 0 || q > cellcount) { runlevel = 0; return false; } while(q--) { cells.emplace_back(); cellinfo& s = cells.back(); s.patterndir = -1; double a, b, c; ignore(fscanf(f, "%lf%lf%lf", &a, &b, &c)); s.p = hpxyz(a, b, c); for(auto c0: all) s.relmatrices[c0] = calc_relative_matrix(c0, h, s.p); s.owner = h; } } fclose(f); make_cells_of_heptagon(); runlevel = 2; return true; } void cancel_map_creation() { base = NULL; runlevel = 0; popScreen(); gridmaking = false; stop_game(); geometry = orig_geometry; need_reset_geometry = true; start_game(); } string irrmapfile = "irregularmap.txt"; string irrhelp = "This option creates irregular grids to play the game on. " "Currently rather slow algorithms are used, " "so not recommended with too high density or " "with too large periodic base geometry. " "For technical reasons, the density cannot be too small."; void show_gridmaker() { cmode = sm::SIDE; gamescreen(0); dialog::init(XLAT("irregular grid")); dialog::addSelItem(XLAT("density"), fts(density), 'd'); dialog::add_action([] { dialog::editNumber(density, 1, 10, .1, 4, XLAT("density"), XLAT(irrhelp)); dialog::reaction = [] () { int s = cellcount; if(density < 1) density = 1; cellcount = int(isize(currentmap->allcells()) * density + .5); printf("density = %lf cellcount = %d\n", double(density), cellcount); if(cellcount > s) runlevel = 1; if(cellcount < s) runlevel = 0; }; }); dialog::addSelItem(XLAT("min edge to median"), fts(quality), 'q'); dialog::add_action([] { dialog::editNumber(quality, 0, 1, .05, .2, XLAT("quality"), XLAT( "The smallest allowed ratio of edge length to median edge length. " "Tilings with low values are easier to generate, but tend to be more ugly." )); dialog::reaction = [] () { printf("quality = %lf\n", double(density)); if(runlevel > 4) runlevel = 4; }; }); dialog::addBreak(100); for(int i=0; i<5; i++) dialog::addInfo(status[i]); dialog::addBreak(100); dialog::addSelItem(XLAT("activate"), XLAT(runlevel == 10 ? "ready" : "wait..."), 'f'); if(runlevel == 10) dialog::add_action(start_game_on_created_map); dialog::addItem(XLAT("cancel"), 'c'); dialog::add_action(cancel_map_creation); dialog::addItem(XLAT("save"), 's'); dialog::add_action([] () { dialog::openFileDialog(irrmapfile, XLAT("irregular to save:"), ".txt", [] () { if(save_map(irrmapfile)) { addMessage(XLAT("Map saved to %1", irrmapfile)); return true; } else { addMessage(XLAT("Failed to save map to %1", irrmapfile)); return false; } }); }); dialog::addItem(XLAT("load"), 'l'); dialog::add_action([] () { dialog::openFileDialog(irrmapfile, XLAT("irregular to load:"), ".txt", [] () { if(load_map(irrmapfile)) { addMessage(XLAT("Map loaded from %1", irrmapfile)); return true; } else { addMessage(XLAT("Failed to load map from %1", irrmapfile)); return false; } }); }); dialog::addSelItem(XLAT("bitruncation count"), its(bitruncations_requested), 'b'); dialog::add_action([] () { dialog::editNumber(bitruncations_requested, 0, 5, 1, 1, XLAT("bitruncation const"), XLAT("Bitruncation introduces some regularity, allowing more sophisiticated floor tilings and textures.")); dialog::reaction = [] () { if(bitruncations_requested > bitruncations_performed && runlevel > 5) runlevel = 5; if(bitruncations_requested < bitruncations_performed) runlevel = 0; }; }); dialog::addItem(XLAT("reset"), 'r'); dialog::add_action([] () { runlevel = 0; }); dialog::addHelp(); dialog::display(); keyhandler = [] (int sym, int uni) { handlePanning(sym, uni); if(uni == 'h' || sym == SDLK_F1) gotoHelp(XLAT(irrhelp)); dialog::handleNavigation(sym, uni); // no exit }; } void visual_creator() { stop_game(); orig_geometry = geometry; switch(geometry) { case gNormal: geometry = gKleinQuartic; break; case gOctagon: geometry = gBolza2; break; default: ; break; } nonbitrunc = true; gp::on = false; need_reset_geometry = true; start_game(); if(base) delete base; base = currentmap; drawthemap(); cellcount = int(isize(base->allcells()) * density + .5); pushScreen(show_gridmaker); runlevel = 0; gridmaking = true; } void auto_creator() { irr::on = false; int cc = cellcount; bitruncations_requested = bitruncations_performed; visual_creator(); cellcount = cc; density = cc * 1. / isize(base->allcells()); printf("Creating the irregular map automatically...\n"); while(runlevel < 10) step(1000); start_game_on_created_map(); } #if CAP_COMMANDLINE int readArgs() { using namespace arg; if(0) ; else if(argis("-irrvis")) { PHASE(3); restart_game(); visual_creator(); showstartmenu = false; } else if(argis("-irrdens")) { PHASE(2); shift(); density = argf(); } else if(argis("-irrload")) { PHASE(3); restart_game(); visual_creator(); showstartmenu = false; shift(); load_map(args()); while(runlevel < 10) step(1000); start_game_on_created_map(); } else return 1; return 0; } #endif unsigned char density_code() { if(isize(cells) < 128) return isize(cells); else { int t = 127, a = isize(cells); while(a > 127) a = a * 9/10, t++; return t; } } bool pseudohept(cell* c) { return cells[cellindex[c]].is_pseudohept; } bool ctof(cell* c) { return cells[cellindex[c]].patterndir == -1; } bool supports(eGeometry g) { return among(g, gNormal, gKleinQuartic, gOctagon, gBolza2, gFieldQuotient, gSphere, gSmallSphere, gTinySphere); } array get_masters(cell *c) { int d = cells[cellindex[c]].patterndir; heptspin s = periodmap[c->master].base; heptspin s0 = heptspin(c->master, 0) + (d - s.spin); return make_array(s0.at, (s0 + wstep).at, (s0 + 1 + wstep).at); } auto hook = #if CAP_COMMANDLINE addHook(hooks_args, 100, readArgs) + #endif addHook(hooks_drawcell, 100, draw_cell_schematics) + addHook(shmup::hooks_turn, 100, step); }} /* if(mouseover && !ctof(mouseover)) { for(auto h: gp::get_masters(mouseover)) queueline(ggmatrix(h->c7)*C0, shmup::ggmatrix(mouseover)*C0, 0xFFFFFFFF); } */