2019-06-03 10:09:00 +02:00
# include <stdio.h>
# include <stdlib.h>
# include <assert.h>
# include <math.h>
# include <time.h>
# include <gdk/gdkkeysyms.h>
# include <gtk/gtk.h>
# define DEFAULT_WIDTH 800
# define DEFAULT_HEIGHT 800
2021-01-12 15:55:25 +01:00
struct ball_face ;
2019-06-03 10:09:00 +02:00
struct ball {
2019-06-03 22:24:06 +02:00
double x ;
double y ;
2019-06-03 10:09:00 +02:00
unsigned int radius ;
2019-06-03 22:24:06 +02:00
double v_x ;
double v_y ;
2019-06-05 14:31:11 +02:00
2019-12-18 21:18:21 +01:00
double angle ;
double v_angle ;
2021-01-12 15:55:25 +01:00
struct ball_face * face ;
2019-06-03 10:09:00 +02:00
} ;
2019-08-20 16:15:29 +02:00
double delta = 0.01 ; /* seconds */
2019-08-10 11:11:14 +02:00
2019-08-20 16:15:29 +02:00
unsigned int width = DEFAULT_WIDTH ;
unsigned int height = DEFAULT_HEIGHT ;
2019-06-03 10:09:00 +02:00
2019-08-20 16:15:29 +02:00
unsigned int radius_min = 5 ;
unsigned int radius_max = 10 ;
2019-06-03 10:09:00 +02:00
2019-08-20 16:15:29 +02:00
unsigned int v_max = 100 ;
unsigned int v_min = 0 ;
2019-06-03 10:09:00 +02:00
2019-12-18 21:18:21 +01:00
unsigned int v_angle_min = 0 ;
unsigned int v_angle_max = 100 ;
2019-06-03 10:09:00 +02:00
struct ball * balls = 0 ;
2019-06-04 10:13:02 +02:00
unsigned int n_balls = 50 ;
2019-06-03 10:09:00 +02:00
2019-08-20 16:15:29 +02:00
double g_y = 20 ;
double g_x = 0 ;
2019-06-03 10:09:00 +02:00
2019-08-20 16:15:29 +02:00
double clear_alpha = 1.0 ;
2019-07-29 15:48:15 +02:00
2019-08-20 16:15:29 +02:00
void random_velocity ( struct ball * p ) {
2019-07-05 15:58:10 +02:00
double r2 ;
do {
p - > v_x = v_min + rand ( ) % ( v_max + 1 - v_min ) ;
p - > v_y = v_min + rand ( ) % ( v_max + 1 - v_min ) ;
r2 = p - > v_x * p - > v_x + p - > v_y * p - > v_y ;
} while ( r2 > v_max * v_max | | r2 < v_min * v_min ) ;
}
2019-06-03 22:24:06 +02:00
void balls_init_state ( ) {
2019-06-03 10:09:00 +02:00
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 ;
2019-06-04 10:13:02 +02:00
2019-06-03 10:09:00 +02:00
for ( unsigned int i = 0 ; i < n_balls ; + + i ) {
balls [ i ] . x = border + rand ( ) % w ;
balls [ i ] . y = border + rand ( ) % h ;
2019-07-05 15:58:10 +02:00
random_velocity ( balls + i ) ;
2019-06-05 14:51:02 +02:00
if ( rand ( ) % 2 )
balls [ i ] . v_x = - balls [ i ] . v_x ;
if ( rand ( ) % 2 )
balls [ i ] . v_y = - balls [ i ] . v_y ;
2019-06-04 10:13:02 +02:00
balls [ i ] . radius = radius_min + rand ( ) % ( radius_max + 1 - radius_min ) ;
2019-12-18 21:18:21 +01:00
unsigned int v_angle_360 = ( v_angle_min + rand ( ) % ( v_angle_max + 1 - v_angle_min ) ) % 360 ;
balls [ i ] . v_angle = 2 * M_PI * v_angle_360 / 360 ;
balls [ i ] . angle = ( rand ( ) % 360 ) * 2 * M_PI / 360 ;
2019-06-03 22:24:06 +02:00
}
}
2019-08-20 16:15:29 +02:00
void ball_collision ( struct ball * p , struct ball * q ) {
2019-06-03 22:24:06 +02:00
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 ;
}
2019-06-03 10:09:00 +02:00
}
}
2021-01-12 15:55:25 +01:00
#if 0
static void tangential_friction1 ( double u , double p , double r , double * v , double * q ) {
static const double a = 0.0 ;
/*
* 2 2 2 2 2 2
* sqrt ( ( 6 - 2 a ) u + 4 a p r u + ( 3 - 2 a ) p r ) + a u - a p r
* v = - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* 3
*
* 2 2 2 2 2 2
* sqrt ( ( 6 - 2 a ) u + 4 a p r u + ( 3 - 2 a ) p r ) - 2 a u + 2 a p r
* q = - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* 3 r
*/
double a2 = a * a ;
double u2 = u * u ;
double p2 = p * p ;
double r2 = r * r ;
double sr = sqrt ( ( 6 - 2 * a2 ) * u2 + 4 * a2 * p * r * u + ( 3 - 2 * a2 ) * p2 * r2 ) ;
* v = ( sr + a * u - a * p * r ) / 3 ;
* q = ( sr - 2 * a * u + 2 * a * p * r ) / ( 3 * r ) ;
}
static void tangential_friction2 ( double u , double p , double r , double * v , double * q ) {
static const double a = 1.0 ;
/*
* 2 2 2 2 2 2
* sqrt ( ( 6 - 2 a ) u + 4 a p r u + ( 3 - 2 a ) p r ) + a u + a p r
* v = - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* 3
*
* 2 2 2 2 2 2
* sqrt ( ( 6 - 2 a ) u + 4 a p r u + ( 3 - 2 a ) p r ) - 2 a u - 2 a p r
* q = - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* 3 r
*/
double a2 = a * a ;
double u2 = u * u ;
double p2 = p * p ;
double r2 = r * r ;
double sr = sqrt ( ( 6 - 2 * a2 ) * u2 + 4 * a2 * p * r * u + ( 3 - 2 * a2 ) * p2 * r2 ) ;
* v = ( sr + a * u + a * p * r ) / 3 ;
* q = ( sr - 2 * a * u - 2 * a * p * r ) / ( 3 * r ) ;
}
2021-06-07 12:03:55 +02:00
static void tangential_friction3 ( double u , double p , double r , double * v_ptr , double * q_ptr ) {
static const double a = 0.9 ;
double v , q ;
v = ( 1 - a ) * u + a * p * r ;
/* 2 2 2 2 2 2
* sqrt ( ( 4 a - 2 a ) u + ( 4 a - 4 a ) p r u + ( 1 - 2 a ) p r )
* q = - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* r
*/
q = sqrt ( 2 * a * ( 2 - a ) * u * u + 4 * a * ( a - 1 ) * p * r * u + ( 1 - 2 * a * a ) * p * p * r * r ) / r ;
if ( p * r > v )
q = - q ;
* v_ptr = v ;
* q_ptr = q ;
2021-01-12 15:55:25 +01:00
}
2021-06-07 12:03:55 +02:00
# endif
2021-01-12 15:55:25 +01:00
2019-08-19 10:45:55 +02:00
void ball_update_state ( struct ball * p ) {
2019-06-03 10:09:00 +02:00
p - > x + = delta * p - > v_x + delta * delta * g_x / 2.0 ;
p - > v_x + = delta * g_x ;
p - > y + = delta * p - > v_y + delta * delta * g_y / 2.0 ;
p - > v_y + = delta * g_y ;
2021-01-12 15:55:25 +01:00
if ( p - > x + p - > radius > width ) { /* right wall */
2019-06-03 22:24:06 +02:00
if ( p - > v_x > 0 ) {
p - > x - = p - > x + p - > radius - width ;
p - > v_x = - p - > v_x ;
2021-01-12 15:55:25 +01:00
#if 0
2021-06-07 12:03:55 +02:00
/* tangential friction */
2021-01-12 15:55:25 +01:00
tangential_friction ( p - > v_y , - p - > v_angle , p - > radius , & ( p - > v_y ) , & ( p - > v_angle ) ) ;
# endif
2019-06-03 22:24:06 +02:00
}
2021-01-12 15:55:25 +01:00
} else if ( p - > x < p - > radius ) { /* left wall */
2019-06-03 22:24:06 +02:00
if ( p - > v_x < 0 ) {
p - > x + = p - > radius - p - > x ;
p - > v_x = - p - > v_x ;
2021-01-12 15:55:25 +01:00
#if 0
2021-06-07 12:03:55 +02:00
/* tangential friction */
2021-01-12 15:55:25 +01:00
tangential_friction ( p - > v_y , p - > v_angle , p - > radius , & ( p - > v_y ) , & ( p - > v_angle ) ) ;
# endif
2019-06-03 22:24:06 +02:00
}
2019-06-03 10:09:00 +02:00
}
2021-01-12 15:55:25 +01:00
if ( p - > y + p - > radius > height ) { /* bottom wall */
2019-06-03 22:24:06 +02:00
if ( p - > v_y > 0 ) {
p - > y - = p - > y + p - > radius - height ;
p - > v_y = - p - > v_y ;
2021-06-07 12:03:55 +02:00
#if 0
2021-01-12 15:55:25 +01:00
/* tangential friction */
tangential_friction3 ( p - > v_x , p - > v_angle , p - > radius , & ( p - > v_x ) , & ( p - > v_angle ) ) ;
# endif
2019-06-03 22:24:06 +02:00
}
2021-01-12 15:55:25 +01:00
} else if ( p - > y < p - > radius ) { /* top wall */
2019-06-03 22:24:06 +02:00
if ( p - > v_y < 0 ) {
p - > y + = p - > radius - p - > y ;
p - > v_y = - p - > v_y ;
2021-01-12 15:55:25 +01:00
#if 0
2021-06-07 12:03:55 +02:00
/* tangential friction */
2021-01-12 15:55:25 +01:00
tangential_friction ( p - > v_x , - p - > v_angle , p - > radius , & ( p - > v_x ) , & ( p - > v_angle ) ) ;
p - > v_angle = - p - > v_angle ;
# endif
2019-06-03 22:24:06 +02:00
}
2019-06-03 10:09:00 +02:00
}
2019-12-18 21:18:21 +01:00
p - > angle + = delta * p - > v_angle ;
2021-01-12 15:55:25 +01:00
while ( p - > angle > = 2 * M_PI )
2019-12-18 21:18:21 +01:00
p - > angle - = 2 * M_PI ;
2021-01-12 15:55:25 +01:00
while ( p - > angle < 0 )
p - > angle + = 2 * M_PI ;
2019-06-03 10:09:00 +02:00
}
2021-06-07 12:03:55 +02:00
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 ;
}
}
2019-08-19 10:45:55 +02:00
void movement_and_borders ( ) {
2019-06-03 10:09:00 +02:00
for ( int i = 0 ; i < n_balls ; + + i )
ball_update_state ( balls + i ) ;
}
2019-08-20 16:15:29 +02:00
/* Collision check with index
*/
struct rectangle {
double min_x ; /* left */
double min_y ; /* bottom */
double max_x ; /* right */
double max_y ; /* top */
} ;
struct bt_node {
struct ball * ball ;
struct rectangle r ;
struct bt_node * left ;
struct bt_node * right ;
} ;
struct bt_node * c_index = 0 ;
static struct bt_node * c_index_init_node ( struct bt_node * n , struct ball * b ) {
n - > ball = b ;
n - > r . min_x = b - > x - b - > radius ;
n - > r . min_y = b - > y - b - > radius ;
n - > r . max_x = b - > x + b - > radius ;
n - > r . max_y = b - > y + b - > radius ;
n - > left = 0 ;
n - > right = 0 ;
return n ;
}
static void c_index_add_ball ( struct bt_node * n , const struct ball * b ) {
if ( n - > r . min_x > b - > x - b - > radius )
n - > r . min_x = b - > x - b - > radius ;
if ( n - > r . min_y > b - > y - b - > radius )
n - > r . min_y = b - > y - b - > radius ;
if ( n - > r . max_x < b - > x + b - > radius )
n - > r . max_x = b - > x + b - > radius ;
if ( n - > r . max_y < b - > y + b - > radius )
n - > r . max_y = b - > y + b - > radius ;
}
static void c_index_insert ( struct bt_node * t , struct bt_node * n , struct ball * b ) {
double w = width ;
double h = height ;
double ref_x = 0.0 ;
double ref_y = 0.0 ;
2019-08-20 17:23:54 +02:00
c_index_init_node ( n , b ) ;
2019-08-20 16:15:29 +02:00
for ( ; ; ) {
c_index_add_ball ( t , b ) ;
if ( w > h ) { /* horizontal split */
if ( b - > x < = t - > ball - > x ) {
if ( t - > left ) {
w = t - > ball - > x - ref_x ;
t = t - > left ;
} else {
2019-08-20 17:23:54 +02:00
t - > left = n ;
2019-08-20 16:15:29 +02:00
return ;
}
} else {
if ( t - > right ) {
w - = t - > ball - > x - ref_x ;
ref_x = t - > ball - > x ;
t = t - > right ;
} else {
2019-08-20 17:23:54 +02:00
t - > right = n ;
2019-08-20 16:15:29 +02:00
return ;
}
}
} else { /* vertical split */
if ( b - > y < = t - > ball - > y ) {
if ( t - > left ) {
h = t - > ball - > y - ref_y ;
t = t - > left ;
} else {
2019-08-20 17:23:54 +02:00
t - > left = n ;
2019-08-20 16:15:29 +02:00
return ;
}
} else {
if ( t - > right ) {
h - = t - > ball - > y - ref_y ;
ref_y = t - > ball - > y ;
t = t - > right ;
} else {
2019-08-20 17:23:54 +02:00
t - > right = n ;
2019-08-20 16:15:29 +02:00
return ;
}
}
}
}
}
void c_index_build ( ) {
c_index_init_node ( c_index , balls ) ;
2019-08-20 17:23:54 +02:00
for ( int i = 1 ; i < n_balls ; + + i )
2019-08-20 16:15:29 +02:00
c_index_insert ( c_index , c_index + i , balls + i ) ;
}
struct bt_node * * c_index_stack = 0 ;
unsigned int c_index_stack_top = 0 ;
2019-08-21 10:26:58 +02:00
static void c_index_stack_clear ( ) {
2019-08-20 16:15:29 +02:00
c_index_stack_top = 0 ;
}
static void c_index_stack_push ( struct bt_node * n ) {
c_index_stack [ c_index_stack_top + + ] = n ;
}
static struct bt_node * c_index_stack_pop ( ) {
if ( c_index_stack_top > 0 )
return c_index_stack [ - - c_index_stack_top ] ;
else
return 0 ;
}
static int c_index_ball_in_rectangle ( const struct bt_node * n , const struct ball * b ) {
return n - > r . min_x < = b - > x + b - > radius
& & n - > r . max_x > = b - > x - b - > radius
& & n - > r . min_y < = b - > y + b - > radius
& & n - > r . max_y > = b - > y - b - > radius ;
}
static int c_index_must_check ( const struct bt_node * n , const struct ball * b ) {
return n ! = 0 & & n - > ball < b & & c_index_ball_in_rectangle ( n , b ) ;
}
void c_index_check_collisions ( ) {
for ( struct ball * b = balls + 1 ; b < balls + n_balls ; + + b ) {
c_index_stack_clear ( ) ;
struct bt_node * n = c_index ;
do {
ball_collision ( n - > ball , b ) ;
if ( c_index_must_check ( n - > left , b ) ) {
2019-08-21 10:26:58 +02:00
if ( c_index_must_check ( n - > right , b ) )
2019-08-20 16:15:29 +02:00
c_index_stack_push ( n - > right ) ;
n = n - > left ;
} else if ( c_index_must_check ( n - > right , b ) ) {
n = n - > right ;
} else {
n = c_index_stack_pop ( ) ;
}
} while ( n ) ;
}
}
int c_index_init ( ) {
if ( ! c_index )
c_index = malloc ( sizeof ( struct bt_node ) * n_balls ) ;
if ( ! c_index )
return 0 ;
if ( ! c_index_stack )
c_index_stack = malloc ( sizeof ( struct bt_node * ) * n_balls ) ;
if ( ! c_index_stack )
return 0 ;
return 1 ;
}
void c_index_destroy ( ) {
if ( c_index )
free ( c_index ) ;
if ( c_index_stack )
free ( c_index_stack ) ;
c_index = 0 ;
c_index_stack = 0 ;
}
/* Trivial collision check
*/
void check_collisions_simple ( ) {
2019-08-19 10:45:55 +02:00
for ( int i = 0 ; i < n_balls ; + + i )
for ( int j = i + 1 ; j < n_balls ; + + j )
ball_collision ( balls + i , balls + j ) ;
}
2019-08-20 16:15:29 +02:00
void check_collisions_with_index ( ) {
c_index_build ( ) ;
c_index_check_collisions ( ) ;
}
void ( * check_collisions ) ( ) = 0 ;
void update_state ( ) {
if ( check_collisions )
2019-08-19 10:45:55 +02:00
check_collisions ( ) ;
movement_and_borders ( ) ;
}
2019-07-29 19:25:45 +02:00
/* Graphics System
*/
2019-08-20 16:15:29 +02:00
GtkWidget * window ;
2019-12-05 16:34:31 +01:00
GtkWidget * canvas ;
2019-07-05 16:47:29 +02:00
2019-08-20 16:15:29 +02:00
int gravity_vector_countdown = 0 ;
int gravity_vector_init = 300 ;
2019-07-29 15:49:42 +02:00
2019-12-05 16:34:31 +01:00
void draw_gravity_vector ( cairo_t * cr ) {
2019-07-29 15:49:42 +02:00
if ( gravity_vector_countdown ! = 0 ) {
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 ;
}
2019-07-28 15:03:27 +02:00
}
2019-07-29 22:45:32 +02:00
const char * face_filename = 0 ;
2021-01-03 13:05:23 +01:00
int face_rotation = 0 ;
2019-07-29 22:45:32 +02:00
2019-12-18 21:18:21 +01:00
static const double linear_rotation_unit = 2.0 ;
2019-07-29 22:45:32 +02:00
2021-01-12 15:55:25 +01:00
unsigned int faces_count ;
struct ball_face * * faces ;
struct ball_face {
unsigned int rotations ;
cairo_surface_t * * c_faces ;
} ;
2021-06-07 13:38:13 +02:00
static double random_color_component ( ) {
return 1.0 * ( rand ( ) % 200 + 56 ) / 255 ;
} ;
2021-01-12 15:55:25 +01:00
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 ;
2021-01-03 13:05:23 +01:00
if ( face & & rotation ) {
2021-01-12 15:55:25 +01:00
f - > rotations = 2 * M_PI * radius / linear_rotation_unit ;
2019-12-18 21:18:21 +01:00
} else {
2021-01-12 15:55:25 +01:00
f - > rotations = 1 ;
2019-12-18 21:18:21 +01:00
}
2021-01-12 15:55:25 +01:00
f - > c_faces = malloc ( sizeof ( cairo_surface_t * ) * f - > rotations ) ;
if ( ! f - > c_faces ) {
free ( f ) ;
return 0 ;
}
for ( int i = 0 ; i < f - > rotations ; + + i ) {
f - > c_faces [ i ] = gdk_window_create_similar_surface ( gtk_widget_get_window ( window ) ,
CAIRO_CONTENT_COLOR_ALPHA ,
2 * radius , 2 * radius ) ;
assert ( f - > c_faces [ i ] ) ;
cairo_t * ball_cr = cairo_create ( f - > c_faces [ i ] ) ;
cairo_translate ( ball_cr , radius , radius ) ;
cairo_arc ( ball_cr , 0.0 , 0.0 , radius , 0 , 2 * M_PI ) ;
2019-07-29 22:45:32 +02:00
cairo_clip ( ball_cr ) ;
2019-07-29 23:04:26 +02:00
2019-12-18 21:18:21 +01:00
if ( face ) {
int face_x_offset = cairo_image_surface_get_width ( face ) / 2 ;
int face_y_offset = cairo_image_surface_get_height ( face ) / 2 ;
2021-01-12 15:55:25 +01:00
cairo_rotate ( ball_cr , i * 2 * M_PI / f - > rotations ) ;
cairo_scale ( ball_cr , 1.0 * radius / face_x_offset , 1.0 * radius / face_y_offset ) ;
2019-12-18 21:18:21 +01:00
cairo_set_source_surface ( ball_cr , face , - face_x_offset , - face_y_offset ) ;
2019-07-29 22:45:32 +02:00
cairo_paint ( ball_cr ) ;
2021-01-09 20:33:40 +01:00
} else {
2021-06-07 13:38:13 +02:00
cairo_pattern_t * pat ;
pat = cairo_pattern_create_radial ( - 0.2 * radius , - 0.2 * radius , 0.2 * radius ,
2021-06-08 21:00:31 +02:00
- 0.2 * radius , - 0.2 * radius , 1.3 * radius ) ;
2021-06-07 13:38:13 +02:00
double col_r = random_color_component ( ) ;
double col_g = random_color_component ( ) ;
double col_b = random_color_component ( ) ;
cairo_pattern_add_color_stop_rgba ( pat , 0 , col_r , col_g , col_b , 1 ) ;
cairo_pattern_add_color_stop_rgba ( pat , 1 , col_r / 3 , col_g / 3 , col_b / 3 , 1 ) ;
cairo_set_source ( ball_cr , pat ) ;
cairo_arc ( ball_cr , 0.0 , 0.0 , radius , 0 , 2 * M_PI ) ;
cairo_fill ( ball_cr ) ;
2019-07-29 22:45:32 +02:00
}
2021-01-12 15:55:25 +01:00
cairo_surface_flush ( f - > c_faces [ i ] ) ;
2019-07-29 22:45:32 +02:00
cairo_destroy ( ball_cr ) ;
}
2021-01-12 15:55:25 +01:00
return f ;
2019-12-18 21:18:21 +01:00
}
void init_graphics ( ) {
cairo_surface_t * face_surface = 0 ;
if ( face_filename ) {
face_surface = cairo_image_surface_create_from_png ( face_filename ) ;
if ( cairo_surface_status ( face_surface ) ! = CAIRO_STATUS_SUCCESS ) {
cairo_surface_destroy ( face_surface ) ;
face_surface = 0 ;
fprintf ( stderr , " could not create surface from PNG file %s \n " , face_filename ) ;
}
}
2021-01-12 15:55:25 +01:00
if ( face_surface ) {
faces_count = radius_max + 1 - radius_min ;
faces = malloc ( sizeof ( struct ball_face * ) * faces_count ) ;
for ( unsigned int i = 0 ; i < faces_count ; + + i )
faces [ i ] = 0 ;
for ( struct ball * b = balls ; b ! = balls + n_balls ; + + b ) {
unsigned int r_idx = b - > radius - radius_min ;
if ( ! faces [ r_idx ] )
faces [ r_idx ] = new_ball_face ( b - > radius , face_surface , face_rotation ) ;
b - > face = faces [ r_idx ] ;
}
2019-07-29 22:45:32 +02:00
cairo_surface_destroy ( face_surface ) ;
2021-01-12 15:55:25 +01:00
} else {
faces_count = n_balls ;
faces = malloc ( sizeof ( struct ball_face * ) * faces_count ) ;
for ( unsigned int i = 0 ; i < n_balls ; + + i )
balls [ i ] . face = faces [ i ] = new_ball_face ( balls [ i ] . radius , 0 , face_rotation ) ;
}
2019-07-29 19:25:45 +02:00
}
2019-08-20 16:15:29 +02:00
void destroy_graphics ( ) {
2021-01-12 15:55:25 +01:00
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 ] ) ;
}
2019-12-18 21:18:21 +01:00
}
2021-01-12 15:55:25 +01:00
free ( faces ) ;
faces = 0 ;
faces_count = 0 ;
2019-07-29 19:25:45 +02:00
}
2019-06-03 10:09:00 +02:00
2019-08-20 16:15:29 +02:00
void draw_balls_onto_window ( ) {
2019-06-03 10:09:00 +02:00
/* clear pixmap */
2021-01-12 15:55:25 +01:00
GdkWindow * window = gtk_widget_get_window ( canvas ) ;
cairo_region_t * c_region = cairo_region_create ( ) ;
GdkDrawingContext * d_context = gdk_window_begin_draw_frame ( window , c_region ) ;
cairo_t * cr = gdk_drawing_context_get_cairo_context ( d_context ) ;
2019-07-29 15:48:15 +02:00
cairo_set_source_rgba ( cr , 0.0 , 0.0 , 0.0 , clear_alpha ) ;
2019-07-28 14:55:39 +02:00
cairo_paint ( cr ) ;
2019-06-03 10:09:00 +02:00
2019-12-05 16:34:31 +01:00
draw_gravity_vector ( cr ) ;
2019-07-28 15:03:27 +02:00
2019-06-04 10:13:02 +02:00
/* draw balls */
2021-01-12 15:55:25 +01:00
for ( const struct ball * b = balls ; b ! = balls + n_balls ; + + b ) {
2019-07-29 22:45:32 +02:00
cairo_save ( cr ) ;
2021-01-12 15:55:25 +01:00
cairo_translate ( cr , b - > x - b - > radius , b - > y - b - > radius ) ;
2019-12-18 21:18:21 +01:00
unsigned int face_id ;
2021-01-12 15:55:25 +01:00
if ( b - > face - > rotations = = 1 )
2019-12-18 21:18:21 +01:00
face_id = 0 ;
else {
2021-01-12 15:55:25 +01:00
face_id = b - > face - > rotations * b - > angle / ( 2 * M_PI ) ;
assert ( face_id < b - > face - > rotations ) ;
if ( face_id > = b - > face - > rotations )
face_id % = b - > face - > rotations ;
2019-12-18 21:18:21 +01:00
}
2021-01-12 15:55:25 +01:00
cairo_set_source_surface ( cr , b - > face - > c_faces [ face_id ] , 0 , 0 ) ;
2019-07-29 22:45:32 +02:00
cairo_paint ( cr ) ;
cairo_restore ( cr ) ;
2019-06-03 10:09:00 +02:00
}
2021-01-12 15:55:25 +01:00
gdk_window_end_draw_frame ( window , d_context ) ;
cairo_region_destroy ( c_region ) ;
2019-06-03 10:09:00 +02:00
}
2019-12-05 16:34:31 +01:00
gint configure_event ( GtkWidget * widget , GdkEventConfigure * event ) {
2021-01-12 15:55:25 +01:00
if ( width = = gtk_widget_get_allocated_width ( widget ) & & height = = gtk_widget_get_allocated_height ( widget ) )
2019-06-03 10:09:00 +02:00
return FALSE ;
2021-01-12 15:55:25 +01:00
width = gtk_widget_get_allocated_width ( widget ) ;
height = gtk_widget_get_allocated_height ( widget ) ;
2019-07-28 14:55:39 +02:00
2021-06-07 12:03:55 +02:00
reposition_within_borders ( ) ;
2019-06-03 10:09:00 +02:00
return TRUE ;
}
2019-08-20 16:15:29 +02:00
gint keyboard_input ( GtkWidget * widget , GdkEventKey * event ) {
2019-06-03 10:09:00 +02:00
if ( event - > type ! = GDK_KEY_PRESS )
return FALSE ;
switch ( event - > keyval ) {
2019-07-28 15:03:27 +02:00
case GDK_KEY_Up :
g_y - = 10 ;
2019-07-29 15:49:42 +02:00
gravity_vector_countdown = gravity_vector_init ;
2019-07-28 15:03:27 +02:00
break ;
case GDK_KEY_Down :
g_y + = 10 ;
2019-07-29 15:49:42 +02:00
gravity_vector_countdown = gravity_vector_init ;
2019-07-28 15:03:27 +02:00
break ;
case GDK_KEY_Left :
g_x - = 10 ;
2019-07-29 15:49:42 +02:00
gravity_vector_countdown = gravity_vector_init ;
2019-07-28 15:03:27 +02:00
break ;
case GDK_KEY_Right :
g_x + = 10 ;
2019-07-29 15:49:42 +02:00
gravity_vector_countdown = gravity_vector_init ;
2019-07-28 15:03:27 +02:00
break ;
2019-07-29 15:49:42 +02:00
case GDK_KEY_G :
case GDK_KEY_g :
gravity_vector_countdown = gravity_vector_init ;
2019-07-28 15:03:27 +02:00
break ;
2019-06-03 10:09:00 +02:00
case GDK_KEY_Q :
case GDK_KEY_q :
gtk_main_quit ( ) ;
break ;
default :
return FALSE ;
}
return TRUE ;
}
2019-08-20 16:15:29 +02:00
gboolean expose_event ( GtkWidget * widget , GdkEventExpose * event , gpointer data ) {
2019-12-05 16:34:31 +01:00
draw_balls_onto_window ( ) ;
2019-06-03 10:09:00 +02:00
return TRUE ;
}
2019-08-20 16:15:29 +02:00
void destroy_window ( void ) {
2019-06-03 10:09:00 +02:00
gtk_main_quit ( ) ;
}
2019-06-04 10:18:50 +02:00
void print_usage ( const char * progname ) {
2019-06-03 10:09:00 +02:00
fprintf ( stderr ,
2019-07-28 14:55:39 +02:00
" usage: %s [options...] \n "
" options: \n "
" \t <width>x<height> \n "
2019-12-01 22:06:31 +01:00
" \t n=<number of balls> \n "
2019-07-28 14:55:39 +02:00
" \t fx=<x-force> \n "
" \t fy=<y-force> \n "
" \t radius=<min-radius>-<max-radius> \n "
" \t v=<min-velocity>-<max-velocity> \n "
2019-07-29 15:48:15 +02:00
" \t delta=<frame-delta-time> \n "
2019-08-10 11:13:26 +02:00
" \t face=<filename> \n "
2019-07-29 22:57:21 +02:00
" \t clear=<clear-alpha> \n "
2019-08-21 12:14:25 +02:00
" \t stats=<sample-count> :: rendering timing statitstics (0=disabled, default) \n "
2021-01-03 13:05:23 +01:00
" \t collisions=<C> :: n=no collisions, s=simple, i=index \n "
" \t -r :: activate face rotation \n " ,
2019-06-03 10:09:00 +02:00
progname ) ;
}
2019-08-21 12:14:25 +02:00
unsigned int stats_sampling = 0 ;
2019-07-29 22:57:21 +02:00
2019-06-04 10:18:50 +02:00
gboolean timeout ( gpointer user_data ) {
2019-07-29 22:57:21 +02:00
guint64 start = 0 , elapsed_usec ;
2019-08-21 12:14:25 +02:00
if ( stats_sampling > 0 )
2019-07-29 22:57:21 +02:00
start = g_get_monotonic_time ( ) ;
2019-06-03 10:09:00 +02:00
update_state ( ) ;
2019-07-28 14:55:39 +02:00
draw_balls_onto_window ( ) ;
2019-06-03 10:09:00 +02:00
2019-08-21 12:14:25 +02:00
if ( stats_sampling > 0 ) {
2019-07-29 22:57:21 +02:00
elapsed_usec = g_get_monotonic_time ( ) - start ;
2019-06-03 10:09:00 +02:00
2019-07-29 22:57:21 +02:00
static guint64 elapsed_usec_total = 0 ;
static unsigned int samples = 0 ;
2019-08-21 12:14:25 +02:00
if ( samples = = stats_sampling ) {
printf ( " \r frame rendering: time = %lu usec, max freq = %.2f (avg over %u samples) " , elapsed_usec_total / samples , ( 1000000.0 * samples ) / elapsed_usec_total , samples ) ;
2019-07-29 22:57:21 +02:00
fflush ( stdout ) ;
samples = 0 ;
elapsed_usec_total = 0 ;
}
+ + samples ;
elapsed_usec_total + = elapsed_usec ;
2019-06-03 10:09:00 +02:00
}
return TRUE ;
}
2019-06-04 10:18:50 +02:00
int main ( int argc , const char * argv [ ] ) {
2019-06-03 10:09:00 +02:00
int w = DEFAULT_WIDTH ;
int h = DEFAULT_HEIGHT ;
2019-06-04 10:18:50 +02:00
for ( int i = 1 ; i < argc ; + + i ) {
2019-06-03 10:09:00 +02:00
if ( sscanf ( argv [ i ] , " %dx%d " , & w , & h ) = = 2 )
continue ;
if ( sscanf ( argv [ i ] , " n=%u " , & n_balls ) = = 1 )
continue ;
2019-06-03 22:24:06 +02:00
if ( sscanf ( argv [ i ] , " fx=%lf " , & g_x ) = = 1 )
2019-06-03 10:09:00 +02:00
continue ;
2019-06-03 22:24:06 +02:00
if ( sscanf ( argv [ i ] , " fy=%lf " , & g_y ) = = 1 )
2019-06-03 10:09:00 +02:00
continue ;
2019-06-04 10:13:02 +02:00
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 )
2019-06-03 10:09:00 +02:00
continue ;
2019-06-03 22:24:06 +02:00
if ( sscanf ( argv [ i ] , " delta=%lf " , & delta ) = = 1 )
2019-06-03 10:09:00 +02:00
continue ;
2019-07-29 14:21:55 +02:00
if ( strncmp ( argv [ i ] , " face= " , 5 ) = = 0 ) {
2019-07-29 22:45:32 +02:00
face_filename = argv [ i ] + 5 ;
2019-07-29 14:21:55 +02:00
continue ;
}
2019-07-29 15:48:15 +02:00
if ( sscanf ( argv [ i ] , " clear=%lf " , & clear_alpha ) = = 1 )
continue ;
2019-08-21 12:14:25 +02:00
if ( sscanf ( argv [ i ] , " stats=%u " , & stats_sampling ) = = 1 )
2019-07-29 22:57:21 +02:00
continue ;
2019-08-20 16:15:29 +02:00
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 ;
}
2019-08-10 11:11:14 +02:00
}
2021-01-03 13:05:23 +01:00
if ( strcmp ( argv [ i ] , " -r " ) = = 0 ) {
face_rotation = 1 ;
continue ;
}
2019-06-03 10:09:00 +02:00
print_usage ( argv [ 0 ] ) ;
return 1 ;
}
balls = malloc ( sizeof ( struct ball ) * n_balls ) ;
assert ( balls ) ;
2019-08-20 16:15:29 +02:00
assert ( c_index_init ( ) ) ;
2019-06-03 10:09:00 +02:00
balls_init_state ( ) ;
gtk_init ( 0 , 0 ) ;
2019-12-05 16:34:31 +01:00
window = gtk_window_new ( GTK_WINDOW_TOPLEVEL ) ;
gtk_window_set_default_size ( GTK_WINDOW ( window ) , width , height ) ;
2019-07-05 16:47:29 +02:00
gtk_window_set_position ( GTK_WINDOW ( window ) , GTK_WIN_POS_CENTER ) ;
2019-12-05 16:34:31 +01:00
gtk_window_set_title ( GTK_WINDOW ( window ) , " Game " ) ;
2019-07-05 16:47:29 +02:00
g_signal_connect ( window , " destroy " , G_CALLBACK ( destroy_window ) , NULL ) ;
2019-12-05 16:34:31 +01:00
g_signal_connect ( G_OBJECT ( window ) , " delete-event " , G_CALLBACK ( destroy_window ) , NULL ) ;
2019-07-29 19:25:45 +02:00
g_signal_connect ( window , " key-press-event " , G_CALLBACK ( keyboard_input ) , NULL ) ;
2019-06-03 10:09:00 +02:00
gtk_widget_set_events ( window , GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_KEY_PRESS_MASK ) ;
2019-12-05 16:34:31 +01:00
canvas = gtk_drawing_area_new ( ) ;
2021-06-07 12:03:55 +02:00
2019-12-05 16:34:31 +01:00
g_signal_connect ( G_OBJECT ( canvas ) , " configure-event " , G_CALLBACK ( configure_event ) , NULL ) ;
gtk_container_add ( GTK_CONTAINER ( window ) , canvas ) ;
g_timeout_add ( delta * 1000 , timeout , canvas ) ;
2019-06-03 10:09:00 +02:00
gtk_widget_show_all ( window ) ;
2019-12-05 16:34:31 +01:00
init_graphics ( ) ;
2019-06-03 10:09:00 +02:00
gtk_main ( ) ;
2019-08-21 12:14:25 +02:00
if ( stats_sampling > 0 )
2019-07-29 22:57:21 +02:00
printf ( " \n " ) ;
2019-07-29 22:45:32 +02:00
destroy_graphics ( ) ;
2019-08-20 16:15:29 +02:00
c_index_destroy ( ) ;
2019-06-03 10:09:00 +02:00
free ( balls ) ;
2019-07-05 16:47:29 +02:00
2019-06-03 10:09:00 +02:00
return 0 ;
}