diff --git a/Makefile b/Makefile index bbb4902..af9a5b3 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,6 @@ +.PHONY: default +default: all + GTK_PACKAGES=gdk-pixbuf-2.0 gtk+-3.0 GTK_CFLAGS=$(shell pkg-config --cflags $(GTK_PACKAGES)) GTK_LIBS=$(shell pkg-config --libs $(GTK_PACKAGES)) @@ -8,10 +11,16 @@ CFLAGS=-Wall -g -O2 $(PROFILING_CFLAGS) $(GTK_CFLAGS) LIBS=$(GTK_LIBS) -lm PROGS=balls -OBJS=balls.o c_index.o +OBJS=balls.o c_index.o game.o gravity.o spaceship.o main.o -.PHONY: default -default: all +# dependencies (gcc -MM *.c) +balls.o: balls.c game.h balls.h gravity.h +c_index.o: c_index.c balls.h game.h c_index.h +game.o: game.c game.h +gravity.o: gravity.c gravity.h game.h +main.o: main.c game.h balls.h c_index.h gravity.h spaceship.h +spaceship.o: spaceship.c balls.h game.h +stats.o: stats.c .PHONY: run run: balls diff --git a/balls.c b/balls.c index 70b4cbf..240e8bf 100644 --- a/balls.c +++ b/balls.c @@ -1,22 +1,10 @@ -#include #include -#include #include -#include - -#include -#include - -#define DEFAULT_WIDTH 800 -#define DEFAULT_HEIGHT 800 +#include +#include "game.h" #include "balls.h" -#include "c_index.h" - -double delta = 0.01; /* seconds */ - -unsigned int width = DEFAULT_WIDTH; -unsigned int height = DEFAULT_HEIGHT; +#include "gravity.h" unsigned int radius_min = 5; unsigned int radius_max = 10; @@ -30,10 +18,7 @@ unsigned int v_angle_max = 100; struct ball * balls = 0; unsigned int n_balls = 50; -double g_y = 20; -double g_x = 0; - -void random_velocity(struct ball * p) { +static void random_velocity(struct ball * p) { double r2; do { p->v_x = v_min + rand() % (v_max + 1 - v_min); @@ -42,23 +27,7 @@ void random_velocity(struct ball * p) { } while (r2 > v_max*v_max || r2 < v_min*v_min); } -struct ball spaceship; -double spaceship_thrust = 0; -int spaceship_thrust_countdown = 0; -int spaceship_thrust_init = 50; - -void spaceship_init_state () { - spaceship.x = width/2; - spaceship.y = height/2; - spaceship.radius = 30; - spaceship.v_x = 0; - spaceship.v_y = 0; - spaceship.angle = 0; - spaceship.v_angle = 0; -} - void balls_init_state () { - srand(time(NULL)); static const unsigned int border = 10; unsigned int w = width < 2*border ? 1 : width - 2*border; unsigned int h = height < 2*border ? 1 : height - 2*border; @@ -78,31 +47,6 @@ void balls_init_state () { } } -void ball_elastic_collision (struct ball * p, struct ball * q) { - double dx = q->x - p->x; - double dy = q->y - p->y; - double d2 = dx*dx + dy*dy; - double r = p->radius + q->radius; - if (d2 <= r*r) { - double dv_x = q->v_x - p->v_x; - double dv_y = q->v_y - p->v_y; - - double mp = p->radius * p->radius; - double mq = q->radius * q->radius; - - double f = dv_x*dx + dv_y*dy; - - if (f < 0) { - f /= d2*(mp + mq); - p->v_x += 2*mq*f*dx; - p->v_y += 2*mq*f*dy; - - q->v_x -= 2*mp*f*dx; - q->v_y -= 2*mp*f*dy; - } - } -} - void ball_update_state (struct ball * p) { p->x += delta*p->v_x + delta*delta*g_x/2.0; p->v_x += delta*g_x; @@ -140,90 +84,40 @@ void ball_update_state (struct ball * p) { p->angle += 2*M_PI; } -void spaceship_update_state () { - if (spaceship_thrust > 0) { - double fx = cos(spaceship.angle)*spaceship_thrust*4.0; - double fy = sin(spaceship.angle)*spaceship_thrust*4.0; +void ball_elastic_collision (struct ball * p, struct ball * q) { + double dx = q->x - p->x; + double dy = q->y - p->y; + double d2 = dx*dx + dy*dy; + double r = p->radius + q->radius; + if (d2 <= r*r) { + double dv_x = q->v_x - p->v_x; + double dv_y = q->v_y - p->v_y; - spaceship.x += delta*delta*fx/2.0; - spaceship.v_x += delta*fx; - spaceship.y += delta*delta*fy/2.0; - spaceship.v_y += delta*fy; - if (spaceship_thrust_countdown > 0) - --spaceship_thrust_countdown; - else - spaceship_thrust = 0; - } - ball_update_state(&spaceship); -} + double mp = p->radius * p->radius; + double mq = q->radius * q->radius; -void reposition_within_borders () { - for(int i = 0; i < n_balls; ++i) { - struct ball * p = balls + i; - if (p->x < p->radius) - p->x = p->radius; - else if (p->x + p->radius > width) - p->x = width - p->radius; - if (p->y < p->radius) - p->y = p->radius; - else if (p->y + p->radius > height) - p->y = height - p->radius; + double f = dv_x*dx + dv_y*dy; + + if (f < 0) { + f /= d2*(mp + mq); + p->v_x += 2*mq*f*dx; + p->v_y += 2*mq*f*dy; + + q->v_x -= 2*mp*f*dx; + q->v_y -= 2*mp*f*dy; + } } } -void movement_and_borders () { - for(int i = 0; i < n_balls; ++i) - ball_update_state(balls + i); - spaceship_update_state(); -} - -/* Trivial collision check - */ - -void check_collisions_simple () { - for(int i = 0; i < n_balls; ++i) - for(int j = i + 1; j < n_balls; ++j) - ball_elastic_collision(balls + i, balls + j); - for(int j = 0; j < n_balls; ++j) - ball_elastic_collision(&spaceship, balls + j); -} - -void check_collisions_with_index () { - c_index_build(); - c_index_check_collisions(ball_elastic_collision); -} - -void (*check_collisions)() = 0; - -void update_state () { - if (check_collisions) - check_collisions(); - movement_and_borders(); -} - -/* Graphics System - */ - -GtkWidget * canvas; - -int gravity_vector_countdown = 0; -int gravity_vector_init = 300; - -void draw_gravity_vector(cairo_t * cr) { - if (gravity_vector_countdown != 0) { - cairo_save(cr); - cairo_new_path(cr); - cairo_move_to(cr, width/2, height/2); - cairo_line_to(cr, width/2 + g_x, height/2 + g_y); - cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); - cairo_set_line_width(cr, 1.0); - cairo_stroke(cr); - cairo_arc(cr, width/2 + g_x, height/2 + g_y, 3, 0, 2*M_PI); - cairo_fill(cr); - if (gravity_vector_countdown > 0) - --gravity_vector_countdown; - cairo_restore(cr); - } +void ball_reposition (struct ball * b) { + if (b->x < b->radius) + b->x = b->radius; + else if (b->x + b->radius > width) + b->x = width - b->radius; + if (b->y < b->radius) + b->y = b->radius; + else if (b->y + b->radius > height) + b->y = height - b->radius; } const char * face_filename = 0; @@ -231,8 +125,8 @@ int face_rotation = 0; static const double linear_rotation_unit = 2.0; -unsigned int faces_count; -struct ball_face ** faces; +static unsigned int faces_count; +static struct ball_face ** faces; struct ball_face { unsigned int rotations; @@ -243,7 +137,7 @@ static double random_color_component() { return 1.0*(rand() % 200 + 56)/255; }; -struct ball_face * new_ball_face(unsigned int radius, cairo_surface_t * face, int rotation) { +static struct ball_face * new_ball_face(unsigned int radius, cairo_surface_t * face, int rotation) { struct ball_face * f = malloc(sizeof(struct ball_face)); if (!f) return 0; @@ -293,7 +187,7 @@ struct ball_face * new_ball_face(unsigned int radius, cairo_surface_t * face, in return f; } -void init_graphics() { +static void balls_init_faces () { cairo_surface_t * face_surface = 0; if (face_filename) { @@ -324,47 +218,7 @@ void init_graphics() { } } -void destroy_graphics() { - if (!faces) - return; - for (int i = 0; i < faces_count; ++i) { - if (faces[i]) { - if (faces[i]->c_faces) { - for (unsigned int j = 0; j < faces[i]->rotations; ++j) - cairo_surface_destroy(faces[i]->c_faces[j]); - free(faces[i]->c_faces); - } - free(faces[i]); - } - } - free(faces); - faces = 0; - faces_count = 0; -} - -void draw_space_ship (cairo_t * cr) { - static const double one_over_sqrt_2 = 0.70710678118654752440; - cairo_save(cr); - cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, 1.0); - cairo_translate(cr, spaceship.x, spaceship.y); - cairo_rotate(cr, spaceship.angle); - cairo_arc(cr, 0, 0, spaceship.radius, 0, 2*M_PI); - cairo_stroke(cr); - cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 1.0); - cairo_move_to (cr, 0, 0); - cairo_line_to (cr, -one_over_sqrt_2*spaceship.radius, one_over_sqrt_2*spaceship.radius); - cairo_line_to (cr, spaceship.radius, 0); - cairo_line_to (cr, -one_over_sqrt_2*spaceship.radius, -one_over_sqrt_2*spaceship.radius); - cairo_line_to (cr, 0, 0); - cairo_stroke(cr); - for (unsigned int i = 0; i < spaceship_thrust; i += 5) { - cairo_arc(cr, 0, 0, spaceship.radius + i, 0.7*M_PI, 1.3*M_PI); - cairo_stroke(cr); - } - cairo_restore(cr); -} - -void draw_balls (cairo_t * cr) { +void balls_draw (cairo_t * cr) { for (const struct ball * b = balls; b != balls + n_balls; ++b) { cairo_save(cr); cairo_translate(cr, b->x - b->radius, b->y - b->radius); @@ -383,257 +237,32 @@ void draw_balls (cairo_t * cr) { } } -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); - draw_gravity_vector(cr); - draw_balls(cr); - draw_space_ship(cr); - return FALSE; -} - -gint configure_event (GtkWidget *widget, GdkEventConfigure * event) { - if (width == gtk_widget_get_allocated_width(widget) && height == gtk_widget_get_allocated_height(widget)) - return FALSE; - - width = gtk_widget_get_allocated_width(widget); - height = gtk_widget_get_allocated_height(widget); - - reposition_within_borders(); - return TRUE; -} - -gint keyboard_input (GtkWidget *widget, GdkEventKey *event) { - if (event->type != GDK_KEY_PRESS) - return FALSE; - switch(event->keyval) { - case GDK_KEY_Up: - g_y -= 10; - gravity_vector_countdown = gravity_vector_init; - break; - case GDK_KEY_Down: - g_y += 10; - gravity_vector_countdown = gravity_vector_init; - break; - case GDK_KEY_Left: - g_x -= 10; - gravity_vector_countdown = gravity_vector_init; - break; - case GDK_KEY_Right: - g_x += 10; - gravity_vector_countdown = gravity_vector_init; - break; - case GDK_KEY_G: - case GDK_KEY_g: - gravity_vector_countdown = gravity_vector_init; - break; - case GDK_KEY_Q: - case GDK_KEY_q: - gtk_main_quit(); - break; - default: - return FALSE; - } - return TRUE; -} - -gboolean mouse_scroll (GtkWidget *widget, GdkEvent *event, gpointer user_data) { - if (event->type == GDK_SCROLL) { - GdkEventScroll * e = (GdkEventScroll*) event; - switch (e->direction) { - case GDK_SCROLL_SMOOTH: { - double dx, dy; - gdk_event_get_scroll_deltas (event, &dx, &dy); - spaceship.angle -= dx/4; - if (spaceship.angle < 0) - spaceship.angle += 2*M_PI; - else if (spaceship.angle > 2*M_PI) - spaceship.angle -= 2*M_PI; - spaceship_thrust += dy; - if (spaceship_thrust > 0) - spaceship_thrust_countdown = spaceship_thrust_init; - else - spaceship_thrust = 0; - break; - } - case GDK_SCROLL_LEFT: - g_x -= 10; - gravity_vector_countdown = gravity_vector_init; - break; - case GDK_SCROLL_RIGHT: - g_x += 10; - gravity_vector_countdown = gravity_vector_init; - break; - case GDK_SCROLL_UP: - g_y += 10; - gravity_vector_countdown = gravity_vector_init; - break; - case GDK_SCROLL_DOWN: - g_y -= 10; - gravity_vector_countdown = gravity_vector_init; - break; - } - } - return TRUE; -} - -void destroy_window (void) { - gtk_main_quit(); -} - -void print_usage (const char * progname) { - fprintf(stderr, - "usage: %s [options...]\n" - "options:\n" - "\tx\n" - "\tn=\n" - "\tfx=\n" - "\tfy=\n" - "\tradius=-\n" - "\tv=-\n" - "\tdelta= (in seconds)\n" - "\tface=\n" - "\tstats= :: rendering timing statitstics (0=disabled, default)\n" - "\tcollisions= :: n=no collisions, s=simple, i=index\n" - "\t-r :: activate face rotation\n", - progname); -} - -static unsigned int stats_sampling = 0; -static guint64 stats_update_usec = 0; -static unsigned int stats_update_samples = 0; -static guint64 stats_draw_usec = 0; -static unsigned int stats_draw_samples = 0; - -gboolean draw_event (GtkWidget *widget, cairo_t * cr, gpointer data) { - if (stats_sampling > 0) { - guint64 start = g_get_monotonic_time (); - draw_frame (widget, cr, data); - stats_draw_usec += g_get_monotonic_time () - start; - ++stats_draw_samples; - } else - draw_frame (widget, cr, data); - return FALSE; -} - -gboolean timeout (gpointer user_data) { - if (stats_sampling > 0) { - guint64 start = g_get_monotonic_time (); - update_state(); - stats_update_usec += g_get_monotonic_time () - start; - if (++stats_update_samples == stats_sampling) { - float uavg = 1.0*stats_update_usec / stats_update_samples; - float davg = 1.0*stats_draw_usec / stats_draw_samples; - printf("\rupdate = %.0f us, draw = %.0f us, load = %.0f%% (%u update, %u draw) ", - uavg, davg, (uavg+davg)/(10000.0*delta), - stats_update_samples, stats_draw_samples); - fflush(stdout); - stats_update_usec = 0; - stats_update_samples = 0; - stats_draw_usec = 0; - stats_draw_samples = 0; - } - } else { - update_state(); - } - gtk_widget_queue_draw(canvas); - return TRUE; -} - -int main (int argc, const char *argv[]) { - int w = DEFAULT_WIDTH; - int h = DEFAULT_HEIGHT; - - for (int i = 1; i < argc; ++i) { - if (sscanf(argv[i], "%dx%d", &w, &h) == 2) - continue; - if (sscanf(argv[i], "n=%u", &n_balls) == 1) - continue; - if (sscanf(argv[i], "fx=%lf", &g_x) == 1) - continue; - if (sscanf(argv[i], "fy=%lf", &g_y) == 1) - continue; - if (sscanf(argv[i], "radius=%u-%u", &radius_min, &radius_max) == 2) - continue; - if (sscanf(argv[i], "v=%u-%u", &v_min, &v_max) == 2) - continue; - if (sscanf(argv[i], "delta=%lf", &delta) == 1) - continue; - if (strncmp(argv[i], "face=", 5) == 0) { - face_filename = argv[i] + 5; - continue; - } - if (sscanf(argv[i], "stats=%u", &stats_sampling) == 1) - continue; - char collisions; - if (sscanf(argv[i], "collisions=%c", &collisions) == 1) { - switch (collisions) { - case 'i': - case 'I': - check_collisions = check_collisions_with_index; - continue; - case '0': - case 'N': - case 'n': - check_collisions = 0; - continue; - case 's': - case 'S': - check_collisions = check_collisions_simple; - continue; +static void balls_destroy_faces () { + if (!faces) + return; + for (int i = 0; i < faces_count; ++i) { + if (faces[i]) { + if (faces[i]->c_faces) { + for (unsigned int j = 0; j < faces[i]->rotations; ++j) + cairo_surface_destroy(faces[i]->c_faces[j]); + free(faces[i]->c_faces); } + free(faces[i]); } - if (strcmp(argv[i], "-r") == 0) { - face_rotation = 1; - continue; - } - print_usage(argv[0]); - return 1; } + free (faces); + faces = 0; + faces_count = 0; +} +void balls_destroy () { + balls_destroy_faces (); + free (balls); +} + +void balls_init () { balls = malloc(sizeof(struct ball)*n_balls); assert(balls); - - assert(c_index_init()); - - balls_init_state(); - spaceship_init_state(); - - gtk_init(0, 0); - - GtkWidget * window = gtk_window_new (GTK_WINDOW_TOPLEVEL); - gtk_window_set_default_size(GTK_WINDOW(window), width, height); - gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); - gtk_window_set_title(GTK_WINDOW(window), "Game"); - - g_signal_connect(window, "destroy", G_CALLBACK(destroy_window), NULL); - g_signal_connect (G_OBJECT (window), "delete-event", G_CALLBACK(destroy_window), NULL); - g_signal_connect(G_OBJECT (window), "key-press-event", G_CALLBACK(keyboard_input), NULL); - gtk_widget_set_events (window, GDK_EXPOSURE_MASK | GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK | GDK_KEY_PRESS_MASK); - - canvas = gtk_drawing_area_new (); - - g_signal_connect (G_OBJECT (canvas), "configure-event", G_CALLBACK(configure_event), NULL); - g_signal_connect (G_OBJECT (canvas), "draw", G_CALLBACK (draw_event), NULL); - g_signal_connect (G_OBJECT (canvas), "scroll-event",G_CALLBACK(mouse_scroll), NULL); - gtk_widget_set_events (canvas, GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK); - - gtk_container_add (GTK_CONTAINER (window), canvas); - - g_timeout_add (delta * 1000, timeout, canvas); - - gtk_widget_show_all(window); - - init_graphics(); - - gtk_main(); - - if (stats_sampling > 0) - printf("\n"); - - destroy_graphics(); - c_index_destroy(); - free(balls); - - return 0; + balls_init_state (); + balls_init_faces (); } diff --git a/balls.h b/balls.h index 14f2ad6..880f3d3 100644 --- a/balls.h +++ b/balls.h @@ -1,6 +1,8 @@ #ifndef BALLS_H_INCLUDED #define BALLS_H_INCLUDED +#include + struct ball_face; struct ball { @@ -17,11 +19,26 @@ struct ball { struct ball_face * face; }; -extern unsigned int width; -extern unsigned int height; - extern struct ball * balls; extern unsigned int n_balls; +extern unsigned int radius_min; +extern unsigned int radius_max; + +extern unsigned int v_max; +extern unsigned int v_min; + +extern unsigned int v_angle_min; +extern unsigned int v_angle_max; + +extern const char * face_filename; +extern int face_rotation; + +extern void balls_init (); +extern void balls_destroy (); +extern void ball_update_state (struct ball * p); +extern void ball_elastic_collision (struct ball * p, struct ball * q); +extern void ball_reposition (struct ball * b); +extern void balls_draw (cairo_t * cr); #endif diff --git a/c_index.c b/c_index.c index 5831960..61ee3f5 100644 --- a/c_index.c +++ b/c_index.c @@ -1,6 +1,7 @@ #include #include "balls.h" +#include "game.h" #include "c_index.h" /* Collision check with index diff --git a/game.c b/game.c new file mode 100644 index 0000000..ab4a9f0 --- /dev/null +++ b/game.c @@ -0,0 +1,20 @@ +#include +#include +#include +#include +#include + +#include +#include + +#include "game.h" + +double delta = DEFAULT_DELTA; /* seconds */ + +unsigned int width = DEFAULT_WIDTH; +unsigned int height = DEFAULT_HEIGHT; + +/* Graphics System + */ +GtkWidget * canvas; + diff --git a/game.h b/game.h new file mode 100644 index 0000000..cba025d --- /dev/null +++ b/game.h @@ -0,0 +1,31 @@ +#ifndef GAME_H_INCLUDED +#define GAME_H_INCLUDED + +#include + +/* simulation/game framework + */ + +extern double delta; /* simulation time delta in seconds */ + +#define DEFAULT_DELTA 0.01 + +extern unsigned int width; /* game canvas width */ +extern unsigned int height; /* game canvas height */ + +#define DEFAULT_WIDTH 800 +#define DEFAULT_HEIGHT 800 + +extern GtkWidget * canvas; /* game canvas object */ + +#if 0 +extern void game_init (); /* implemented by the application */ + +/* game frame function to be provided by the game application */ +extern gboolean game_draw_frame (GtkWidget * widget, cairo_t * cr, gpointer data); + +extern void set_game_resize_callback (gboolean (*game_resize) ()); +extern void set_game_keyboard_input_callback (gint (*keyboard_input)(GtkWidget *, GdkEventKey *)); +extern void set_game_mouse_scroll_callback (gboolean (*mouse_scroll)(GtkWidget *, GdkEvent *, gpointer)); +#endif +#endif diff --git a/gravity.c b/gravity.c new file mode 100644 index 0000000..0b5eb64 --- /dev/null +++ b/gravity.c @@ -0,0 +1,39 @@ +#include +#include + +#include "gravity.h" +#include "game.h" + +double g_y = 20; +double g_x = 0; + +static int gravity_vector_countdown = 0; +static int gravity_vector_init = 300; + +void gravity_draw (cairo_t * cr) { + if (gravity_vector_countdown != 0) { + cairo_save(cr); + cairo_new_path(cr); + cairo_move_to(cr, width/2, height/2); + cairo_line_to(cr, width/2 + g_x, height/2 + g_y); + cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); + cairo_set_line_width(cr, 1.0); + cairo_stroke(cr); + cairo_arc(cr, width/2 + g_x, height/2 + g_y, 3, 0, 2*M_PI); + cairo_fill(cr); + if (gravity_vector_countdown > 0) + --gravity_vector_countdown; + cairo_restore(cr); + } +} + +void gravity_show () { + gravity_vector_countdown = gravity_vector_init; +}; + +void gravity_change (double dx, double dy) { + g_x += dx; + g_y += dy; + gravity_show (); +}; + diff --git a/gravity.h b/gravity.h new file mode 100644 index 0000000..08bf4f1 --- /dev/null +++ b/gravity.h @@ -0,0 +1,11 @@ +#ifndef GRAVITY_H_INCLUDED +#define GRAVITY_H_INCLUDED + +extern double g_y; +extern double g_x; + +extern void gravity_draw (cairo_t * cr); +extern void gravity_change (double dx, double dy); +extern void gravity_show (); + +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..bff379b --- /dev/null +++ b/main.c @@ -0,0 +1,279 @@ +#include +#include +#include +#include +#include + +#include +#include + +#define DEFAULT_WIDTH 800 +#define DEFAULT_HEIGHT 800 + +#include "game.h" +#include "balls.h" +#include "c_index.h" +#include "gravity.h" +#include "spaceship.h" + +/* Trivial collision check + */ +void check_collisions_simple () { + for(int i = 0; i < n_balls; ++i) + for(int j = i + 1; j < n_balls; ++j) + ball_elastic_collision(balls + i, balls + j); + for(int j = 0; j < n_balls; ++j) + ball_elastic_collision(&spaceship, balls + j); +} + +void check_collisions_with_index () { + c_index_build(); + c_index_check_collisions(ball_elastic_collision); + for(int j = 0; j < n_balls; ++j) + ball_elastic_collision(&spaceship, balls + j); +} + +void (*check_collisions)() = 0; + +void update_state () { + if (check_collisions) + check_collisions(); + for(int i = 0; i < n_balls; ++i) + ball_update_state(balls + i); + spaceship_update_state(); +} + +/* Graphics System + */ + +void game_init () { + srand (time(NULL)); + balls_init (); + assert(c_index_init()); + spaceship_init_state (); +} + +void game_destroy () { + c_index_destroy (); + balls_destroy (); +} + +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); + balls_draw (cr); + spaceship_draw (cr); + return FALSE; +} + +gint configure_event (GtkWidget *widget, GdkEventConfigure * event) { + if (width == event->width && height == event->height) + return FALSE; + + width = gtk_widget_get_allocated_width (widget); + height = gtk_widget_get_allocated_height (widget); + + for (struct ball * b = balls; b != balls + n_balls; ++b) + ball_reposition (b); + ball_reposition (&spaceship); + return TRUE; +} + +gint keyboard_input (GtkWidget *widget, GdkEventKey *event) { + if (event->type != GDK_KEY_PRESS) + return FALSE; + switch(event->keyval) { + case GDK_KEY_Up: + gravity_change (0, -10); + break; + case GDK_KEY_Down: + gravity_change (0, 10); + break; + case GDK_KEY_Left: + gravity_change (-10, 0); + break; + case GDK_KEY_Right: + gravity_change (10, 0); + break; + case GDK_KEY_G: + case GDK_KEY_g: + gravity_show (); + break; + case GDK_KEY_Q: + case GDK_KEY_q: + gtk_main_quit(); + break; + default: + return FALSE; + } + return TRUE; +} + +gboolean mouse_scroll (GtkWidget *widget, GdkEvent *event, gpointer user_data) { + if (event->type == GDK_SCROLL) { + GdkEventScroll * e = (GdkEventScroll*) event; + switch (e->direction) { + case GDK_SCROLL_SMOOTH: { + double dx, dy; + gdk_event_get_scroll_deltas (event, &dx, &dy); + spaceship_control (dx, dy); + break; + } + default: + break; + } + } + return TRUE; +} + +void destroy_window (void) { + gtk_main_quit(); +} + +void print_usage (const char * progname) { + fprintf(stderr, + "usage: %s [options...]\n" + "options:\n" + "\tx\n" + "\tn=\n" + "\tfx=\n" + "\tfy=\n" + "\tradius=-\n" + "\tv=-\n" + "\tdelta= (in seconds)\n" + "\tface=\n" + "\tstats= :: rendering timing statitstics (0=disabled, default)\n" + "\tcollisions= :: n=no collisions, s=simple, i=index\n" + "\t-r :: activate face rotation\n", + progname); +} + +static unsigned int stats_sampling = 0; +static guint64 stats_update_usec = 0; +static unsigned int stats_update_samples = 0; +static guint64 stats_draw_usec = 0; +static unsigned int stats_draw_samples = 0; + +gboolean draw_event (GtkWidget *widget, cairo_t * cr, gpointer data) { + if (stats_sampling > 0) { + guint64 start = g_get_monotonic_time (); + draw_frame (widget, cr, data); + stats_draw_usec += g_get_monotonic_time () - start; + ++stats_draw_samples; + } else + draw_frame (widget, cr, data); + return FALSE; +} + +gboolean timeout (gpointer user_data) { + if (stats_sampling > 0) { + guint64 start = g_get_monotonic_time (); + update_state(); + stats_update_usec += g_get_monotonic_time () - start; + if (++stats_update_samples == stats_sampling) { + float uavg = 1.0*stats_update_usec / stats_update_samples; + float davg = 1.0*stats_draw_usec / stats_draw_samples; + printf("\rupdate = %.0f us, draw = %.0f us, load = %.0f%% (%u update, %u draw) ", + uavg, davg, (uavg+davg)/(10000.0*delta), + stats_update_samples, stats_draw_samples); + fflush(stdout); + stats_update_usec = 0; + stats_update_samples = 0; + stats_draw_usec = 0; + stats_draw_samples = 0; + } + } else { + update_state(); + } + gtk_widget_queue_draw(canvas); + return TRUE; +} + +int main (int argc, const char *argv[]) { + int w = DEFAULT_WIDTH; + int h = DEFAULT_HEIGHT; + + for (int i = 1; i < argc; ++i) { + if (sscanf(argv[i], "%dx%d", &w, &h) == 2) + continue; + if (sscanf(argv[i], "n=%u", &n_balls) == 1) + continue; + if (sscanf(argv[i], "fx=%lf", &g_x) == 1) + continue; + if (sscanf(argv[i], "fy=%lf", &g_y) == 1) + continue; + if (sscanf(argv[i], "radius=%u-%u", &radius_min, &radius_max) == 2) + continue; + if (sscanf(argv[i], "v=%u-%u", &v_min, &v_max) == 2) + continue; + if (sscanf(argv[i], "delta=%lf", &delta) == 1) + continue; + if (strncmp(argv[i], "face=", 5) == 0) { + face_filename = argv[i] + 5; + continue; + } + if (sscanf(argv[i], "stats=%u", &stats_sampling) == 1) + continue; + char collisions; + if (sscanf(argv[i], "collisions=%c", &collisions) == 1) { + switch (collisions) { + case 'i': + case 'I': + check_collisions = check_collisions_with_index; + continue; + case '0': + case 'N': + case 'n': + check_collisions = 0; + continue; + case 's': + case 'S': + check_collisions = check_collisions_simple; + continue; + } + } + if (strcmp(argv[i], "-r") == 0) { + face_rotation = 1; + continue; + } + print_usage(argv[0]); + return 1; + } + + gtk_init(0, 0); + + GtkWidget * window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_default_size(GTK_WINDOW(window), width, height); + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_title(GTK_WINDOW(window), "Game"); + + g_signal_connect(window, "destroy", G_CALLBACK(destroy_window), NULL); + g_signal_connect (G_OBJECT (window), "delete-event", G_CALLBACK(destroy_window), NULL); + g_signal_connect(G_OBJECT (window), "key-press-event", G_CALLBACK(keyboard_input), NULL); + gtk_widget_set_events (window, GDK_EXPOSURE_MASK | GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK | GDK_KEY_PRESS_MASK); + + canvas = gtk_drawing_area_new (); + + g_signal_connect (G_OBJECT (canvas), "configure-event", G_CALLBACK(configure_event), NULL); + g_signal_connect (G_OBJECT (canvas), "draw", G_CALLBACK (draw_event), NULL); + g_signal_connect (G_OBJECT (canvas), "scroll-event",G_CALLBACK(mouse_scroll), NULL); + gtk_widget_set_events (canvas, GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK); + + gtk_container_add (GTK_CONTAINER (window), canvas); + + g_timeout_add (delta * 1000, timeout, canvas); + + gtk_widget_show_all(window); + + game_init (); + + gtk_main(); + + if (stats_sampling > 0) + printf("\n"); + + game_destroy (); + + return 0; +} diff --git a/spaceship.c b/spaceship.c new file mode 100644 index 0000000..d60dc74 --- /dev/null +++ b/spaceship.c @@ -0,0 +1,72 @@ +#include +#include + +#include "balls.h" +#include "game.h" + +struct ball spaceship; +double spaceship_thrust = 0; +int spaceship_thrust_countdown = 0; +int spaceship_thrust_init = 50; + +void spaceship_init_state () { + spaceship.x = width/2; + spaceship.y = height/2; + spaceship.radius = 30; + spaceship.v_x = 0; + spaceship.v_y = 0; + spaceship.angle = 0; + spaceship.v_angle = 0; +} + +void spaceship_update_state () { + if (spaceship_thrust > 0) { + double fx = cos(spaceship.angle)*spaceship_thrust*4.0; + double fy = sin(spaceship.angle)*spaceship_thrust*4.0; + + spaceship.x += delta*delta*fx/2.0; + spaceship.v_x += delta*fx; + spaceship.y += delta*delta*fy/2.0; + spaceship.v_y += delta*fy; + if (spaceship_thrust_countdown > 0) + --spaceship_thrust_countdown; + else + spaceship_thrust = 0; + } + ball_update_state(&spaceship); +} + +void spaceship_draw (cairo_t * cr) { + static const double one_over_sqrt_2 = 0.70710678118654752440; + cairo_save(cr); + cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, 1.0); + cairo_translate(cr, spaceship.x, spaceship.y); + cairo_rotate(cr, spaceship.angle); + cairo_arc(cr, 0, 0, spaceship.radius, 0, 2*M_PI); + cairo_stroke(cr); + cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 1.0); + cairo_move_to (cr, 0, 0); + cairo_line_to (cr, -one_over_sqrt_2*spaceship.radius, one_over_sqrt_2*spaceship.radius); + cairo_line_to (cr, spaceship.radius, 0); + cairo_line_to (cr, -one_over_sqrt_2*spaceship.radius, -one_over_sqrt_2*spaceship.radius); + cairo_line_to (cr, 0, 0); + cairo_stroke(cr); + for (unsigned int i = 0; i < spaceship_thrust; i += 5) { + cairo_arc(cr, 0, 0, spaceship.radius + i, 0.7*M_PI, 1.3*M_PI); + cairo_stroke(cr); + } + cairo_restore(cr); +} + +void spaceship_control (double dx, double dy) { + spaceship.angle -= dx/4; + if (spaceship.angle < 0) + spaceship.angle += 2*M_PI; + else if (spaceship.angle > 2*M_PI) + spaceship.angle -= 2*M_PI; + spaceship_thrust += dy; + if (spaceship_thrust > 0) + spaceship_thrust_countdown = spaceship_thrust_init; + else + spaceship_thrust = 0; +} diff --git a/spaceship.h b/spaceship.h new file mode 100644 index 0000000..91f6796 --- /dev/null +++ b/spaceship.h @@ -0,0 +1,11 @@ +#ifndef SPACESHIP_H_INCLUDED +#define SPACESHIP_H_INCLUDED + +extern struct ball spaceship; + +extern void spaceship_init_state (); +extern void spaceship_update_state (); +extern void spaceship_control (double dx, double dy); +extern void spaceship_draw (cairo_t * cr); + +#endif diff --git a/stats.c b/stats.c new file mode 100644 index 0000000..8ca594a --- /dev/null +++ b/stats.c @@ -0,0 +1,7 @@ + +static unsigned int stats_sampling = 0; +static guint64 stats_update_usec = 0; +static unsigned int stats_update_samples = 0; +static guint64 stats_draw_usec = 0; +static unsigned int stats_draw_samples = 0; + diff --git a/stats.h b/stats.h new file mode 100644 index 0000000..88722e3 --- /dev/null +++ b/stats.h @@ -0,0 +1,24 @@ +#ifndef STATS_H_INCLUDED +#define STATS_H_INCLUDED + +struct timing_stats { + guint64 usec; + unsigned int samples; + unsigned int target_samples; +}; + +static inline float stats_avg_usec (const timing_stats * s) { + return 1.0 * s->usec / s->samples; +} + +static inline + +#define STATS_TAKE_SAMPLE(s,f) \ + if ((s)->target_samples > 0) { \ + guint64 start = g_get_monotonic_time (); \ + do { f; } while 0; + (s)->usec += g_get_monotonic_time () - start; + ++(s)->samples; + } else { do { f; } while 0; } + +#endif