@ -1,7 +1,7 @@
# include <chipmunk.h>
# include "types.h"
# include "types.h"
# include <stdlib.h> // malloc
# include <stdio.h> // assert logging
# include <stdio.h>
void __assert ( bool cond , const char * file , int line , const char * cond_string )
void __assert ( bool cond , const char * file , int line , const char * cond_string )
{
{
@ -17,251 +17,257 @@ void __assert(bool cond, const char *file, int line, const char *cond_string)
// super try not to depend on external libraries like enet or sokol to keep build process simple,
// super try not to depend on external libraries like enet or sokol to keep build process simple,
// gamestate its own portable submodule. If need to link to other stuff document here:
// gamestate its own portable submodule. If need to link to other stuff document here:
// - debug
// - debug.c for debug drawing
// - chipmunk
static void integrate_acceleration ( struct Body * body , float dt )
void initialize ( struct GameState * gs )
{
{
// position
gs - > space = cpSpaceNew ( ) ;
}
void destroy ( struct GameState * gs )
{
for ( int i = 0 ; i < MAX_PLAYERS ; i + + )
{
{
V2 current = body - > position ;
box_destroy ( & gs - > players [ i ] . box ) ;
body - > position = V2add ( body - > position , V2sub ( current , body - > old_position ) ) ;
body - > position = V2add ( body - > position , V2scale ( body - > acceleration , dt * dt ) ) ;
body - > old_position = current ;
}
}
for ( int i = 0 ; i < gs - > num_boxes ; i + + )
// rotation
{
{
float current = body - > rotation ;
box_destroy ( & gs - > boxes [ i ] ) ;
body - > rotation = body - > rotation + ( current - body - > old_rotation ) ;
body - > rotation = body - > rotation + body - > angular_acceleration * dt * dt ;
body - > old_rotation = current ;
}
}
gs - > num_boxes = 0 ;
cpSpaceDestroy ( gs - > space ) ;
gs - > space = NULL ;
}
}
struct ProcessBody
struct Box box_new ( struct GameState * gs , V2 pos )
{
{
V2 vertices [ 4 ] ;
assert ( gs - > space ! = NULL ) ;
struct Body * body ;
float halfbox = BOX_SIZE / 2.0f ;
} ;
cpBody * body = cpSpaceAddBody ( gs - > space , cpBodyNew ( BOX_MASS , cpMomentForBox ( BOX_MASS , BOX_SIZE , BOX_SIZE ) ) ) ;
cpShape * shape = cpBoxShapeNew ( body , BOX_SIZE , BOX_SIZE , 0.0f ) ;
cpSpaceAddShape ( gs - > space , shape ) ;
cpBodySetPosition ( body , cpv ( pos . x , pos . y ) ) ;
return ( struct Box ) {
. body = body ,
. shape = shape ,
} ;
}
struct ProcessBody make_process_body ( struct Body * from )
void box_destroy ( struct Box * box )
{
{
float halfbox = BOX_SIZE / 2.0f ;
cpShapeFree ( box - > shape ) ;
struct ProcessBody to_return =
cpBodyFree ( box - > body ) ;
{
box - > shape = NULL ;
. vertices = {
box - > body = NULL ;
// important that the first one is the upper right, used to deduce rotation from vertex position
// @Robust instead of array of vertices have type? like struct with upper_right upper_left etc
V2add ( from - > position , V2rotate ( ( V2 ) { . x = halfbox , . y = - halfbox } , from - > rotation ) ) , // upper right
V2add ( from - > position , V2rotate ( ( V2 ) { . x = halfbox , . y = halfbox } , from - > rotation ) ) , // bottom right
V2add ( from - > position , V2rotate ( ( V2 ) { . x = - halfbox , . y = halfbox } , from - > rotation ) ) , // lower left
V2add ( from - > position , V2rotate ( ( V2 ) { . x = - halfbox , . y = - halfbox } , from - > rotation ) ) , // upper left
} ,
. body = from ,
} ;
return to_return ;
}
}
static void project ( struct ProcessBody * from , V2 axis , float * min , float * max )
static V2 cp_to_v2 ( cpVect v )
{
{
float DotP = V2dot ( axis , from - > vertices [ 0 ] ) ;
return ( V2 ) { . x = v . x , . y = v . y } ;
}
// Set the minimum and maximum values to the projection of the first vertex
static cpVect v2_to_cp ( V2 v )
* min = DotP ;
{
* max = DotP ;
return cpv ( v . x , v . y ) ;
}
for ( int I = 1 ; I < 4 ; I + + )
V2 box_pos ( struct Box box )
{
{
// Project the rest of the vertices onto the axis and extend
return cp_to_v2 ( cpBodyGetPosition ( box . body ) ) ;
// the interval to the left/right if necessary
}
DotP = V2dot ( axis , from - > vertices [ I ] ) ;
V2 box_vel ( struct Box box )
{
return cp_to_v2 ( cpBodyGetVelocity ( box . body ) ) ;
}
float box_rotation ( struct Box box )
{
return cpBodyGetAngle ( box . body ) ;
}
float box_angular_velocity ( struct Box box )
{
return cpBodyGetAngularVelocity ( box . body ) ;
}
* min = fmin ( DotP , * min ) ;
# define memwrite(out, variable) \
* max = fmax ( DotP , * max ) ;
for ( char b = 0 ; b < sizeof ( variable ) ; b + + ) \
{ \
* * out = ( ( char * ) & variable ) [ b ] ; \
* out + = 1 ; \
}
}
# define memread(in, variable_pointer) \
for ( char b = 0 ; b < sizeof ( * variable_pointer ) ; b + + ) \
{ \
( ( char * ) variable_pointer ) [ b ] = * * in ; \
* in + = 1 ; \
}
void ser_float ( char * * out , float f )
{
memwrite ( out , f ) ;
}
void des_float ( char * * in , float * f )
{
memread ( in , f ) ;
}
void ser_int ( char * * out , int i )
{
memwrite ( out , i ) ;
}
}
static float interval_distance ( float min_a , float max_a , float min_b , float max_b )
void des_int ( char * * in , int * i )
{
{
if ( min_a < min_b )
memread ( in , i ) ;
return min_b - max_a ;
else
return min_a - max_b ;
}
}
static void move_vertices ( V2 * vertices , int num , V2 shift )
void ser_bool ( char * * out , bool b )
{
{
for ( int i = 0 ; i < num ; i + + )
* * out = ( char ) b ;
* out + = 1 ;
}
void des_bool ( char * * in , bool * b )
{
* b = ( bool ) * * in ;
* in + = 1 ;
}
void ser_V2 ( char * * out , V2 v )
{
ser_float ( out , v . x ) ;
ser_float ( out , v . y ) ;
}
void des_V2 ( char * * in , V2 * v )
{
des_float ( in , & v - > x ) ;
des_float ( in , & v - > y ) ;
}
void ser_box ( char * * out , struct Box * b )
{
// box must not be null, dummy!
assert ( b - > body ! = NULL ) ;
ser_V2 ( out , box_pos ( * b ) ) ;
ser_V2 ( out , box_vel ( * b ) ) ;
ser_float ( out , box_rotation ( * b ) ) ;
ser_float ( out , box_angular_velocity ( * b ) ) ;
}
// takes gamestate as argument to place box in the gamestates space
void des_box ( char * * in , struct Box * b , struct GameState * gs )
{
assert ( b - > body = = NULL ) ; // destroy the box before deserializing into it
V2 pos = { 0 } ;
V2 vel = { 0 } ;
float rot = 0.0f ;
float angular_vel = 0.0f ;
des_V2 ( in , & pos ) ;
des_V2 ( in , & vel ) ;
des_float ( in , & rot ) ;
des_float ( in , & angular_vel ) ;
* b = box_new ( gs , pos ) ;
cpBodySetVelocity ( b - > body , v2_to_cp ( vel ) ) ;
cpBodySetAngle ( b - > body , rot ) ;
cpBodySetAngularVelocity ( b - > body , angular_vel ) ;
}
void ser_player ( char * * out , struct Player * p )
{
ser_bool ( out , p - > connected ) ;
if ( p - > connected )
{
{
vertices [ i ] = V2add ( vertices [ i ] , shift ) ;
ser_box ( out , & p - > box ) ;
ser_V2 ( out , p - > input ) ;
}
}
}
}
void process ( struct GameState * gs , float dt )
void des_player( char * * in , struct Player * p , struct GameState * gs )
{
{
// process input
des_bool ( in , & p - > connected ) ;
int num_bodies = gs - > num_boxes ;
if ( p - > connected )
for ( int i = 0 ; i < MAX_PLAYERS ; i + + )
{
{
struct Player * p = & gs - > players [ i ] ;
des_box ( in , & p - > box , gs ) ;
if ( ! p - > connected )
des_V2 ( in , & p - > input ) ;
continue ;
p - > body . acceleration = V2scale ( p - > input , 5.0f ) ;
p - > body . angular_acceleration = p - > input . x * 10.0f ;
num_bodies + = 1 ;
}
}
}
// @Robust do this without malloc
// @Robust really think about if <= makes more sense than < here...
# define LEN_CHECK() assert(bytes - original_bytes <= max_len)
struct ProcessBody * bodies = malloc ( sizeof * bodies * num_bodies ) ;
void into_bytes ( struct ServerToClient * msg , char * bytes , int * out_len , int max_len )
int cur_body_index = 0 ;
{
assert ( msg - > cur_gs ! = NULL ) ;
assert ( msg ! = NULL ) ;
struct GameState * gs = msg - > cur_gs ;
char * original_bytes = bytes ;
ser_int ( & bytes , msg - > your_player ) ;
for ( int i = 0 ; i < MAX_PLAYERS ; i + + )
for ( int i = 0 ; i < MAX_PLAYERS ; i + + )
{
{
struct Player * p = & gs - > players [ i ] ;
ser_player ( & bytes , & gs - > players [ i ] ) ;
if ( ! p - > connected )
LEN_CHECK ( ) ;
continue ;
integrate_acceleration ( & p - > body , dt ) ;
bodies [ cur_body_index ] = make_process_body ( & p - > body ) ;
cur_body_index + + ;
}
}
// @Robust invalid message on num boxes bigger than max boxes
ser_int ( & bytes , gs - > num_boxes ) ;
LEN_CHECK ( ) ;
for ( int i = 0 ; i < gs - > num_boxes ; i + + )
for ( int i = 0 ; i < gs - > num_boxes ; i + + )
{
{
integrate_acceleration ( & gs - > boxes [ i ] . body , dt ) ;
ser_box ( & bytes , & gs - > boxes [ i ] ) ;
bodies [ cur_body_index ] = make_process_body ( & gs - > boxes [ i ] . body ) ;
LEN_CHECK ( ) ;
cur_body_index + + ;
}
}
assert ( cur_body_index = = num_bodies ) ;
* out_len = bytes - original_bytes ;
}
// Collision
void from_bytes ( struct ServerToClient * msg , char * bytes , int max_len )
// @Robust handle when bodies are overlapping (even perfectly)
{
for ( int i = 0 ; i < num_bodies ; i + + )
struct GameState * gs = msg - > cur_gs ;
{
for ( int ii = 0 ; ii < num_bodies ; ii + + )
{
if ( ii = = i )
continue ;
struct ProcessBody * from = & bodies [ i ] ;
struct ProcessBody * to = & bodies [ ii ] ;
dbg_line ( from - > body - > position , to - > body - > position ) ;
float MinDistance = 10000.0f ;
struct Edge
{
struct ProcessBody * parent ;
V2 * from ;
V2 * to ;
} ;
struct ProcessBody * bodies [ 2 ] = { from , to } ;
bool was_collision = false ;
V2 normal = { 0 } ;
struct Edge edge = { 0 } ;
for ( int body_i = 0 ; body_i < 2 ; body_i + + )
{
struct ProcessBody * body = bodies [ body_i ] ;
for ( int edge_from_i = 0 ; edge_from_i < 3 ; edge_from_i + + )
{
int edge_to_i = edge_from_i + 1 ;
V2 * edge_from = & body - > vertices [ edge_from_i ] ;
V2 * edge_to = & body - > vertices [ edge_to_i ] ;
// normal vector of edge
V2 axis = ( V2 ) {
. x = edge_from - > y - edge_to - > y ,
. y = edge_to - > x - edge_from - > x ,
} ;
axis = V2normalize ( axis ) ;
float min_from , min_to , max_from , max_to = 0.0f ;
project ( from , axis , & min_from , & max_from ) ;
project ( to , axis , & min_to , & max_to ) ;
float distance = interval_distance ( min_from , min_to , max_from , max_to ) ;
if ( distance > 0.0f )
break ;
else if ( fabsf ( distance ) < MinDistance )
{
MinDistance = fabsf ( distance ) ;
was_collision = true ;
normal = axis ;
edge = ( struct Edge ) {
. parent = & body ,
. from = edge_from ,
. to = edge_to ,
} ;
}
}
}
float depth = MinDistance ;
if ( was_collision )
{
float intersection_depth = from_interval [ 1 ] - to_interval [ 0 ] ;
move_vertices ( from - > vertices , 4 , V2scale ( axis , intersection_depth * - 0.5f ) ) ;
move_vertices ( to - > vertices , 4 , V2scale ( axis , intersection_depth * 0.5f ) ) ;
}
}
}
// Wall
char * original_bytes = bytes ;
if ( true )
// destroy and free all chipmunk
destroy ( gs ) ;
initialize ( gs ) ;
des_int ( & bytes , & msg - > your_player ) ;
LEN_CHECK ( ) ;
for ( int i = 0 ; i < MAX_PLAYERS ; i + + )
{
{
for ( int i = 0 ; i < num_bodies ; i + + )
des_player ( & bytes , & gs - > players [ i ] , gs ) ;
{
LEN_CHECK ( ) ;
for ( int v_i = 0 ; v_i < 4 ; v_i + + )
{
V2 * vert = & bodies [ i ] . vertices [ v_i ] ;
if ( vert - > x > 2.0f )
{
vert - > x = 2.0f ;
}
}
}
}
}
// Correct for differences in vertex position
des_int ( & bytes , & gs - > num_boxes ) ;
const int edge_update_iters = 3 ;
LEN_CHECK ( ) ;
for ( int iter = 0 ; iter < edge_update_iters ; iter + + )
for ( int i = 0 ; i < gs - > num_boxes ; i + + )
{
{
for ( int i = 0 ; i < num_bodies ; i + + )
des_box ( & bytes , & gs - > boxes [ i ] , gs ) ;
{
LEN_CHECK ( ) ;
for ( int v_i = 0 ; v_i < 3 ; v_i + + )
{
int other_v_i = v_i + 1 ;
V2 * from = & bodies [ i ] . vertices [ v_i ] ;
V2 * to = & bodies [ i ] . vertices [ other_v_i ] ;
V2 line = V2sub ( * to , * from ) ;
float len = V2length ( line ) ;
float diff = len - BOX_SIZE ;
line = V2normalize ( line ) ;
* from = V2add ( * from , V2scale ( line , diff * 0.5f ) ) ;
* to = V2sub ( * to , V2scale ( line , diff * 0.5f ) ) ;
}
}
}
}
}
// Reupdate the positions of the bodies based on how the vertices changed
void process ( struct GameState * gs , float dt )
for ( int i = 0 ; i < num_bodies ; i + + )
{
assert ( gs - > space ! = NULL ) ;
// process input
for ( int i = 0 ; i < MAX_PLAYERS ; i + + )
{
{
float upper_right_angle = V2angle ( V2sub ( bodies [ i ] . vertices [ 0 ] , bodies [ i ] . body - > position ) ) ;
struct Player * p = & gs - > players [ i ] ;
bodies [ i ] . body - > rotation = upper_right_angle - ( PI / 4.0f ) ;
if ( ! p - > connected )
continue ;
V2 avg = { 0 } ;
cpBodyApplyForceAtWorldPoint ( p - > box . body , v2_to_cp ( V2scale ( p - > input , 5.0f ) ) , v2_to_cp ( box_pos ( p - > box ) ) ) ;
for ( int v_i = 0 ; v_i < 4 ; v_i + + )
{
avg = V2add ( avg , bodies [ i ] . vertices [ v_i ] ) ;
}
avg = V2scale ( avg , 1.0f / 4.0f ) ;
bodies [ i ] . body - > position = avg ;
}
}
free( bodies ) ;
cpSpaceStep ( gs - > space , dt ) ;
}
}