From 920bf9608887e2ff982aa52ac1cadf1a941a1715 Mon Sep 17 00:00:00 2001 From: Zeno Rogue Date: Sun, 24 Apr 2022 19:48:45 +0200 Subject: [PATCH] nilrider:: first commit --- rogueviz/nilrider/level.cpp | 312 +++++++++++++++++++++++ rogueviz/nilrider/levels.cpp | 430 ++++++++++++++++++++++++++++++++ rogueviz/nilrider/nilrider.cpp | 290 +++++++++++++++++++++ rogueviz/nilrider/nilrider.h | 130 ++++++++++ rogueviz/nilrider/planning.cpp | 287 +++++++++++++++++++++ rogueviz/nilrider/statues.cpp | 148 +++++++++++ rogueviz/nilrider/timestamp.cpp | 244 ++++++++++++++++++ 7 files changed, 1841 insertions(+) create mode 100644 rogueviz/nilrider/level.cpp create mode 100644 rogueviz/nilrider/levels.cpp create mode 100644 rogueviz/nilrider/nilrider.cpp create mode 100644 rogueviz/nilrider/nilrider.h create mode 100644 rogueviz/nilrider/planning.cpp create mode 100644 rogueviz/nilrider/statues.cpp create mode 100644 rogueviz/nilrider/timestamp.cpp diff --git a/rogueviz/nilrider/level.cpp b/rogueviz/nilrider/level.cpp new file mode 100644 index 00000000..e12d3e28 --- /dev/null +++ b/rogueviz/nilrider/level.cpp @@ -0,0 +1,312 @@ +namespace nilrider { + +void level::init() { + if(initialized) return; + initialized = true; + + unil_texture = new texture::texture_data; + + auto& tex = *unil_texture; + + real_minx = HUGE_VAL; + real_miny = HUGE_VAL; + real_maxx = -HUGE_VAL; + real_maxy = -HUGE_VAL; + + if(flags & nrlPolar) + scale = 1; + else + scale = abs(maxx - minx) / isize(map_tiles[0]); + + println(hlog, "SCALE IS ", this->scale); + + int tY = isize(map_tiles); + int tX = isize(map_tiles[0]); + + tex.twidth = tex.tx = tX * 64; + tex.theight = tex.ty = tY * 64; + tex.stretched = false; + tex.strx = tex.tx; + tex.stry = tex.ty; + tex.base_x = 0; + tex.base_y = 0; + tex.whitetexture(); + println(hlog, "tX=", tX, " tY=", tY, " tex=", tex.tx, "x", tex.ty); + + for(int y=0; ytextureid; + } + + for(auto st: statues) queuepoly(V * st.T, *st.shape, st.color); + + queuepoly(V, shField, 0xFFFF00FF); + + auto& poly = queuepoly(V, shFloor, 0xFFFFFFFF); // 0xFFFFFFFF); + poly.tinf = &uniltinf; + uniltinf.texture_id = unil_texture->textureid; + } + +} diff --git a/rogueviz/nilrider/levels.cpp b/rogueviz/nilrider/levels.cpp new file mode 100644 index 00000000..a5915bfc --- /dev/null +++ b/rogueviz/nilrider/levels.cpp @@ -0,0 +1,430 @@ +namespace nilrider { + +ld f_heisenberg0(hyperpoint h) { return 0; } + +ld rot_plane(hyperpoint h) { + return h[0] * h[1] / 2; + } + +ld f_rot_well(hyperpoint h) { + return h[0] * h[1] / 2 + h[0] * h[0] + h[1] * h[1]; + } + +ld long_x(hyperpoint h) { + return h[0] * h[1]; + } + +ld geodesics_0(hyperpoint h) { + ld r = hypot_d(2, h); + ld phi = atan2(h[1], h[0]); + + ld z = (phi / 2 / M_PI) * (M_PI * r * r + 2 * M_PI); + return z + rot_plane(h); + } + +ld geodesics_at_4(hyperpoint h) { + ld r = 4; + ld phi = atan2(h[1], h[0]); + + ld z = (phi / 2 / M_PI) * (M_PI * r * r + 2 * M_PI); + return z + rot_plane(h); + } + +map bcols = { + {' ', 0xFF101010}, + {'W', 0xFFFFFFFF}, + {'g', 0xFF008000}, + {'h', 0xFF20A020}, + {'r', 0xFFFF4040}, + {'u', 0xFF4040FF}, + {'b', 0xFF804000}, + {'l', 0xFF0000C0}, + {'f', 0xFF603000}, + {'F', 0xFF804000}, + {'2', 0xFF404040}, + {'4', 0xFF808080}, + {'6', 0xFFC0C0C0}, + }; + +map > submaps = { + {'o', { + "WWWWWWWWWWWWWWWW", + "W22222222222222W", + "W22222666622222W", + "W22266222266222W", + "W22622222222622W", + "W22622222222622W", + "W26222222222262W", + "W262222WW222262W", + "W262222WW222262W", + "W26222222222262W", + "W22622222222622W", + "W22622222222622W", + "W22266222266222W", + "W22222666622222W", + "W22222222222222W", + "WWWWWWWWWWWWWWWW" + }}, + {'x', { + "WWWWWWWWWWWWWWWW", + "W22222222222222W", + "W22222222222222W", + "W22222222222222W", + "W22222222222222W", + "W22222222222222W", + "W22222622622222W", + "W222222rW222222W", + "W222222Wr222222W", + "W22222622622222W", + "W22222222222222W", + "W22222222222222W", + "W22222222222222W", + "W22222222222222W", + "W22222222222222W", + "WWWWWWWWWWWWWWWW" + }}, + {'b', { + " ", + " rrr rrr rrr rrr", + " ", + "rr rrr rrr rrr r", + " ", + " rrr rrr rrr rrr", + " ", + "rr rrr rrr rrr r", + " ", + " rrr rrr rrr rrr", + " ", + "rr rrr rrr rrr r", + " ", + " rrr rrr rrr rrr", + " ", + "rr rrr rrr rrr r", + }}, + {'f', { + "FfFfFfFfFfFfFfFf", + "fFfFfFfFfFfFfFfF", + "FfFfFfFfFfFfFfFf", + "fFfFfFfFfFfFfFfF", + "FfFfFfFfFfFfFfFf", + "fFfFfFfFfFfFfFfF", + "FfFfFfFfFfFfFfFf", + "fFfFfFfFfFfFfFfF", + "FfFfFfFfFfFfFfFf", + "fFfFfFfFfFfFfFfF", + "FfFfFfFfFfFfFfFf", + "fFfFfFfFfFfFfFfF", + "FfFfFfFfFfFfFfFf", + "fFfFfFfFfFfFfFfF", + "FfFfFfFfFfFfFfFf", + "fFfFfFfFfFfFfFfF", + }}, + {'l', { + "llllllllllllllll", + "llllllllllllllll", + "llllllllllllllll", + "llllllllllllllll", + "llllllllllllllll", + "llllllllllllllll", + "llllllllllllllll", + "llllllllllllllll", + "llllllllllllllll", + "llllllllllllllll", + "llllllllllllllll", + "llllllllllllllll", + "llllllllllllllll", + "llllllllllllllll", + "llllllllllllllll", + "llllllllllllllll", + }}, + {'g', { + "ghghghghghghghgh", + "hghghghghghghghg", + "ghghghghghghghgh", + "hghghghghghghghg", + "ghghghghghghghgh", + "hghghghghghghghg", + "ghghghghghghghgh", + "hghghghghghghghg", + "ghghghghghghghgh", + "hghghghghghghghg", + "ghghghghghghghgh", + "hghghghghghghghg", + "ghghghghghghghgh", + "hghghghghghghghg", + "ghghghghghghghgh", + "hghghghghghghghg", + }}, + {'G', { + "ghghghghghghghgh", + "hghghghghghWhghg", + "ghghrhghghWlWhgh", + "hghrWrhghghWhghg", + "ghghrhghghghghgh", + "hghghghghghghghg", + "ghghghghghghghgh", + "hghghghlhghghghg", + "ghghghlWlhghghgh", + "hghghghlhghghghg", + "ghghghghghghgrgh", + "hghglghghghgrWrg", + "ghglWlghghghgrgh", + "hghglghghghghghg", + "ghghghghghghghgh", + "hghghghghghghghg", + }}, + {'r', { + "rrrrrrrrrrrrrrru", + "ubbbbbbbbbbbbbbu", + "ubbbbbbbbbbbbbbu", + "ubbbbbbbbbbbbbbu", + "ubbbbbbbbbbbbbbu", + "ubbbbbbbbbbbbbbu", + "ubbbbbbbbbbbbbbu", + "ubbbbbbbbbbbbbbu", + "ubbbbbbbbbbbbbbu", + "ubbbbbbbbbbbbbbu", + "ubbbbbbbbbbbbbbu", + "ubbbbbbbbbbbbbbu", + "ubbbbbbbbbbbbbbu", + "ubbbbbbbbbbbbbbu", + "ubbbbbbbbbbbbbbu", + "urrrrrrrrrrrrrrr", + }}, + {'*', { + "WWWWWW WW WWWWWW", + "W W", + "W W", + "W W", + "W W", + "W rr W", + " rr ", + "W r r W", + "W r r W", + " r r ", + "W r r W", + "W rrrrrrrr W", + "W W", + "W W", + "W W", + "WWWWWW WW WWWWWW", + }}, + {'+', { + "gh WW gh", + "hg WW hg", + " WW ", + " ", + " ", + " WW ", + " WW ", + "WWW WWWWWW WWW", + "WWW WWWWWW WWW", + " WW ", + " WW ", + " ", + " ", + " WW ", + "gh WW gh", + "hg WW hg", + }}, + {'-', { + "ghghghghghghghgh", + "hghghghghghghghg", + " ", + " ", + " ", + " ", + " ", + "WWW WWWWWW WWW", + "WWW WWWWWW WWW", + " ", + " ", + " ", + " ", + " ", + "ghghghghghghghgh", + "hghghghghghghghg", + }}, + {'|', { + "gh WW gh", + "hg WW hg", + "gh WW gh", + "hg hg", + "gh gh", + "hg WW hg", + "gh WW gh", + "hg WW hg", + "gh WW gh", + "hg WW hg", + "gh WW gh", + "hg hg", + "gh gh", + "hg WW hg", + "gh WW gh", + "hg WW hg", + }}, + }; + +level rotplane( + "Trying to be horizontal", 'r', 0, + "All the lines going through the center are horizontal.", + -7.5*dft_block, 7.5*dft_block, 8.5*dft_block, -8.5*dft_block, + { + "ggggggggggggggg!", + "ggggggfffgggggg!", + "ggggggfffgggggg!", + "gggg|ggggg|gggg!", + "ggg-+-----+-ggg!", + "gggg|ggggf|gggg!", + "ggGg|g+ggg|grgG!", + "gGgg|g|xgo|gggg!", + "ggGg|g|ggg|grgg!", + "gggg|g|ggg|gggg!", + "gg--+-+---+--gg!", + "gggg|ggggg|gggg!", + "gggggggGGgggggg!", + "ggggggggggggggg!", + "ggggggggggggggg!", + "!!!!!!!!!!!!!!!!" + }, + 6, 6, + rot_plane + ); + +level longtrack( + "A Long Track", 'l', 0, + "The main street is horizontal, as well as the lines orthogonal to it.", + 0*dft_block, +2.5*dft_block, 64*dft_block, -1.5*dft_block, + { + "gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg", + "gggggggrgggggggrggggggggrGggggggggGGggggGGGgggggGGGGggggggggggGG", + "g+------------------------------------------------------------+G", + "gggggfffffggggggggggggggggggggggggggggggggggggggggggggggggggggGG" + }, + 0, 1, + long_x + ); + +level geodesical( + "Roads are Geodesics", 'g', nrlPolar, + "All the roads here are helical geodesics.", + -45*degree, 3*dft_block, 225*degree, 0, + // -8*dft_block, +8*dft_block, +8*dft_block, 0, + { + "ffffffffffffffff", + "----------------", + "----------------", + "----------------", + "----------------", + "----------------", + "----------------", + "bbbbbbbbbbbbbbbb", + }, + 0, 6, + geodesics_0 + ); + +level geodesical4( + "Helical Geodesic", 's', nrlPolar, + "The main road here is a helical geodesic. Orthogonal lines are horizontal.", + -80*degree, 8.5*dft_block, 260*degree, 0.5*dft_block, + // -8*dft_block, +8*dft_block, +8*dft_block, 0, + { + "!!!!!!!!!!!!!!!!", + "ffffffffffffffff", + "gggggggggggggggg", + "ggGggggggggGgggg", + "+--------------+", + "gggggGggggGggggg", + "gggGgggggGgggggg", + "ffffffffffffffff", + }, + 0, 5, + geodesics_at_4 + ); + +level heisenberg0( + "Heisenberg Zero", 'z', 0, + "This is the plane z=0 in the Heisenberg group model of Nil. The roads are x=0, y=0 axes.", + -7.5*dft_block, 7.5*dft_block, 8.5*dft_block, -8.5*dft_block, + { + "ggggggg|ggggggg!", + "grggggg|gggggrg!", + "ggggggg|ggggggg!", + "gggffgg|ggggggg!", + "gggffgg|ggfrggg!", + "ggggggg|gggggGg!", + "ggggggg|ggggggg!", + "-------+-------!", + "ggggggg|ggggggg!", + "gggGgog|ggggggg!", + "ggggggg|ggrgrgg!", + "gggGgGg|ggggggg!", + "ggggggg|ggggggg!", + "grggggg|gggggrg!", + "ggggggg|ggggggg!", + "!!!!!!!!!!!!!!!!" + }, + 8, 8, + f_heisenberg0 + ); + +level rotwell( + "Deep Well", 'd', 0, + "Can you escape this well?", + -7.5*dft_block, 7.5*dft_block, 8.5*dft_block, -8.5*dft_block, + { + "ggggggggggggggg!", + "gogggggggggggog!", + "ggggg--*--ggggg!", + "gggg*ggggg*gggg!", + "ggg*ggGfGgg*ggg!", + "gg|ggfgggfgg|gg!", + "gg|gGgggggGg|gg!", + "gg*gfggxggfg*gg!", + "gg|gGgggggGg|gg!", + "gg|ggfgggfgg|gg!", + "ggg*ggGfGgg*ggg!", + "gggg*ggggg*gggg!", + "ggggg--*--ggggg!", + "gogggggggggggog!", + "ggggggggggggggg!", + "!!!!!!!!!!!!!!!!" + }, + 8, 8, + f_rot_well + ); + +level labyrinth( + "Labyrinth", 'l', 0, + "Go clockwise. The squares of this level have half of their usual length.", + -7.5*dft_block/2, 7.5*dft_block/2, 8.5*dft_block/2, -8.5*dft_block/2, + { + "ogggrfffffffffo!", + "g*ggrgggggggggg!", + "ggggrgggggggggg!", + "ggggrgggggggggg!", + "ggggrgggrrggggg!", + "ggggrgGGGrrgggg!", + "ggggrGgggGrgggg!", + "ggggrGgxgGrgggg!", + "ggggrGgggGrgggg!", + "ggggrrGGGrrgggg!", + "gggggrrrrrggggg!", + "ggggggggggggggg!", + "ggggggggggggggg!", + "ggggggggggggggg!", + "offfffffffffffo!", + "!!!!!!!!!!!!!!!!" + }, + 8, 8, + rot_plane + ); + +level *curlev = &rotplane; + +vector all_levels = { + &rotplane, &longtrack, &geodesical, &geodesical4, &heisenberg0, &rotwell, &labyrinth + }; + +} diff --git a/rogueviz/nilrider/nilrider.cpp b/rogueviz/nilrider/nilrider.cpp new file mode 100644 index 00000000..fb53e70a --- /dev/null +++ b/rogueviz/nilrider/nilrider.cpp @@ -0,0 +1,290 @@ +#include "nilrider.h" +#include "statues.cpp" +#include "timestamp.cpp" +#include "levels.cpp" +#include "level.cpp" +#include "planning.cpp" + +namespace nilrider { + +/** is the game paused? */ +bool paused = false; + +bool planning_mode = false; +bool view_simulation = false; +int simulation_start_tick; + +void frame() { + if(planning_mode && !view_simulation) return; + + shiftmatrix V = ggmatrix(cwt.at); + + curlev->draw_level(V); + + curlev->current.draw_unilcycle(V); + } + +bool turn(int delta) { + if(planning_mode && !view_simulation) return false; + Uint8 *keystate = SDL_GetKeyState(NULL); + if(keystate[SDLK_RIGHT] && !paused) curlev->current.heading_angle -= delta / 1000.; + if(keystate[SDLK_LEFT] && !paused) curlev->current.heading_angle += delta / 1000.; + + if(keystate[SDLK_UP] && !paused) min_gfx_slope -= delta / 1000.; + if(keystate[SDLK_DOWN] && !paused) min_gfx_slope += delta / 1000.; + + curlev->current.heading_angle += mouseaim_x; + min_gfx_slope += mouseaim_y; + + #if CAP_VR + if(vrhr::active()) { + curlev->current.heading_angle += vrhr::vraim_x * delta / 400; + min_gfx_slope -= vrhr::vraim_y * delta / 400; + } + #endif + + if(min_gfx_slope < -90*degree) min_gfx_slope = -90*degree; + if(min_gfx_slope > +90*degree) min_gfx_slope = +90*degree; + + if(!paused && !view_simulation) for(int i=0; ihistory.push_back(curlev->current); + bool b = curlev->current.tick(curlev); + if(b) timer += 1. / tps; + else curlev->history.pop_back(); + } + + if(!paused) curlev->current.centerview(curlev); + return false; + } + +void main_menu(); + +void run() { + cmode = sm::MAP; + clearMessages(); + dialog::init(); + if(view_simulation && !paused) { + int ttick = gmod(ticks - simulation_start_tick, isize(curlev->history)); + timer = ttick * 1. / tps; + curlev->current = curlev->history[ttick]; + curlev->current.centerview(curlev); + } + gamescreen(0); + if(planning_mode && !view_simulation) { + curlev->draw_planning_screen(); + if(!holdmouse) { + auto t0 = SDL_GetTicks(); + while(SDL_GetTicks() < t0 + 100) { + if(!curlev->simulate()) break; + } + } + } + curlev->current.draw_instruments(timer); + + if(paused && !planning_mode) { + displayButton(current_display->xcenter, current_display->ycenter, mousing ? XLAT("paused -- click to unpause") : XLAT("paused -- press p to continue"), 'p', 8); + } + + int x = vid.fsize; + auto show_button = [&] (char c, string s, color_t col = dialog::dialogcolor) { + if(displayButtonS(x, vid.yres - vid.fsize, s, col, 0, vid.fsize)) + getcstat = c; + x += textwidth(vid.fsize, s) + vid.fsize; + }; + + if(planning_mode && !view_simulation) { + for(auto& b: buttons) show_button(b.first, b.second, planmode == b.first ? 0xFFD500 : dialog::dialogcolor); + show_button('s', "simulation"); + } + + if(planning_mode && view_simulation) { + show_button('s', "return"); + show_button('p', "pause", paused ? 0xFF0000 : dialog::dialogcolor); + } + + if(!planning_mode) { + show_button('p', "pause", paused ? 0xFF0000 : dialog::dialogcolor); + } + + show_button('v', "menu"); + + dialog::add_key_action('v', [] { + paused = true; + pushScreen(main_menu); + }); + dialog::add_key_action('p', [] { + paused = !paused; + if(view_simulation && !paused) + simulation_start_tick = ticks - timer * tps; + }); + dialog::add_key_action('-', [] { + paused = false; + }); + dialog::add_key_action('b', [] { + if(planning_mode) + simulation_start_tick += 500; + else { + for(int i=0; i<500; i++) if(!curlev->history.empty()) curlev->history.pop_back(); + curlev->current = curlev->history.back(); + timer = isize(curlev->history) * 1. / tps; + } + }); + if(planning_mode) dialog::add_key_action('s', [] { + view_simulation = !view_simulation; + paused = false; + simulation_start_tick = ticks; + }); + dialog::display(); + + keyhandler = [] (int sym, int uni) { + if(paused) handlePanning(sym, uni); + if(planning_mode && !view_simulation && curlev->handle_planning(sym, uni)) return; + dialog::handleNavigation(sym, uni); + }; + } + +int speedlimit = 0; +vector speedlimit_names = {"none", "yellow", "green", "full"}; + +void pick_level() { + clearMessages(); + dialog::init(XLAT("select the track"), 0xC0C0FFFF, 150, 100); + for(auto l: all_levels) { + dialog::addItem(l->name, l->hotkey); + dialog::add_action([l] { + curlev = l; + recompute_plan_transform = true; + l->init(); + popScreen(); + }); + } + dialog::addBreak(100); + dialog::addBack(); + dialog::display(); + } + +void pick_game() { + clearMessages(); + dialog::init(XLAT("how do you want to play?"), 0xC0C0FFFF, 150, 100); + dialog::addSelItem("selected track", curlev->name, 't'); + dialog::add_action_push(pick_level); + dialog::addInfo(curlev->longdesc); + dialog::addBreak(100); + add_edit(speedlimit); + add_edit(planning_mode); + dialog::addBreak(100); + dialog::addBack(); + dialog::display(); + } + +void settings() { + dialog::init(XLAT("settings"), 0xC0C0FFFF, 150, 100); + dialog::addItem("RogueViz settings", 'r'); + dialog::add_key_action('r', [] { + pushScreen(showSettings); + }); + dialog::addBreak(100); + dialog::display(); + dialog::addBack(); + } + +reaction_t on_quit = [] { exit(0); }; + +void main_menu() { + clearMessages(); + dialog::init(XLAT("Nil Rider"), 0xC0C0FFFF, 150, 100); + + dialog::addItem("continue", 'c'); + dialog::add_action(popScreen); + + if(!planning_mode) { + dialog::addItem("restart", 'r'); + dialog::add_action([] { + curlev->current = curlev->start; + timer = 0; + paused = false; + popScreen(); + }); + + dialog::addItem("view the replay", 'v'); + dialog::add_action([] { + }); + + dialog::addItem("save the replay", 'e'); + dialog::add_action([] { + }); + } + else { + dialog::addItem("save this plan", 's'); + dialog::add_action([] { + }); + } + + dialog::addItem("change track or game settings", 't'); + dialog::add_action_push(pick_game); + + dialog::addItem("change other settings", 'o'); + dialog::add_action_push(settings); + + dialog::addItem("quit", 'q'); + dialog::add_action([] { + on_quit(); + }); + + dialog::display(); + } + +bool on; + +void initialize() { + + check_cgi(); + cgi.prepare_shapes(); + + init_statues(); + + curlev->init(); + + param_enum(planning_mode, "nil_planning", "nil_planning", false) + -> editable({{"manual", "control the unicycle manually"}, {"planning", "try to plan the optimal route!"}}, "game mode", 'p'); + + param_enum(speedlimit, "nil_speedlimit", "nil_speedlimit", 0) + -> editable({ + {"no limit", "reach the goals as fast as you wan"}, + {"yellow", "your speed must be in the yellow zone to collect"}, + {"green", "your speed must be in the green zone to collect"}, + {"full", "you must fully stop to collect"} + }, "speed limit", 's'); + + rv_hook(hooks_frame, 100, frame); + rv_hook(shmup::hooks_turn, 100, turn); + on = true; + on_cleanup_or_next([] { on = false; }); + pushScreen(run); + } + +auto celldemo = arg::add3("-unilcycle", initialize) + arg::add3("-unilplan", [] { planning_mode = true; }) + arg::add3("-viewsim", [] { view_simulation = true; }) + + arg::add3("-oqc", [] { on_quit = popScreenAll; }) + + arg::add3("-fullsim", [] { + /* for animations */ + popScreenAll(); + rv_hook(anims::hooks_anim, 100, [] { + int ttick = ticks % isize(curlev->history); + timer = ttick * 1. / tps; + curlev->current = curlev->history[ttick]; + curlev->current.centerview(curlev); + anims::moved(); + }); + }) + arg::add3("-unillevel", [] { + arg::shift(); + for(auto l: all_levels) if(appears(l->name, arg::args())) curlev = l; + if(on) curlev->init(); + }) + + arg::add3("-simplemodel", [] { + nisot::geodesic_movement = false; + pmodel = mdPerspective; + pconf.rotational_nil = 0; + }); + + +} diff --git a/rogueviz/nilrider/nilrider.h b/rogueviz/nilrider/nilrider.h new file mode 100644 index 00000000..ff78d7e8 --- /dev/null +++ b/rogueviz/nilrider/nilrider.h @@ -0,0 +1,130 @@ +#include "../rogueviz.h" + +namespace nilrider { + +using namespace rogueviz; + +struct level; + +struct timestamp { + hyperpoint where; /**< the current position of the unicycle */ + ld heading_angle; /**< the current heading angle */ + ld vel; /**< the current velocity in units per second */ + ld circpos; /**< controls the wheel graphics */ + ld slope; /**< the current slope */ + ld t; /**< planning spline parameter */ + bool tick(level*);/**< one tick of the simulation -- returns false if the unicycle has stopped or crashed */ + void centerview(level*); + void draw_unilcycle(const shiftmatrix&); + void draw_instruments(ld t); + ld energy_in_squares(); + }; + +struct planpoint { + hyperpoint at; + hyperpoint vel; + planpoint(hyperpoint a, hyperpoint v): at(a), vel(v) {}; + }; + +constexpr flagtype nrlPolar = Flag(1); + +struct statue { + transmatrix T; + hpcshape *shape; + color_t color; + }; + +struct level { + string name; + char hotkey; + string longdesc; + flagtype flags; + ld minx, miny, maxx, maxy; + vector map_tiles; + ld startx, starty; + ld scale; + std::function surface; + + bool initialized; + + level(string name, char hotkey, flagtype flags, string longdesc, ld minx, ld miny, ld maxx, ld maxy, const vector& mt, ld sx, ld sy, const std::function& surf) : + name(name), hotkey(hotkey), longdesc(longdesc), flags(flags), minx(minx), miny(miny), maxx(maxx), maxy(maxy), map_tiles(mt), startx(sx), starty(sy), surface(surf) { initialized = false; } + + ld real_minx, real_miny, real_maxx, real_maxy; + + /* data */ + hpcshape shFloor; /**< the 3D model of floor */ + hpcshape shPlanFloor; /**< the 3D model of floor for planning */ + hpcshape shField; /**< the 3D model of the 'field' */ + hpcshape shCastle; /**< the 3D model of the 'castle' */ + + vector statues; + + /** the texture data used for the ground */ + texture::texture_data *unil_texture; + + /** the texture used for the ground */ + basic_textureinfo uniltinf; + + /** the texture used for the ground */ + basic_textureinfo castle_tinf; + + /** starting timestamp */ + timestamp start; + + /** current timestamp */ + timestamp current; + + /** initialize textures and start */ + void init(); + + vector history; + + /** plan for the planning mode */ + vector plan; + void init_plan(); + bool simulate(); + void draw_planning_screen(); + void draw_level(const shiftmatrix& V); + shiftmatrix plan_transform; + + hyperpoint get_spline(ld t); + hyperpoint mappt(ld x, ld y, int s); + ld safe_alt(hyperpoint h, ld mul = 1); + void compute_plan_transform(); + bool handle_planning(int sym, int uni); + }; + +/** ticks per second */ +inline const ld tps = 1000; + +/** wheel radius */ +inline ld whrad = 0.05; + +/** epsilon used to measure slope */ +inline ld slope_eps = 0.01; + +/** gravity acceleration constant, in units per second squared */ +inline ld gravity = 1 / 16.; + +/** the distance of camera from the wheel */ +inline ld whdist = 0.5; + +/** minimum slope for rendering */ +inline ld min_gfx_slope = +M_PI/2; + +/** current slope for rendering */ +inline ld gfx_slope = 0; + +/** the timer */ +inline ld timer = 0; + +/** default block unit */ +inline double dft_block = 1; + +extern map bcols; +extern map > submaps; + +hyperpoint sym_to_heis(hyperpoint H); + +} diff --git a/rogueviz/nilrider/planning.cpp b/rogueviz/nilrider/planning.cpp new file mode 100644 index 00000000..2afe7bb7 --- /dev/null +++ b/rogueviz/nilrider/planning.cpp @@ -0,0 +1,287 @@ +namespace nilrider { + +hyperpoint get_spline(ld t); + +bool level::simulate() { + if(history.empty()) + history.push_back(start); + auto at = history.back(); + + if(at.t >= isize(plan) - 1.001) return false; + + ld goal_t; + + if(1) { + int steps = 20; + ld min_t, max_t; + + if(isize(history) == 1) { + steps = 60; + min_t = at.t; + max_t = at.t + 0.5; + } + else { + ld ldiff = history.back().t - history[history.size() - 2].t; + min_t = at.t; + max_t = min(at.t + ldiff + .1, isize(plan)-1); + } + + auto f = [&] (ld t) { + hyperpoint h = get_spline(t); + auto copy = at; + copy.heading_angle = atan2(h[1] - at.where[1], h[0] - at.where[0]); + copy.tick(this); + return sqhypot_d(2, copy.where-h); + }; + + string seq = ""; + + for(int i=0; i > buttons = { + {'p', "pan"}, {'a', "add"}, {'m', "move"}, {'i', "insert"}, {'d', "delete"} + }; + +bool recompute_plan_transform = true; + +void level::compute_plan_transform() { + dynamicval pm(pmodel, mdDisk); + dynamicval g(geometry, gEuclid); + dynamicval ga(vid.always3, false); + dynamicval gi(ginf[gEuclid].g, giEuclid2); + auto& cd = current_display; + auto sId = shiftless(Id); + ld pix = 1 / (2 * cgi.hcrossf / cgi.crossf); + ld scale_x = (vid.xres - 2 * vid.fsize) / abs(real_maxx-real_minx); + ld scale_y = (vid.yres - 2 * vid.fsize) / abs(real_maxy-real_miny); + ld scale = min(scale_x, scale_y); + plan_transform = sId * atscreenpos(cd->xcenter, cd->ycenter, pix * scale) * eupush(-(real_minx+real_maxx)/2, (real_miny+real_maxy)/2) * MirrorY; + } + +void level::draw_planning_screen() { + dynamicval g(geometry, gEuclid); + dynamicval pm(pmodel, mdDisk); + dynamicval ga(vid.always3, false); + dynamicval gi(ginf[gEuclid].g, giEuclid2); + initquickqueue(); + + if(recompute_plan_transform) { + compute_plan_transform(); + recompute_plan_transform = false; + } + + auto& T = plan_transform; + + auto scr_to_map = [&] (hyperpoint h) { + transmatrix mousef = inverse(unshift(T)) * atscreenpos(h[0], h[1], 1); + h = mousef * C0; + h /= h[2]; + return h; + }; + + mousept = scr_to_map(hpxy(mousex, mousey)); + + box = scr_to_map(hpxy(mousex + 5, mousey))[0] - mousept[0]; + + /* draw the map */ + auto& p = queuepolyat(T, shPlanFloor, 0xFFFFFFFF, PPR::FLOOR); + p.tinf = &uniltinf; + uniltinf.texture_id = unil_texture->textureid; + + auto draw_sq = [&] (hyperpoint h, color_t col, PPR prio) { + curvepoint(hpxy(h[0]+box, h[1]+box)); + curvepoint(hpxy(h[0]+box, h[1]-box)); + curvepoint(hpxy(h[0]-box, h[1]-box)); + curvepoint(hpxy(h[0]-box, h[1]+box)); + curvepoint(hpxy(h[0]+box, h[1]+box)); + queuecurve(T, 0xFF, col, prio); + }; + + auto draw_line = [&] (hyperpoint h1, hyperpoint h2, color_t col, PPR prio) { + curvepoint(hpxy(h1[0], h1[1])); + curvepoint(hpxy(h2[0], h2[1])); + queuecurve(T, col, 0, prio); + }; + + /* draw the plan */ + for(auto& pp: plan) { + draw_sq(pp.at - pp.vel, 0xFF8080FF, PPR::ITEM); + draw_sq(pp.at + pp.vel, 0x80FF80FF, PPR::ITEM); + draw_sq(pp.at, 0xFFFF00FF, PPR::ITEM); + draw_line(pp.at - pp.vel, pp.at + pp.vel, 0x80, PPR::BFLOOR); + } + + bool after = false; + + if(history.empty()) history.push_back(start); + + closest_t = history.back().t; + ld closest_dist = box * 2; + + vid.linewidth *= 3; + int ps = isize(plan); + for(int t=0; t<=100*(ps-1); t++) { + ld tt = t / 100.; + if(tt > history.back().t && !after) { + queuecurve(T, 0xFFFFFFC0, 0, PPR::LIZEYE); + after = true; + } + hyperpoint h = get_spline(tt); + curvepoint(hpxy(h[0], h[1])); + ld dist = sqhypot_d(2, h - mousept); + if(dist < closest_dist) closest_dist = dist, closest_t = tt; + } + queuecurve(T, after ? 0xFF8080C0 : 0xFFFFFFC0, 0, PPR::LIZEYE); + vid.linewidth /= 3; + + if(!history.empty()) { + int mint = 0, maxt = isize(history)-1; + while(mint < maxt) { + int t = (mint + maxt + 1) / 2; + if(history[t].t > closest_t) maxt = t-1; + else mint = t; + } + + current = history[mint]; + timer = mint * 1. / tps; + } + + draw_sq(get_spline(closest_t), 0x8080FFFF, PPR::ITEM); + draw_sq(current.where, 0xFF8000FF, PPR::ITEM); + draw_sq(mousept, 0x8080FFFF, PPR::ITEM); + + quickqueue(); + + glflush(); + getcstat = '-'; + } + +hyperpoint mousept_drag; + +int move_id = -1, move_dir = 0; + +bool level::handle_planning(int sym, int uni) { + if(sym == PSEUDOKEY_WHEELUP || sym == SDLK_PAGEUP) { + dynamicval g(geometry, gEuclid); + plan_transform.T = atscreenpos(mousex, mousey, 1.2) * inverse(atscreenpos(mousex, mousey, 1)) * plan_transform.T; + return true; + } + if(sym == PSEUDOKEY_WHEELDOWN || sym == SDLK_PAGEDOWN) { + dynamicval g(geometry, gEuclid); + plan_transform.T = atscreenpos(mousex, mousey, 1) * inverse(atscreenpos(mousex, mousey, 1.2)) * plan_transform.T; + return true; + } + for(auto& b: buttons) if(uni == b.first) { planmode = uni; return true; } + auto clean_history_to = [&] (int i) { + while(history.size() > 1 && history.back().t > i) history.pop_back(); + }; + switch(planmode) { + case 'p': + if(uni == '-' && !holdmouse) { + mousept_drag = mousept; + holdmouse = true; + return true; + } + else if(uni == '-' && holdmouse) { + dynamicval g(geometry, gEuclid); + plan_transform.T = plan_transform.T * eupush(mousept-mousept_drag); + return true; + } + return false; + case 'a': + if(uni == '-' && !holdmouse) { + plan.emplace_back(mousept, hpxy(0, 0)); + holdmouse = true; + return true; + } + else if(uni == '-' && holdmouse) { + plan.back().vel = mousept - plan.back().at; + return true; + } + return false; + case 'm': case 'd': { + if(!holdmouse) { + ld len = box * 2; + move_id = -1; + auto check = [&] (hyperpoint h, int id, int dir) { + ld d = sqhypot_d(2, h - mousept); + if(d < len) { len = d; move_id = id; move_dir = dir; } + }; + int next_id = 0; + for(auto p: plan) { + check(p.at, next_id, 0); + check(p.at + p.vel, next_id, 1); + check(p.at - p.vel, next_id, -1); + next_id++; + } + } + if(uni == '-' && planmode == 'd' && move_id > 0) { + plan.erase(plan.begin() + move_id); + clean_history_to(move_id - 1); + return true; + } + else if(uni == '-' && planmode == 'm' && (move_id + move_dir * move_dir > 0) && !holdmouse) { + holdmouse = true; + println(hlog, "moving ", tie(move_id, move_dir)); + return true; + } + else if(uni == '-' && planmode == 'm' && holdmouse) { + println(hlog, "moving further ", tie(move_id, move_dir)); + if(move_dir == 0) plan[move_id].at = mousept; + else plan[move_id].vel = move_dir * (mousept - plan[move_id].at); + println(hlog, "set to ", tie(plan[move_id].at, plan[move_id].vel)); + clean_history_to(move_id - 1); + return true; + } + return false; + } + case 'i': { + if(uni == '-') { + planpoint pt(C0, C0); + pt.at = get_spline(closest_t); + pt.vel = (get_spline(closest_t + 1e-3) - pt.at) / 1e-3; + plan.insert(plan.begin() + int(ceil(closest_t)), pt); + clean_history_to(int(closest_t)); + return true; + } + return false; + } + default: + return false; + } + } + +} diff --git a/rogueviz/nilrider/statues.cpp b/rogueviz/nilrider/statues.cpp new file mode 100644 index 00000000..a3a01df7 --- /dev/null +++ b/rogueviz/nilrider/statues.cpp @@ -0,0 +1,148 @@ +namespace hr { +namespace bricks { + + void draw_ro(); + + extern void build(bool in_pair); + + struct brick { + euc::coord co; + color_t col; + int walls; + hyperpoint location; + hpcshape shRotWall[6]; + }; + + extern vector bricks; + } +} + +namespace nilrider { + +hpcshape shMini[6]; + +void create_minitriangle() { + using namespace bricks; + hyperpoint ctr = Hypc; + for(auto& b: bricks::bricks) ctr += b.location; + ctr /= ctr[3]; + transmatrix B = gpushxto0(ctr); + + ld radius = 0; + ld sca = .18; + + for(int f=0; f<6; f++) { + cgi.bshape(shMini[f], PPR::WALL); + shMini[f].flags |= POLY_TRIANGLES; + + for(auto& b: bricks::bricks) { + transmatrix V = eupush(b.location); + + int which = b.walls; + if(!((1<surface(wnext); + + wnext = gpushxto0(where) * wnext; + slope = atan(wnext[2] / eps); + + auto ovel = vel; + + vel -= sin(slope) * gravity / tps; + if(vel < 0) { + vel = 0; + return false; + } + + auto mvel = (vel + ovel) / 2; + where[0] += cos(heading_angle) * mvel * cos(slope) / tps; + where[1] += sin(heading_angle) * mvel * cos(slope) / tps; + where[2] = lev->surface(where); + circpos += mvel / whrad / tps; + + return true; + } + +void timestamp::centerview(level *lev) { + // static bool once = false; if(once) return; once = true; + auto w = where; + w[2] += 0.2 * lev->scale; + hyperpoint front = rgpushxto0(w) * sym_to_heis(hyperpoint(1e-3 * cos(heading_angle), 1e-3*sin(heading_angle), 0, 1)); + hyperpoint up = w; up[2] += 1e-3; + + set_view(w, front, up); + + transmatrix T = View; + + ld gfx_slope = binsearch(-90*degree, min(slope, min_gfx_slope), [&] (ld slope) { + View = T; + rotate_view(cspin(1, 2, slope)); + for(int i=0; i<8; i++) { + shift_view(ztangent(whdist * lev->scale / 8.)); + hyperpoint p = inverse(View) * C0; + ld room = p[2] - lev->surface(p); + if(room < .1 * lev->scale) return true; + } + return false; + }); + + View = T; + rotate_view(cspin(1, 2, gfx_slope)); + shift_view(ztangent(whdist * lev->scale)); + centerover = cwt.at; + playermoved = false; + } + +void timestamp::draw_instruments(ld t) { + dynamicval g(geometry, gEuclid); + dynamicval pm(pmodel, mdDisk); + dynamicval ga(vid.always3, false); + dynamicval gi(ginf[gEuclid].g, giEuclid2); + initquickqueue(); + + ld rad = 40; + + ld cx = rad * 2; + ld cy = rad * 2; + + auto sId = shiftless(Id); + + ld pix = 1 / (2 * cgi.hcrossf / cgi.crossf); + + // clinometer + + cx += rad * 1.2; + + for(int i=-90; i<=90; i++) + curvepoint(atscreenpos(cx+cos(i * degree)*rad, cy-sin(i*degree)*rad, 1) * C0); + + curvepoint(atscreenpos(cx, cy+rad, 1) * C0); + queuecurve(sId, 0x000000FF, 0xFFFFFF80, PPR::ZERO); + + curvepoint(hpxy(0, 0)); + curvepoint(hpxy(rad, 0)); + /* curvepoint(hpxy(rad/4, 0)); + curvepoint(hpxy(0, rad)); + curvepoint(hpxy(-rad/4, 0)); + curvepoint(hpxy(rad/4, 0)); */ + queuecurve(sId * atscreenpos(cx, cy, pix) * spin(min_gfx_slope), 0x40, 0x40, PPR::ZERO); + + curvepoint(hpxy(rad/4, 0)); + curvepoint(hpxy(0, rad)); + curvepoint(hpxy(-rad/4, 0)); + curvepoint(hpxy(rad/4, 0)); + queuecurve(sId * atscreenpos(cx, cy, pix) * spin(90 * degree + slope), 0xFF, 0x40C040FF, PPR::ZERO); + + // compass + + cx -= rad * 1.2; + + for(int i=0; i<360; i++) + curvepoint(atscreenpos(cx-cos(i * degree)*rad, cy-sin(i*degree)*rad, 1) * C0); + + queuecurve(sId, 0x000000FF, 0xFFFFFF80, PPR::ZERO); + + for(int d: {1}) { + + // d == +1: direction arrow + // d == -1: compass + + curvepoint(hpxy(rad/4, 0)); + curvepoint(hpxy(0, rad)); + curvepoint(hpxy(-rad/4, 0)); + + queuecurve(sId * atscreenpos(cx, cy, pix) * spin(d * (90*degree + heading_angle)), 0xFF, d > 0 ? 0x0000FFFF : 0xFF0000FF, PPR::ZERO); + + curvepoint(hpxy(rad/4, 0)); + curvepoint(hpxy(0, -rad)); + curvepoint(hpxy(-rad/4, 0)); + curvepoint(hpxy(rad/4, 0)); + queuecurve(sId * atscreenpos(cx, cy, pix) * spin(d * (90*degree + heading_angle)), 0xFF, 0xFFFFFFFF, PPR::ZERO); + } + + // speed meter + + cx += rad * 3.4; + + for(int i=0; i<360; i++) + curvepoint(atscreenpos(cx-cos(i * degree)*rad, cy-sin(i*degree)*rad, 1) * C0); + queuecurve(sId, 0x000000FF, 0xFFFFFF80, PPR::ZERO); + + auto e_to_angle = [] (ld energy) { + return 135*degree - 3 * atan(energy/10); + }; + + vector short_lines = {2, 3, 4, 6, 7, 8, 9, 30, 40, 60, 70, 80, 90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000}; + + for(auto h: short_lines) { + auto a = e_to_angle(h); + curvepoint(hpxy(-sin(a)*rad*.95, -cos(a)*rad*.95)); + curvepoint(hpxy(-sin(a)*rad*.85, -cos(a)*rad*.85)); + queuecurve(sId * atscreenpos(cx, cy, pix), 0xFF, 0, PPR::ZERO); + } + + vector long_lines = {0, 1, 5, 10, 20, 50}; + + for(auto h: long_lines) { + auto a = e_to_angle(h); + curvepoint(hpxy(-sin(a)*rad*.95, -cos(a)*rad*.95)); + curvepoint(hpxy(-sin(a)*rad*.75, -cos(a)*rad*.75)); + queuecurve(sId * atscreenpos(cx, cy, pix), 0xFF, 0, PPR::ZERO); + displaystr(cx -sin(a)*rad*.65, cy -cos(a)*rad*.65, 0, 8, its(h), 0, 8); + } + + curvepoint(hpxy(rad/4, 0)); + curvepoint(hpxy(0, -rad)); + curvepoint(hpxy(-rad/4, 0)); + curvepoint(hpxy(rad/4, 0)); + queuecurve(sId * atscreenpos(cx, cy, pix) * spin(e_to_angle(energy_in_squares())), 0xFF, 0xFF8080FF, PPR::ZERO); + + quickqueue(); + glflush(); + + string s = format("%d:%02d.%02d", int(t / 60), int(t) % 60, int(frac(t) * 100)); + + displaystr(vid.xres - vid.fsize, vid.fsize*2, 0, vid.fsize * 2, s, 0, 16); + } + +}