mirror of
				https://github.com/zenorogue/hyperrogue.git
				synced 2025-10-31 14:02:59 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			781 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			781 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "rogueviz.h"
 | |
| 
 | |
| // This module allows creating complex animations with smooth camera movement
 | |
| 
 | |
| // enable with -smoothcam option, or the new "smooth camera" option in the Animations dialog
 | |
| 
 | |
| // it still seems a bit buggy
 | |
| 
 | |
| // to add: insert positions? split/merge segments? improved join?
 | |
| 
 | |
| namespace rogueviz {
 | |
| namespace smoothcam {
 | |
| struct frame {
 | |
|   string title;
 | |
|   cell *where;
 | |
|   transmatrix sView;
 | |
|   transmatrix V;
 | |
|   transmatrix ori;
 | |
|   ld front_distance, up_distance;
 | |
|   ld interval;
 | |
|   map<string, ld> params;
 | |
|   };
 | |
| 
 | |
| struct animation {
 | |
|   cell *start_cell;
 | |
|   transmatrix start;
 | |
|   ld start_interval;
 | |
|   vector<frame> frames;
 | |
|   };
 | |
| 
 | |
| vector<string> smoothcam_params;
 | |
| }
 | |
| }
 | |
| 
 | |
| namespace hr {
 | |
| 
 | |
|   using pcell = cell*;
 | |
| 
 | |
|   static void hwrite(hstream& hs, const pcell& c) {
 | |
|     hs.write<int>(mapstream::cellids[c]);
 | |
|     }
 | |
| 
 | |
|   static void hread(hstream& hs, pcell& c) {
 | |
|     int32_t at = hs.get<int>();
 | |
|     c = mapstream::cellbyid[at];
 | |
|     }
 | |
| 
 | |
|   static void hread(hstream& hs, transmatrix& h) { for(int i=0; i<MDIM; i++) hread(hs, h[i]); }
 | |
|   static void hwrite(hstream& hs, const transmatrix& h) { for(int i=0; i<MDIM; i++) hwrite(hs, h[i]); }
 | |
| 
 | |
|   void hwrite(hstream& hs, const rogueviz::smoothcam::animation& anim) {
 | |
|     hwrite(hs, anim.start_cell, anim.start, anim.start_interval, anim.frames);
 | |
|     }
 | |
| 
 | |
|   void hread(hstream& hs, rogueviz::smoothcam::animation& anim) {
 | |
|     hread(hs, anim.start_cell, anim.start, anim.start_interval, anim.frames);
 | |
|     }
 | |
| 
 | |
|   void hwrite(hstream& hs, const rogueviz::smoothcam::frame& frame) {
 | |
|     hwrite(hs, frame.title, frame.where, frame.sView, frame.V, frame.ori, frame.front_distance, frame.up_distance, frame.interval);
 | |
|     for(auto pa: rogueviz::smoothcam::smoothcam_params) { hwrite(hs, frame.params.at(pa)); }
 | |
|     }
 | |
| 
 | |
|   void hread(hstream& hs, rogueviz::smoothcam::frame& frame) {
 | |
|     hread(hs, frame.title, frame.where, frame.sView, frame.V, frame.ori, frame.front_distance, frame.up_distance, frame.interval);
 | |
|     for(auto pa: rogueviz::smoothcam::smoothcam_params) { hread(hs, frame.params[pa]); }
 | |
|     }
 | |
| 
 | |
|   }
 | |
| 
 | |
| namespace rogueviz {
 | |
| 
 | |
| 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.";
 | |
| 
 | |
| map<cell*, map<hyperpoint, string> > labels;
 | |
| map<cell*, vector<vector<hyperpoint> > > traces;
 | |
| 
 | |
| vector<animation> anims;
 | |
| 
 | |
| vector<animation> anims_backup;
 | |
| 
 | |
| void backup() {
 | |
|   anims_backup = anims;
 | |
|   }
 | |
| 
 | |
| void append_backup() {
 | |
|   swap(anims_backup, anims);
 | |
|   for(auto a: anims_backup) anims.push_back(a);
 | |
|   anims_backup.clear();
 | |
|   }
 | |
| 
 | |
| 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;
 | |
|   }
 | |
| 
 | |
