diff --git a/classes.cpp b/classes.cpp index e9dcbee0..bb45f1fe 100644 --- a/classes.cpp +++ b/classes.cpp @@ -980,6 +980,8 @@ enum eModel : int { // 32..38 mdWerner, mdAitoff, mdHammer, mdLoximuthal, mdMiller, mdGallStereographic, mdWinkelTripel, // 39.. + mdPoorMan, mdPanini, mdRetroCraig, mdRetroLittrow, mdRetroHammer, + // 44.. mdGUARD, mdPixel, mdHyperboloidFlat, mdPolynomial, mdManual }; #endif @@ -1031,6 +1033,11 @@ EX vector mdinf = { {X3("Miller projection"), mf::euc_boring | mf::band, DEFAULTS}, // scale latitude 4/5 -> Mercator -> 5/4 {X3("Gall stereographic"), mf::euc_boring | mf::band, DEFAULTS}, // like central cylindrical but stereographic {X3("Winkel tripel"), mf::euc_boring | mf::broken, DEFAULTS}, // mean of equirec and Aitoff + {X3("Poor man's square"), mf::euc_boring, DEFAULTS}, // + {X3("Panini projection"), mf::euc_boring, DEFAULTS}, // + {X3("Craig retroazimuthal"), mf::euc_boring | mf::broken, DEFAULTS}, // retroazimuthal cylindrical + {X3("Littrow retroazimuthal"), mf::euc_boring | mf::broken, DEFAULTS}, // retroazimuthal conformal + {X3("Hammer retroazimuthal"), mf::euc_boring, DEFAULTS}, // retroazimuthal equidistant {X3("guard"), 0, DEFAULTS}, {X3("polynomial"), mf::conformal, DEFAULTS}, }; diff --git a/drawing.cpp b/drawing.cpp index a0bc789c..10903952 100644 --- a/drawing.cpp +++ b/drawing.cpp @@ -293,6 +293,8 @@ EX bool two_sided_model() { if(pmodel == mdHyperboloid) return !euclid; // if(pmodel == mdHemisphere) return true; if(pmodel == mdDisk) return sphere; + if(pmodel == mdRetroLittrow) return sphere; + if(pmodel == mdRetroHammer) return sphere; if(pmodel == mdHemisphere) return true; if(pmodel == mdRotatedHyperboles) return true; if(pmodel == mdSpiral && pconf.spiral_cone < 360) return true; @@ -305,6 +307,12 @@ EX int get_side(const hyperpoint& H) { double horizon = curnorm / pconf.alpha; return (H[2] <= -horizon) ? -1 : 1; } + if(pmodel == mdRetroLittrow && sphere) { + return H[2] >= 0 ? 1 : -1; + } + if(pmodel == mdRetroHammer && sphere) { + return H[2] >= 0 ? 1 : -1; + } if(pmodel == mdRotatedHyperboles) return H[1] > 0 ? -1 : 1; if(pmodel == mdHyperboloid && hyperbolic) diff --git a/geom-exp.cpp b/geom-exp.cpp index 7bc4af78..00b07650 100644 --- a/geom-exp.cpp +++ b/geom-exp.cpp @@ -384,7 +384,9 @@ void ge_select_tiling() { EX string current_proj_name() { bool h = hyperbolic || sn::in(); - if(vpconf.model != mdDisk) + if(vpconf.model == mdPanini && vpconf.alpha == 1) + return XLAT("stereographic Panini"); + else if(vpconf.model != mdDisk) return models::get_model_name(vpconf.model); else if(h && vpconf.alpha == 1) return XLAT("Poincaré model"); diff --git a/hypgraph.cpp b/hypgraph.cpp index b0c21f7e..083fec01 100644 --- a/hypgraph.cpp +++ b/hypgraph.cpp @@ -1074,6 +1074,76 @@ EX void apply_other_model(shiftpoint H_orig, hyperpoint& ret, eModel md) { } } + case mdRetroCraig: { + makeband(H_orig, ret, [] (ld& x, ld& y) { + if(x) + y = x / sin_auto(x) * (sin_auto(y) * cos_auto(x) - tan_auto(pconf.loximuthal_parameter) * cos_auto(y)); + else + y = sin_auto(y) - tan_auto(pconf.loximuthal_parameter) * cos_auto(y); + }); + break; + } + + case mdRetroLittrow: { + makeband(H_orig, ret, [] (ld& x, ld& y) { + tie(x, y) = make_pair( + sin_auto(x) / cos_auto(y), + cos_auto(x) * tan_auto(y) + ); + }); + break; + } + + case mdRetroHammer: { + ld d = hdist(H, ypush0(pconf.loximuthal_parameter)); + makeband(H_orig, ret, [d,H] (ld& x, ld& y) { + if(x == 0 && y == 0) return; + + if(x) + y = x / sin_auto(x) * (sin_auto(y) * cos_auto(x) - tan_auto(pconf.loximuthal_parameter) * cos_auto(y)); + else + y = sin_auto(y) - tan_auto(pconf.loximuthal_parameter) * cos_auto(y); + + ld scale = d / hypot(x, y); + if(H[2] < 0) scale = -scale; + x *= scale; + y *= scale; + }); + break; + } + + case mdPanini: { + ld proh = sqrt(H[2]*H[2] + curvature() * H[0] * H[0]); + H /= proh; + H /= (H[2] + pconf.alpha); + ret = H; + ret[2] = 0; ret[3] = 1; + break; + } + + case mdPoorMan: { + find_zlev(H); + H = space_to_perspective(H); + + models::apply_orientation_yz(H[1], H[2]); + models::apply_orientation(H[0], H[1]); + + ld u = H[0], v = H[1]; + if(abs(u) > 1e-3 && abs(v) > 1e-3) { + ld r2 = u*u+v*v; + ld scale = sqrt((-r2+sqrt(r2*(r2+4*u*u*v*v*(r2-2))))/(2*(r2-2))) / u / v; + if(u*v<0) scale = -scale; + H = scale * H; + } + ret = H; + ret[2] = 0; + ret[3] = 1; + + models::apply_orientation(ret[1], ret[0]); + models::apply_orientation_yz(ret[2], ret[1]); + break; + } + case mdGUARD: case mdManual: break; } diff --git a/models.cpp b/models.cpp index 5be00d96..71abf96c 100644 --- a/models.cpp +++ b/models.cpp @@ -200,6 +200,8 @@ EX namespace models { if(GDIM == 2 && pm == mdEquivolume) return false; if(GDIM == 3 && among(pm, mdBall, mdHyperboloid, mdFormula, mdPolygonal, mdRotatedHyperboles, mdSpiral, mdHemisphere)) return false; if(pm == mdCentralInversion && !euclid) return false; + if(pm == mdPoorMan) return hyperbolic; + if(pm == mdRetroHammer) return hyperbolic; return true; } @@ -442,7 +444,7 @@ EX namespace models { dialog::addBreak(50); } - if(among(vpmodel, mdDisk, mdBall, mdHyperboloid, mdRotatedHyperboles)) { + if(among(vpmodel, mdDisk, mdBall, mdHyperboloid, mdRotatedHyperboles, mdPanini)) { dialog::addSelItem(XLAT("projection distance"), fts(vpconf.alpha) + " (" + current_proj_name() + ")", 'p'); dialog::add_action(projectionDialog); } @@ -636,13 +638,17 @@ EX namespace models { }); } - if(vpmodel == mdLoximuthal) { + if(among(vpmodel, mdLoximuthal, mdRetroHammer, mdRetroCraig)) { dialog::addSelItem(XLAT("parameter"), fts(vpconf.loximuthal_parameter), 'b'); - dialog::add_action([](){ + dialog::add_action([vpmodel](){ dialog::editNumber(vpconf.loximuthal_parameter, -M_PI/2, M_PI/2, .1, 0, XLAT("parameter"), + (vpmodel == mdLoximuthal ? "This model is similar to azimuthal equidistant, but based on loxodromes (lines of constant geographic direction) rather than geodesics. " "The loximuthal projection maps (the shortest) loxodromes to straight lines of the same length, going through the starting point. " - "This setting changes the latitude of the starting point." + "This setting changes the latitude of the starting point." : + "In retroazimuthal projections, a point is drawn at such a point that the azimuth *from* that point to the chosen central point is correct. " + "For example, if you should move east, the point is drawn to the right. This parameter is the latitude of the central point.") + + string(hyperbolic ? "\n\n(In hyperbolic geometry directions are assigned according to the Lobachevsky coordinates.)" : "") ); }); } @@ -827,7 +833,7 @@ EX namespace models { PHASEFROM(2); if(pmodel == mdCollignon) shift_arg_formula(vpconf.collignon_parameter); else if(pmodel == mdMiller) shift_arg_formula(vpconf.miller_parameter); - else if(pmodel == mdLoximuthal) shift_arg_formula(vpconf.loximuthal_parameter); + else if(among(pmodel, mdLoximuthal, mdRetroCraig, mdRetroHammer)) shift_arg_formula(vpconf.loximuthal_parameter); else if(among(pmodel, mdAitoff, mdHammer, mdWinkelTripel)) shift_arg_formula(vpconf.aitoff_parameter); if(pmodel == mdWinkelTripel) shift_arg_formula(vpconf.winkel_parameter); }