diff --git a/balls.cc b/balls.cc index a4d91ff..5f107ee 100644 --- a/balls.cc +++ b/balls.cc @@ -18,6 +18,55 @@ unsigned int v_angle_max = 100; ball * balls = nullptr; unsigned int n_balls = 50; +// Coefficient of restitution: +// C_r == 1.0 ==> perfectly elastic collisions +// C_r == 0.0 ==> perfectly inelastic collisions +// +static double C_r = 1.0; +static int c_r_display_countdown = 0; +static int c_r_display_init = 300; + +void restitution_coefficient_set (double c) { + C_r = c; + if (C_r > 1.0) + C_r = 1.0; + else if (C_r < 0.0) + C_r = 0.0; +} + +double restitution_coefficient_get () { + return C_r; +} + +void restitution_coefficient_change (double d) { + C_r += d; + if (C_r > 1.0) + C_r = 1.0; + else if (C_r < 0.0) + C_r = 0.0; + restitution_coefficient_show (); +} + +void restitution_coefficient_show () { + c_r_display_countdown = c_r_display_init; +}; + +void restitution_coefficient_draw (cairo_t * cr) { + static const double margin = 20; + if (c_r_display_countdown != 0) { + cairo_save(cr); + cairo_new_path(cr); + cairo_move_to(cr, margin, margin); + cairo_line_to(cr, margin + (width - 2*margin)*C_r, margin); + cairo_set_source_rgb(cr, 1.0, 1.0, 0.0); + cairo_set_line_width(cr, margin/2); + cairo_stroke(cr); + if (c_r_display_countdown > 0) + --c_r_display_countdown; + cairo_restore(cr); + } +} + static vec2d random_velocity() { double r2; vec2d v; @@ -49,43 +98,45 @@ void balls_init_state () { } } +void ball_walls_collision (ball * p) { + if (p->position.x + p->radius > width) { /* right wall */ + if (p->velocity.x > 0) { + p->position.x -= p->position.x + p->radius - width; + p->velocity.x = -C_r*p->velocity.x; + } + } else if (p->position.x < p->radius) { /* left wall */ + if (p->velocity.x < 0) { + p->position.x += p->radius - p->position.x; + p->velocity.x = -C_r*p->velocity.x; + } + } + if (p->position.y + p->radius > height) { /* bottom wall */ + if (p->velocity.y > 0) { + p->position.y -= p->position.y + p->radius - height; + p->velocity.y = -C_r*p->velocity.y; + } + } else if (p->position.y < p->radius) { /* top wall */ + if (p->velocity.y < 0) { + p->position.y += p->radius - p->position.y; + p->velocity.y = -C_r*p->velocity.y; + } + } +} + void ball_update_state (ball * p) { vec2d g = gravity_vector (p); p->position += delta*p->velocity + delta*delta*g/2.0; p->velocity += delta*g; - - if (p->position.x + p->radius > width) { /* right wall */ - if (p->velocity.x > 0) { - p->position.x -= p->position.x + p->radius - width; - p->velocity.x = -p->velocity.x; - } - } else if (p->position.x < p->radius) { /* left wall */ - if (p->velocity.x < 0) { - p->position.x += p->radius - p->position.x; - p->velocity.x = -p->velocity.x; - } - } - - if (p->position.y + p->radius > height) { /* bottom wall */ - if (p->velocity.y > 0) { - p->position.y -= p->position.y + p->radius - height; - p->velocity.y = -p->velocity.y; - } - } else if (p->position.y < p->radius) { /* top wall */ - if (p->velocity.y < 0) { - p->position.y += p->radius - p->position.y; - p->velocity.y = -p->velocity.y; - } - } p->angle += delta*p->v_angle; while (p->angle >= 2*M_PI) p->angle -= 2*M_PI; while (p->angle < 0) p->angle += 2*M_PI; + ball_walls_collision (p); } -void ball_elastic_collision (ball * p, ball * q) { +void ball_ball_collision (ball * p, ball * q) { vec2d pq = q->position - p->position; double d2 = vec2d::dot(pq,pq); double r = p->radius + q->radius; @@ -94,13 +145,19 @@ void ball_elastic_collision (ball * p, ball * q) { double mp = p->radius * p->radius; double mq = q->radius * q->radius; + double m_total = mp + mp; + + double d = sqrt(d2); + vec2d pq_overlap = (r - d)/d*pq; + p->position -= pq_overlap*mq/m_total; + q->position += pq_overlap*mp/m_total; double f = vec2d::dot(pq_v, pq); if (f < 0) { f /= d2*(mp + mq); - p->velocity += 2*mq*f*pq; - q->velocity -= 2*mp*f*pq; + p->velocity += 2*C_r*mq*f*pq; + q->velocity -= 2*C_r*mp*f*pq; } } } diff --git a/balls.h b/balls.h index 85a4939..d98a7c8 100644 --- a/balls.h +++ b/balls.h @@ -39,8 +39,14 @@ extern int face_rotation; extern void balls_init (); extern void balls_destroy (); extern void ball_update_state (ball * p); -extern void ball_elastic_collision (ball * p, ball * q); +extern void ball_ball_collision (ball * p, ball * q); extern void ball_reposition (ball * b); extern void balls_draw (cairo_t * cr); +extern void restitution_coefficient_show (); +extern void restitution_coefficient_draw (cairo_t * cr); +extern void restitution_coefficient_set (double c); +extern double restitution_coefficient_get (); +extern void restitution_coefficient_change (double d); + #endif diff --git a/gravity.cc b/gravity.cc index 719d72b..aeada86 100644 --- a/gravity.cc +++ b/gravity.cc @@ -91,6 +91,11 @@ void gravity_collisions (ball * begin, ball * end) { double d2 = vec2d::dot(b_c, b_c); double r = b->radius + g_r; if (d2 <= r*r) { + double d = sqrt(d2); + if (d <= b->radius) + b->position = vec2d{width/2.0,height/2.0+r}; + else + b->position += (r - d)/d*b_c; double f = vec2d::dot(b->velocity, b_c); if (f < 0) { f /= d2; diff --git a/main.cc b/main.cc index b37481e..9aa088c 100644 --- a/main.cc +++ b/main.cc @@ -21,18 +21,18 @@ void check_collisions_simple () { for(unsigned int i = 0; i < n_balls; ++i) for(unsigned int j = i + 1; j < n_balls; ++j) - ball_elastic_collision(balls + i, balls + j); + ball_ball_collision(balls + i, balls + j); for(unsigned int j = 0; j < n_balls; ++j) - ball_elastic_collision(&spaceship, balls + j); + ball_ball_collision(&spaceship, balls + j); gravity_collisions (balls, balls + n_balls); gravity_collisions (&spaceship, &spaceship + 1); } void check_collisions_with_index () { c_index_build(); - c_index_check_collisions(ball_elastic_collision); + c_index_check_collisions(ball_ball_collision); for(unsigned int j = 0; j < n_balls; ++j) - ball_elastic_collision(&spaceship, balls + j); + ball_ball_collision(&spaceship, balls + j); gravity_collisions (balls, balls + n_balls); gravity_collisions (&spaceship, &spaceship + 1); } @@ -66,6 +66,7 @@ gboolean draw_frame (GtkWidget * widget, cairo_t *cr, gpointer data) { cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); cairo_paint(cr); gravity_draw (cr); + restitution_coefficient_draw (cr); balls_draw (cr); spaceship_draw (cr); return FALSE; @@ -100,6 +101,12 @@ gint keyboard_input (GtkWidget *widget, GdkEventKey *event) { case GDK_KEY_Right: gravity_change (10, 0); break; + case GDK_KEY_R: + restitution_coefficient_change (0.01); + break; + case GDK_KEY_r: + restitution_coefficient_change (-0.01); + break; case GDK_KEY_G: case GDK_KEY_g: gravity_show (); @@ -150,6 +157,7 @@ void print_usage (const char * progname) { "\tface=\n" "\tstats= :: rendering timing statitstics (0=disabled, default)\n" "\tcollisions= :: n=no collisions, s=simple, i=index\n" + "\trestitution= :: restitution coefficient C_r\n" "\t-r :: activate face rotation\n", progname); } @@ -247,6 +255,11 @@ int main (int argc, const char *argv[]) { face_rotation = 1; continue; } + double c_r; + if (sscanf(argv[i], "restitution=%lf", &c_r) == 1) { + restitution_coefficient_set (c_r); + continue; + } print_usage(argv[0]); return 1; }