| animation *current_segment;
 | |
| 
 | |
| void start_segment() {
 | |
|   anims.emplace_back();
 | |
|   auto& anim = anims.back();
 | |
|   anim.start_cell = centerover;
 | |
|   anim.start = Id;
 | |
|   if(det(View) < 0) anim.start = MirrorX * anim.start;
 | |
|   last_view = anim.start;
 | |
|   current_position = anim.start;
 | |
|   current_segment = &anim;
 | |
|   }
 | |
| 
 | |
| /** does not work correctly -- should adjust to the current cell */
 | |
| 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();
 | |
|   auto& l = anims[anims.size()-2];
 | |
|   anims.back().frames.push_back(l.frames[n-2]);
 | |
|   anims.back().frames.push_back(l.frames[n-1]);
 | |
|   anims.back().start_cell = l.start_cell;
 | |
|   anims.back().start = l.start;
 | |
|   anims.back().start_interval = 0;
 | |
|   }
 | |
| 
 | |
| map<cell*, int> indices;
 | |
| 
 | |
| string gentitle() {
 | |
|   return lalign(0, centerover, ":", indices[centerover]++);
 | |
|   }
 | |
| 
 | |
| bool animate_on;
 | |
| bool view_labels, view_trace;
 | |
| 
 | |
| void edit_interval(ld& v) {
 | |
|   dialog::add_action([&v] {
 | |
|     dialog::editNumber(v, -10, 10, 1, 0, "interval", "");
 | |
|     });
 | |
|   }
 | |
| 
 | |
| transmatrix try_harder_relative_matrix(cell *at, cell *from) {
 | |
|   transmatrix U = Id;
 | |
|   int d = celldistance(at, from);
 | |
|   again:
 | |
|   while(d > 0) {
 | |
|     forCellIdEx(c1, i, at) {
 | |
|       int d1 = celldistance(c1, from);
 | |
|       if(d1 < d) {
 | |
|         U = currentmap->iadj(at, i) * U;
 | |
|         d = d1;
 | |
|         at = c1;
 | |
|         goto again;
 | |
|         }
 | |
|       }
 | |
|     println(hlog, "still failed");
 | |
|     return Id;
 | |
|     }
 | |
|   println(hlog, "got U = ", U);
 | |
|   return U;
 | |
|   }
 | |
| 
 | |
| frame new_frame() {
 | |
|   frame f;
 | |
|   f.title = gentitle();
 | |
|   f.where = centerover;
 | |
|   f.sView = View;
 | |
|   f.V = current_position;
 | |
|   f.ori = ortho_inverse(NLP);
 | |
|   f.front_distance = 1;
 | |
|   f.up_distance = 1;
 | |
|   f.interval = 0;
 | |
|   for(auto p: smoothcam_params) f.params[p] = real(params[p]->get_cld());
 | |
|   return f;
 | |
|   };
 | |
| 
 | |
| void edit_segment(int aid) {
 | |
|   cmode = sm::PANNING;
 | |
|   gamescreen();
 | |
|   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::addItem("copy before", 'c');
 | |
|   dialog::add_action([aid] {
 | |
|     auto a = anims[aid];
 | |
|     anims.insert(anims.begin() + aid, a);
 | |
|     current_segment = nullptr;
 | |
|     popScreen();
 | |
|     });
 | |
|   dialog::addItem("swap with the last segment", 'x');
 | |
|   dialog::add_action([aid] {
 | |
|     swap(anims.back(), anims[aid]);
 | |
|     current_segment = nullptr;
 | |
|     popScreen();
 | |
|     });
 | |
|   dialog::addBack();
 | |
|   dialog::display();
 | |
|   }
 | |
| 
 | |
| void generate_trace();
 | |
| 
 | |
