1 #include "rogueviz.h"
2 
3 /** \brief Snowball visualization
4  *
5  *  This visualization puts small objects ('snowballs') randomly throughout the space.
6  *  It provides a way to visualize the geometry without any tessellation.
7  *
8  *  Should work for tessellations where every tile is congruent.
9  *
10  *  The snow_lambda parameter gives the expected number of snowballs per cell.
11  *  (The number in every region has Poisson distribution with mean proportional to its area.)
12  *
13  *  Freezes for tessellations with ideal vertices
14  *
15  *
16  *
17  **/
18 
19 namespace rogueviz {
20 
21 namespace snow {
22 
23 ld snow_lambda = 0;
24 
25 color_t snow_color = 0xFFFFFFFF;
26 
27 bool snow_test = false;
28 
29 /* intense brightness */
30 bool snow_intense = false;
31 
32 /* a funny glitch */
33 bool snow_glitch = false;
34 
35 /* disable textures */
36 bool snow_texture = true;
37 
38 int snow_shape = 0;
39 
40 map<cell*, vector<transmatrix> > matrices_at;
41 
shapeid(int i)42 hpcshape& shapeid(int i) {
43   switch(i) {
44     case 0:
45       return cgi.shSnowball;
46     case 1:
47       return cgi.shHeptaMarker;
48     case 2:
49       return cgi.shDisk;
50     default:
51       return cgi.shDisk;
52     }
53   }
54 
random_snow_matrix(cell * c)55 transmatrix random_snow_matrix(cell *c) {
56   if(snow_glitch) {
57     // in the standard tiling, this is incorrect but fun
58     hyperpoint h = C0;
59     h[0] = randd() - .5;
60     h[1] = randd() - .5;
61     h[2] = randd() - .5;
62     h[2] = -h[2];
63     return rgpushxto0(h);
64     }
65   else if(prod) {
66     transmatrix T = PIU(random_snow_matrix(c));
67     return mscale(T, (randd() - .5) * cgi.plevel);
68     }
69   else if(hybri && !prod) {
70     return rots::lift_matrix(PIU(random_snow_matrix(c))); // * zpush((randd() - .5) * cgi.plevel);
71     }
72   else if(nonisotropic || bt::in()) {
73 
74     int co = bt::expansion_coordinate();
75     ld aer = bt::area_expansion_rate();
76 
77     hyperpoint h;
78     // randd() - .5;
79 
80     for(int a=0; a<3; a++) {
81       if(a != co || aer == 1)
82         h[a] = randd() * 2 - 1;
83       else {
84         ld r = randd();
85         h[co] = log(hr::lerp(1, aer, r)) / log(aer) * 2 - 1;
86         }
87       }
88     return bt::normalized_at(h);
89     }
90   else {
91     while(true) {
92       ld maxr = WDIM == 2 ? cgi.rhexf : cgi.corner_bonus;
93       ld vol = randd() * wvolarea_auto(maxr);
94       ld r = binsearch(0, maxr, [vol] (ld r) { return wvolarea_auto(r) > vol; });
95       transmatrix T = random_spin();
96       hyperpoint h = T * xpush0(r);
97       cell* c1 = c;
98       virtualRebase(c1, h);
99       if(c1 == c)
100         return T * xpush(r);
101       }
102     }
103   }
104 
draw_snow(cell * c,const shiftmatrix & V)105 bool draw_snow(cell *c, const shiftmatrix& V) {
106 
107   if(!matrices_at.count(c)) {
108     auto& v = matrices_at[c];
109     int cnt = 0;
110     ld prob = randd();
111     ld poisson = exp(-snow_lambda);
112     while(cnt < 2*snow_lambda+100) {
113       if(prob < poisson) break;
114       prob -= poisson;
115       cnt++;
116       poisson *= snow_lambda / cnt;
117       }
118     if(snow_test) {
119       if(c != cwt.at)
120         cnt = 0;
121       else {
122         c->wall = waFloorA;
123         cnt = snow_lambda;
124         }
125       }
126 
127     for(int t=0; t<cnt; t++)
128       v.push_back(random_snow_matrix(c));
129     }
130 
131   poly_outline = 0xFF;
132   for(auto& T: matrices_at[c]) {
133     auto& p = queuepoly(V * T, shapeid(snow_shape), snow_color);
134     if(!snow_texture) p.tinf = nullptr;
135     if(snow_intense) p.flags |= POLY_INTENSE;
136     }
137 
138   return false;
139   }
140 
141 string cap = "non-Euclidean snowballs/";
142 
snow_slide(vector<tour::slide> & v,string title,string desc,reaction_t t)143 void snow_slide(vector<tour::slide>& v, string title, string desc, reaction_t t) {
144   using namespace tour;
145   v.push_back(
146     tour::slide{cap + title, 18, LEGAL::NONE | QUICKGEO, desc,
147 
148   [t] (presmode mode) {
149     setCanvas(mode, '0');
150 
151     slidecommand = "auto-movement";
152     if(mode == pmKey) {
153       using namespace anims;
154       tour::slide_backup(ma, ma == maTranslation ? maNone : maTranslation);
155       tour::slide_backup<ld>(shift_angle, 0);
156       tour::slide_backup<ld>(movement_angle, 90);
157       }
158 
159     if(mode == pmStart) {
160       stop_game();
161       tour::slide_backup(mapeditor::drawplayer, false);
162       tour::slide_backup<ld>(snow_lambda, 1);
163       tour::slide_backup(snow_color, 0xC0C0C0FF);
164       tour::slide_backup(snow_intense, true);
165       tour::slide_backup(smooth_scrolling, true);
166       t();
167       start_game();
168       playermoved = false;
169       }
170     }}
171     );
172   }
173 
show()174 void show() {
175   cmode = sm::SIDE | sm::MAYDARK;
176   gamescreen(0);
177   dialog::init(XLAT("snowballs"), 0xFFFFFFFF, 150, 0);
178 
179   dialog::addSelItem("lambda", fts(snow_lambda), 'l');
180   dialog::add_action([]() {
181     dialog::editNumber(snow_lambda, 0, 100, 1, 10, "lambda", "snowball density");
182     dialog::reaction = [] { matrices_at.clear(); };
183     });
184 
185   dialog::addSelItem("size", fts(snow_shape), 's');
186   dialog::add_action([]() {
187     snow_shape = (1 + snow_shape) % 3;
188     });
189 
190   dialog::addBack();
191   dialog::display();
192   }
193 
o_key(o_funcs & v)194 void o_key(o_funcs& v) {
195   if(snow_lambda) v.push_back(named_dialog("snowballs", show));
196   }
197 
198 auto hchook = addHook(hooks_drawcell, 100, draw_snow)
199 
__anon9a3ba7ce0602() 200 + addHook(hooks_clearmemory, 40, [] () {
201     matrices_at.clear();
202     })
203 
204 + addHook(hooks_o_key, 80, o_key)
205 
206 #if CAP_COMMANDLINE
__anon9a3ba7ce0702null207 + addHook(hooks_args, 100, [] {
208   using namespace arg;
209 
210   if(0) ;
211   else if(argis("-snow-lambda")) {
212     shift_arg_formula(snow_lambda);
213     }
214   else if(argis("-snow-shape")) {
215     shift(); snow_shape = argi();
216     }
217   else if(argis("-snow-test")) {
218     snow_test = true;
219     }
220   else if(argis("-snow-color")) {
221     shift(); snow_color = arghex();
222     }
223   else if(argis("-snow-intense")) {
224     snow_intense = true;
225     }
226   else if(argis("-snow-no-texture")) {
227     snow_texture = false;
228     }
229   else if(argis("-snow-glitch")) {
230     snow_test = true;
231     }
232   else return 1;
233   return 0;
234   })
235 #endif
236 
__anon9a3ba7ce0802(string s, vector<tour::slide>& v) 237 + addHook_rvslides(161, [] (string s, vector<tour::slide>& v) {
238   if(s != "noniso") return;
239   v.push_back(tour::slide{
240     cap+"snowball visualization", 10, tour::LEGAL::NONE | tour::QUICKSKIP,
241     "Non-Euclidean visualizations usually show some regular constructions. Could we visualize the geometries themselves? Let's distribute the snowballs randomly."
242     "\n\n"
243     "You can use mouse to look in different directions. Press 5 to turn the automatic movement on or off. Press 'o' to change density and shape."
244     ,
245     [] (tour::presmode mode) {
246       slide_url(mode, 'y', "YouTube link", "https://www.youtube.com/watch?v=leuleS9SpiA");
247       slide_url(mode, 't', "Twitter link", "https://twitter.com/ZenoRogue/status/1245367263936512001");
248       }
249     });
250   snow_slide(v, "Euclidean geometry", "This is the Euclidean space. Looks a bit like space flight in some old video games.", [] {
251     set_geometry(gCubeTiling);
252     snow_lambda = 20;
253     });
254   snow_slide(v, "Euclidean geometry (torus)",
255     "Some gamers incorrectly call warped worlds (like Asteroids) \"non-Euclidean\"; the animation for them would look the same, just a bit more regular. When playing these games, I have always wondered why the stars move so fast. Such far objects should not move.", [] {
256     auto& T0 = euc::eu_input.user_axes;
257     auto bak = T0;
258     for(int i=0; i<3; i++)
259     for(int j=0; j<3; j++)
260       T0[i][j] = i==j? 1: 0;
261     euc::build_torus3();
262     set_geometry(gCubeTiling);
263     snow_lambda = 20;
264     tour::on_restore([bak] { auto& T0 = euc::eu_input.user_axes; stop_game(); T0 = bak; euc::build_torus3(); start_game(); });
265     });
266   snow_slide(v, "Hyperbolic geometry", "To the contrary, in hyperbolic geometry, parallax works in a completely different way. Everything moves. This space is expanding everywhere. Exponentially. In every geometry, snowballs close to us behave in a similar way as in the Euclidean space.", [] {
267     set_geometry(gSpace534);
268     snow_lambda = 20;
269     });
270   snow_slide(v, "H2xE", "This geometry is non-isotropic: it is hyperbolic in horizontal direction and Euclidean in (roughly) vertical direction. Since the space expands faster horizontally, the snowballs no longer look circular.", [] {
271     set_geometry(gNormal);
272     set_variation(eVariation::pure);
273     set_geometry(gProduct);
274     snow_lambda = 20;
275     });
276   snow_slide(v, "Non-isotropic hyperbolic geometry", "This geometry is hyperbolic in both directions, but the curvatures are different.", [] {
277     set_geometry(gNIH);
278     snow_lambda = 20;
279     });
280   snow_slide(v, "Spherical geometry", "Do not forget about the spherical geometry. It is weird in general. When we leave the snowballs behind us, they look as if they were in front of us. Due to geometric lensing, snowballs in the antipodal point look as if they were close to us.", [] {
281     set_geometry(gCell120);
282     snow_lambda = 5;
283     });
284   snow_slide(v, "S2xE geometry", "Snowballs which are directly above or below us will look like rings, but it is hard to catch them in exactly the right spot.", [] {
285     set_geometry(gSphere);
286     set_variation(eVariation::pure);
287     set_geometry(gProduct);
288     snow_lambda = 20;
289     });
290   snow_slide(v, "Nil", "Nil geometry, used for impossible figure constructions. Euclidean plane with another dimension added. Making a loop in the Euclidean plane makes you move in this third dimension, proportionally to the area of the loop. (Using larger snowballs here.)", [] {
291     set_geometry(gNil);
292     tour::slide_backup<ld>(sightranges[geometry], 7);
293     tour::slide_backup(snow_shape, 2);
294     snow_lambda = 5;
295     });
296   snow_slide(v, "SL(2,R)", "Here is SL(2,R), like Nil but based on hyperbolic plane instead. Geometric lensing effects are strong in both Nil and SL(2,R). (Starting with S^2 yields spherical geometry.)", [] {
297     set_geometry(gNormal);
298     set_variation(eVariation::pure);
299     set_geometry(gRotSpace);
300     snow_lambda = 5;
301     });
302 #if CAP_SOLV
303   snow_slide(v, "Solv", "Solv geometry. Like the non-isotropic hyperbolic geometry but where the horizontal and vertical curvatures work in the other way.", [] {
304     set_geometry(gSol);
305     // tour::slide_backup(snow_shape, 2);
306     snow_lambda = 3;
307     });
308 #endif
309   });
310 
311 }
312 }
313