diff --git a/rogueviz/rogueviz-all.cpp b/rogueviz/rogueviz-all.cpp index ed106206..0d87e008 100644 --- a/rogueviz/rogueviz-all.cpp +++ b/rogueviz/rogueviz-all.cpp @@ -4,6 +4,7 @@ #include "rogueviz.cpp" #include "presentation.cpp" #include "objmodels.cpp" +#include "smoothcam.cpp" #include "kohonen.cpp" #include "staircase.cpp" diff --git a/rogueviz/smoothcam.cpp b/rogueviz/smoothcam.cpp new file mode 100644 index 00000000..548c95d3 --- /dev/null +++ b/rogueviz/smoothcam.cpp @@ -0,0 +1,367 @@ +#include "rogueviz.h" + +// This module allows creating complex animations with smooth camera movement + +// to add: insert positions? split/merge segments? edit front_distance and up_distance? + +namespace hr { + +using pcell = cell*; + +void hwrite(hstream& hs, const pcell& c) { + hs.write(mapstream::cellids[c]); + } + +void hread(hstream& hs, pcell& c) { + int32_t at = hs.get(); + c = mapstream::cellbyid[at]; + } + +namespace smoothcam { + +string smooth_camera_help = + "This feature lets you create animations with complex but smooth camera movement.\n\n" + "An animation is composed from a number of segments.\n\n" + "In each segment, you can provide a number of positions, and times for them. " + "For example, if you add a camera position A at time 0 and a camera position B at time 1, " + "we will move linearly from A to B. Polynomial approximation is used inside a segment, " + "while separate segments are animated independently.\n\n" + "The 'interval' values are the interval between the current and next position. " + "The total sum of 'interval' values is made equal to the 'animation period'. " + "If you place two positions X and Y with interval 0 between them, X will be used" + "as the actual position, while Y-X will be the first derivative. Thus, for example, " + "placing two equal positions with interval 0 will force the camera to smoothly stop."; + +struct frame { + string title; + cell *where; + transmatrix sView; + transmatrix V; + transmatrix ori; + ld front_distance, up_distance; + ld interval; + }; + +struct animation { + cell *start_cell; + transmatrix start; + ld start_interval; + vector frames; + }; + +map > labels; + +vector anims; + +transmatrix last_view, current_position, last_view_comp; +cell *last_centerover; + +// during the animation, transform original coordinates to the current view coordinates +transmatrix last_computed; +ld last_time; + +void analyze_view_pre() { + current_position = current_position * last_view * inverse(View); + } + +void analyze_view_post() { + last_view = View; + } + +void start_segment() { + anims.emplace_back(); + auto& anim = anims.back(); + anim.start_cell = centerover; + anim.start = Id; + last_view = Id; + current_position = Id; + } + +void join_segment() { + int n = anims.back().frames.size(); + if(n < 2) return; + auto s1 = anims.back().frames[n-2]; + auto s2 = anims.back().frames[n-1]; + start_segment(); + anims.back().frames.push_back(s1); + anims.back().frames.push_back(s2); + } + +map indices; + +string gentitle() { + return lalign(0, centerover, ":", indices[centerover]++); + } + +bool animate_on; +bool view_labels; + +void edit_interval(ld& v) { + dialog::add_action([&v] { + dialog::editNumber(v, -10, 10, 1, 0, "interval", ""); + }); + } + +void edit_segment(int aid) { + cmode = sm::SIDE; + gamescreen(0); + dialog::init(XLAT("animation segment"), 0xFFFFFFFF, 150, 0); + dialog::addSelItem("interval", fts(anims[aid].start_interval), 'i'); + edit_interval(anims[aid].start_interval); + dialog::addItem("delete", 'd'); + dialog::add_action([aid] { + anims.erase(anims.begin()+aid); + if(anims.empty()) start_segment(); + popScreen(); + }); + dialog::addItem("mirror", 'm'); + dialog::add_action([aid] { + auto a = anims[aid]; + reverse(a.frames.begin(), a.frames.end()); + ld* last = &a.start_interval; + for(auto& f: a.frames) { swap(*last, f.interval); last = &f.interval; } + anims.push_back(std::move(a)); + popScreen(); + }); + dialog::addBack(); + dialog::display(); + } + +void edit_step(animation& anim, int id) { + cmode = sm::SIDE; + gamescreen(0); + dialog::init(XLAT("animation step"), 0xFFFFFFFF, 150, 0); + auto& f = anim.frames[id]; + dialog::addSelItem("title", f.title, 't'); + dialog::addSelItem("interval", fts(f.interval), 'i'); + edit_interval(f.interval); + dialog::addItem("delete", 'd'); + dialog::add_action([&anim, id] { + anim.frames.erase(anim.frames.begin()+id); + popScreen(); + }); + dialog::addItem("edit", 'e'); + dialog::add_action([&f] { + f.where = centerover; + f.sView = View; + f.V = current_position; + }); + dialog::addItem("recall", 'r'); + dialog::add_action([&f] { + View = f.sView * calc_relative_matrix(centerover, f.where, inverse(View) * C0); + NLP = ortho_inverse(f.ori); + }); + dialog::addBack(); + dialog::display(); + } + +void show() { + cmode = sm::SIDE; + gamescreen(0); + dialog::init(XLAT("smooth camera"), 0xFFFFFFFF, 150, 0); + char key = 'A'; + int aid = 0; + + labels.clear(); + + for(auto& anim: anims) { + dialog::addSelItem("segment", fts(anim.start_interval), key++); + dialog::add_action_push([aid] { edit_segment(aid); }); + int id = 0; + for(auto& f: anim.frames) { + labels[f.where][inverse(f.sView) * C0] = f.title; + dialog::addSelItem(f.title + " [" + its(celldistance(f.where, centerover)) + "]", fts(f.interval), key++); + dialog::add_action_push([&anim, id] { edit_step(anim, id); }); + id++; + } + aid++; + } + + dialog::addItem("create a new position", 'a'); + dialog::add_action([] { + println(hlog, "current_position is ", current_position * C0); + anims.back().frames.push_back(frame{gentitle(), centerover, View, current_position, ortho_inverse(NLP), 1, 1, 0}); + }); + + dialog::addItem("create a new segment", 'b'); + dialog::add_action(start_segment); + + dialog::addItem("increase interval by 1", 's'); + dialog::add_key_action('s', [] { + if(!anims.back().frames.empty()) + anims.back().frames.back().interval += 1; + else + anims.back().start_interval+=1; + }); + + dialog::addItem("join a new segment", 'j'); + dialog::add_action(join_segment); + + dialog::addBoolItem_action("view the labels", view_labels, 'l'); + + dialog::addBoolItem("run the animation", animate_on, 'r'); + dialog::add_action([] { + animate_on = !animate_on; + last_time = HUGE_VAL; + }); + + dialog::addHelp(smooth_camera_help); + dialog::addBack(); + dialog::display(); + + keyhandler = [] (int sym, int uni) { + handlePanning(sym, uni); + dialog::handleNavigation(sym, uni); + if(doexiton(sym, uni)) popScreen(); + }; + } + +int last_segment; + +void handle_animation() { + if(!animate_on) return; + + ld total_total; + + vector totals; + for(auto& anim: anims) { + ld total = anim.start_interval; + for(auto& f: anim.frames) + total += f.interval; + totals.push_back(total); + total_total += total; + } + + ld t = ticks / anims::period; + t = frac(t); + t *= total_total; + int segment = 0; + while(totals[segment] < t) t -= totals[segment++]; + + auto& anim = anims[segment]; + + if(t < last_time || segment != last_segment) { + last_time = 0; + last_segment = segment; + View = anim.start; + last_view_comp = View; + centerover = anim.start_cell; + } + + ld total = anim.start_interval; + vector times; + for(auto& f: anim.frames) { + times.push_back(total); + total += f.interval; + } + + hyperpoint pts[3]; + + for(int j=0; j<3; j++) { + for(int i=0; i values; + for(auto& f: anim.frames) { + hyperpoint h; + if(j == 0) + h = tC0(f.V); + if(j == 1) { + h = tC0(parallel_transport(f.V, f.ori, zpush0(f.front_distance))); + } + if(j == 2) { + h = tC0(parallel_transport(f.V, f.ori, ypush0(-f.up_distance))); + } + values.push_back(h[i]); + } + + int n = isize(values); + + for(int ss=1; ss<=n-1; ss++) + for(int a=0; a(17); + hwrite(f, anims); + }); + anims.clear(); + start_segment(); + } + +void enable_and_show() { + showstartmenu = false; + start_game(); + enable(); + pushScreen(show); + } + +auto hooks = arg::add3("-smoothcam", enable_and_show) + + addHook(dialog::hooks_display_dialog, 100, [] () { + if(current_screen_cfunction() == anims::show) { + dialog::addItem(XLAT("smooth camera"), 'C'); + dialog::add_action(enable_and_show); + } + }) + + + addHook(mapstream::hooks_loadmap, 100, [] (fhstream& f, int id) { + if(id == 17) { + enable(); + hread(f, anims); + } + }); + +}}