| void edit_step(animation& anim, int id) {
 | |
|   cmode = 0;
 | |
|   gamescreen();
 | |
|   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::addSelItem("front distance", fts(f.front_distance), 'f');
 | |
|   dialog::add_action([&f] {
 | |
|     dialog::editNumber(f.front_distance, -5, 5, .1, 1, "front distance", "");
 | |
|     });
 | |
|   dialog::addSelItem("up distance", fts(f.up_distance), 'u');
 | |
|   dialog::add_action([&f] {
 | |
|     dialog::editNumber(f.up_distance, -5, 5, .1, 1, "up distance", "");
 | |
|     });
 | |
| 
 | |
|   char key = '1';
 | |
|   for(auto pa: smoothcam_params) {
 | |
|     dialog::addSelItem(pa, fts(f.params[pa]), key++);
 | |
|     }
 | |
| 
 | |
|   dialog::addItem("delete", 'd');
 | |
|   dialog::add_action([&anim, id] {
 | |
|     anim.frames.erase(anim.frames.begin()+id);
 | |
|     popScreen();
 | |
|     });
 | |
|   if(&anim == current_segment) {
 | |
|     dialog::addItem("change to current camera location", 'e');
 | |
|     dialog::add_action([&f] {
 | |
|       f.where = centerover;
 | |
|       f.sView = View;
 | |
|       f.V = current_position;
 | |
|       popScreen();
 | |
|       });
 | |
|     }
 | |
|   dialog::addItem("move the camera here", 'r');
 | |
|   dialog::add_action([&f] {
 | |
|     transmatrix Rel = calc_relative_matrix(centerover, f.where, inverse(View) * C0);
 | |
|     println(hlog, "Rel = ", Rel);
 | |
|     if(eqmatrix(Rel, Id) && centerover != f.where)
 | |
|       Rel = try_harder_relative_matrix(centerover, f.where);
 | |
|     View = f.sView * Rel;
 | |
|     NLP = ortho_inverse(f.ori);
 | |
|     playermoved = false;
 | |
|     current_display->which_copy = 
 | |
|       nonisotropic ? gpushxto0(tC0(view_inverse(View))) :
 | |
|       View;
 | |
|     popScreen();
 | |
|     });
 | |
|   dialog::addItem("edit this segment and move the camera here", 'p');
 | |
|   dialog::add_action([&anim, &f] {
 | |
|     last_view = View = f.sView;
 | |
|     NLP = ortho_inverse(f.ori);
 | |
|     centerover = f.where;
 | |
|     current_position = f.V;    
 | |
|     playermoved = false;
 | |
|     current_display->which_copy = 
 | |
|       nonisotropic ? gpushxto0(tC0(view_inverse(View))) :
 | |
|       View;
 | |
|     current_segment = &anim;
 | |
|     popScreen();
 | |
|     });
 | |
|   dialog::addItem("start a new segment from here", 'n');
 | |
|   dialog::add_action([&f] {
 | |
|     View = f.sView;
 | |
|     centerover = f.where;
 | |
|     playermoved = false;
 | |
|     NLP = ortho_inverse(f.ori);
 | |
|     current_display->which_copy = 
 | |
|       nonisotropic ? gpushxto0(tC0(view_inverse(View))) :
 | |
|       View;
 | |
|     start_segment();
 | |
|     popScreen();
 | |
|     });
 | |
|   if(&anim == current_segment) {
 | |
|     dialog::addItem("insert the current position before this", 'j');
 | |
|     dialog::add_action([&anim, id] {
 | |
|       anim.frames.insert(anim.frames.begin() + id, new_frame());
 | |
|       popScreen();
 | |
|       });
 | |
|     }
 | |
| 
 | |
|   dialog::addBack();
 | |
|   dialog::display();
 | |
|   }
 | |
| 
 | |
| int last_segment;
 | |
| 
 | |
| ld test_t = 0;
 | |
| ld c_front_dist = 0, c_up_dist = 0;
 | |
| void handle_animation(ld t);
 | |
| 
 | |
| bool side = true;
 | |
| 
 | |
