1
0
mirror of https://github.com/zenorogue/hyperrogue.git synced 2026-05-08 08:11:21 +00:00

mapeditor:: compass-and-ruler functionality

This commit is contained in:
Zeno Rogue
2026-05-02 10:06:13 +02:00
parent b99e3b4358
commit 71f7c52793
3 changed files with 404 additions and 8 deletions

View File

@@ -1497,6 +1497,16 @@ EX void initConfig() {
param_color(lp->color, "lpcolor-" + lp->lpname, true, lp->color);
param_f(lp->multiplier, "lpwidth-" + lp->lpname);
}
auto display_the_ruler = [] { rulers::active = true; dialog::get_di().dialogflags |= sm::DRAW; };
// ruler settings
param_color(rulers::ruler_color, "ruler_color", true)->editable("ruler color", "", 'C')
-> set_sets(display_the_ruler);
param_f(rulers::ruler_width, "ruler_width")->editable(0, 10, 0.1, "ruler width", "", 'W')
-> set_sets(display_the_ruler);
param_f(rulers::measuring_unit, "ruler_measure")->editable(0, 5, 0.1, "measuring unit", "A mark is displayed on the ruler, on every multiple of this distance, and a bigger one every 10 multiples. Set to 0 to disable.", 'M')
-> set_sets(display_the_ruler);
// special graphics

View File

@@ -1926,6 +1926,7 @@ namespace sm {
static constexpr flagtype MOUSEAIM = Flag(28); // mouse aiming active here
static constexpr flagtype DIALOG_OFFMAP = Flag(29); // try hard to keep dialogs off the map
static constexpr flagtype NO_EXIT = Flag(30); // do not allow to exit the current dialog
static constexpr flagtype EDIT_RULER = Flag(31); // in mapeditor::rulers::
}
#endif

View File

