mirror of
				https://github.com/zenorogue/hyperrogue.git
				synced 2025-10-31 05:52:59 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			552 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			552 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "rogueviz.h"
 | |
| 
 | |
| namespace hr {
 | |
| 
 | |
| #if CAP_TEXTURE
 | |
| namespace planets {
 | |
| 
 | |
| texture::texture_data earth, neptune, mars, moon, sun;
 | |
| 
 | |
| map<void*, ld> radius;
 | |
| map<void*, string> pname;
 | |
| map<void*, char> key;
 | |
| 
 | |
| ld longit = 0, latit = 0;
 | |
| 
 | |
| bool loaded;
 | |
| 
 | |
| void load_planets() {
 | |
|   if(!loaded) {
 | |
|     earth.twidth = earth.theight = 0; earth.stretched = true;
 | |
|     if(file_exists("textures/earth.png"))
 | |
|       earth.readtexture("textures/earth.png");
 | |
|     else
 | |
|       earth.readtexture("textures/earth320.png");
 | |
|     earth.loadTextureGL();
 | |
|     radius[&earth] = 6371;
 | |
|     pname[&earth] = "Earth";
 | |
|     key[&earth] = 'e';
 | |
| 
 | |
|     neptune.twidth = neptune.theight = 0; neptune.stretched = true;
 | |
|     neptune.readtexture("textures/neptune.png");
 | |
|     neptune.loadTextureGL();
 | |
|     radius[&neptune] = 24622;
 | |
|     pname[&neptune] = "Neptune";
 | |
|     key[&neptune] = 'n';
 | |
| 
 | |
|     sun.twidth = sun.theight = 0; sun.stretched = true;
 | |
|     sun.readtexture("textures/sun.png");
 | |
|     sun.loadTextureGL();
 | |
|     radius[&sun] = 696340;
 | |
|     pname[&sun] = "Sun";
 | |
|     key[&sun] = 's';
 | |
| 
 | |
|     moon.twidth = moon.theight = 0; moon.stretched = true;
 | |
|     moon.readtexture("textures/moon.png");
 | |
|     moon.loadTextureGL();
 | |
|     radius[&moon] = 1371.1;
 | |
|     pname[&moon] = "Moon";
 | |
|     key[&moon] = 'm';
 | |
| 
 | |
|     mars.twidth = mars.theight = 0; mars.stretched = true;
 | |
|     mars.readtexture("textures/mars.png");
 | |
|     mars.loadTextureGL();
 | |
|     radius[&mars] = 3389.5;
 | |
|     pname[&mars] = "Mars";
 | |
|     key[&mars] = 'x';
 | |
| 
 | |
|     loaded = true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
| basic_textureinfo tv, tv1;
 | |
| 
 | |
| bool anim = false;
 | |
| 
 | |
| int index = 0;
 | |
| 
 | |
| string mname;
 | |
| 
 | |
| void rev_band_conformal(ld& x, ld& y) {
 | |
|   x /= 2; y /= 2;
 | |
|   y = asin(tanh(y));
 | |
|   }
 | |
| 
 | |
| template<class T> void reverse_band(hyperpoint h, hyperpoint& h1, const T& f) {
 | |
|   ld x = h[0] * M_PI;
 | |
|   ld y = h[1] * M_PI;
 | |
|   f(x, y);
 | |
|   h1 = xpush(x) * ypush(y) * C0;
 | |
|   }
 | |
| 
 | |
| void reverse_model(hyperpoint h, hyperpoint& h1, eModel md) {
 | |
|   switch(pmodel) {
 | |
|     case mdDisk:
 | |
|       h1 = perspective_to_space(h);
 | |
|       return;
 | |
| 
 | |
|     case mdEquidistant:
 | |
|       h[0] *= M_PI; h[1] *= M_PI;
 | |
|       h1 = direct_exp(h);
 | |
|       break;
 | |
| 
 | |
|     case mdEquiarea: {
 | |
| 
 | |
|       ld d = hypot_d(2, h);
 | |
|       // d = sqrt(2*(1 - cos(d0))) / 2;
 | |
|       ld d0 = acos(1 - pow(2 * d, 2) / 2);
 | |
| 
 | |
|       h[0] *= d0 / d; h[1] *= d0 / d;
 | |
|       h1 = direct_exp(h);
 | |
|       break;
 | |
|       }
 | |
|     
 | |
|     case mdBand: {
 | |
|       reverse_band(h, h1, rev_band_conformal);
 | |
|       break;
 | |
|       }
 | |
| 
 | |
|     case mdBandEquiarea: {
 | |
|       reverse_band(h, h1, [] (ld& x, ld& y) { y = asin_auto(y); });
 | |
|       break;
 | |
|       }
 | |
| 
 | |
|     case mdBandEquidistant: {
 | |
|       reverse_band(h, h1, [] (ld& x, ld& y) {});
 | |
|       break;
 | |
|       }
 | |
|     
 | |
|     case mdSinusoidal: {
 | |
|       reverse_band(h, h1, [] (ld& x, ld& y) { x /= cos_auto(y); });
 | |
|       break;
 | |
|       }
 | |
| 
 | |
|     default:
 | |
|       throw "unknown";
 | |
|     }
 | |
|   }
 | |
| 
 | |
| bool kill_off(hyperpoint h1, hyperpoint h2, hyperpoint& h3) {
 | |
|   if(pmodel == mdEquidistant) {
 | |
|     if(hypot_d(2, h2) > 1) {
 | |
|       h3 = point3(0, 0, -1); 
 | |
|       return true;
 | |
|       }
 | |
|     }
 | |
|   if(pmodel == mdEquiarea) {
 | |
|     if(hypot_d(2, h2) > 1) {
 | |
|       h3 = point3(0, 0, -1); 
 | |
|       return true;
 | |
|       }     
 | |
|     }
 | |
|   if(pmodel == mdDisk && pconf.alpha == 0) {
 | |
|     if(abs(h1[2]) < 1e-6) return true;
 | |
|     if(h1[2] < 0) h3 = -h3;
 | |
|     }
 | |
|   if(pmodel == mdDisk && pconf.alpha > 1) {
 | |
|     if(hypot_d(2, h2) > 1 / pconf.alpha) {
 | |
|       h2 = h2 * .999 / pconf.alpha / hypot_d(2, h2);
 | |
|       return true;
 | |
|       }
 | |
|     if(h1[2] < 0) h3[2] = -h3[2];
 | |
|     }
 | |
|   if(among(pmodel, mdBand, mdBandEquidistant, mdBandEquiarea)) {
 | |
|     if(abs(h1[0]) < 1e-4 && h1[2] < 0) return true;
 | |
|     if(pmodel == mdBand && (h2[0] < -2 || h2[0] > 2)) return true;
 | |
|     if(pmodel != mdBand && (h2[0] < -1 || h2[0] > 1)) return true;
 | |
|     if(pmodel == mdBandEquidistant && (h2[1] < -.5 || h2[1] > .5)) return true;
 | |
|     }
 | |
|   return false;
 | |
|   }
 | |
| 
 | |
| int bad;
 | |
| 
 | |
| void test_reverse() {
 | |
|   int ok = 0, killed = 0;
 | |
|   bad = 0;
 | |
|   for(int a=0; a<100; a++) {
 | |
|     hyperpoint h = random_spin3() * C0;
 | |
|     hyperpoint h1;
 | |
|     hyperpoint h2;
 | |
|     applymodel(shiftless(h), h1);
 | |
|     reverse_model(h1, h2, pmodel);
 | |
|     bool ko = kill_off(h, h1, h2);
 | |
|     if(hdist(h, h2) < 1e-5)
 | |
|       ok++;
 | |
|     else if(ko)
 | |
|       killed++;
 | |
|     else {
 | |
|       bad++;
 | |
|       println(hlog, h, " -> ", h1, " -> ", h2);
 | |
|       }
 | |
|     }
 | |
|   // println(hlog, "ok ", ok, " bad ", bad, " killed ", killed);
 | |
|   }
 | |
| 
 | |
| texture::texture_data *tgt_planet = &moon;
 | |
| texture::texture_data *src_planet = &earth;
 | |
| 
 | |
| int alpha = 255;
 | |
| 
 | |
| void pick_model() {
 | |
|   switch(index) {
 | |
|     case 0: 
 | |
|       mname = "azimuthal equidistant";
 | |
|       pmodel = mdEquidistant;
 | |
|       break;
 | |
| 
 | |
|     case 1: 
 | |
|       mname = "azimuthal equal area";
 | |
|       pmodel = mdEquiarea;
 | |
|       break;
 | |
|     
 | |
|     case 2:
 | |
|       mname = "gnomonic";
 | |
|       pmodel = mdDisk;
 | |
|       pconf.alpha = 0;
 | |
|       break;
 | |
| 
 | |
|     case 3:
 | |
|       mname = "stereographic";
 | |
|       pmodel = mdDisk;
 | |
|       pconf.alpha = 1;
 | |
|       break;
 | |
| 
 | |
|     case 4:
 | |
|       mname = "orthographic";
 | |
|       pmodel = mdDisk;
 | |
|       pconf.alpha = 999999;
 | |
|       break;
 | |
| 
 | |
|     case 5:
 | |
|       mname = "Mercator projection";
 | |
|       pmodel = mdBand;
 | |
|       break;
 | |
| 
 | |
|     case 6:
 | |
|       mname = "equirectangular projection";
 | |
|       pmodel = mdBandEquidistant;
 | |
|       break;
 | |
| 
 | |
|     case 7:
 | |
|       mname = "cylindrical equal area";
 | |
|       pmodel = mdBandEquiarea;
 | |
|       break;
 | |
|     }    
 | |
|   }
 | |
| 
 | |
| ld prec = 5;
 | |
| 
 | |
| void draw_earth() {
 | |
|   load_planets();
 | |
| 
 | |
|   shiftmatrix S = ggmatrix(currentmap->gamestart()) * spin90();
 | |
| 
 | |
|   ld mte = radius[src_planet] / radius[tgt_planet];
 | |
|   
 | |
|   if(true) {
 | |
|     tv1.tvertices.clear();
 | |
|     if(prec<0.5) prec=1;
 | |
|     for(int x=-180; x<180; x+=prec)
 | |
|     for(int 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)
 | |
|         };
 | |
|       
 | |
|       auto tform = [&] (hyperpoint euc) {
 | |
|         return xpush(euc[0] * degree) * ypush(euc[1] * degree) * C0;
 | |
|         };  
 | |
|       
 | |
|       for(int i=0; i<6; i++) {
 | |
|   
 | |
|         hyperpoint h = bases[i];
 | |
|         hyperpoint h1 = tform(h);
 | |
|         curvepoint(h1);
 | |
|         hyperpoint vi = point31((h[0] + 180) / 360., (h[1] + 90) / 180., 0);
 | |
|         tv1.tvertices.push_back(glhr::pointtogl(vi));
 | |
|   
 | |
|         color_t col = tgt_planet->get_texture_pixel(vi[0]*tgt_planet->twidth, vi[1]*tgt_planet->theight);
 | |
|         col &= 0xFFFFFF;
 | |
|         addaura(S*h1, col, 0);
 | |
|         }
 | |
|       }
 | |
|     
 | |
|     if(isize(tv1.tvertices)) {
 | |
|       color_t full = 0xFFFFFFFF;
 | |
|       part(full, 0) = 255;
 | |
|         
 | |
|       auto& poly = queuecurve(S, 0, full, PPR::LINE);
 | |
|       poly.flags |= POLY_TRIANGLES;
 | |
|       poly.tinf = &tv1;
 | |
|       poly.offset_texture = 0;
 | |
|       tv1.texture_id = tgt_planet->textureid;
 | |
|       }
 | |
|     }
 | |
|   
 | |
|   if(true) {
 | |
| 
 | |
|     tv.tvertices.clear();
 | |
|     
 | |
|     dynamicval<eModel> md(pmodel, pmodel);
 | |
|     dynamicval<ld> ma(pconf.alpha, pconf.alpha);
 | |
|     
 | |
|     pick_model();
 | |
|     
 | |
|     test_reverse(); 
 | |
|     
 | |
|     bool twoside = (pmodel == mdDisk && pconf.alpha > 100);
 | |
|     
 | |
|     for(int x=-180; x<180; x+=prec)
 | |
|     for(int y=-90; y<90; y+=prec) 
 | |
|     for(int si: {-1, 1}) {
 | |
|     
 | |
|       if(si == 1 && !twoside) continue;
 | |
|     
 | |
|       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)
 | |
|         };
 | |
|       
 | |
|       int errs = 0;
 | |
|     
 | |
|       for(int i=0; i<6; i+=3) {
 | |
|   
 | |
|         array<hyperpoint, 3> tforms;
 | |
|         
 | |
|         errs = 0;
 | |
|   
 | |
|         for(int j=0; j<3; j++) {
 | |
|           hyperpoint euc = bases[i+j];
 | |
|           hyperpoint h = xpush(euc[0] * degree) * ypush(euc[1] * degree) * C0;
 | |
| 
 | |
|           h = cspin(1, 2, latit*degree) * cspin(0, 2, -longit*degree) * h;
 | |
|           
 | |
|           hyperpoint h1, h2;
 | |
|           applymodel(shiftless(h), h1);
 | |
|           h1[0] *= mte;
 | |
|           h1[1] *= mte;
 | |
|           
 | |
|           if(twoside) {
 | |
|             if(h[2] * si < 0) errs++;
 | |
|             else if(hypot_d(2, h1) > 1 / pconf.alpha) {
 | |
|               h1 = h1 * .999999 / hypot_d(2, h1) / pconf.alpha;
 | |
|               errs++;
 | |
|               }
 | |
|             }
 | |
|           
 | |
|           reverse_model(h1, h2, pmodel);
 | |
|           
 | |
|           if(kill_off(h, h1, h2)) errs++;
 | |
|           
 | |
|           tforms[j] = h2;
 | |
|           }
 | |
|         
 | |
|         if(twoside && errs && mte < 1) continue;
 | |
|         
 | |
|         if(errs < 3) {
 | |
|           for(int j=0; j<3; j++) {
 | |
|             hyperpoint h = bases[i+j];
 | |
|             hyperpoint h1 = tforms[j];
 | |
|             curvepoint(h1);
 | |
|             hyperpoint vi = point31((h[0] + 180) / 360., (h[1] + 90) / 180., 0);
 | |
|             tv.tvertices.push_back(glhr::pointtogl(vi));
 | |
|   
 | |
|             color_t col = src_planet->get_texture_pixel(vi[0]*src_planet->twidth, vi[1]*src_planet->theight);
 | |
|             col &= 0xFFFFFF;
 | |
|             addaura(S*h1, col, 0);
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|       
 | |
|     if(isize(tv.tvertices)) {
 | |
|       color_t full = 0xFFFFFFFF;
 | |
|       part(full, 0) = alpha;
 | |
|         
 | |
|       auto& poly = queuecurve(S, 0, full, PPR::LINE);
 | |
|       poly.flags |= POLY_TRIANGLES;
 | |
|       poly.tinf = &tv;
 | |
|       poly.offset_texture = 0;
 | |
|       tv.texture_id = src_planet->textureid;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
| EX bool ourStats() {
 | |
|   draw_centerover = false;
 | |
| 
 | |
|   displayfr(10, 10 + 2 * vid.fsize, 2, vid.fsize * 2, mname, 0xFFFFFF, 0);
 | |
|   displayfr(vid.xres - 10, vid.yres - 10 - 2 * vid.fsize, 2, vid.fsize * 2, pname[src_planet] + " to " + pname[tgt_planet], 0xFFFFFF, 16);
 | |
| 
 | |
|   // nohelp = true;
 | |
|   // nomenukey = true;
 | |
|   clearMessages();
 | |
| 
 | |
|   glflush();
 | |
|   
 | |
|   // hide_hud = false;
 | |
| 
 | |
|   return true;
 | |
|   }
 | |
| 
 | |
| texture::texture_data* get_planet(char ch) {
 | |
|   if(ch == 'a') return &mars;
 | |
|   if(ch == 'm') return &moon;
 | |
|   if(ch == 's') return &sun;
 | |
|   if(ch == 'n') return &neptune;
 | |
|   if(ch == 'e') return &earth;
 | |
|   return nullptr;
 | |
|   }
 | |
| 
 | |
| ld max_alpha = 0;
 | |
| 
 | |
| EX void compare() {
 | |
|   if(max_alpha) {
 | |
|     ld t = ticks * 1. / anims::period;
 | |
|     t = t - floor(t);
 | |
|     static ld lrot = 0;
 | |
|     ld rot = 0;
 | |
|     ld dark = 1;
 | |
|     if(t > .1 && t < .9) {
 | |
|       rot = (t - .1) / .8;
 | |
|       rot = rot * rot * (3-2*rot);
 | |
|       }
 | |
|     else {
 | |
|       dark = t < .5 ? 10 * t : 10 * (1 - t);
 | |
|       dark = dark * dark * (3-2*dark);
 | |
|       }
 | |
|     alpha = dark * max_alpha;
 | |
|     View = cspin(0, 2, (rot - lrot) * TAU) * View;
 | |
|     lrot = rot;
 | |
|     anims::moved();
 | |
|     }
 | |
|   }
 | |
| 
 | |
| void choose_projection() {
 | |
|   cmode = sm::SIDE | sm::MAYDARK;
 | |
|   gamescreen();
 | |
|   dialog::init(XLAT("choose projection"), 0xFFFFFFFF, 150, 0);
 | |
|   dynamicval<int> di(index);
 | |
|   for(int i=0; i<8; i++) {
 | |
|     index = i;
 | |
|     dynamicval<eModel> md(pmodel, pmodel);
 | |
|     dynamicval<ld> ma(pconf.alpha, pconf.alpha);
 | |
|     pick_model();
 | |
|     dialog::addItem(mname, 'a'+i);
 | |
|     dialog::add_action([i] { index = i; popScreen(); });
 | |
|     }
 | |
|   dialog::addBack();
 | |
|   dialog::display();
 | |
|   }
 | |
| 
 | |
| void choose_planet(texture::texture_data *& t) {
 | |
|   cmode = sm::SIDE | sm::MAYDARK;
 | |
|   gamescreen();
 | |
|   dialog::init(XLAT("choose the planet"), 0xFFFFFFFF, 150, 0);
 | |
|   for(auto opt: {&earth, &moon, &sun, &mars, &neptune}) {
 | |
|     dialog::addSelItem(pname[opt], its(radius[opt]) + " km", key[opt]);
 | |
|     dialog::add_action([&t, opt] { t = opt; });
 | |
|     }
 | |
|   dialog::addBack();
 | |
|   dialog::display();  
 | |
|   }
 | |
| 
 | |
| void show() {
 | |
|   cmode = sm::SIDE | sm::MAYDARK;
 | |
|   gamescreen();
 | |
|   dialog::init(XLAT("projections between planets"), 0xFFFFFFFF, 150, 0);
 | |
|   add_edit(alpha);
 | |
|   add_edit(latit);
 | |
|   add_edit(longit);
 | |
|   add_edit(max_alpha);
 | |
|   add_edit(prec);
 | |
|   dialog::addSelItem("projection used", mname, 'p');
 | |
|   dialog::add_action_push(choose_projection);
 | |
|   dialog::addSelItem("source planet", pname[src_planet], 's');
 | |
|   dialog::add_action_push([] { choose_planet(src_planet); });
 | |
|   dialog::addSelItem("target planet", pname[tgt_planet], 't');
 | |
|   dialog::add_action_push([] { choose_planet(tgt_planet); });
 | |
|   
 | |
|   if(bad) dialog::addInfo("BAD", 0xFF0000);
 | |
|   dialog::addBack();
 | |
|   dialog::display();
 | |
|   }
 | |
| 
 | |
| void enable() {
 | |
|   using rogueviz::rv_hook;
 | |
|   rv_hook(hooks_frame, 100, draw_earth);
 | |
|   rv_hook(hooks_prestats, 100, ourStats);
 | |
|   rv_hook(hooks_o_key, 80, [] (o_funcs& v) { v.push_back(named_dialog("planet projections", show)); });
 | |
|   }
 | |
| 
 | |
| auto msc =   
 | |
|   arg::add3("-moon", enable)
 | |
|   + arg::add3("-ebody", [] {
 | |
|     string s = arg::shift_args();
 | |
|     src_planet = get_planet(s[0]);
 | |
|     tgt_planet = get_planet(s[1]);
 | |
|     if(!src_planet) src_planet = &earth;
 | |
|     if(!tgt_planet) tgt_planet = &moon;
 | |
|     })
 | |
|   + addHook(anims::hooks_anim, 100, compare)
 | |
|   + addHook(hooks_configfile, 100, [] {
 | |
|     param_i(alpha, "moon_alpha")
 | |
|     ->editable(0, 255, 15, "alpha (transparency)", "", 'a');
 | |
|     param_i(index, "moon_index")
 | |
|     ->editable(0, 7, 1, "projection index", "", 'i');
 | |
|     param_f(latit, "moon_lat")
 | |
|     ->editable(0, 7, 1, "latitude of the reference point", "", 'l');
 | |
|     param_f(longit, "moon_long")
 | |
|     ->editable(0, 7, 1, "longitude of the reference point", "", 'L');
 | |
|     param_f(max_alpha, "moon_max_alpha")
 | |
|     ->editable(0, 255, 15, "animation alpha max", "", 'm');
 | |
|     param_f(prec, "moon_precision")
 | |
|     ->editable(0, 30, .5, "precision", "larger values are less precise", 'p');
 | |
|     })
 | |
|   + addHook_rvslides(51, [] (string s, vector<tour::slide>& v) {
 | |
|       if(s != "projections") return;
 | |
|       using namespace tour;
 | |
| 
 | |
|       v.push_back(slide{
 | |
|         "projections/sphere to sphere", 10, LEGAL::NONE | QUICKGEO,
 | |
| 
 | |
|         "We can also project a sphere to a sphere of different curvature. For example, what about the azimuthal equidistant projection from Earth to Moon? "
 | |
|         "This projection correctly maps the angles and distances from a chosen point at Earth. "
 | |
|         "Press '5' to use the place on Earth you are in as the chosen point, try other projections, or change the other settings!"
 | |
|         ,
 | |
|         [] (presmode mode) {
 | |
|           slide_url(mode, 't', "Twitter link (with description)", "https://twitter.com/ZenoRogue/status/1339946298460483589");
 | |
|           setPlainCanvas(mode, [] {
 | |
|             set_geometry(gSphere);
 | |
|             });
 | |
|           
 | |
|           if(mode == pmStart) {
 | |
|             enable();
 | |
|             slide_backup(canvas_default_wall, waInvisibleFloor);
 | |
|             slide_backup(pmodel, mdDisk);
 | |
|             slide_backup(pconf.scale, 1000);
 | |
|             slide_backup(pconf.alpha, 1000);
 | |
|             slide_backup(mapeditor::drawplayer, false);
 | |
|             start_game();
 | |
|             slide_backup(max_alpha, 192);
 | |
|             }
 | |
|           slidecommand = "options";
 | |
|           if(mode == tour::pmKey) pushScreen(show);
 | |
|           }});
 | |
|       });
 | |
| 
 | |
| }
 | |
| #endif
 | |
| }
 | |
| 
 | 