| void snap_to_center() {
 | |
|   cmode = side ? sm::SIDE : 0;
 | |
|   gamescreen();
 | |
|   draw_crosshair();
 | |
|   dialog::init(XLAT("snap to center"), 0xFFFFFFFF, 150, 0);
 | |
|   
 | |
|   dialog::addItem("center on mouse", ' ');
 | |
|   dialog::add_action([] {
 | |
|     View = gpushxto0(unshift(mapeditor::mouse_snap())) * View;
 | |
|     });
 | |
| 
 | |
|   dialog::addItem("mouse up", 'w');
 | |
|   dialog::add_action([] {
 | |
|     View = spin90() * spintox(unshift(mapeditor::mouse_snap())) * View;
 | |
|     });
 | |
| 
 | |
|   dialog::addItem("mouse down", 's');
 | |
|   dialog::add_action([] {
 | |
|     View = spin270() * spintox(unshift(mapeditor::mouse_snap())) * View;
 | |
|     });
 | |
| 
 | |
|   dialog::addItem("mouse left", 'a');
 | |
|   dialog::add_action([] {
 | |
|     View = spin180() * spintox(unshift(mapeditor::mouse_snap())) * View;
 | |
|     });
 | |
| 
 | |
|   dialog::addItem("mouse left", 'd');
 | |
|   dialog::add_action([] {
 | |
|     View = Id * spintox(unshift(mapeditor::mouse_snap())) * View;
 | |
|     });
 | |
| 
 | |
|   dialog::addBack();
 | |
|   dialog::display();
 | |
|   
 | |
|   keyhandler = [] (int sym, int uni) {
 | |
|     handlePanning(sym, uni);
 | |
|     dialog::handleNavigation(sym, uni);
 | |
|     if(doexiton(sym, uni)) popScreen();
 | |
|     };
 | |
|   }
 | |
| 
 | |
