#include <math.h>
#include <stdlib.h>
#include <time.h>
#include <gtk/gtk.h>
#define BALLS_COUNT 5
const double pi = 3.141592653589793;
const double g = 100.0;
const guint interval_ms = 10;
typedef struct {
double x, y, vx, vy, ax, ay, r, m;
} Ball;
Ball balls[BALLS_COUNT];
double ring_x = 500.0;
double ring_y = 500.0;
GtkWidget *window;
double ring_normalize(double a, double ring_size) {
if (!ring_size) return a;
return a - ring_size*floor(a/ring_size);
}
double ring_delta(double a, double b, double ring_size) {
if (!ring_size) return a - b;
double delta = ring_normalize(a - b, ring_size);
if (delta > ring_size/2) delta -= ring_size;
return delta;
}
gboolean timeout(gpointer data) {
// draw do 100 frames per seconds (see interval_ms)
// and 1000 calculation cycles per each frame
double dt = (double)interval_ms/1000.0/1000.0;
for(int t = 0; t < 1000; ++t) {
// reset accelerations
for(int i = 0; i < BALLS_COUNT; ++i) {
balls[i].ax = 0;
balls[i].ay = 0;
}
// accumulate accelerations and detect collisions
for(int i = 0; i < BALLS_COUNT; ++i) {
for(int j = i+1; j < BALLS_COUNT; ++j) {
double dx = ring_delta(balls[j].x, balls[i].x, ring_x);
double dy = ring_delta(balls[j].y, balls[i].y, ring_y);
double d = sqrt(dx*dx + dy*dy);
double nx = dx/d;
double ny = dy/d;
if (d > balls[i].r + balls[j].r) {
// balls don't touches, just apply gravity acceleration
double ax = g*balls[i].m*balls[j].m*nx/(d*d);
double ay = g*balls[i].m*balls[j].m*ny/(d*d);
balls[i].ax = balls[i].ax + ax;
balls[i].ay = balls[i].ay + ay;
balls[j].ax = balls[j].ax - ax;
balls[j].ay = balls[j].ay - ay;
} else {
// collision detected
double dvx = balls[j].vx - balls[i].vx;
double dvy = balls[j].vy - balls[i].vy;
double dv = dvx*nx + dvy*ny;
if (dv < 0) {
// calc velocities after ricochet, if balls still flies towards each other
double v0 = 2.0*dv*balls[j].m/(balls[i].m + balls[j].m);
double v1 = -2.0*dv*balls[i].m/(balls[i].m + balls[j].m);
balls[i].vx = balls[i].vx + v0*nx;
balls[i].vy = balls[i].vy + v0*ny;
balls[j].vx = balls[j].vx + v1*nx;
balls[j].vy = balls[j].vy + v1*ny;
}
}
}
}
// apply accelerations
for(int i = 0; i < BALLS_COUNT; ++i) {
balls[i].vx = balls[i].vx + balls[i].ax*dt/balls[i].m;
balls[i].vy = balls[i].vy + balls[i].ay*dt/balls[i].m;
balls[i].x = ring_normalize(balls[i].x + balls[i].vx*dt, ring_x);
balls[i].y = ring_normalize(balls[i].y + balls[i].vy*dt, ring_y);
}
}
gtk_widget_queue_draw(window);
return TRUE;
}
gboolean draw(GtkWidget *widget, cairo_t *cr, gpointer data) {
cairo_set_source_rgba(cr, 0.9, 0.9, 0.9, 1);
cairo_paint(cr);
cairo_set_source_rgba(cr, 0, 0, 0, 1);
cairo_set_line_width(cr, 1.0);
for(int i = 0; i < BALLS_COUNT; ++i) {
double r = balls[i].r;
for(int j = -1; j <= 1; ++j) {
for(int k = -1; k <= 1; ++k) {
double x = balls[i].x + ring_x*j;
double y = balls[i].y + ring_y*k;
cairo_arc(cr, x, y, r, 0, 2*pi);
cairo_stroke(cr);
}
}
}
return TRUE;
}
void activate(GtkApplication* app, gpointer data) {
srand(time(NULL));
for(int i = 0; i < BALLS_COUNT; ++i) {
double r = 10.0 + 30.0*(rand()/(double)(RAND_MAX));
balls[i].x = ring_x*rand()/(double)(RAND_MAX);
balls[i].y = ring_y*rand()/(double)(RAND_MAX);
balls[i].vx = 0.0;
balls[i].vy = 0.0;
balls[i].r = r;
balls[i].m = r*r*r;
}
window = gtk_application_window_new (app);
g_signal_connect(window, "draw", G_CALLBACK(draw), NULL);
gtk_window_set_default_size(GTK_WINDOW(window), (int)ring_x, (int)ring_y);
gtk_widget_show_all(window);
g_timeout_add(interval_ms, timeout, NULL);
}
int main(int argc, char **argv) {
GtkApplication *application = gtk_application_new(NULL, 0);
g_signal_connect(application, "activate", G_CALLBACK(activate), NULL);
int status = g_application_run(G_APPLICATION(application), argc, argv);
g_object_unref(application);
return status;
}