mirror of
				https://github.com/zenorogue/hyperrogue.git
				synced 2025-11-04 07:43:02 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			465 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			465 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
#include "rogueviz.h"
 | 
						|
 | 
						|
// used in: https://www.youtube.com/watch?v=H7NKhKTjHVE&feature=youtu.be
 | 
						|
// run: -analogs
 | 
						|
 | 
						|
namespace hr {
 | 
						|
 | 
						|
namespace analogs {
 | 
						|
 | 
						|
patterns::ePattern wp;
 | 
						|
flagtype wpf;
 | 
						|
 | 
						|
texture::texture_data earth;
 | 
						|
 | 
						|
bool loaded;
 | 
						|
 | 
						|
bool textured = true;
 | 
						|
 | 
						|
basic_textureinfo tv;
 | 
						|
 | 
						|
int earthpart;
 | 
						|
 | 
						|
string spherename = "A";
 | 
						|
string hypername = "B";
 | 
						|
 | 
						|
vector<reaction_t> models_to_use = {
 | 
						|
  [] {
 | 
						|
    if(sphere) {
 | 
						|
      pmodel = mdDisk;
 | 
						|
      pconf.alpha = 1000;
 | 
						|
      pconf.scale *= pconf.alpha;
 | 
						|
      View = cspin(1, 2, 20 * degree) * View;
 | 
						|
      }
 | 
						|
    else {
 | 
						|
      pmodel = mdHyperboloid;
 | 
						|
      pconf.top_z = 4;
 | 
						|
      pconf.ballangle = -20;
 | 
						|
      pconf.scale = .75;
 | 
						|
      }
 | 
						|
    spherename = "Euclidean sphere";
 | 
						|
    hypername = "Minkowski hyperboloid";
 | 
						|
    },
 | 
						|
  [] {
 | 
						|
    pmodel = mdDisk;
 | 
						|
    pconf.alpha = 1;
 | 
						|
    pconf.scale = .9;
 | 
						|
    spherename = "stereographic projection";
 | 
						|
    if(sphere) pconf.scale *= .75;
 | 
						|
    hypername = "Poincaré disk model";    
 | 
						|
    },
 | 
						|
  [] {
 | 
						|
    pmodel = mdDisk;
 | 
						|
    pconf.alpha = 0;
 | 
						|
    pconf.scale = sphere ? 0.25 : .9;
 | 
						|
    spherename = "gnomonic projection";
 | 
						|
    hypername = "Beltrami-Klein disk model";    
 | 
						|
    },
 | 
						|
  [] {
 | 
						|
    pmodel = mdDisk;
 | 
						|
    pconf.alpha = 1000;
 | 
						|
    pconf.scale *= pconf.alpha;
 | 
						|
    if(hyperbolic) pconf.scale *= .25;
 | 
						|
    spherename = "orthographic projection";
 | 
						|
    hypername = "Gans model";
 | 
						|
    },
 | 
						|
  [] {
 | 
						|
    pmodel = mdEquidistant;
 | 
						|
    spherename = "azimuthal equidistant";
 | 
						|
    },
 | 
						|
  [] {
 | 
						|
    pmodel = mdEquiarea;
 | 
						|
    spherename = "azimuthal equi-area";
 | 
						|
    }, 
 | 
						|
  [] {
 | 
						|
    pmodel = mdBandEquidistant;
 | 
						|
    pconf.scale = .5;
 | 
						|
    if(sphere) pconf.scale *= 2;
 | 
						|
    spherename = "equirectangular projection";
 | 
						|
    hypername = "Lobachevsky coordinates";
 | 
						|
    },
 | 
						|
  [] {
 | 
						|
    pmodel = mdBand;
 | 
						|
    pconf.scale = .5;
 | 
						|
    if(sphere) pconf.scale *= 0.9;
 | 
						|
    spherename = "Mercator projection";
 | 
						|
    hypername = "band model";
 | 
						|
    },
 | 
						|
  [] {
 | 
						|
    pmodel = mdBandEquiarea;
 | 
						|
    pconf.scale = .5;
 | 
						|
    pconf.stretch = M_PI;
 | 
						|
    spherename = "cylindrical equal-area";
 | 
						|
    },
 | 
						|
  [] {
 | 
						|
    pmodel = mdCentralCyl;
 | 
						|
    pconf.scale = .5;
 | 
						|
    spherename = "central cylindrical";
 | 
						|
    },
 | 
						|
  [] {
 | 
						|
    pmodel = mdGallStereographic;
 | 
						|
    pconf.scale = .5;
 | 
						|
    pconf.scale *= 1.8;
 | 
						|
    spherename = "Gall stereographic";
 | 
						|
    },
 | 
						|
  [] {
 | 
						|
    pmodel = mdMiller;
 | 
						|
    pconf.scale = .5;
 | 
						|
    spherename = "Miller cylindrical";
 | 
						|
    },
 | 
						|
  [] {
 | 
						|
    pmodel = mdLoximuthal;
 | 
						|
    pconf.scale = .5;
 | 
						|
    if(sphere) pconf.scale *= 2;
 | 
						|
    spherename = "loximuthal projection";
 | 
						|
    pconf.loximuthal_parameter = 15 * degree;
 | 
						|
    },
 | 
						|
  [] {
 | 
						|
    pmodel = mdSinusoidal;
 | 
						|
    pconf.scale = .5;
 | 
						|
    if(sphere) pconf.scale *= 1.5;
 | 
						|
    spherename = "(co)sinusoidal projection";
 | 
						|
    },
 | 
						|
  [] {
 | 
						|
    pmodel = mdMollweide;
 | 
						|
    pconf.scale = .5;
 | 
						|
    if(sphere) pconf.scale *= 2;
 | 
						|
    spherename = "Mollweide projection";
 | 
						|
    },
 | 
						|
  [] {
 | 
						|
    pmodel = mdCollignon;
 | 
						|
    pconf.scale = .5;
 | 
						|
    if(sphere) pconf.scale *= 2;
 | 
						|
    spherename = "Collignon projection";
 | 
						|
    },
 | 
						|
  [] {
 | 
						|
    pmodel = mdTwoPoint;
 | 
						|
    pconf.scale = .5;
 | 
						|
    if(sphere) pconf.scale *= 2;
 | 
						|
    spherename = "two-point equidistant";
 | 
						|
    },
 | 
						|
  [] {
 | 
						|
    pmodel = mdSimulatedPerspective;
 | 
						|
    pconf.scale = .5;
 | 
						|
    spherename = "two-point azimuthal";
 | 
						|
    },
 | 
						|
  [] {
 | 
						|
    pmodel = mdAitoff;
 | 
						|
    pconf.scale = .5;
 | 
						|
    if(sphere) pconf.scale *= 2;
 | 
						|
    spherename = "Aitoff projection";
 | 
						|
    },
 | 
						|
  [] {
 | 
						|
    pmodel = mdHammer;
 | 
						|
    pconf.scale = .5;
 | 
						|
    if(sphere) pconf.scale *= 1.3;
 | 
						|
    spherename = "Hammer projection";
 | 
						|
    },
 | 
						|
  [] {
 | 
						|
    pmodel = mdWinkelTripel;
 | 
						|
    pconf.scale = .5;
 | 
						|
    if(sphere) pconf.scale *= 2;
 | 
						|
    spherename = "Winkel tripel projection";
 | 
						|
    },
 | 
						|
  [] {
 | 
						|
    pmodel = mdWerner;
 | 
						|
    pconf.scale = .3;
 | 
						|
    spherename = "Werner projection";
 | 
						|
    },
 | 
						|
  };
 | 
						|
 | 
						|
ld prec = 5;
 | 
						|
 | 
						|
void draw_earth() {
 | 
						|
  if(textured && !loaded) {
 | 
						|
    earth.twidth = earth.theight = 0; earth.stretched = true;
 | 
						|
    // earth.readtexture("extra/to-earth.png");
 | 
						|
    earth.readtexture(file_exists("textures/earth.png") ? "textures/earth.png" : "textures/earth320.png");
 | 
						|
    earth.loadTextureGL();
 | 
						|
    loaded = true;
 | 
						|
    }
 | 
						|
 | 
						|
  auto tform = [] (hyperpoint euc) {
 | 
						|
    return xpush(euc[0] * degree) * ypush(euc[1] * degree) * C0;
 | 
						|
    };  
 | 
						|
  
 | 
						|
  if(sphere && textured) {
 | 
						|
  
 | 
						|
  shiftmatrix S = ggmatrix(currentmap->gamestart());
 | 
						|
  tv.tvertices.clear();
 | 
						|
  
 | 
						|
  if(prec < .5) prec = 1;
 | 
						|
  
 | 
						|
  for(ld x=-180; x<180; x+=prec)
 | 
						|
  for(ld y=-90; y<90; y+=prec) {
 | 
						|
  
 | 
						|
    vector<hyperpoint> bases = {
 | 
						|
      point31(x, y, 0), 
 | 
						|
      point31(x+prec, y, 0), 
 | 
						|
      point31(x, y+prec, 0), 
 | 
						|
      point31(x+prec, y, 0), 
 | 
						|
      point31(x, y+prec, 0), 
 | 
						|
      point31(x+prec, y+prec, 0)
 | 
						|
      };
 | 
						|
    
 | 
						|
    bool ok = true;
 | 
						|
 | 
						|
    if(pmodel == mdSimulatedPerspective) for(hyperpoint base: bases) {
 | 
						|
      hyperpoint h = S.T * tform(base);
 | 
						|
      if(h[2] <= 0.1) ok = false;
 | 
						|
      }
 | 
						|
      
 | 
						|
    if(among(pmodel, mdEquidistant, mdEquiarea)) for(hyperpoint base: bases) {
 | 
						|
      hyperpoint h = S.T * tform(base);
 | 
						|
      if(h[2] <= -0.999) ok = false;
 | 
						|
      }
 | 
						|
      
 | 
						|
    if(ok) for(auto base: bases) {
 | 
						|
      hyperpoint h = base;
 | 
						|
      hyperpoint h1 = tform(h);
 | 
						|
      curvepoint(h1);
 | 
						|
      
 | 
						|
      hyperpoint vi = point31((h[0] + 180) / 360., (h[1] + 90) / 180., 0);
 | 
						|
      tv.tvertices.push_back(glhr::pointtogl(vi));
 | 
						|
 | 
						|
      color_t col = earth.get_texture_pixel(vi[0]*earth.twidth, vi[1]*earth.theight);
 | 
						|
      col &= 0xFFFFFF;
 | 
						|
      addaura(S*h1, col, 0);
 | 
						|
      }
 | 
						|
    }
 | 
						|
    
 | 
						|
  if(isize(tv.tvertices)) {
 | 
						|
    color_t full = 0xFFFFFFFF;
 | 
						|
    part(full, 0) = earthpart;
 | 
						|
      
 | 
						|
    auto& poly = queuecurve(ggmatrix(currentmap->gamestart()), 0, full, PPR::LINE);
 | 
						|
    poly.flags |= POLY_TRIANGLES;
 | 
						|
    poly.tinf = &tv;
 | 
						|
    poly.offset_texture = 0;
 | 
						|
    tv.texture_id = earth.textureid;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  }
 | 
						|
 | 
						|
bool cycle_models = false;
 | 
						|
 | 
						|
bool animate = true;
 | 
						|
 | 
						|
EX void compare() {
 | 
						|
  if(!animate) return;
 | 
						|
  centerover = cwt.at;
 | 
						|
  spherename = "";
 | 
						|
  hypername = "";
 | 
						|
  ld t = ticks * 1. / anims::period;
 | 
						|
  t = frac(t);  
 | 
						|
  
 | 
						|
  if(cycle_models) {
 | 
						|
    int mtu = isize(models_to_use);
 | 
						|
    t *= mtu;
 | 
						|
    if(anims::period < 100) {
 | 
						|
      t = ticks % mtu;
 | 
						|
      println(hlog, "t = ", t);
 | 
						|
      }
 | 
						|
    int index = t;
 | 
						|
    t = frac(t);
 | 
						|
    pconf.alpha = 1;
 | 
						|
    pconf.scale = .9;
 | 
						|
    pconf.stretch = 1;
 | 
						|
    models_to_use[index]();
 | 
						|
    }
 | 
						|
  View = Id;
 | 
						|
  ld t4 = t * 5;
 | 
						|
  int at4 = t4;
 | 
						|
  t4 -= at4;
 | 
						|
  t4 = t4 * t4 * (3 - 2 * t4);
 | 
						|
  earthpart = 192;
 | 
						|
  if(at4 == 0)
 | 
						|
    earthpart = lerp(255, earthpart, t4);
 | 
						|
  else if(at4 == 1)
 | 
						|
    View = spin(t4 * 180 * degree) * View;
 | 
						|
  else if(at4 == 2)
 | 
						|
    View = xpush(t4 * M_PI) * spin(M_PI) * View;
 | 
						|
  else if(at4 == 3)
 | 
						|
    View = ypush(t4 * M_PI) * xpush(M_PI) * spin(M_PI) * View;
 | 
						|
  else if(at4 == 4) {
 | 
						|
    View = ypush(M_PI) * xpush(M_PI) * spin(M_PI) * View;
 | 
						|
    earthpart = lerp(255, earthpart, 1-t4);
 | 
						|
    }
 | 
						|
  anims::moved();
 | 
						|
  no_find_player = true;
 | 
						|
  vid.cells_drawn_limit = 20000;
 | 
						|
  pconf.twopoint_param = 0.5;
 | 
						|
  neon_nofill = true;
 | 
						|
  if(hyperbolic && pmodel == mdWerner)
 | 
						|
    neon_mode = eNeon::neon;
 | 
						|
  else
 | 
						|
    neon_mode = eNeon::none;
 | 
						|
  vid.smart_area_based = (pmodel != mdHyperboloid);
 | 
						|
  
 | 
						|
  if(!inHighQual) {
 | 
						|
    vid.cells_drawn_limit = 1000;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
EX bool ourStats() {
 | 
						|
  draw_centerover = false;
 | 
						|
 | 
						|
  displayfr(10, 10 + 2 * vid.fsize, 2, vid.fsize * 2, spherename, 0xFFFFFF, 0);
 | 
						|
 | 
						|
  displayfr(vid.xres - 10, vid.yres - (10 + 2 * vid.fsize), 2, vid.fsize * 2, hypername, 0xFFFFFF, 16);
 | 
						|
  
 | 
						|
  nohelp = true;
 | 
						|
  nomenukey = true;
 | 
						|
  clearMessages();
 | 
						|
 | 
						|
  glflush();
 | 
						|
  
 | 
						|
  hide_hud = false;
 | 
						|
 | 
						|
  return true;
 | 
						|
  }
 | 
						|
 | 
						|
bool restrict_cell(cell *c, const shiftmatrix& V) {
 | 
						|
  if(hyperbolic && zebra40(c) >= 40)
 | 
						|
    c->landparam = 0x0080C0;
 | 
						|
  if(hyperbolic && pmodel == mdWerner && hdist0(V.T * C0) > 3)
 | 
						|
    return true;
 | 
						|
  return false;
 | 
						|
  }
 | 
						|
 | 
						|
int current_index = -1;
 | 
						|
 | 
						|
void choose_projection() {
 | 
						|
  cmode = sm::SIDE | sm::MAYDARK;
 | 
						|
  gamescreen(0);
 | 
						|
  dialog::init(XLAT("choose projection"), 0xFFFFFFFF, 150, 0);
 | 
						|
  for(int i=0; i<isize(models_to_use); i++) {
 | 
						|
    hypername = "";
 | 
						|
    if(1) {
 | 
						|
      dynamicval<projection_configuration> vp(pconf, pconf);
 | 
						|
      dynamicval<transmatrix> v(View, View);
 | 
						|
      models_to_use[i]();
 | 
						|
      }
 | 
						|
    dialog::addBoolItem(hypername == "" ? spherename : spherename + " / " + hypername, i == current_index, 'a'+i);
 | 
						|
    dialog::add_action([i] { 
 | 
						|
      current_index = i;
 | 
						|
      dual::switch_to(0);
 | 
						|
      models_to_use[i]();
 | 
						|
      dual::switch_to(1);
 | 
						|
      models_to_use[i]();
 | 
						|
      });
 | 
						|
    }
 | 
						|
  dialog::addBack();
 | 
						|
  dialog::display();
 | 
						|
  }
 | 
						|
 | 
						|
void show() {
 | 
						|
  cmode = sm::SIDE | sm::MAYDARK;
 | 
						|
  gamescreen(0);
 | 
						|
  dialog::init(XLAT("hyperbolic analogs"), 0xFFFFFFFF, 150, 0);
 | 
						|
  add_edit(prec);
 | 
						|
  dialog::addItem("choose a projection", 'p');
 | 
						|
  dialog::add_action_push(choose_projection);
 | 
						|
  add_edit(earthpart);
 | 
						|
  add_edit(textured);
 | 
						|
  add_edit(animate);
 | 
						|
  add_edit(cycle_models);
 | 
						|
  
 | 
						|
  dialog::addBack();
 | 
						|
  dialog::display();
 | 
						|
  }
 | 
						|
 | 
						|
void enable() {
 | 
						|
  using rogueviz::rv_hook;
 | 
						|
  
 | 
						|
  vid.linequality = 4;
 | 
						|
  enable_canvas();
 | 
						|
  patterns::whichCanvas = 'F';
 | 
						|
 | 
						|
  colortables['F'][0] = 0x80C080;
 | 
						|
  colortables['F'][1] = 0x80A080;
 | 
						|
  pconf.scale = .3;
 | 
						|
  
 | 
						|
  vid.use_smart_range = 2;
 | 
						|
  vid.smart_range_detail = 2;
 | 
						|
  
 | 
						|
  mapeditor::drawplayer = false;
 | 
						|
  showstartmenu = false;
 | 
						|
  
 | 
						|
  dual::enable();
 | 
						|
 | 
						|
  dual::switch_to(0);
 | 
						|
  set_geometry(gSphere);
 | 
						|
  set_variation(eVariation::pure);
 | 
						|
  enable_canvas();
 | 
						|
 | 
						|
  dual::switch_to(1);
 | 
						|
  set_geometry(gNormal);
 | 
						|
  set_variation(eVariation::pure);
 | 
						|
  enable_canvas();
 | 
						|
 | 
						|
  rv_hook(hooks_frame, 100, draw_earth);
 | 
						|
  rv_hook(hooks_drawcell, 100, restrict_cell);
 | 
						|
  rv_hook(anims::hooks_anim, 100, compare);
 | 
						|
  rv_hook(hooks_prestats, 100, ourStats);
 | 
						|
  rv_hook(hooks_o_key, 80, [] (o_funcs& v) { v.push_back(named_dialog("hyperbolic analogs", show)); });
 | 
						|
  }
 | 
						|
 | 
						|
auto msc = arg::add3("-analogs", enable)
 | 
						|
  + addHook(hooks_configfile, 100, [] {
 | 
						|
    param_f(prec, "analogs_precision")
 | 
						|
    ->editable(0, 30, .5, "precision", "larger values are less precise", 'p');
 | 
						|
    param_b(animate, "analogs_animate")
 | 
						|
    ->editable("animate", 'a');
 | 
						|
    param_b(cycle_models, "analogs_cycle")
 | 
						|
    ->editable("cycle models in the animation", 'm');
 | 
						|
    param_i(earthpart, "earthpart")
 | 
						|
    ->editable(0, 255, 15, "Earth transparency", "", 't');
 | 
						|
    param_b(textured, "analogs_texture")
 | 
						|
    ->editable("draw Earth", 'T');
 | 
						|
    })
 | 
						|
  + addHook_rvslides(131, [] (string s, vector<tour::slide>& v) {
 | 
						|
      if(s != "mixed") return;
 | 
						|
      using namespace tour;
 | 
						|
 | 
						|
      v.push_back(slide{
 | 
						|
        "projections/Earth and the hyperbolic plane", 10, LEGAL::NONE | QUICKGEO,
 | 
						|
 | 
						|
        "Cartographers need to project the surface of Earth to a flat paper. However, since the surface of Earth is curved, there is no perfect way to do this. "
 | 
						|
        "Some projections will be conformal (map angles and small shapes faithfully), equidistant (map distances along SOME lines faithfully), equal-area (map areas proportionally), etc., "
 | 
						|
        "but no map will be all at once. Cartographers use many projections.\n\n"
 | 
						|
        
 | 
						|
        "We need these projections because the Earth has positive curvature, while the paper has no curvature. Interestingly, "
 | 
						|
        "most of the popular projections can be generalized, as projections from surface of curvature any K1 to surfaces of any curvature K2!\n\n"
 | 
						|
        
 | 
						|
        "This slide focuses on the hyperbolic analogs of popular spherical projections (projecting H² to E²). "
 | 
						|
        "Press '5' to enable cycling between different projections, or 'o' for more options."
 | 
						|
        ,
 | 
						|
        [] (presmode mode) {
 | 
						|
          slide_url(mode, 'y', "YouTube link (with description)", "https://youtu.be/H7NKhKTjHVE");
 | 
						|
          slide_url(mode, 'm', "HyperRogue page about projections", "http://www.roguetemple.com/z/hyper/models.php");
 | 
						|
          setCanvas(mode, '0');
 | 
						|
          if(mode == pmStart) {
 | 
						|
            enable();
 | 
						|
            start_game();
 | 
						|
            slide_backup(cycle_models);
 | 
						|
            slide_backup(anims::period);
 | 
						|
            }
 | 
						|
          if(mode == pmStop) {
 | 
						|
            dual::disable();
 | 
						|
            start_game();
 | 
						|
            }
 | 
						|
          if(mode == pmKey) {
 | 
						|
            cycle_models = !cycle_models;
 | 
						|
            if(cycle_models) anims::period *= 12;
 | 
						|
            else anims::period /= 12;
 | 
						|
            }
 | 
						|
          }});
 | 
						|
      
 | 
						|
      callhooks(rogueviz::pres::hooks_build_rvtour, "projections", v);
 | 
						|
      });
 | 
						|
 | 
						|
}
 | 
						|
}
 | 
						|
 |