| void show() {
 | |
|   /* might not be changed automatically */
 | |
|   if(vid.fixed_yz) spinEdge_full();
 | |
| 
 | |
|   cmode = side ? sm::SIDE : 0;
 | |
|   gamescreen();
 | |
|   draw_crosshair();
 | |
|   dialog::init(XLAT("smooth camera"), 0xFFFFFFFF, 150, 0);
 | |
|   int aid = 0;
 | |
|   
 | |
|   labels.clear();
 | |
|   
 | |
|   dialog::start_list(2000, 2000, 'A');
 | |
|   
 | |
|   for(auto& anim: anims) {
 | |
|     dialog::addSelItem("segment #" + its(aid) + (&anim == current_segment ? "*" : ""), fts(anim.start_interval), dialog::list_fake_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;
 | |
|       string dist;
 | |
|       
 | |
|       if(f.where != centerover)
 | |
|         dist = its(celldistance(f.where, centerover)) + " cells";
 | |
|       else {
 | |
|         hyperpoint h1 = tC0(iso_inverse(View));
 | |
|         hyperpoint h2 = tC0(iso_inverse(f.sView));
 | |
|         ld d = hdist(h1, h2);
 | |
|         if(d > 1e-3)
 | |
|           dist = fts(d) + "au";
 | |
|         else {
 | |
|           transmatrix T = f.sView * iso_inverse(View);
 | |
|           dist = fts(kz(acos_clamp(T[2][2])/degree)) + "°/" + fts(kz(acos_clamp(T[1][1])/degree)) + "°";
 | |
|           }
 | |
|         }
 | |
|         
 | |
|       dialog::addSelItem(f.title + " [" + dist + "]", fts(f.interval), dialog::list_fake_key++);
 | |
|       dialog::add_action_push([&anim, id] { edit_step(anim, id); });
 | |
|       id++;
 | |
|       }
 | |
|     aid++;
 | |
|     }
 | |
|   dialog::end_list();
 | |
| 
 | |
|   if(current_segment) {
 | |
|     dialog::addItem("create a new position", 'a');
 | |
|     dialog::add_action([] {
 | |
|       current_segment->frames.push_back(new_frame());
 | |
|       });
 | |
|     }
 | |
| 
 | |
|   dialog::addItem("create a new segment", 'b');
 | |
|   dialog::add_action(start_segment);
 | |
| 
 | |
|   if(current_segment) {
 | |
|     dialog::addItem("increase interval by 1", 's');
 | |
|     dialog::add_key_action('s', [] {
 | |
|       if(!current_segment->frames.empty())
 | |
|         current_segment->frames.back().interval += 1;
 | |
|       else
 | |
|         current_segment->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("view the trace", view_trace, 't');
 | |
|   dialog::add_action([] {
 | |
|     view_trace = !view_trace;
 | |
|     if(view_trace) generate_trace();
 | |
|     });
 | |
|   dialog::addBoolItem_action("side display", side, 'm');
 | |
| 
 | |
|   dialog::addItem("test the animation", 't');
 | |
|   dialog::add_action([] {
 | |
|     animate_on = false;
 | |
|     last_time = HUGE_VAL;
 | |
|     last_segment = -1;
 | |
|     test_t = 0;
 | |
|     dialog::editNumber(test_t, 0, 100, 0.1, 0, "enter the percentage", "");
 | |
|     dialog::get_di().reaction = [] {
 | |
|       handle_animation(test_t / 100);
 | |
|       };
 | |
|     dialog::get_di().extra_options = [] {
 | |
|       dialog::addSelItem("current segment", its(last_segment), 'C');
 | |
|       dialog::addSelItem("current front", fts(c_front_dist), 'F');
 | |
|       dialog::addSelItem("current up", fts(c_up_dist), 'U');
 | |
|       };
 | |
|     });
 | |
| 
 | |
|   dialog::addBoolItem("view the crosshair", crosshair_size, 'x');
 | |
|   dialog::add_action([] { crosshair_size = crosshair_size ? 0 : 10; });
 | |
| 
 | |
|   dialog::addBoolItem("run the animation", animate_on, 'r');
 | |
|   dialog::add_action([] {
 | |
|     animate_on = !animate_on;
 | |
|     last_time = HUGE_VAL;
 | |
|     });
 | |
| 
 | |
|   if(GDIM == 2) {
 | |
|     dialog::addItem("centering", 'z');
 | |
|     dialog::add_action_push(snap_to_center);
 | |
|     }
 | |
|   else {
 | |
|     dialog::addItem("mirror Y view", 'Y');
 | |
|     dialog::add_action([] {
 | |
|       rotate_view(MirrorY);
 | |
|       });
 | |
|     }
 | |
|     
 | |
|   dialog::addHelp();
 | |
|   dialog::add_action([] { gotoHelp(smooth_camera_help); });
 | |
|   dialog::addBack();
 | |
|   dialog::display();
 | |
|   
 | |
|   keyhandler = [] (int sym, int uni) {
 | |
|     handlePanning(sym, uni);
 | |
|     dialog::handleNavigation(sym, uni);
 | |
|     if(doexiton(sym, uni)) popScreen();
 | |
|     };
 | |
|   }
 | |
| 
 | |
| void prepare_for_interpolation(hyperpoint& h) {
 | |
|   if(gproduct) {
 | |
|     h[3] = zlevel(h);
 | |
|     ld t = exp(h[3]);
 | |
|     h[0] /= t;
 | |
|     h[1] /= t;
 | |
|     h[2] /= t;
 | |
|     }
 | |
|   }
 | |
| 
 | |
| void after_interpolation(hyperpoint& h) {
 | |
|   if(gproduct) {
 | |
|     ld v = exp(h[3]) / sqrt(abs(intval(h, Hypc)));
 | |
|     h[0] *= v;
 | |
|     h[1] *= v;
 | |
|     h[2] *= v;
 | |
|     }
 | |
|   else
 | |
|     h = normalize(h);
 | |
|   }
 | |
| 
 | |
| ld interpolate(vector<ld> values, const vector<ld>& times, ld t) {
 | |
|   int n = isize(values);
 | |
|   // print(hlog, "interpolate: ", kz(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] + (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]);
 | |
|       }
 | |
|     values.pop_back();
 | |
|     }
 | |
| 
 | |
|   // println(hlog, " -> ", values[0], " based on ", times, " -> ", t);
 | |
|   return values[0];
 | |
|   }
 | |
| 
 | |
| 
 | |
| void handle_animation(ld t) {
 | |
|   ld total_total = 0;
 | |
|   
 | |
|   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;
 | |
|     }
 | |
|     
 | |
|   if(total_total == 0) return;
 | |
| 
 | |
|   t = frac(t);
 | |
|   t *= total_total;
 | |
|   int segment = 0;
 | |
|   while(totals[segment] < t && segment < isize(totals)-1) t -= totals[segment++];
 | |
| 
 | |
|   auto& anim = anims[segment];
 | |
| 
 | |
|   if(t < last_time || segment != last_segment) {
 | |
|     last_time = 0;
 | |
|     last_segment = segment;
 | |
|     current_segment = &anim;
 | |
|     View = anim.start;
 | |
|     last_view_comp = View;
 | |
|     centerover = anim.start_cell;
 | |
|     current_position = Id;
 | |
|     last_view = View;
 | |
|     last_centerover = centerover;
 | |
|     }
 | |
| 
 | |
|   ld total = anim.start_interval;
 | |
|   vector<ld> times;
 | |
|   for(auto& f: anim.frames) {
 | |
|     times.push_back(total);
 | |
|     total += f.interval;
 | |
|     }
 | |
| 
 | |
|   transmatrix V = View;
 | |
| 
 | |
|   for(auto pa: smoothcam_params) {
 | |
|     vector<ld> values;
 | |
|     for(auto& f: anim.frames) values.push_back(f.params[pa]);
 | |
|     ld val = interpolate(values, times, t);
 | |
|     params[pa]->set_cld(val);
 | |
|     }
 | |
| 
 | |
|   if(embedded_plane && embedded_shift_method_choice != smcNone) {
 | |
|     hyperpoint interm = C03;
 | |
| 
 | |
|     for(int j=0; j<3; j++) {
 | |
|       vector<ld> values;
 | |
|       for(auto& f: anim.frames)
 | |
|         values.push_back(cgi.emb->actual_to_intermediate(f.V*C0)[j]);
 | |
|       interm[j] = interpolate(values, times, t);
 | |
|       }
 | |
|     transmatrix Rot = Id;
 | |
|     for(int j=0; j<3; j++) for(int k=0; k<3; k++) {
 | |
|       vector<ld> values;
 | |
|       for(auto& f: anim.frames) {
 | |
|         transmatrix Rot = inverse(cgi.emb->map_relative_push(f.V*C0) * lzpush(cgi.emb->center_z())) * f.V;
 | |
|         if(nisot::local_perspective_used) Rot = Rot * f.ori;
 | |
|         values.push_back(Rot[j][k]);
 | |
|         }
 | |
|       Rot[j][k] = interpolate(values, times, t);
 | |
|       }
 | |
|     View = inverse(cgi.emb->intermediate_to_actual_translation(interm) * lzpush(cgi.emb->center_z())); NLP = Id;
 | |
|     fix_rotation(Rot);
 | |
|     rotate_view(inverse(Rot));
 | |
|     }
 | |
| 
 | |
|   else {
 | |
|     hyperpoint pts[3];
 | |
| 
 | |
|     for(int j=0; j<3; j++) {
 | |
|       for(int i=0; i<4; i++) {
 | |
|         vector<ld> values;
 | |
|         for(auto& f: anim.frames) {
 | |
|           hyperpoint h;
 | |
|           if(j == 0)
 | |
|             h = tC0(f.V);
 | |
|           if(j == 1) {
 | |
|             h = tC0(shift_object(f.V, f.ori, ztangent(f.front_distance), (hyperbolic || euclid || sphere) ? smIsotropic : smGeodesic));
 | |
|             }
 | |
|           if(j == 2) {
 | |
|             h = tC0(shift_object(f.V, f.ori, ctangent(1, -f.up_distance), (hyperbolic || euclid || sphere) ? smIsotropic : smGeodesic));
 | |
|             }
 | |
|           prepare_for_interpolation(h);
 | |
|           values.push_back(h[i]);
 | |
|           }
 | |
| 
 | |
|         pts[j][i] = interpolate(values, times, t);
 | |
|         }
 | |
|       after_interpolation(pts[j]);
 | |
|       }
 | |
| 
 | |
|     set_view(pts[0], pts[1], pts[2]);
 | |
|     c_front_dist = geo_dist(pts[0], pts[1]);
 | |
|     c_up_dist = geo_dist(pts[0], pts[2]);
 | |
|     if(det(last_view_comp) < 0) View = MirrorX * View;
 | |
|     }
 | |
| 
 | |
|   if(vid.fixed_yz) spinEdge_full();
 | |
| 
 | |
|   transmatrix T = View * inverse(last_view_comp);
 | |
|   last_view_comp = View;
 | |
|   
 | |
|   View = T * V;
 | |
|   fixmatrix(View);
 | |
|   
 | |
|   if(invalid_matrix(View)) println(hlog, "invalid_matrix ", View, " at t = ", t);
 | |
|   last_time = t;
 | |
|   }
 | |
