1 #include "rogueviz.h"
2 
3 /** A physics visualization of balls in a shell.
4  *
5  *  Compile with HyperRogue, enable a 3D geometry (e.g. Nil), and watch.
6  *  This is not configurable yet... you may need to manually change the gravity direction, or the number of balls
7  *  (it is not optimized, and it does not work in real time with the default number of balls).
8  */
9 
10 namespace rogueviz {
11 
12 namespace balls {
13 
14 bool on = true;
15 
16 struct ball {
17   hyperpoint at;
18   hyperpoint vel;
19   };
20 
21 vector<ball> balls;
22 
23 ld r_small_ball = .1;
24 ld r_big_ball = 1;
25 
26 hpcshape shSmallBall, shBigBall, shShell;
27 
initialize(int max_ball)28 void initialize(int max_ball) {
29   on = true;
30 
31   cgi.make_ball(shSmallBall, r_small_ball, 2);
32   cgi.make_ball(shBigBall, r_big_ball, 4);
33 
34   cgi.bshape(shShell, PPR::WALL);
35   shShell.flags |= POLY_TRIANGLES;
36 
37   auto pt = [] (int i, int j) {
38     cgi.hpcpush(direct_exp(/* cspin(0, 2, -30*degree) **/ cspin(2, 1, 90*degree) * cspin(0, 1, j * degree) * cspin(0, 2, i * M_PI / 2 / 16) * ztangent(r_big_ball)));
39     };
40 
41   for(int i=0; i<16; i++)
42   for(int j=0; j<360; j++) {
43     pt(i, j);
44     pt(i, j+1);
45     pt(i+1, j);
46     pt(i, j+1);
47     pt(i+1, j);
48     pt(i+1, j+1);
49     }
50   cgi.finishshape();
51   cgi.extra_vertices();
52 
53   for(int a=-max_ball; a<=max_ball; a++)
54   for(int b=-max_ball; b<=max_ball; b++)
55   for(int c=-max_ball; c<=max_ball; c++)
56   {
57     hyperpoint h = point3(0.21*a + 1e-2, 0.21*b, 0.21*c);
58 
59     if(hypot_d(3, h) > r_big_ball - r_small_ball) continue;
60 
61     transmatrix T = rgpushxto0(direct_exp(h));
62 
63     balls.emplace_back(ball{T*C0, T*ztangent(1e-3)});
64     }
65 
66   }
67 
draw_balls(cell * c,const shiftmatrix & V)68 bool draw_balls(cell *c, const shiftmatrix& V) {
69   if(!on) return false;
70 
71   if(c == currentmap->gamestart()) {
72     for(auto& b: balls)
73       queuepoly(V * rgpushxto0(b.at), shSmallBall, 0xFFFFFFFF);
74     queuepoly(V, shShell, 0x0000F0FF);
75     }
76 
77   return false;
78   }
79 
inner(hyperpoint a,hyperpoint b)80 ld inner(hyperpoint a, hyperpoint b) {
81   ld s = a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
82   if(hyperbolic) return s - a[3] * b[3];
83   if(sphere) return s + a[3] * b[3];
84   return s;
85   }
86 
geodesic_steps(hyperpoint & at,hyperpoint & vel,int qty)87 void geodesic_steps(hyperpoint& at, hyperpoint& vel, int qty) {
88   if(nonisotropic) {
89     vel /= qty;
90     for(int i=0; i<qty; i++)
91       nisot::geodesic_step(at, vel);
92     vel *= qty;
93     }
94   else {
95     ld d = sqrt(inner(vel, vel));
96     tie(at, vel) = make_pair(
97       at * cos_auto(d) + vel * sin_auto(d)/d,
98       vel * cos_auto(d) - at * sin_auto(d) * sig(3) * d
99       );
100     }
101   }
102 
103 ld elastic_in = .2;
104 ld elastic_out = .2;
105 
106 ld gravity = 1;
107 
turn(int delta)108 bool turn(int delta) {
109   if(!on) return false;
110   for(int i=0; i<delta; i++) {
111     for(auto& b: balls) {
112       /* gravity direction: z */
113       b.vel += ctangent(2, 1e-6) * gravity;
114 
115       geodesic_steps(b.at, b.vel, 1);
116 
117       if(!nonisotropic && !euclid) {
118         ld e = sqrt(abs(inner(b.at, b.at)));
119         b.at /= e;
120         ld e2 = inner(b.at, b.vel) * sig(3);
121         b.vel -= b.at * e2;
122         }
123 
124       hyperpoint v = inverse_exp(shiftless(b.at));
125       ld d = hypot_d(3, v);
126       ld rbs = r_big_ball - r_small_ball;
127       if(d > rbs) {
128         hyperpoint c = C0, ve = v * rbs / d;
129         geodesic_steps(c, ve, 20);
130         hyperpoint ort = ve / d;
131         transmatrix T = gpushxto0(b.at);
132         b.vel -= inner(T*b.vel, T*ort) * ort * (1 + elastic_out);
133 
134         b.at = c;
135         if(!nonisotropic && !euclid) {
136           ld e2 = inner(b.at, b.vel) * sig(3);
137           b.vel -= b.at * e2;
138           }
139         }
140       }
141 
142     /* This is not optimized. It should use a partition of the space,
143      * to tell which balls have a chance to touch each other. */
144 
145     for(auto& b1: balls)
146     for(auto& b2: balls) {
147       if(&b2 == &b1) break;
148       hyperpoint dif = inverse_exp(shiftless(gpushxto0(b1.at) * b2.at));
149       ld d = hypot_d(3, dif);
150       if(d < r_small_ball * 2) {
151         hyperpoint ort1 = (dif / d);
152         ld vel1 = +inner(gpushxto0(b1.at) * b1.vel, ort1);
153         hyperpoint ort2 = inverse_exp(shiftless(gpushxto0(b2.at) * b1.at)) / d;
154         ld vel2 = +inner(gpushxto0(b2.at) * b2.vel, ort2);
155         ld vels = vel1 + vel2;
156         if(vels < 0) continue;
157 
158         vels *= (1 + elastic_in) / 2;
159 
160         b1.vel -= rgpushxto0(b1.at) * (vels * ort1);
161         b2.vel -= rgpushxto0(b2.at) * (vels * ort2);
162         }
163       }
164     }
165   return false;
166   }
167 
args()168 int args() {
169   using namespace arg;
170 
171   if(0) ;
172 
173   else if(argis("-ball-physics")) {
174     start_game();
175     check_cgi();
176     cgi.require_shapes();
177     shift();
178     initialize(argi());
179     View = cspin(1, 2, M_PI/2);
180     }
181 
182   else return 1;
183   return 0;
184   }
185 
186 
187 auto celldemo = addHook(hooks_drawcell, 100, draw_balls) +
188     addHook(shmup::hooks_turn, 100, turn) +
189     addHook(hooks_args, 100, args) +
__anon2a412fb50202() 190     addHook(hooks_clearmemory, 40, [] () {
191       balls.clear();
192       on = false;
193       });
194 
195 }
196 }
197