Blob Blame History Raw
#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;
}