| 
 | |
| void handle_animation0() {
 | |
|   if(!animate_on) return;
 | |
|   handle_animation(ticks / anims::period);
 | |
|   anims::moved();
 | |
|   // println(hlog, "at ", cview());
 | |
|   }
 | |
| 
 | |
| void generate_trace() {
 | |
|   last_time = HUGE_VAL;
 | |
|   dynamicval<transmatrix> tN(NLP, NLP);
 | |
|   dynamicval<transmatrix> tV(View, View);
 | |
|   dynamicval<transmatrix> tC(current_display->which_copy, current_display->which_copy);
 | |
|   dynamicval<cell*> tc(centerover, centerover);
 | |
|   cell* cview = nullptr;
 | |
|   vector<hyperpoint> at;
 | |
|   traces.clear();
 | |
|   auto send = [&] {
 | |
|     if(cview && !at.empty()) traces[cview].push_back(at);
 | |
|     cview = centerover;
 | |
|     at.clear();
 | |
|     };
 | |
|   for(ld t=0; t<=1024; t ++) {
 | |
|     handle_animation(t / 1024);
 | |
|     if(cview != centerover) send();
 | |
|     at.push_back(inverse(View) * C0);
 | |
|     optimizeview();
 | |
|     if(cview != centerover) {
 | |
|       send();
 | |
|       at.push_back(inverse(View) * C0);
 | |
|       }
 | |
|     }
 | |
|   send();
 | |
|   }
 | |
