diff --git a/cell.cpp b/cell.cpp index d1b54985..a8b73216 100644 --- a/cell.cpp +++ b/cell.cpp @@ -402,6 +402,141 @@ EX bool is_in_disk(cell *c) { return *it == c; } +bool sierpinski3(gp::loc g) { + int x = g.first; + int y = g.second; + set> visited; + while(true) { + if(visited.count({x,y})) return false; + visited.insert({x,y}); + // if(x == -1 && y == -2) return false; + // if(x == -2 && y == -3) return false; + if(x == 0 && y == 0) return true; + if((x&1) == 1 && (y&1) == 1) return false; + x >>= 1; + y >>= 1; + // x--; y++; + tie(x, y) = make_pair(-x-y, x); + } + } + +bool sierpinski46(gp::loc g) { + int x = g.first; + int y = g.second; + set> visited; + if(S7 == 6) + x += 2785684, y += 289080; + else + x += 75239892, y += 7913772; + while(true) { + if(visited.count({x,y})) return false; + visited.insert({x,y}); + if(x == 0 && y == 0) return true; + int dx = gmod(x, 3); + int dy = gmod(y, 3); + if(dx == 1 && dy == 1) return false; + if(S7 == 6 && dx == dy) return false; + x = (x-dx) / 3; + y = (y-dy) / 3; + } + } + +bool menger_sponge(euc::coord c) { + c[0] += 528120*9; c[1] += 438924*9; c[2] += 306712*9; + set visited; + while(true) { + if(visited.count(c)) return false; + visited.insert(c); + if(c[0] == 0 && c[1] == 0 && c[2] == 0) return true; + int ones = 0; + for(int i=0; i<3; i++) { + int d = gmod(c[i], 3); + c[i] = (c[i] - d) / 3; + if(d == 1) ones++; + } + if(ones >= 2) return false; + } + } + +bool sierpinski_tet(euc::coord c) { + set visited; + c[0] += 16 * (1+8+64+512); + c[1] += 16 * (1+8+64+512); + c[1] += 32 * (1+8+64+512); + c[2] += 32 * (1+8+64+512); + c[0] += 64 * (1+8+64+512); + c[1] += 64 * (1+8+64+512); + while(true) { + if(visited.count(c)) return false; + visited.insert(c); + if(c[0] == 0 && c[1] == 0 && c[2] == 0) return true; + int ones = 0; + for(int i=0; i<3; i++) { + int d = gmod(c[i], 2); + c[i] = (c[i] - d) / 2; + if(d == 1) ones++; + } + if(ones & 1) return false; + } + } + +EX bool is_in_fractal(cell *c) { + if(fake::in()) return FPIU(is_in_fractal(c)); + if(mhybrid) { c = hybrid::get_where(c).first; return PIU(is_in_fractal(c)); } + switch(geometry) { + case gSierpinski3: + return sierpinski3(euc::full_coords2(c)); + case gSierpinski4: + case gSixFlake: + return sierpinski46(euc::full_coords2(c)); + case gMengerSponge: + return menger_sponge(euc::get_ispacemap()[c->master]); + case gSierpinskiTet: { + return sierpinski_tet(euc::get_ispacemap()[c->master]); + } + default: + return true; + } + } + +EX cell *fractal_rep(cell *c) { + switch(geometry) { + case gSierpinski3: { + auto co = euc::full_coords2(c); + co.first += 4; + co.first &= ~15; + co.first -= 4; + co.second += 2; + co.second &= ~15; + co.second -= 2; + if(co.first == -4 && co.second == -2) co.first = 0, co.second = 0; + return euc::get_at(euc::to_coord(co))->c7; + } + case gSierpinski4: + case gSixFlake: { + auto co = euc::full_coords2(c); + if(S7 == 6) co.first += 4; + co.first -= gmod(co.first, 9); + co.second -= gmod(co.second, 9); + return euc::get_at(euc::to_coord(co))->c7; + } + case gSierpinskiTet: { + auto co = euc::get_ispacemap()[c->master]; + co[0] &=~7; + co[1] &=~7; + co[2] &=~7; + return euc::get_at(co)->c7; + } + case gMengerSponge: { + auto co = euc::get_ispacemap()[c->master]; + for(int i=0; i<3; i++) co[i] = co[i] - gmod(co[i], 9); + return euc::get_at(co)->c7; + } + default: + throw hr_exception("unknown fractal"); + } + } + /** create a map in the current geometry */ EX void initcells() { DEBB(DF_INIT, ("initcells")); diff --git a/celldrawer.cpp b/celldrawer.cpp index 4484ec20..ce982937 100644 --- a/celldrawer.cpp +++ b/celldrawer.cpp @@ -1751,6 +1751,22 @@ void celldrawer::draw_features_and_walls_3d() { #if MAXMDIM >= 4 color_t dummy; int ofs = currentmap->wall_offset(c); + + if((cgflags & qFRACTAL) && c->wall == waChasm && c->land == laMemory) { + for(int a=0; atype; a++) if(c->move(a) && c->move(a)->land != laMemory) { + if(anyshiftclick) { + auto& poly = queuepoly(V, cgi.shPlainWall3D[ofs+a], 0xFFFFFFFF - 0xF0F0F00 * get_darkval(c, a)); + poly.tinf = &floor_texture_vertices[cgi.shFullFloor.id]; + ensure_vertex_number(*poly.tinf, poly.cnt); + } + else { + auto& poly = queuepoly(V, cgi.shWireframe3D[ofs + a], 0); + poly.outline = 0xFFFFFFFF; + } + } + if(anyshiftclick) return; + } + if(isWall3(c, wcol)) { if(!no_wall_rendering) { if(c->wall == waChasm && c->land == laMemory && !in_perspective()) { diff --git a/classes.cpp b/classes.cpp index 72e4b4a0..c808a300 100644 --- a/classes.cpp +++ b/classes.cpp @@ -751,6 +751,7 @@ enum eGeometry { gSpace345, gSpace353, gSpace354, gSpace355, gHalfBring, gAperiodicHat, + gSierpinski3, gSierpinski4, gSixFlake, gMengerSponge, gSierpinskiTet, gGUARD}; enum eGeometryClass { gcHyperbolic, gcEuclid, gcSphere, gcSol, gcNIH, gcSolN, gcNil, gcProduct, gcSL2 }; @@ -830,6 +831,8 @@ static const flagtype qCAT = Flag(28); static const flagtype qAPERIODIC = Flag(29); static const flagtype qHAT = Flag(30); +static const flagtype qFRACTAL = Flag(31); + // note: dnext assumes that x&7 equals 7 static const int SEE_ALL = 50; // note: check_football_colorability in arbitrile.cpp assumes OINF is divisible by 3 @@ -958,7 +961,12 @@ EX vector ginf = { {"{3,5,4}","none", "{3,5,4} hyperbolic honeycomb", "354", 20, 5, qIDEAL | qULTRA, giHyperb3, {{7, 2}}, eVariation::pure}, {"{3,5,5}","none", "{3,5,5} hyperbolic honeycomb", "355", 20, 5, qIDEAL | qULTRA, giHyperb3, {{7, 2}}, eVariation::pure}, {"{5,4}", "pBring", "projective Bring's Surface", "pBring", 5, 4, qsSMALLN, giHyperb2, {{6, 4}}, eVariation::bitruncated}, - {"hat", "none", "aperiodic hat", "hat", 14, 3, qAPERIODIC | qHAT, giEuclid2, {{7, 7}}, eVariation::pure}, + {"hat", "none", "aperiodic hat", "hat", 14, 3, qAPERIODIC | qHAT, giEuclid2, {{7, 7}}, eVariation::pure}, + {"triangle","none", "Sierpiński triangle", "S3", 6, 3, qFRACTAL, giEuclid2, {{10, 10}}, eVariation::pure}, + {"carpet", "none", "Sierpiński carpet", "S4", 4, 4, qFRACTAL, giEuclid2, {{10, 10}}, eVariation::pure}, + {"6-flake","none", "6-flake fractal", "S6", 6, 3, qFRACTAL, giEuclid2, {{10, 10}}, eVariation::pure}, + {"{4,3,4}","none", "Menger sponge", "S8", 6, 4, qFRACTAL, giEuclid3, {{10, 10}}, eVariation::pure}, + {"rh{4,3,4}","none", "Sierpiński tetrahedron", "S4b", 12, 3, qFRACTAL, giEuclid3, {{10, 10}}, eVariation::pure}, }; // bits: 9, 10, 15, 16, (reserved for later) 17, 18 diff --git a/euclid.cpp b/euclid.cpp index 65b75831..9eda2fa5 100644 --- a/euclid.cpp +++ b/euclid.cpp @@ -46,10 +46,12 @@ EX namespace euc { switch(g) { case gCubeTiling: + case gMengerSponge: shifttable = { +D0, +D1, +D2 }; break; case gRhombic3: + case gSierpinskiTet: shifttable = { D0+D1, D0+D2, D1+D2, D1-D2, D0-D2, D0-D1 }; break; @@ -58,10 +60,13 @@ EX namespace euc { break; case gEuclid: + case gSierpinski3: + case gSixFlake: shifttable = { D0, D1, D1-D0, -D0, -D1, D0-D1 }; break; case gEuclidSquare: + case gSierpinski4: shifttable = { D0, D1, -D0, -D1 }; break; diff --git a/geom-exp.cpp b/geom-exp.cpp index d2c6a145..e93ef5bd 100644 --- a/geom-exp.cpp +++ b/geom-exp.cpp @@ -346,6 +346,8 @@ void set_or_configure_geometry(eGeometry g) { /** is g2 the same tiling as the current geometry (geometry)? */ bool same_tiling(eGeometry g2) { + /* no quotients for fractals */ + if(cgflags & qFRACTAL) return g2 == geometry; if(g2 == gCrystal) return S3 == 4; if(g2 == gFieldQuotient && (hyperbolic || (geometry == gCubeTiling && reg3::cubes_reg3)) && standard_tiling()) @@ -602,7 +604,7 @@ EX void select_quotient_screen() { } EX void select_quotient() { - if(meuclid && !aperiodic && !arcm::in() && !reg3::cubes_reg3) { + if(meuclid && !aperiodic && !arcm::in() && !reg3::cubes_reg3 && !(cgflags & qFRACTAL)) { euc::prepare_torus3(); pushScreen(euc::show_torus3); } @@ -1029,6 +1031,8 @@ EX void showEuclideanMenu() { menuitem_nilwidth('v'); } else if((WDIM == 3 || aperiodic || arb::in()) && !reg3::in() && geometry != gCubeTiling) dialog::addBreak(100); + else if(cgclass && qFRACTAL) + dialog::addBreak(100); else menuitem_change_variation('v'); diff --git a/graph.cpp b/graph.cpp index a2eafc55..7253b783 100644 --- a/graph.cpp +++ b/graph.cpp @@ -3970,7 +3970,7 @@ EX int colorhash(color_t i) { EX bool isWall3(cell *c, color_t& wcol) { if(isWall(c)) return true; - if(c->wall == waChasm && c->land == laMemory) { wcol = 0x606000; return true; } + if(c->wall == waChasm && c->land == laMemory && (anyshiftclick || !(cgflags & qFRACTAL))) { wcol = 0x606000; return true; } if(c->wall == waInvisibleFloor) return false; // if(chasmgraph(c)) return true; if(among(c->wall, waMirror, waCloud, waMineUnknown, waMineMine)) return true; diff --git a/help.cpp b/help.cpp index 46675783..fcdeb014 100644 --- a/help.cpp +++ b/help.cpp @@ -240,7 +240,7 @@ EX void buildCredits() { "Kojiguchi Kazuki, baconcow, Alan, SurelyYouJest, hotdogPi, DivisionByZero, xXxWeedGokuxXx, jpystynen, Dmitry Marakasov, Alexandre Moine, Arthur O'Dwyer, " "Triple_Agent_AAA, bluetailedgnat, Allalinor, Shitford, KittyTac, Christopher King, KosGD, TravelDemon, Bubbles, rdococ, frozenlake, MagmaMcFry, " "Snakebird Priestess, roaringdragon2, Stopping Dog, bengineer8, Sir Light IJIJ, ShadeBlade, Saplou, shnourok, Ralith, madasa, 6% remaining, Chimera245, Remik Pi, alien foxcat thing, " - "Piotr Grochowski, Ann, still-flow, tyzone, Paradoxica, LottieRatWorld, aismallard, albatross, EncodedSpirit, Jacob Mandelson, CrashTuvai, cvoight, jennlbw, Kali Ranya" + "Piotr Grochowski, Ann, still-flow, tyzone, Paradoxica, LottieRatWorld, aismallard, albatross, EncodedSpirit, Jacob Mandelson, CrashTuvai, cvoight, jennlbw, Kali Ranya, spiritbackup" ); #ifdef EXTRALICENSE help += EXTRALICENSE; diff --git a/landgen.cpp b/landgen.cpp index 7f9a6c9c..ef01f1f5 100644 --- a/landgen.cpp +++ b/landgen.cpp @@ -2226,7 +2226,7 @@ EX void giantLandSwitch(cell *c, int d, cell *from) { int hardchance = items[itRuby] + yendor::hardness(); if(hardchance > 25) hardchance = 25; bool hardivy = hrand(100) < hardchance; - if(hat::in() ? buildIvy(c, 0, 4) : (hardivy ? buildIvy(c, 1, 9) : buildIvy(c, 0, c->type)) && !peace::on) + if((cgflags & qFRACTAL) ? buildIvy(c, 0, 2) : hat::in() ? buildIvy(c, 0, 4) : (hardivy ? buildIvy(c, 1, 9) : buildIvy(c, 0, c->type)) && !peace::on) c->item = itRuby; } } @@ -2826,7 +2826,9 @@ EX void setland_randomwalk(cell *c) { if(c->land) return; if(hrand(10) == 0) setland(c, currentlands[hrand(isize(currentlands))]); else { - cell *c2 = c->cmove(hrand(c->type)); + cell *c2 = nullptr; + for(int i=0; i<10 && !(c2 && (!disksize || is_in_disk(c2)) && is_in_fractal(c2)); i++) + c2 = c->cmove(hrand(c->type)); setland_randomwalk(c2); c->land = c2->land; } @@ -2836,6 +2838,11 @@ EX eLand random_land() { return hrand_elt(isize(cheatdest_list) ? cheatdest_list : currentlands); } +EX void share_land(cell *c, cell *c2) { + if(!c2->land) setland(c2, random_land()); + c->land = c2->land; + } + EX void set_land_for_geometry(cell *c) { if(!c->land && isize(currentlands)) { @@ -2844,15 +2851,17 @@ EX void set_land_for_geometry(cell *c) { return; } /* note: Nil patched chaos done in setLandNil */ - if(ls::patched_chaos() && stdeuc) { + if(ls::patched_chaos() && (cgflags & qFRACTAL)) { + share_land(c, fractal_rep(c)); + } + else if(ls::patched_chaos() && stdeuc) { cell *c2 = c; while(true) { forCellCM(c3, c2) if(cdist50(c3) < cdist50(c2)) { c2 = c3; goto again; } break; again: ; } - if(!c2->land) setland(c2, random_land()); - c->land = c2->land; + share_land(c, c2); return; } if(ls::patched_chaos() && aperiodic) { @@ -2861,8 +2870,7 @@ EX void set_land_for_geometry(cell *c) { c2 = c->master->cmove(0)->cmove(0)->cmove(1)->cmove(1)->c7; else c2 = c->master->cmove(0)->cmove(0)->cmove(0)->cmove(0)->cmove(0)->cmove(1)->cmove(1)->cmove(1)->cmove(1)->cmove(1)->c7; - if(!c2->land) setland(c2, random_land()); - c->land = c2->land; + share_land(c, c2); return; } if(land_structure == lsChaosRW) { @@ -3058,7 +3066,7 @@ EX void setdist(cell *c, int d, cell *from) { } } - if(disksize && !is_in_disk(c)) { + if((disksize && !is_in_disk(c)) || ((cgflags & qFRACTAL) && !is_in_fractal(c))) { setland(c, laMemory); if(!isMultitile(c)) c->monst = moNone; c->item = itNone; diff --git a/landlock.cpp b/landlock.cpp index 238cf591..9b218602 100644 --- a/landlock.cpp +++ b/landlock.cpp @@ -159,6 +159,8 @@ EX void fix_land_structure_choice() { land_structure = lsSingle; if(aperiodic && !among(land_structure, lsChaosRW, lsTotalChaos, lsPatchedChaos, lsSingle)) land_structure = lsPatchedChaos; + if((cgflags & qFRACTAL) && !among(land_structure, lsChaosRW, lsTotalChaos, lsPatchedChaos, lsSingle)) + land_structure = lsPatchedChaos; } EX bool landUnlockedRPM(eLand n) {