hyperrogue/rogueviz/planets.cpp

549 lines
14 KiB
C++

#include "rogueviz.h"
namespace hr {
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");
setCanvas(mode, '0');
if(mode == pmStart) {
enable();
set_geometry(gSphere);
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);
}});
});
}
}