| 
 | |
| 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);
 | |
|   if(view_trace) 
 | |
|     for(auto& v: traces[c]) {
 | |
|       for(auto p: v)
 | |
|         curvepoint(p);
 | |
|       queuecurve(V, 0xFFD500FF, 0, PPR::FLOOR);
 | |
|       for(auto p: v)
 | |
|         curvepoint(p);
 | |
|       queuecurve(V, 0x80000080, 0, PPR::SUPERLINE);
 | |
|       }
 | |
|   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_animation0);
 | |
|   rogueviz::rv_hook(hooks_drawcell, 100, draw_labels);
 | |
|   rogueviz::rv_hook(hooks_o_key, 190, [] (o_funcs& v) { v.push_back(named_dialog("smoothcam", show)); });
 | |
|   rogueviz::rv_hook(mapstream::hooks_savemap, 100, [] (hstream& 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)
 | |
|   + arg::add3("-smoothcam-on", [] { 
 | |
|     enable_and_show(); 
 | |
|     animate_on = true;
 | |
|     last_time = HUGE_VAL;
 | |
|     })
 | |
|   + arg::add3("-smoothcam-param", [] {
 | |
|     arg::shift(); smoothcam_params.push_back( arg::args() );
 | |
|     })
 | |
|   + arg::add3("-smoothcam-anim-on", [] {
 | |
|     animate_on = true;
 | |
|     last_time = HUGE_VAL;
 | |
|     })
 | |
|   + 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, [] (hstream& f, int id) {
 | |
|     if(id == 17) {
 | |
|       enable();
 | |
|       hread(f, anims);
 | |
|       current_segment = &anims.back();
 | |
|       }
 | |
|     });
 | |
| 
 | |
| void save_animation(hstream& f) {
 | |
|   if(f.vernum >= 0xA930) hwrite(f, smoothcam_params);
 | |
|   hwrite(f, anims);
 | |
|   }
 | |
| 
 | |
| void load_animation(hstream& f) {
 | |
|   if(f.vernum >= 0xA930) hread(f, smoothcam_params);
 | |
|   hread(f, anims);
 | |
|   current_segment = &anims.back();
 | |
|   }
 | |
| 
 | |
| auto hooksw = addHook(hooks_swapdim, 100, [] {
 | |
|   last_segment = -1;
 | |
|   for(auto& anim: anims) {
 | |
|     anim.start = Id;
 | |
|     for(auto& f: anim.frames) {
 | |
|       transmatrix NLP = inverse(f.ori);
 | |
|       swapmatrix_iview(f.ori, f.V);
 | |
|       swapmatrix_view(NLP, f.sView);
 | |
|       }
 | |
|     }
 | |
|   });
 | |
| 
 | |
| }}
 | 
