mirror of
				https://github.com/zenorogue/hyperrogue.git
				synced 2025-10-30 13:32:59 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			468 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			468 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "rogueviz.h"
 | |
| 
 | |
| // used in: https://www.youtube.com/watch?v=H7NKhKTjHVE&feature=youtu.be
 | |
| // run: -analogs
 | |
| 
 | |
| namespace hr {
 | |
| #if CAP_TEXTURE
 | |
| 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._deg) * View;
 | |
|       }
 | |
|     else {
 | |
|       pmodel = mdHyperboloid;
 | |
|       pconf.top_z = 4;
 | |
|       pconf.ball() = spin(-20._deg);
 | |
|       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._deg;
 | |
|     },
 | |
|   [] {
 | |
|     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 * M_PI) * View;
 | |
|   else if(at4 == 2)
 | |
|     View = xpush(t4 * M_PI) * spin180() * View;
 | |
|   else if(at4 == 3)
 | |
|     View = ypush(t4 * M_PI) * xpush(M_PI) * spin180() * View;
 | |
|   else if(at4 == 4) {
 | |
|     View = ypush(M_PI) * xpush(M_PI) * spin180() * 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();
 | |
|   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();
 | |
|   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();
 | |
| 
 | |
|   ccolor::set_colors(ccolor::football, {0x80C080, 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");
 | |
|           setWhiteCanvas(mode);
 | |
|           if(mode == pmStart) {
 | |
|             slide_backup(mapeditor::drawplayer);
 | |
|             slide_backup(vid.use_smart_range);
 | |
|             slide_backup(vid.smart_range_detail);
 | |
|             slide_backup(vid.linequality);
 | |
|             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);
 | |
|       });
 | |
| 
 | |
| }
 | |
| #endif
 | |
| }
 | |
| 
 | 
