1
0
mirror of https://github.com/zenorogue/hyperrogue.git synced 2024-12-20 23:50:27 +00:00
hyperrogue/conformal.cpp

985 lines
29 KiB
C++
Raw Normal View History

2016-08-26 09:58:03 +00:00
// Hyperbolic Rogue -- the conformal/history mode
// Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details
2016-08-26 09:58:03 +00:00
namespace hr {
#if ISMOBWEB
typedef double precise;
#else
typedef long double precise;
#endif
2017-07-16 21:00:55 +00:00
2016-08-26 09:58:03 +00:00
namespace polygonal {
typedef long double xld;
typedef complex<xld> cld;
2016-08-26 09:58:03 +00:00
int SI = 4;
2017-03-23 10:53:57 +00:00
ld STAR = 0;
2016-08-26 09:58:03 +00:00
int deg = ISMOBWEB ? 2 : 20;
2016-08-26 09:58:03 +00:00
precise matrix[MSI][MSI];
precise ans[MSI];
2016-08-26 09:58:03 +00:00
cld coef[MSI];
2017-07-16 21:00:55 +00:00
ld coefr[MSI], coefi[MSI];
2016-08-26 09:58:03 +00:00
int maxcoef, coefid;
void solve() {
2017-07-16 21:00:55 +00:00
if(pmodel == mdPolynomial) {
for(int i=0; i<MSI; i++) coef[i] = cld(coefr[i], coefi[i]);
return;
}
if(pmodel != mdPolygonal) return;
if(SI < 3) SI = 3;
2016-08-26 09:58:03 +00:00
for(int i=0; i<MSI; i++) ans[i] = cos(M_PI / SI);
for(int i=0; i<MSI; i++)
for(int j=0; j<MSI; j++) {
precise i0 = (i+0.) / (MSI-1);
2016-08-26 09:58:03 +00:00
// i0 *= i0;
// i0 = 1 - i0;
i0 *= M_PI;
matrix[i][j] =
cos(i0 * (j + 1./SI)) * (STAR > 0 ? (1+STAR) : 1)
- sin(i0 * (j + 1./SI)) * (STAR > 0 ? STAR : STAR/(1+STAR));
}
for(int i=0; i<MSI; i++) {
precise dby = matrix[i][i];
2016-08-26 09:58:03 +00:00
for(int k=0; k<MSI; k++) matrix[i][k] /= dby;
ans[i] /= dby;
for(int j=i+1; j<MSI; j++) {
precise sub = matrix[j][i];
2016-08-26 09:58:03 +00:00
ans[j] -= ans[i] * sub;
for(int k=0; k<MSI; k++)
matrix[j][k] -= sub * matrix[i][k];
}
}
for(int i=MSI-1; i>=0; i--) {
for(int j=0; j<i; j++) {
precise sub = matrix[j][i];
2016-08-26 09:58:03 +00:00
ans[j] -= ans[i] * sub;
for(int k=0; k<MSI; k++)
matrix[j][k] -= sub * matrix[i][k];
}
}
}
pair<ld, ld> compute(ld x, ld y, int prec) {
if(x*x+y*y > 1) {
xld r = hypot(x,y);
x /= r;
y /= r;
}
2017-03-23 10:53:57 +00:00
if(pmodel == mdPolynomial) {
2016-08-26 09:58:03 +00:00
cld z(x,y);
cld res (0,0);
for(int i=maxcoef; i>=0; i--) { res += coef[i]; if(i) res *= z; }
return make_pair(real(res), imag(res));
}
cld z(x, y);
cld res (0,0);
cld zp = 1; for(int i=0; i<SI; i++) zp *= z;
for(int i=prec; i>0; i--) {
res += ans[i];
res *= zp;
}
res += ans[0]; res *= z;
return make_pair(real(res), imag(res));
}
pair<ld, ld> compute(ld x, ld y) { return compute(x,y,deg); }
void drawBoundary(int color) {
queuereset(mdUnchanged, PPR::CIRCLE);
2017-03-23 10:53:57 +00:00
2018-10-23 14:58:19 +00:00
ld C, S;
auto& ho = conformal::model_orientation;
if(ho == 0) C = 1, S = 0;
else if(ho == 180) C = -1, S = 0;
else C = cos(ho * M_PI/180), S = sin(ho * M_PI / 180);
2017-03-23 10:53:57 +00:00
for(int r=0; r<=2000; r++) {
cld z = exp(cld(0, 2*M_PI * r / 2000.0));
pair<xld,xld> z2 = compute(real(z), imag(z), deg);
2017-03-23 10:53:57 +00:00
hyperpoint h;
2018-10-23 14:58:19 +00:00
h[0] = (z2.first * C - z2.second * S) * vid.radius;
h[1] = (z2.second * C + z2.first * S) * vid.radius;
2018-02-03 12:41:49 +00:00
h[2] = stereo::scrdist;
2017-03-23 10:53:57 +00:00
curvepoint(h);
2016-08-26 09:58:03 +00:00
}
2017-03-23 10:53:57 +00:00
2018-08-28 12:27:23 +00:00
queuecurve(color, 0, PPR::CIRCLE);
queuereset(pmodel, PPR::CIRCLE);
2016-08-26 09:58:03 +00:00
}
}
2017-07-22 23:33:27 +00:00
#if CAP_SDL
2016-08-26 09:58:03 +00:00
namespace spiral {
typedef long double ld;
typedef complex<long double> cld;
int shiftx, shifty, velx, vely;
vector<pair<short, short> > quickmap;
int CX, CY, SX, SY, Yshift;
2017-04-08 15:18:29 +00:00
vector<SDL_Surface*> band;
SDL_Surface *out;
2016-08-26 09:58:03 +00:00
bool displayhelp = true;
color_t& bandpixel(int x, int y) {
2017-04-08 15:18:29 +00:00
int i = 0;
2018-06-22 12:47:24 +00:00
while(i < isize(band) && x >= band[i]->w)
2017-04-08 15:18:29 +00:00
x -= band[i]->w, i++;
return qpixel(band[i], x, y);
}
2016-08-26 09:58:03 +00:00
void precompute() {
2017-04-08 15:18:29 +00:00
CX = 0;
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(band); i++) CX += band[i]->w;
2017-04-08 15:18:29 +00:00
if(CX == 0) { printf("ERROR: no CX\n"); return; }
CY = band[0]->h;
2016-08-26 09:58:03 +00:00
SX = out->w;
SY = out->h;
ld k = -2*M_PI*M_PI / log(2.6180339);
// cld mnoznik = cld(0, M_PI) / cld(k, M_PI);
cld factor = cld(0, -CY/2/M_PI/M_PI) * cld(k, M_PI);
Yshift = CY * k / M_PI;
quickmap.clear();
double xc = ((SX | 1) - 2) / 2.;
double yc = ((SY | 1) - 2) / 2.;
for(int y=0; y<SY; y++)
for(int x=0; x<SX; x++) {
cld z(x-xc, y-yc);
cld z1 = log(z);
z1 = z1 * factor;
quickmap.push_back(make_pair(int(real(z1)) % CX, int(imag(z1))));
}
}
void draw() {
int c = 0;
for(int y=0; y<SY; y++) for(int x=0; x<SX; x++) {
pair<short,short> p = quickmap[c++];
int cx = p.first + shiftx;
int cy = p.second + + shifty;
int d = cy / CY;
cy -= d * CY; cx -= d * Yshift;
if(cy<0) cy += CY, cx += Yshift;
cx %= CX; if(cx<0) cx += CX;
2017-04-08 15:18:29 +00:00
qpixel(out, x, y) = bandpixel(cx, cy);
2016-08-26 09:58:03 +00:00
}
}
2017-04-08 15:18:29 +00:00
void loop(vector<SDL_Surface*> _band) {
2016-08-26 09:58:03 +00:00
band = _band;
out = s;
precompute();
2017-04-08 15:18:29 +00:00
if(CX == 0) return;
2016-08-26 09:58:03 +00:00
shiftx = shifty = 0;
velx=1; vely=1;
bool dosave = false;
bool saveGL = vid.usingGL;
if(saveGL) switchGL(); // { vid.usingGL = false; setvideomode(); }
2016-08-26 09:58:03 +00:00
while(true) {
time_t timer;
timer = time(NULL);
char buf[128];
strftime(buf, 128, "spiral-%y%m%d-%H%M%S" IMAGEEXT, localtime(&timer));
SDL_LockSurface(s);
draw();
if(dosave) { dosave = false; IMAGESAVE(s, buf); }
SDL_UnlockSurface(s);
if(displayhelp) {
2017-07-04 13:38:33 +00:00
displaystr(SX/2, vid.fsize*2, 0, vid.fsize, "arrows = navigate, ESC = return, h = hide help", forecolor, 8);
displaystr(SX/2, SY - vid.fsize*2, 0, vid.fsize, XLAT("s = save to " IMAGEEXT, buf), forecolor, 8);
2016-08-26 09:58:03 +00:00
}
SDL_UpdateRect(s, 0, 0, 0, 0);
shiftx += velx; shifty += vely;
SDL_Event event;
while(SDL_PollEvent(&event)) switch (event.type) {
case SDL_VIDEORESIZE: {
resize_screen_to(event.resize.w, event.resize.h);
precompute();
break;
}
2016-08-26 09:58:03 +00:00
case SDL_QUIT: case SDL_MOUSEBUTTONDOWN:
goto breakloop;
case SDL_KEYDOWN: {
int key = event.key.keysym.sym;
// int uni = event.key.keysym.unicode;
if(key == SDLK_RIGHT) velx++;
if(key == SDLK_LEFT) velx--;
if(key == SDLK_UP) vely++;
if(key == SDLK_DOWN) vely--;
if(key == SDLK_ESCAPE) goto breakloop;
if(key == 'h') displayhelp = !displayhelp;
if(key == 's') dosave = true;
}
}
}
breakloop:
quickmap.clear();
if(saveGL) switchGL(); // { vid.usingGL = true; setvideomode(); }
2016-08-26 09:58:03 +00:00
}
}
#endif
bool isbad(ld z) { return !isfinite(z) || fabs(z) > 1e6; }
namespace conformal {
2017-07-10 18:47:38 +00:00
void handleKeyC(int sym, int uni);
2016-08-26 09:58:03 +00:00
int lastprogress;
2017-07-10 18:47:38 +00:00
void progress_screen() {
gamescreen(0);
mouseovers = "";
}
2016-08-26 09:58:03 +00:00
void progress(string str) {
2017-07-22 23:33:27 +00:00
#if CAP_SDL
2016-08-26 09:58:03 +00:00
int tick = SDL_GetTicks();
if(tick > lastprogress + 250) {
lastprogress = tick;
msgs.clear();
addMessage(str);
drawscreen();
}
#endif
}
bool on;
vector<shmup::monster*> v;
int llv;
double phase;
vector<pair<cell*, eMonster> > killhistory;
vector<pair<cell*, eItem> > findhistory;
vector<cell*> movehistory;
bool includeHistory;
2017-03-23 10:53:57 +00:00
ld lvspeed = 1;
2016-08-26 09:58:03 +00:00
int bandhalf = 200;
int bandsegment = 16000;
2018-03-25 13:07:11 +00:00
ld rotation = 0;
int do_rotate = 1;
2018-10-23 14:58:19 +00:00
ld model_orientation, halfplane_scale;
ld ocos, osin;
ld cos_ball, sin_ball;
2018-10-23 14:58:19 +00:00
bool model_straight;
2018-10-23 18:08:57 +00:00
ld top_z = 5;
ld model_transition = 1;
2018-10-23 14:58:19 +00:00
2016-08-26 09:58:03 +00:00
bool autoband = false;
bool autobandhistory = false;
bool dospiral = true;
ld extra_line_steps = 0;
2016-08-26 09:58:03 +00:00
void clear() {
on = false;
2018-06-22 12:47:24 +00:00
int N = isize(v);
2016-08-26 09:58:03 +00:00
for(int i=0; i<N; i++) delete v[i];
v.resize(0);
}
void create() {
if(celldist(cwt.at) == 0) {
2016-08-26 09:58:03 +00:00
addMessage("Must go a distance from the starting point");
return;
}
on = true;
cell *c = cwt.at;
2016-08-26 09:58:03 +00:00
while(true) {
shmup::monster *m = new shmup::monster;
m->at = Id;
m->base = c;
v.push_back(m);
if(c == currentmap->gamestart()) break;
2016-08-26 09:58:03 +00:00
for(int i=0; i<c->type; i++)
if(celldist(c->move(i)) < celldist(c)) {
c = c->move(i);
2016-08-26 09:58:03 +00:00
break;
}
}
reverse(v.begin(), v.end());
2018-06-22 12:47:24 +00:00
int Q = isize(v)-1;
// virtualRebase(v[0], false);
// virtualRebase(v[Q], false);
2016-08-26 09:58:03 +00:00
for(int i=0; i<1000; i++) {
progress(XLAT("Preparing the line (%1/1000)...", its(i+1)));
for(int j=1; j<Q; j++) if((j^i)&1) {
// virtualRebase(v[j], false);
2016-08-26 09:58:03 +00:00
hyperpoint prev = calc_relative_matrix(v[j-1]->base, v[j]->base, C0) *
2016-08-26 09:58:03 +00:00
v[j-1]->at * C0;
hyperpoint next = calc_relative_matrix(v[j+1]->base, v[j]->base, C0) *
2016-08-26 09:58:03 +00:00
v[j+1]->at * C0;
hyperpoint hmid = mid(prev, next);
transmatrix at = rgpushxto0(hmid);
2016-08-26 09:58:03 +00:00
v[j]->at = at * rspintox(inverse(at) * next);
2016-08-26 09:58:03 +00:00
fixmatrix(v[j]->at);
}
}
hyperpoint next0 = calc_relative_matrix(v[1]->base, v[0]->base, C0) * v[1]->at * C0;
v[0]->at = v[0]->at * rspintox(inverse(v[0]->at) * next0);
2016-08-26 09:58:03 +00:00
llv = ticks;
phase = 0;
if(0)
for(int j=0; j<=Q; j++) {
hyperpoint cur = v[j]->at * C0;
printf("%4d/%3d. %p [%3d] %s\n", j, Q, v[j]->base, celldist(v[j]->base), display(cur));
}
2016-08-26 09:58:03 +00:00
}
void movetophase() {
2016-08-26 09:58:03 +00:00
int ph = int(phase);
2018-06-22 12:47:24 +00:00
int siz = isize(v);
if(ph<0) ph = 0;
if(ph >= siz-1) ph = siz-2;
2016-08-26 09:58:03 +00:00
viewctr.at = v[ph]->base->master;
2016-08-26 09:58:03 +00:00
viewctr.spin = 0;
View = inverse(master_relative(v[ph]->base) * v[ph]->at);
hyperpoint now = v[ph]->at * C0;
2016-08-26 09:58:03 +00:00
hyperpoint next = calc_relative_matrix(v[ph+1]->base, v[ph]->base, C0) *
v[ph+1]->at * C0;
2016-08-26 09:58:03 +00:00
2018-03-25 13:07:11 +00:00
View = spin(M_PI/180 * rotation) * xpush(-(phase-ph) * hdist(now, next)) * View;
2016-08-26 09:58:03 +00:00
playermoved = false;
centerover.at = v[ph]->base;
compute_graphical_distance();
2016-08-26 09:58:03 +00:00
}
void apply() {
int t = ticks;
phase += (t-llv) * lvspeed / 400.;
llv = t;
2018-06-22 12:47:24 +00:00
int siz = isize(v);
while(phase > siz-1 + extra_line_steps) phase -= (siz + 2 * extra_line_steps-1);
while(phase < - extra_line_steps) phase += (siz + 2 * extra_line_steps-1);
movetophase();
}
2018-10-23 14:58:19 +00:00
void configure() {
ld ball = -vid.ballangle * M_PI / 180;
cos_ball = cos(ball), sin_ball = sin(ball);
2018-10-23 14:58:19 +00:00
ocos = cos(model_orientation * M_PI / 180);
osin = sin(model_orientation * M_PI / 180);
model_straight = (ocos > 1 - 1e-9);
if(conformal::on) conformal::apply();
}
ld measureLength() {
ld r = bandhalf * vid.scale;
2016-08-26 09:58:03 +00:00
ld tpixels = 0;
2018-06-22 12:47:24 +00:00
int siz = isize(v);
2016-08-26 09:58:03 +00:00
for(int j=0; j<siz-1; j++) {
2016-08-26 09:58:03 +00:00
hyperpoint next =
inverse(v[j]->at) *
calc_relative_matrix(v[j+1]->base, v[j]->base, C0) *
2016-08-26 09:58:03 +00:00
v[j+1]->at * C0;
2017-03-23 10:53:57 +00:00
hyperpoint nextscr;
applymodel(next, nextscr);
tpixels += nextscr[0] * r;
if(j == 0 || j == siz-2)
tpixels += nextscr[0] * r * extra_line_steps;
2016-08-26 09:58:03 +00:00
}
return tpixels;
}
void restore();
void restoreBack();
2017-07-22 23:33:27 +00:00
#if CAP_SDL
2016-08-26 09:58:03 +00:00
void createImage(bool dospiral) {
int segid = 1;
if(includeHistory) restore();
int bandfull = 2*bandhalf;
ld len = measureLength();
2016-08-26 09:58:03 +00:00
time_t timer;
timer = time(NULL);
char timebuf[128];
strftime(timebuf, 128, "%y%m%d-%H%M%S", localtime(&timer));
2017-04-08 15:18:29 +00:00
vector<SDL_Surface*> bands;
resetbuffer rbuf;
2016-08-26 09:58:03 +00:00
if(1) {
// block for RAII
dynamicval<videopar> dv(vid, vid);
dynamicval<ld> dr(rotation, 0);
dynamicval<bool> di(inHighQual, true);
renderbuffer glbuf(bandfull, bandfull, vid.usingGL);
vid.xres = vid.yres = bandfull;
2018-05-03 09:29:25 +00:00
glbuf.enable(); vid.radius = bandhalf;
calcparam();
2018-05-03 09:29:25 +00:00
stereo::set_viewport(0);
ld xpos = 0;
int seglen = min(int(len), bandsegment);
SDL_Surface *band = SDL_CreateRGBSurface(SDL_SWSURFACE, seglen, bandfull,32,0,0,0,0);
if(!band) {
addMessage("Could not create an image of that size.");
}
else {
2018-06-22 12:47:24 +00:00
int siz = isize(v);
2016-08-26 09:58:03 +00:00
int bonus = ceil(extra_line_steps);
cell *last_base = NULL;
hyperpoint last_relative;
2017-07-10 18:47:38 +00:00
for(int j=-bonus; j<siz+bonus; j++) {
/*
SDL_Surface *buffer = s;
s = sav;
pushScreen(progress_screen);
char buf[128];
sprintf(buf, "#%03d", segid);
progress(s0 + buf + " ("+its(j+bonus)+"/"+its(siz+bonus+bonus-1)+")"); */
// calcparam(); vid.radius = bandhalf;
phase = j; movetophase();
glbuf.clear(backcolor);
drawfullmap();
if(last_base) {
hyperpoint last = ggmatrix(last_base) * last_relative;
hyperpoint hscr;
applymodel(last, hscr);
ld bwidth = -vid.radius * hscr[0];
printf("bwidth = %lf/%lf\n", bwidth, len);
drawsegment:
SDL_Surface *gr = glbuf.render();
for(int cy=0; cy<bandfull; cy++) for(int cx=0; cx<=bwidth+3; cx++)
qpixel(band, int(xpos+cx), cy) = qpixel(gr, int(bandhalf+cx-bwidth), cy);
if(j == 1-bonus)
xpos = bwidth * (extra_line_steps - bonus);
2016-08-26 09:58:03 +00:00
if(xpos+bwidth > bandsegment) {
char buf[154];
sprintf(buf, "bandmodel-%s-%03d" IMAGEEXT, timebuf, segid++);
2016-08-26 09:58:03 +00:00
IMAGESAVE(band, buf);
if(dospiral)
bands.push_back(band);
else
SDL_FreeSurface(band);
len -= bandsegment; xpos -= bandsegment;
seglen = min(int(len), bandsegment);
band = SDL_CreateRGBSurface(SDL_SWSURFACE, seglen, bandfull,32,0,0,0,0);
goto drawsegment;
}
xpos += bwidth;
}
last_base = viewctr.at->c7;
last_relative = inverse(ggmatrix(last_base)) * C0;
}
}
2017-04-08 15:18:29 +00:00
char buf[154];
sprintf(buf, "bandmodel-%s-%03d" IMAGEEXT, timebuf, segid++);
IMAGESAVE(band, buf);
addMessage(XLAT("Saved the band image as: ") + buf);
if(dospiral)
bands.push_back(band);
else
SDL_FreeSurface(band);
2016-08-26 09:58:03 +00:00
}
rbuf.reset();
stereo::set_viewport(0);
2017-04-08 15:18:29 +00:00
2016-08-26 09:58:03 +00:00
if(includeHistory) restoreBack();
2017-04-08 15:18:29 +00:00
if(dospiral) {
spiral::loop(bands);
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(bands); i++) SDL_FreeSurface(bands[i]);
2017-04-08 15:18:29 +00:00
}
2016-08-26 09:58:03 +00:00
}
#endif
2017-03-23 10:53:57 +00:00
const char *modelnames[MODELCOUNT] = {
"disk", "half-plane", "band", "polygonal", "polynomial",
"azimuthal equidistant", "azimuthal equi-area",
2018-03-26 17:06:47 +00:00
"ball model", "Minkowski hyperboloid", "hemisphere",
2018-04-18 18:52:17 +00:00
"band equidistant", "band equi-area", "sinusoidal", "two-point equidistant",
"fisheye", "Joukowsky transform", "Joukowsky+inversion"
2016-08-26 09:58:03 +00:00
};
2018-03-25 12:04:40 +00:00
string get_model_name(eModel pm) {
return XLAT(
pm == mdBand && sphere ? "Mercator" :
pm == mdHalfplane && euclid ? "inversion" :
pm == mdHemisphere && !hyperbolic ? "sphere" :
pm == mdHyperboloid && euclid ? "plane" :
pm == mdHyperboloid && sphere ? "sphere" :
modelnames[pm]);
}
bool model_available(eModel pm) {
if(sphere && (pm == mdHalfplane || pm == mdBall))
return false;
return true;
2018-03-25 12:04:40 +00:00
}
2018-10-23 14:58:19 +00:00
bool model_has_orientation() {
return
among(pmodel, mdHalfplane, mdPolynomial, mdPolygonal, mdTwoPoint, mdJoukowsky, mdJoukowskyInverted) || mdBandAny();
2018-10-23 14:58:19 +00:00
}
bool model_has_transition() {
return among(pmodel, mdJoukowsky, mdJoukowskyInverted, mdBand);
}
2018-03-25 12:04:40 +00:00
void model_menu() {
2018-03-25 13:07:11 +00:00
cmode = sm::SIDE | sm::MAYDARK | sm::CENTER;
2017-07-12 17:50:39 +00:00
gamescreen(0);
2018-03-25 12:04:40 +00:00
dialog::init(XLAT("models of hyperbolic geometry"));
for(int i=0; i<mdGUARD; i++) {
eModel m = eModel(i);
if(model_available(m))
2018-03-26 17:06:47 +00:00
dialog::addBoolItem(get_model_name(m), pmodel == m, "0123456789!@#$%^&*()" [m]);
2018-03-25 12:04:40 +00:00
}
2017-07-12 17:50:39 +00:00
2018-03-25 12:04:40 +00:00
dialog::addBreak(100);
2018-03-25 13:07:11 +00:00
dialog::addBoolItem(XLAT("rotation"), do_rotate == 2, 'r');
if(do_rotate == 0) dialog::lastItem().value = XLAT("NEVER");
dialog::lastItem().value += " " + its(rotation) + "°";
2018-03-25 12:04:40 +00:00
// if(pmodel == mdBand && sphere)
dialog::addSelItem(XLAT("scale factor"), fts(vid.scale), 'z');
2018-03-25 12:04:40 +00:00
if(abs(vid.alpha-1) > 1e-3 && pmodel != mdBall && pmodel != mdHyperboloid && pmodel != mdHemisphere && pmodel != mdDisk) {
dialog::addBreak(50);
dialog::addInfo("NOTE: this works 'correctly' only if the Poincaré model/stereographic projection is used.");
dialog::addBreak(50);
2018-03-25 12:04:40 +00:00
}
if(pmodel == mdDisk || pmodel == mdBall || pmodel == mdHyperboloid) {
dialog::addSelItem(XLAT("Projection at the ground level"), fts3(vid.alpha), 'p');
}
2018-10-23 14:58:19 +00:00
if(model_has_orientation())
dialog::addSelItem(XLAT("model orientation"), fts(model_orientation), 'l');
if(pmodel == mdPolynomial) {
2017-03-23 10:53:57 +00:00
dialog::addSelItem(XLAT("coefficient"),
2017-07-16 21:00:55 +00:00
fts4(polygonal::coefr[polygonal::coefid]), 'x');
2017-03-23 10:53:57 +00:00
dialog::addSelItem(XLAT("coefficient (imaginary)"),
2017-07-16 21:00:55 +00:00
fts4(polygonal::coefi[polygonal::coefid]), 'y');
2017-03-23 10:53:57 +00:00
dialog::addSelItem(XLAT("which coefficient"), its(polygonal::coefid), 'n');
2016-08-26 09:58:03 +00:00
}
2018-10-23 14:58:19 +00:00
if(pmodel == mdHalfplane) {
dialog::addSelItem(XLAT("half-plane scale"), fts(halfplane_scale), 'b');
}
2018-03-25 13:27:42 +00:00
2018-03-25 12:04:40 +00:00
if(pmodel == mdBall)
dialog::addSelItem(XLAT("projection in ball model"), fts3(vid.ballproj), 'x');
if(pmodel == mdPolygonal) {
2017-03-23 10:53:57 +00:00
dialog::addSelItem(XLAT("polygon sides"), its(polygonal::SI), 'x');
dialog::addSelItem(XLAT("star factor"), fts(polygonal::STAR), 'y');
dialog::addSelItem(XLAT("degree of the approximation"), its(polygonal::deg), 'n');
2016-08-26 09:58:03 +00:00
}
if(pmodel == mdBall || pmodel == mdHyperboloid || pmodel == mdHemisphere) {
dialog::addSelItem(XLAT("camera rotation in 3D models"), fts3(vid.ballangle), 'b');
}
2018-10-23 18:08:57 +00:00
if(pmodel == mdHyperboloid)
dialog::addSelItem(XLAT("maximum z coordinate to show"), fts3(top_z), 'l');
2018-10-23 18:08:57 +00:00
if(model_has_transition())
dialog::addSelItem(XLAT("model transition"), fts3(model_transition), 't');
2018-03-26 17:06:47 +00:00
if(pmodel == mdHemisphere && euclid) {
2018-03-25 12:04:40 +00:00
dialog::addSelItem(XLAT("parameter"), fts3(vid.euclid_to_sphere), 'l');
}
2018-03-26 17:06:47 +00:00
if(pmodel == mdTwoPoint) {
2018-10-23 14:58:19 +00:00
dialog::addSelItem(XLAT("parameter"), fts3(vid.twopoint_param), 'b');
2018-03-26 17:06:47 +00:00
}
dialog::addSelItem(XLAT("vertical stretch"), fts3(vid.stretch), 's');
2018-11-01 17:59:25 +00:00
menuitem_sightrange('R');
2018-03-26 17:06:47 +00:00
2018-03-25 12:04:40 +00:00
dialog::addBreak(100);
dialog::addItem(XLAT("history mode"), 'a');
2018-05-07 18:09:58 +00:00
#if CAP_RUG
2018-03-25 12:04:40 +00:00
dialog::addItem(XLAT("hypersian rug mode"), 'u');
2018-05-07 18:09:58 +00:00
#endif
dialog::addBack();
2018-11-01 18:01:23 +00:00
2018-03-25 12:04:40 +00:00
dialog::display();
2018-11-01 18:01:23 +00:00
mouseovers = XLAT("see http://www.roguetemple.com/z/hyper/models.php");
2018-03-25 12:04:40 +00:00
keyhandler = [] (int sym, int uni) {
dialog::handleNavigation(sym, uni);
if(uni >= '0' && uni <= '9') {
pmodel = eModel(uni - '0');
polygonal::solve();
vid.alpha = 1; vid.scale = 1;
if(pmodel == mdBand && sphere)
vid.scale = .3;
if(pmodel == mdDisk && sphere)
vid.scale = .4;
}
2018-03-26 17:06:47 +00:00
else if(uni == '!')
pmodel = eModel(10);
else if(uni == '@')
pmodel = eModel(11);
else if(uni == '#')
pmodel = eModel(12);
else if(uni == '$')
pmodel = eModel(13);
2018-04-18 18:52:17 +00:00
else if(uni == '%')
pmodel = eModel(14);
else if(uni == '^')
pmodel = eModel(15);
else if(uni == '&')
pmodel = eModel(16);
2018-03-25 12:04:40 +00:00
else if(uni == '6')
vid.alpha = 1, vid.scale = 1;
else if(uni == 'z')
editScale();
else if(uni == 'p')
projectionDialog();
2018-05-07 18:09:58 +00:00
#if CAP_RUG
2018-03-25 12:04:40 +00:00
else if(uni == 'u')
pushScreen(rug::show);
2018-05-07 18:09:58 +00:00
#endif
2018-10-23 14:58:19 +00:00
else if(uni == 'l' && model_has_orientation())
dialog::editNumber(model_orientation, 0, 360, 90, 0, XLAT("model orientation"), "");
2018-10-23 18:08:57 +00:00
else if(uni == 'l' && pmodel == mdHyperboloid)
dialog::editNumber(top_z, 1, 20, 0.25, 4, XLAT("maximum z coordinate to show"), "");
else if(uni == 't')
dialog::editNumber(model_transition, 0, 1, 0.1, 1, XLAT("model transition"),
"You can change this parameter for a transition from another model to this one."
);
2018-10-23 14:58:19 +00:00
else if(uni == 'b' && pmodel == mdHalfplane)
dialog::editNumber(model_orientation, 0, 2, 0.25, 1, XLAT("halfplane scale"), "");
else if(uni == 's') {
dialog::editNumber(vid.stretch, 0, 10, .1, 1, XLAT("vertical stretch"),
"Vertical stretch factor."
);
dialog::extra_options = [] () {
dialog::addBreak(100);
if(sphere && pmodel == mdBandEquiarea) {
dialog::addBoolItem("Gall-Peters", vid.stretch == 2, 'o');
dialog::add_action([] { vid.stretch = 2; dialog::ne.s = "2"; });
}
if(pmodel == mdBandEquiarea) {
// y = K * sin(phi)
// cos(phi) * cos(phi) = 1/K
if(sphere && vid.stretch >= 1) {
ld phi = acos(sqrt(1/vid.stretch));
dialog::addInfo(XLAT("The current value makes the map conformal at the latitude of %1 (%2°).", fts(phi), fts(phi * 180 / M_PI)));
}
else if(hyperbolic && abs(vid.stretch) <= 1 && abs(vid.stretch) >= 1e-9) {
ld phi = acosh(abs(sqrt(1/vid.stretch)));
dialog::addInfo(XLAT("The current value makes the map conformal %1 units from the main line.", fts(phi)));
}
else dialog::addInfo("");
}
};
}
2018-03-25 12:04:40 +00:00
else if(uni == 'a')
pushScreen(history_menu);
2018-03-26 17:06:47 +00:00
else if(uni == 'l' && pmodel == mdHemisphere && euclid) {
2018-03-25 12:04:40 +00:00
dialog::editNumber(vid.euclid_to_sphere, 0, 10, .1, 1, XLAT("parameter"),
"Stereographic projection to a sphere. Choose the radius of the sphere."
);
dialog::scaleLog();
}
2018-10-23 14:58:19 +00:00
else if(uni == 'b' && pmodel == mdTwoPoint) {
2018-03-26 17:06:47 +00:00
dialog::editNumber(vid.twopoint_param, 0, 10, .1, 1, XLAT("parameter"),
"This model maps the world so that the distances from two points "
"are kept. This parameter gives the distance from the two points to "
"the center."
);
dialog::scaleLog();
}
2018-10-23 14:58:19 +00:00
else if(uni == 'b')
config_camera_rotation();
2018-03-25 12:04:40 +00:00
else if(uni == 'x' && pmodel == mdBall)
dialog::editNumber(vid.ballproj, 0, 100, .1, 0, XLAT("projection in ball model"),
"This parameter affects the ball model the same way as the projection parameter affects the disk model.");
else if(sym == 'x' && pmodel == mdPolygonal)
dialog::editNumber(polygonal::SI, 3, 10, 1, 4, XLAT("polygon sides"), "");
else if(sym == 'y' && pmodel == mdPolygonal)
dialog::editNumber(polygonal::STAR, -1, 1, .1, 0, XLAT("star factor"), "");
else if(sym == 'n' && pmodel == mdPolygonal)
dialog::editNumber(polygonal::deg, 2, polygonal::MSI-1, 1, 2, XLAT("degree of the approximation"), "");
else if(sym == 'x' && pmodel == mdPolynomial) {
polygonal::maxcoef = max(polygonal::maxcoef, polygonal::coefid);
int ci = polygonal::coefid + 1;
dialog::editNumber(polygonal::coefr[polygonal::coefid], -10, 10, .01/ci/ci, 0, XLAT("coefficient"), "");
}
else if(sym == 'y' && pmodel == mdPolynomial) {
polygonal::maxcoef = max(polygonal::maxcoef, polygonal::coefid);
int ci = polygonal::coefid + 1;
dialog::editNumber(polygonal::coefi[polygonal::coefid], -10, 10, .01/ci/ci, 0, XLAT("coefficient (imaginary)"), "");
}
else if(sym == 'n' && pmodel == mdPolynomial)
dialog::editNumber(polygonal::coefid, 0, polygonal::MSI-1, 1, 0, XLAT("which coefficient"), "");
2018-03-25 13:07:11 +00:00
else if(sym == 'r') {
if(rotation < 0) rotation = 0;
dialog::editNumber(rotation, 0, 360, 90, 0, XLAT("rotation"),
"This controls the automatic rotation of the world. "
"It affects the line animation in the history mode, and "
"lands which have a special direction. Note that if finding this special direction is a part of the puzzle, "
"it works only in the cheat mode.");
dialog::dialogflags |= sm::CENTER;
dialog::extra_options = [] () {
dialog::addBreak(100);
dialog::addBoolItem("line animation only", conformal::do_rotate == 0, 'n');
dialog::add_action([] () { conformal::do_rotate = 0; });
dialog::addBoolItem("gravity lands", conformal::do_rotate == 1, 'g');
dialog::add_action([] () { conformal::do_rotate = 1; });
dialog::addBoolItem("all directional lands", conformal::do_rotate == 2, 'd');
dialog::add_action([] () { conformal::do_rotate = 2; });
};
2018-03-25 13:07:11 +00:00
}
2018-03-25 12:04:40 +00:00
else if(doexiton(sym, uni)) popScreen();
};
}
bool band_renderable_now() {
return on && (pmodel == mdBand || pmodel == mdBandEquidistant || pmodel == mdBandEquiarea) && !euclid && !sphere;
}
2018-03-25 12:04:40 +00:00
void history_menu() {
cmode = sm::SIDE | sm::MAYDARK;
gamescreen(0);
dialog::init(XLAT("history mode"));
dialog::addBoolItem(XLAT("include history"), (includeHistory), 'i');
// bool notconformal0 = (pmodel >= 5 && pmodel <= 6) && !euclid;
// bool notconformal = notconformal0 || abs(vid.alpha-1) > 1e-3;
dialog::addSelItem(XLAT("model used"), get_model_name(pmodel), 'm');
if(!bounded && !euclid) dialog::addBoolItem(XLAT("prepare the line animation"), (on), 'e');
2017-03-23 10:53:57 +00:00
if(on) dialog::addSelItem(XLAT("animation speed"), fts(lvspeed), 'a');
dialog::addSelItem(XLAT("extend the ends"), fts(extra_line_steps), 'p');
2016-08-26 09:58:03 +00:00
2017-07-22 23:33:27 +00:00
#if CAP_SDL
2017-03-23 10:53:57 +00:00
dialog::addBoolItem(XLAT("render bands automatically"), (autoband), 'o');
2016-08-26 09:58:03 +00:00
if(autoband)
2017-03-23 10:53:57 +00:00
dialog::addBoolItem(XLAT("include history when auto-rendering"), (autobandhistory), 'j');
2016-08-26 09:58:03 +00:00
if(band_renderable_now() || autoband) {
2017-03-23 10:53:57 +00:00
dialog::addSelItem(XLAT("band width"), "2*"+its(bandhalf), 'd');
dialog::addSelItem(XLAT("length of a segment"), its(bandsegment), 's');
dialog::addBoolItem(XLAT("spiral on rendering"), (dospiral), 'g');
if(band_renderable_now())
dialog::addItem(XLAT("render now (length: %1)", fts(measureLength())), 'f');
2016-08-26 09:58:03 +00:00
}
#endif
dialog::addBack();
2017-03-23 10:53:57 +00:00
dialog::display();
2018-03-27 12:09:10 +00:00
mouseovers = XLAT("see http://www.roguetemple.com/z/hyper/models.php");
2017-07-10 18:47:38 +00:00
keyhandler = handleKeyC;
2016-08-26 09:58:03 +00:00
}
2017-07-10 18:47:38 +00:00
void handleKeyC(int sym, int uni) {
2017-03-23 10:53:57 +00:00
dialog::handleNavigation(sym, uni);
2016-08-26 09:58:03 +00:00
2018-03-25 12:04:40 +00:00
if(uni == 'e') {
2016-08-26 09:58:03 +00:00
if(on) clear();
else {
if(canmove && !cheater) {
addMessage("Enable cheat mode or GAME OVER to use this");
return;
}
if(canmove && cheater) cheater++;
create();
}
}
else if(uni == 'o')
autoband = !autoband;
2018-03-25 12:04:40 +00:00
else if(uni == 'm')
pushScreen(model_menu);
2017-03-23 10:53:57 +00:00
else if(sym == 'a')
dialog::editNumber(lvspeed, -5, 5, .1, 1, XLAT("animation speed"), "");
else if(sym == 'd')
dialog::editNumber(bandhalf, 5, 1000, 5, 200, XLAT("band width"), "");
else if(sym == 's')
dialog::editNumber(bandsegment, 500, 32000, 500, 16000, XLAT("band segment"), "");
else if(sym == 'p')
dialog::editNumber(extra_line_steps, 0, 5, 1, 1, XLAT("extend the ends"),
"0 = start at the game start, endat the end position; "
"larger numbers give extra space at the ends."
);
2016-08-26 09:58:03 +00:00
else if(sym == 'g') { dospiral = !dospiral; }
else if(sym == 'i') {
if(canmove && !cheater) {
addMessage("Enable cheat mode or GAME OVER to use this");
return;
}
if(canmove && cheater) cheater++;
includeHistory = !includeHistory;
}
2018-03-25 12:04:40 +00:00
#if CAP_SDL
else if(uni == 'f' && band_renderable_now()) createImage(dospiral);
2018-03-25 12:04:40 +00:00
#endif
2016-08-26 09:58:03 +00:00
else if(sym == 'j') {
autobandhistory = !autobandhistory;
}
2017-07-10 18:47:38 +00:00
else if(doexiton(sym, uni)) popScreen();
2016-08-26 09:58:03 +00:00
}
set<cell*> inmovehistory, inkillhistory, infindhistory;
2016-08-26 09:58:03 +00:00
void restore() {
inmovehistory.clear();
inkillhistory.clear();
infindhistory.clear();
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(movehistory); i++)
inmovehistory.insert(movehistory[i]);
2018-06-22 12:47:24 +00:00
int sk = isize(killhistory);
2016-08-26 09:58:03 +00:00
for(int i=0; i<sk; i++) {
eMonster m = killhistory[i].second;
killhistory[i].second = killhistory[i].first->monst;
killhistory[i].first->monst = m;
inkillhistory.insert(killhistory[i].first);
2016-08-26 09:58:03 +00:00
}
2018-06-22 12:47:24 +00:00
int si = isize(findhistory);
2016-08-26 09:58:03 +00:00
for(int i=0; i<si; i++) {
eItem m = findhistory[i].second;
findhistory[i].second = findhistory[i].first->item;
findhistory[i].first->item = m;
infindhistory.insert(findhistory[i].first);
2016-08-26 09:58:03 +00:00
}
}
void restoreBack() {
2018-06-22 12:47:24 +00:00
int sk = isize(killhistory);
2016-08-26 09:58:03 +00:00
for(int i=sk-1; i>=0; i--) {
eMonster m = killhistory[i].second;
killhistory[i].second = killhistory[i].first->monst;
killhistory[i].first->monst = m;
}
2018-06-22 12:47:24 +00:00
int si = isize(findhistory);
2016-08-26 09:58:03 +00:00
for(int i=si-1; i>=0; i--) {
eItem m = findhistory[i].second;
findhistory[i].second = findhistory[i].first->item;
findhistory[i].first->item = m;
}
}
void renderAutoband() {
2017-07-22 23:33:27 +00:00
#if CAP_SDL
if(!cwt.at || celldist(cwt.at) <= 7) return;
2016-08-26 09:58:03 +00:00
if(!autoband) return;
2017-03-23 10:53:57 +00:00
eModel spm = pmodel;
2016-08-26 09:58:03 +00:00
bool ih = includeHistory;
includeHistory = autobandhistory;
2017-03-23 10:53:57 +00:00
pmodel = mdBand;
2016-08-26 09:58:03 +00:00
create();
createImage(dospiral);
clear();
pmodel = spm;
includeHistory = ih;
#endif
2017-07-10 18:47:38 +00:00
}
auto hooks = addHook(clearmemory, 0, [] () {
conformal::renderAutoband();
conformal::on = false;
conformal::killhistory.clear();
conformal::findhistory.clear();
conformal::movehistory.clear();
conformal::includeHistory = false;
});
2016-08-26 09:58:03 +00:00
}
}