mirror of
				https://github.com/zenorogue/hyperrogue.git
				synced 2025-10-31 14:02:59 +00:00 
			
		
		
		
	rogueviz:: implemented smoothcam
This commit is contained in:
		| @@ -4,6 +4,7 @@ | ||||
| #include "rogueviz.cpp" | ||||
| #include "presentation.cpp" | ||||
| #include "objmodels.cpp" | ||||
| #include "smoothcam.cpp" | ||||
|  | ||||
| #include "kohonen.cpp" | ||||
| #include "staircase.cpp" | ||||
|   | ||||
							
								
								
									
										367
									
								
								rogueviz/smoothcam.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										367
									
								
								rogueviz/smoothcam.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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<int>(mapstream::cellids[c]); | ||||
|   } | ||||
|  | ||||
| void hread(hstream& hs, pcell& c) { | ||||
|   int32_t at = hs.get<int>(); | ||||
|   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<frame> frames; | ||||
|   }; | ||||
|  | ||||
| map<cell*, map<hyperpoint, string> > labels; | ||||
|  | ||||
| vector<animation> 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<cell*, int> 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<ld> 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<ld> 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<MDIM; i++) { | ||||
|       vector<ld> 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<n-ss; a++) { | ||||
|           // combining [a..a+(ss-1)] and [a+1..a+ss] | ||||
|           if(times[a+ss] == times[a]) | ||||
|             values[a] = (values[a+ss] - values[a]) * (t-times[a]); | ||||
|           else | ||||
|             values[a] = (values[a] * (times[a+ss] - t) + values[a+1] * (t - times[a])) / (times[a+ss] - times[a]); | ||||
|           } | ||||
|        | ||||
|       pts[j][i] = values[0]; | ||||
|       } | ||||
|     pts[j] = normalize(pts[j]); | ||||
|     } | ||||
|    | ||||
|   transmatrix V = View; | ||||
|   set_view(pts[0], pts[1], pts[2]); | ||||
|  | ||||
|   transmatrix T = View * inverse(last_view_comp); | ||||
|   last_view_comp = View; | ||||
|    | ||||
|   View = T * V; | ||||
|   fixmatrix(View); | ||||
|    | ||||
|   if(invalid_matrix(View)) exit(1); | ||||
|   anims::moved(); | ||||
|   last_time = t; | ||||
|   } | ||||
|  | ||||
| void hwrite(hstream& hs, const animation& anim) { | ||||
|   hwrite(hs, anim.start_cell, anim.start, anim.start_interval, anim.frames); | ||||
|   } | ||||
|  | ||||
| void hread(hstream& hs, animation& anim) { | ||||
|   hread(hs, anim.start_cell, anim.start, anim.start_interval, anim.frames); | ||||
|   } | ||||
|  | ||||
| void hwrite(hstream& hs, const frame& frame) { | ||||
|   hwrite(hs, frame.title, frame.where, frame.sView, frame.V, frame.ori, frame.front_distance, frame.up_distance, frame.interval); | ||||
|   } | ||||
|  | ||||
| void hread(hstream& hs, frame& frame) { | ||||
|   hread(hs, frame.title, frame.where, frame.sView, frame.V, frame.ori, frame.front_distance, frame.up_distance, frame.interval); | ||||
|   } | ||||
|  | ||||
| bool draw_labels(cell *c, const shiftmatrix& V) { | ||||
|   if(view_labels) for(auto& p: labels[c]) | ||||
|     queuestr(V * rgpushxto0(p.first), .1, p.second, 0xFFFFFFFF, 1); | ||||
|   return false; | ||||
|   } | ||||
|  | ||||
| bool enabled; | ||||
|  | ||||
| void enable() {  | ||||
|   if(enabled) return; | ||||
|   enabled = true; | ||||
|   rogueviz::cleanup.push_back([] { enabled = false; }); | ||||
|   rogueviz::rv_hook(hooks_preoptimize, 75, analyze_view_pre); | ||||
|   rogueviz::rv_hook(hooks_postoptimize, 75, analyze_view_post); | ||||
|   rogueviz::rv_hook(anims::hooks_anim, 100, handle_animation); | ||||
|   rogueviz::rv_hook(hooks_drawcell, 100, draw_labels); | ||||
|   rogueviz::rv_hook(mapstream::hooks_savemap, 100, [] (fhstream& f) { | ||||
|     f.write<int>(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); | ||||
|       } | ||||
|     }); | ||||
|  | ||||
| }} | ||||
		Reference in New Issue
	
	Block a user
	 Zeno Rogue
					Zeno Rogue