diff --git a/history.cpp b/history.cpp new file mode 100644 index 00000000..aaab18a9 --- /dev/null +++ b/history.cpp @@ -0,0 +1,653 @@ +// Hyperbolic Rogue -- the history mode +// Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details + +/** \file history.cpp + * \brief Implementation of the history mode, including the long band and long spiral. + */ + +namespace hr { + +#if CAP_SDL +namespace spiral { + + typedef long double ld; + typedef complex cxld; + + int shiftx, shifty, velx, vely; + + vector > quickmap; + + int CX, CY, SX, SY, Yshift; + + vector band; + SDL_Surface *out; + + bool displayhelp = true; + + color_t& bandpixel(int x, int y) { + int i = 0; + while(i < isize(band) && x >= band[i]->w) + x -= band[i]->w, i++; + return qpixel(band[i], x, y); + } + + void precompute() { + + CX = 0; + for(int i=0; iw; + if(CX == 0) { printf("ERROR: no CX\n"); return; } + CY = band[0]->h; + SX = out->w; + SY = out->h; + + ld k = -2*M_PI*M_PI / log(2.6180339); + +// cxld mnoznik = cxld(0, M_PI) / cxld(k, M_PI); + + cxld factor = cxld(0, -CY/2/M_PI/M_PI) * cxld(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 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; + qpixel(out, x, y) = bandpixel(cx, cy); + } + } + + void loop(vector _band) { + + band = _band; + out = s; + precompute(); + if(CX == 0) return; + shiftx = shifty = 0; + velx=1; vely=1; + bool dosave = false; + + bool saveGL = vid.usingGL; + if(saveGL) switchGL(); // { vid.usingGL = false; setvideomode(); } + + 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) { + 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); + } + 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; + } + case SDL_QUIT: case SDL_MOUSEBUTTONDOWN: + goto breakloop; + + case SDL_KEYDOWN: { + int sym = event.key.keysym.sym; + int uni = event.key.keysym.unicode; + numlock_on = event.key.keysym.mod & KMOD_NUM; + if(DKEY == SDLK_RIGHT) velx++; + if(DKEY == SDLK_LEFT) velx--; + if(DKEY == SDLK_UP) vely++; + if(DKEY == SDLK_DOWN) vely--; + if(sym == SDLK_ESCAPE) goto breakloop; + if(uni == 'h') displayhelp = !displayhelp; + if(uni == 's') dosave = true; + } + } + } + + breakloop: + quickmap.clear(); + if(saveGL) switchGL(); // { vid.usingGL = true; setvideomode(); } + } + + } +#endif + +EX namespace history { + + void handleKeyC(int sym, int uni); + + int lastprogress; + + EX void progress_screen() { + gamescreen(0); + mouseovers = ""; + } + + EX void progress(string str) { +#if CAP_SDL + int tick = SDL_GetTicks(); + if(tick > lastprogress + 250) { + lastprogress = tick; + msgs.clear(); + addMessage(str); + drawscreen(); + } +#endif + } + + EX bool on; + EX vector v; + int llv; + EX double phase; + + EX vector > killhistory; + EX vector > findhistory; + EX vector movehistory; + + EX bool includeHistory; + EX ld lvspeed = 1; + EX int bandhalf = 200; + EX int bandsegment = 16000; + + EX bool autoband = false; + EX bool autobandhistory = false; + EX bool dospiral = true; + + EX ld extra_line_steps = 0; + + EX vector path_for_lineanimation; + + EX void clear() { + on = false; + int N = isize(v); + for(int i=0; i 1)) { + addMessage("Must go a distance from the starting point"); + return; + } + + on = true; + + if(!quotient) try { + path_for_lineanimation = build_shortest_path(start, target); + } + catch(hr_shortest_path_exception&) { + addMessage("Error: could not build a path"); + return; + } + + for(cell *c: path_for_lineanimation) { + shmup::monster *m = new shmup::monster; + m->at = Id; + m->base = c; + v.push_back(m); + } + + v.back()->at = last; + + int Q = isize(v)-1; + // virtualRebase(v[0], false); + // virtualRebase(v[Q], false); + + for(int i=0; i<1000; i++) { + progress(XLAT("Preparing the line (%1/1000)...", its(i+1))); + + for(int j=1; jbase, v[j]->base, C0) * + v[j-1]->at * C0; + + hyperpoint next = calc_relative_matrix(v[j+1]->base, v[j]->base, C0) * + v[j+1]->at * C0; + + hyperpoint hmid = mid(prev, next); + + transmatrix at = rgpushxto0(hmid); + + v[j]->at = at * rspintox(inverse(at) * next); + 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); + + llv = ticks; + phase = 0; + } + + EX void create_playerpath() { + create(currentmap->gamestart(), cwt.at, Id); + } + + EX void create_recenter_to_view(bool precise) { + cell *c = centerover.at ? centerover.at : cwt.at; + create(path_for_lineanimation[0], c, precise ? inverse(ggmatrix(c)) : Id); + } + + EX void movetophase() { + + int ph = int(phase); + int siz = isize(v); + if(ph<0) ph = 0; + if(ph >= siz-1) ph = siz-2; + + heptagon *old = viewctr.at; + + viewctr.at = v[ph]->base->master; + viewctr.spin = 0; + + ld angle = 0; + if(WDIM == 3) { + hyperpoint h = inverse(models::rotmatrix()) * View * hpxy3(1,2,3); + angle = atan2(h[1], h[2]); + } + + View = inverse(master_relative(v[ph]->base) * v[ph]->at); + + hyperpoint now = v[ph]->at * C0; + + hyperpoint next = calc_relative_matrix(v[ph+1]->base, v[ph]->base, C0) * + v[ph+1]->at * C0; + + View = xpush(-(phase-ph) * hdist(now, next)) * View; + if(WDIM == 2) { + View = models::rotmatrix() * View; + } + else { + if(celldistance(v[ph]->base, old->c7) <= 2) { + hyperpoint h1 = View * currentmap->relative_matrix(old, viewctr.at) * hpxy3(1,2,3); + ld angle1 = atan2(h1[1], h1[2]); + View = cspin(2, 1, angle1 - angle) * View; + } + View = models::rotmatrix() * View; + } + + playermoved = false; + centerover.at = v[ph]->base; + compute_graphical_distance(); + } + + EX void apply() { + int t = ticks; + phase += (t-llv) * lvspeed / 400.; + llv = t; + + 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(); + } + + ld measureLength() { + ld r = bandhalf * vid.scale; + + ld tpixels = 0; + int siz = isize(v); + + for(int j=0; jat) * + calc_relative_matrix(v[j+1]->base, v[j]->base, C0) * + v[j+1]->at * C0; + + hyperpoint nextscr; + applymodel(next, nextscr); + tpixels += nextscr[0] * r; + + if(j == 0 || j == siz-2) + tpixels += nextscr[0] * r * extra_line_steps; + } + + return tpixels; + } + + void restore(); + void restoreBack(); + +#if CAP_SDL + void createImage(bool dospiral) { + int segid = 1; + if(includeHistory) restore(); + + int bandfull = 2*bandhalf; + ld len = measureLength(); + + time_t timer; + timer = time(NULL); + char timebuf[128]; + strftime(timebuf, 128, "%y%m%d-%H%M%S", localtime(&timer)); + + vector bands; + + resetbuffer rbuf; + + if(1) { + // block for RAII + dynamicval dv(vid, vid); + dynamicval dr(models::rotation, 0); + dynamicval di(inHighQual, true); + + renderbuffer glbuf(bandfull, bandfull, vid.usingGL); + vid.xres = vid.yres = bandfull; + glbuf.enable(); current_display->radius = bandhalf; + calcparam(); + + 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 { + + int siz = isize(v); + + int bonus = ceil(extra_line_steps); + + cell *last_base = NULL; + hyperpoint last_relative; + + for(int j=-bonus; jradius = 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 = -current_display->radius * hscr[0]; + print(hlog, "bwidth = ", bwidth, "/", len); + + drawsegment: + SDL_Surface *gr = glbuf.render(); + + for(int cy=0; cy bandsegment) { + char buf[154]; + sprintf(buf, "bandmodel-%s-%03d" IMAGEEXT, timebuf, segid++); + + 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; + } + } + + 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); + } + + rbuf.reset(); + + if(includeHistory) restoreBack(); + + if(dospiral) { + spiral::loop(bands); + for(int i=0; i= 5 && pmodel <= 6) && !euclid; + // bool notconformal = notconformal0 || abs(vid.alpha-1) > 1e-3; + + dialog::addSelItem(XLAT("projection"), current_proj_name(), 'm'); + + dialog::addBoolItem(XLAT("animate from start to current player position"), (on), 'e'); + dialog::addBoolItem(XLAT("animate from last recenter to current view"), (on), 'E'); + dialog::addBoolItem(XLAT("animate from last recenter to precise current view"), (on), 'E'-64); + if(on) dialog::addSelItem(XLAT("animation speed"), fts(lvspeed), 'a'); + else dialog::addBreak(100); + dialog::addSelItem(XLAT("extend the ends"), fts(extra_line_steps), 'p'); + +#if CAP_SDL + dialog::addBoolItem(XLAT("render bands automatically"), (autoband), 'o'); + if(autoband) + dialog::addBoolItem(XLAT("include history when auto-rendering"), (autobandhistory), 'j'); + + if(band_renderable_now() || autoband) { + 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'); + } +#endif + + dialog::addBack(); + dialog::display(); + mouseovers = XLAT("see http://www.roguetemple.com/z/hyper/models.php"); + keyhandler = handleKeyC; + } + + void handleKeyC(int sym, int uni) { + dialog::handleNavigation(sym, uni); + + if(uni == 'e' || uni == 'E' || uni == 'E'-64) { + if(on) clear(); + else { + if(canmove && !cheater) { + addMessage("Enable cheat mode or GAME OVER to use this"); + return; + } + if(canmove && cheater) cheater++; + if(uni == 'E') create_recenter_to_view(false); + else if(uni == 'E'-64) create_recenter_to_view(true); + else create_playerpath(); + } + } + else if(uni == 'o') + autoband = !autoband; + else if(uni == 'm') + pushScreen(models::model_menu); + else if(uni == 'a') + dialog::editNumber(lvspeed, -5, 5, .1, 1, XLAT("animation speed"), ""); + else if(uni == 'd') { + dialog::editNumber(bandhalf, 5, 1000, 5, 200, XLAT("band width"), ""); + dialog::bound_low(5); + } + else if(uni == 's') { + dialog::editNumber(bandsegment, 500, 32000, 500, 16000, XLAT("band segment"), ""); + dialog::bound_low(500); + } + else if(uni == '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." + ); + else if(uni == 'g') { dospiral = !dospiral; } + else if(uni == 'i') { + if(canmove && !cheater) { + addMessage("Enable cheat mode or GAME OVER to use this"); + return; + } + if(canmove && cheater) cheater++; + includeHistory = !includeHistory; + } +#if CAP_SDL + else if(uni == 'f' && band_renderable_now()) createImage(dospiral); +#endif + else if(uni == 'j') { + autobandhistory = !autobandhistory; + } + else if(doexiton(sym, uni)) popScreen(); + } + + set inmovehistory, inkillhistory, infindhistory; + + EX void restore() { + inmovehistory.clear(); + inkillhistory.clear(); + infindhistory.clear(); + for(int i=0; imonst; + killhistory[i].first->monst = m; + inkillhistory.insert(killhistory[i].first); + } + int si = isize(findhistory); + for(int i=0; iitem; + findhistory[i].first->item = m; + infindhistory.insert(findhistory[i].first); + } + } + + EX void restoreBack() { + int sk = isize(killhistory); + 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; + } + int si = isize(findhistory); + 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; + } + } + + EX void renderAutoband() { +#if CAP_SDL + if(!cwt.at || celldist(cwt.at) <= 7) return; + if(!autoband) return; + eModel spm = pmodel; + bool ih = includeHistory; + includeHistory = autobandhistory; + pmodel = mdBand; + create_playerpath(); + createImage(dospiral); + clear(); + pmodel = spm; + includeHistory = ih; +#endif + } + + #if CAP_COMMANDLINE + int readArgs() { + using namespace arg; + + if(0) ; + else if(argis("-playerpath")) { + history::create_playerpath(); + } + else return 1; + return 0; + } + + auto hookArg = addHook(hooks_args, 100, readArgs); + #endif + + auto hooks = addHook(clearmemory, 0, [] () { + history::renderAutoband(); + history::on = false; + history::killhistory.clear(); + history::findhistory.clear(); + history::movehistory.clear(); + history::path_for_lineanimation.clear(); + history::includeHistory = false; + }); + + } + +}