@@ -8,6 +8,371 @@
#include "hyper.h"
namespace hr {
EX namespace rulers {
EX bool active = false;
EX color_t ruler_color = 0x8080C030;
EX ld ruler_width = 4;
EX ld measuring_unit = 0.1;
struct ruler;
flagtype HYPER_ONLY = 1;
flagtype THREE_POINTS = 2;
struct rulertype {
char key;
string name;
flagtype flags;
hr::function<void(ruler&)> render;
hr::function<shiftpoint(ruler&, shiftpoint h)> snap;
};
extern rulertype linear;
struct ruler {
cell *c1, *c2, *c3;
hyperpoint h1, h2, h3;
rulertype *type;
void render();
void snap(shiftpoint h);
void reset() { c1 = c2 = c3 = nullptr; }
bool is_valid() {
if(type->flags & THREE_POINTS) {
if(!c3) return false;
}
return c1 && c2 && (c1 != c2 || h1 != h2);
}
ruler() { type = &linear; reset(); }
shiftpoint s1() { return ggmatrix(c1) * h1; }
shiftpoint s2() { return ggmatrix(c2) * h2; }
shiftpoint s3() { return ggmatrix(c3) * h3; }
shiftmatrix tox() {
shiftmatrix T = rgpushxto0(s1());
auto sh2 = inverse_shift(T, s2());
return T * rspintox(sh2);
}
ld dist() { return hdist(s1(), s2()); }
};
ld get_len() {
if(euclid) return 1000;
if(hyperbolic) return 10;
if(sphere) return 3.14;
return 10;
}
ld mmark(int i) {
if(i == 0) return measuring_unit;
if(i % 10 == 0) return measuring_unit * 2/3.;
return measuring_unit * 1/3.;
}
rulertype linear { 'l', "ruler", 0, [] (ruler& r) {
shiftmatrix T = r.tox();
if(sphere) {
for(int a=0; a<360; a++)
queueline(T * xpush0(a*1._deg), T * xpush0((a+1)*1._deg), ruler_color, 0);
}
else
queueline(T * xpush0(-get_len()), T * xpush0(get_len()), ruler_color, 10);
int u = measuring_unit ? min<int>(get_len() / measuring_unit, 1000) : -1;
for(int i=-u; i<=u; i++)
queueline(T * xpush(i * measuring_unit) * ypush0(mmark(i)), T * xpush(i * measuring_unit) * ypush0(-mmark(i)), ruler_color, 1);
},
[] (ruler& r, shiftpoint h) {
shiftmatrix T = r.tox();
auto sh = inverse_shift(T, h);
for(int d=1; d<GDIM; d++) sh[d] = 0; sh = normalize(sh);
return T * sh;
}};
rulertype ortho { 'o', "orthogonal ruler", 0, [] (ruler& r) {
shiftmatrix T = r.tox();;
if(sphere) {
for(int a=0; a<360; a++)
queueline(T * ypush0(a*1._deg), T * ypush0((a+1)*1._deg), ruler_color, 0);
}
else
queueline(T * ypush0(-get_len()), T * ypush0(+get_len()), ruler_color, 10);
if(GDIM == 3)
queueline(T * zpush0(-get_len()), T * zpush0(get_len()), ruler_color, 10);
int u = measuring_unit ? min<int>(get_len() / measuring_unit, 1000) : -1;
if(GDIM == 2) for(int i=-u; i<=u; i++)
queueline(T * ypush(i * measuring_unit) * xpush0(mmark(i)), T * ypush(i * measuring_unit) * xpush0(-mmark(i)), ruler_color, 1);
},
[] (ruler& r, shiftpoint h) {
shiftmatrix T = r.tox();
auto sh = inverse_shift(T, h);
sh[0] = 0; sh = normalize(sh);
return T * sh;
}};
rulertype compass { 'c', "compass", 0, [] (ruler& r) {
shiftmatrix T = r.tox();
ld radius = r.dist();
ld len = sin_auto(radius);
int ll = ceil(360 * len);
if(ll > 1000000) ll = 1000000;
for(int i=0; i<=ll; i++)
curvepoint(xspinpush0(TAU*i/ll, radius));
queuecurve(T, ruler_color, 0, PPR::LINE);
int u = measuring_unit ? min<int>(M_PI*len / measuring_unit, 1000) : -1;
for(int i=-u; i<=u; i++)
queueline(T * xspinpush0(i * measuring_unit / len, radius - mmark(i)),
T * xspinpush0(i * measuring_unit / len, radius + mmark(i)), ruler_color, 1);
},
[] (ruler& r, shiftpoint h) {
shiftmatrix T = r.tox();
auto sh2 = inverse_shift(T, r.s2());
auto sh = inverse_shift(T, h);
ld ratio = sqrt(sqhypot_d(GDIM, sh2) / sqhypot_d(GDIM, sh));
for(int i=0; i<GDIM; i++) sh[i] *= ratio;
sh[GDIM] = sh2[GDIM];
return T * sh;
}};
rulertype equi { 'e', "equidistant", 0, [] (ruler& r) {
shiftmatrix T = r.tox();
ld radius = r.dist();
ld len = get_len();
for(int i=-1000; i<=1000; i++) {
curvepoint(ypush0(i / 1000. * len));
}
queuecurve(T, ruler_color, 0, PPR::LINE);
for(int i=-1000; i<=1000; i++) {
curvepoint(ypush(i / 1000. * len) * xpush0(radius));
}
queuecurve(T, ruler_color, 0, PPR::LINE);
auto car = cos_auto(radius);
int u = measuring_unit ? min<int>(len*car / measuring_unit, 1000) : -1;
for(int i=-u; i<=u; i++)
queueline(T * ypush(i * measuring_unit / car) * xpush0(radius - mmark(i)),
T * ypush(i * measuring_unit / car) * xpush0(radius + mmark(i)), ruler_color, 1);
},
[] (ruler& r, shiftpoint h) {
shiftmatrix T = r.tox();
ld radius = r.dist();
auto sh = inverse_shift(T, h);
ld s = asin_auto(sh[1]);
return T * ypush(s) * xpush0(radius);
}};
rulertype horo { 'h', "horocycle", HYPER_ONLY, [] (ruler& r) {
if(!hyperbolic) return linear.render(r);
shiftmatrix T = r.tox();
auto d = r.dist();
T = T * xpush(d/2) * spin90();
auto offset = deparabolic13(inverse_shift(T, r.s2()))[0];
for(int i=-500; i<500; i++)
curvepoint(parabolic13(point2(offset, sinh(i/100.))));
queuecurve(T, ruler_color, 0, PPR::LINE);
for(int i=-500; i<=500; i++)
queueline(T * parabolic13(point2(offset - mmark(i), (i * measuring_unit) * exp(offset))),
T * parabolic13(point2(offset + mmark(i), (i * measuring_unit) * exp(offset))),
ruler_color, 1);
},
[] (ruler& r, shiftpoint h) {
if(!hyperbolic) return linear.snap(r, h);
shiftmatrix T = r.tox();
auto d = r.dist();
T = T * xpush(d/2) * spin90();
auto offset = deparabolic13(inverse_shift(T, r.s2()))[0];
auto co = deparabolic13(inverse_shift(T, h));
co[0] = offset;
return T * parabolic13(co);
}};
map<ld, vector<ld>> ellipse_cache;
rulertype ellipse { 'E', "ellipse", THREE_POINTS, [] (ruler& r) {
shiftmatrix T = r.tox();
auto d = r.dist();
T = T * xpush(d/2);
hyperpoint h1 = inverse_shift(T, r.s1());
hyperpoint h2 = inverse_shift(T, r.s2());
hyperpoint h3 = inverse_shift(T, r.s3());
ld wanted = hdist(h1, h3) + hdist(h2, h3);
auto& ec = ellipse_cache[wanted];
if(ec.empty()) for(int it=0; it<=360; it++) {
auto p = [&] (ld x) { return xspinpush0(it*1._deg, x); };
ld x = binsearch(0, 5, [&] (ld x) { auto px = p(x); return hdist(h1, px) + hdist(h2, px) >= wanted; });
ec.push_back(x);
}
for(int it=0; it<=360; it++)
curvepoint(xspinpush0(it*1._deg, ec[it]));
queuecurve(T, ruler_color, 0, PPR::LINE);
},
[] (ruler& r, shiftpoint h) {
shiftmatrix T = r.tox();
auto d = r.dist();
T = T * xpush(d/2);
hyperpoint h1 = inverse_shift(T, r.s1());
hyperpoint h2 = inverse_shift(T, r.s2());
hyperpoint h3 = inverse_shift(T, r.s3());
ld wanted = hdist(h1, h3) + hdist(h2, h3);
ld alpha = -atan2(inverse_shift(T, h));
auto p = [&] (ld x) { return xspinpush0(alpha, x); };
ld x = binsearch(0, 5, [&] (ld x) { auto px = p(x); return hdist(h1, px) + hdist(h2, px) >= wanted; });
return T * p(x);
}};
vector<rulertype*> rulers = { &linear, &compass, &ortho, &ellipse, &horo, &equi };
ruler current;
EX void render_current() {
if(!active || !current.is_valid()) return;
dynamicval<ld> lw(vid.linewidth, vid.linewidth * ruler_width);
dynamicval<color_t> rc(ruler_color, ruler_color);
if(cmode & sm::EDIT_RULER) ruler_color |= 0xFF;
current.type->render(current);
}
EX void snap_to_current(shiftpoint& s) {
if(!active || !current.is_valid() || (cmode & sm::EDIT_RULER)) return;
s = current.type->snap(current, s);
}
EX bool edit_first_point, edit_third_point;
EX void handle_key_rulers(int sym, int uni) {
if(mapeditor::handle_wheel_draw(sym, uni)) return;
handlePanning(sym, uni);
dialog::handleNavigation(sym, uni);
if(uni == SDLK_ESCAPE) popScreen();
if(uni == SETMOUSEKEY) {
if(mousekey == newmousekey)
mousekey = '-';
else
mousekey = newmousekey;
}
shiftpoint mh = mapeditor::full_mouseh();
if(uni == '-') {
cell *b = centerover;
shiftmatrix T = rgpushxto0(mh);
auto T1 = inverse_shift(ggmatrix(b), T);
virtualRebase(b, T1);
if((current.type->flags & THREE_POINTS) && edit_third_point) {
current.c3 = b;
current.h3 = tC0(T1);
}
else {
if(edit_first_point || !holdmouse) {
current.c1 = b;
current.h1 = tC0(T1);
}
if(!current.c2 || !edit_first_point) {
current.c2 = b;
current.h2 = tC0(T1);
}
if(!current.c3) {
current.c3 = centerover; current.h3 = C0;
}
}
holdmouse = true;
active = true;
}
else if(doexiton(sym, uni)) popScreen();
}
EX void show() {
cmode = sm::DRAW | sm::PANNING | sm::EDIT_RULER;
if(mapeditor::show_menu) cmode |= sm::SIDE;
gamescreen();
initquickqueue();
if(current.c1)
mapeditor::draw_cross_at(current.s1(), 0x4040FFFF);
if(current.c2)
mapeditor::draw_cross_at(current.s2(), 0xFFFF40FF);
if(current.c3 && (current.type->flags & THREE_POINTS))
mapeditor::draw_cross_at(current.s3(), 0xFF40FFFF);
quickqueue();
if(callhandlers(false, hooks_prestats)) return;
if(!mouseout()) getcstat = '-';
cmode |= sm::DIALOG_STRICT_X;
dialog::init(XLAT("compass and ruler"));
dialog::addHelp(XLAT("draw strictly along a line or curve"));
dialog::addBreak(100);
for(auto r: rulers) {
dialog::addBoolItem(r->name, r == current.type, r->key);
dialog::add_action([r] { current.type = r; });
}
dialog::addBreak(100);
if(current.c1 && current.c2) {
dialog::addItem("swap the points", 'S');
dialog::add_action([] { swap(current.c1, current.c2); swap(current.h1, current.h2); });
}
else dialog::addBreak(100);
if(current.c1 && current.c2 && !holdmouse) {
dialog::addBoolItem_action("edit the first point", edit_first_point, 'P');
}
else dialog::addBreak(100);
if(current.type->flags & THREE_POINTS) {
dialog::addBoolItem_action("edit the third point", edit_third_point, 'T');
}
else dialog::addBreak(100);
add_edit(ruler_color);
add_edit(ruler_width);
add_edit(measuring_unit);
if(GDIM == 2)
dialog::addBoolItem_action(XLAT("snap"), mapeditor::snapping, 'S');
dialog::addBreak(100);
if(current.is_valid()) {
dialog::addItem(XLAT("use this ruler"), 'u');
dialog::add_action([] { active = true; popScreen(); });
}
else dialog::addBreak(100);
if(active) {
dialog::addItem(XLAT("do not use ruler"), 'd');
dialog::add_action([] { active = false; popScreen(); });
}
else dialog::addBreak(100);
if(mapeditor::show_menu) { dialog::display(); }
else dialog::add_key_action(SDLK_ESCAPE, [] { mapeditor::show_menu = true; });
keyhandler = handle_key_rulers;
}
EX }
EX namespace mapeditor {
/* changes when the map is changed */
@@ -175,10 +540,19 @@ EX namespace mapeditor {
else
#endif
fmh = mouseh;
rulers::snap_to_current(fmh);
}
return fmh;
}
EX void draw_cross_at(shiftpoint h, color_t col) {
shiftmatrix T = rgpushxto0(h);
queueline(T * xpush0(-.1), T * xpush0(.1), col);
queueline(T * ypush0(-.1), T * ypush0(.1), col);
if(GDIM == 3)
queueline(T * zpush0(-.1), T * zpush0(.1), col);
}
EX void draw_dtshapes() {
fmh_known = false;
#if CAP_EDIT
@@ -203,11 +577,8 @@ EX namespace mapeditor {
torus_rug_jump(moh, lstart);
queueline(lstart, moh, dtcolor, 4 + vid.linequality, PPR::LINE);
}
else if(!holdmouse && !mouseout()) {
shiftmatrix T = rgpushxto0(moh);
queueline(T * xpush0(-.1), T * xpush0(.1), dtcolor);
queueline(T * ypush0(-.1), T * ypush0(.1), dtcolor);
}
else if(!holdmouse && !mouseout())
draw_cross_at(moh, dtcolor);
}
#endif
}
@@ -2209,6 +2580,8 @@ EX namespace mapeditor {
if(snapping && !mouseout())
queuestr(fmh, 10, "x", 0xC040C0);
rulers::render_current();
}
static ld brush_sizes[10] = {
@@ -2461,6 +2834,9 @@ EX namespace mapeditor {
dialog::addBreak(CAP_TEXTURE ? 700 : 1000);
}
dialog::addBoolItem(XLAT("compass and ruler"), rulers::active, 'R');
dialog::add_action_push(rulers::show);
if(GDIM == 2)
dialog::addBoolItem_action(XLAT("snap"), snapping, 'S');
@@ -2981,16 +3357,23 @@ EX namespace mapeditor {
#endif
EX void handle_key_draw(int sym, int uni) {
EX bool handle_wheel_draw(int sym, int uni) {
if(uni == PSEUDOKEY_WHEELUP && GDIM == 3 && front_step) {
front_edit += front_step * shiftmul; return;
front_edit += front_step * shiftmul; return true;
}
if(uni == PSEUDOKEY_WHEELDOWN && GDIM == 3 && front_step) {
front_edit -= front_step * shiftmul; return;
front_edit -= front_step * shiftmul; return true;
}
return false;
}
EX void handle_key_draw(int sym, int uni) {
if(handle_wheel_draw(sym, uni)) return;
handlePanning(sym, uni);
dialog::handleNavigation(sym, uni);
if(uni == SDLK_ESCAPE) popScreen();
@@ -3112,6 +3495,8 @@ EX namespace mapeditor {
mapeditor::dtshapes.clear();
dt_finish();
drawcell = nullptr;
rulers::current.reset(); rulers::active = false;
rulers::ellipse_cache.clear();
}) +
addHook(hooks_removecells, 0, [] () {
modelcell.clear();