1
1
Fork 0
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

837 lines
24 KiB

#include "game.h"
player_st *glob_player;
void set_motor_newtons(Body *thing, int motorID, double x, double y);
void set_motor_max_velocity(Body *thing, int motorID, double max);
void get_new_physics(Body **phys);
// move the collision poly to the position of the player
void default_update_collision_poly(Body *body) {
for (int i=0; i < body->collision_poly_size; i++) {
double x = body->collision_shape[i].x + body->position.x;
double y = body->collision_shape[i].y + body->position.y;
body->collision_poly[i].x = x;
body->collision_poly[i].y = y;
}
fflush(stdout);
}
void default_motor_curve(Motor *motor) {
// constant
return;
}
FloorPoly* generate_floor_simple(int num_polys) {
FloorPoly *floor = calloc(num_polys, sizeof(FloorPoly));
Vect last, next;
last.x = 10;
last.y = 900;
for (int i = 0; i < num_polys; i++) {
double run = (rand() % 900);
double rise = (rand() % 100) - (rand() % 100);
next.x = last.x + run;
next.y = last.y + rise;
FloorPoly poly;
poly.left = last;
poly.right = next;
get_new_physics(&poly.physics);
poly.physics->position = last;
poly.physics->collision_poly_size = 4;
poly.physics->collision_poly = calloc(4, sizeof(Vect));
poly.physics->collision_shape = calloc(4, sizeof(Vect));
poly.physics->collision_shape[0].x = 0;
poly.physics->collision_shape[0].y = 0;
poly.physics->collision_shape[1].x = run;
poly.physics->collision_shape[1].y = rise;
poly.physics->collision_shape[2].x = run;
poly.physics->collision_shape[2].y = rise+200;
poly.physics->collision_shape[3].x = 0;
poly.physics->collision_shape[3].y = rise+200;
last = next;
floor[i] = poly;
}
return floor;
}
// @param uninitialised Body pointer
// @result: malloc and configure a physics thing pointer
void get_new_physics(Body **phys) {
static int uid = 0;
/* physics */
Body * physics = malloc(sizeof(Body));
memset(physics, 0, sizeof(Body));
// give it the next uid
physics->uid = uid++;
physics->dynamics = false;
physics->glob_gravity = false;
physics->glob_friction = 0.0000;
physics->obj_mass = 100;
//kgs
physics->motors = malloc(sizeof(Motor) * 100);
memset(physics->motors, 0, sizeof(Motor) * 100);
physics->max_motors = 100;
physics->num_motors = 0;
// gravity
add_motor(physics, 0.0, 0.0);
// friction
add_motor(physics, 0.0, 0.0);
physics->updateCollisionPoly = default_update_collision_poly;
*phys = physics;
return;
}
/*void updatePlayerCollision(Body *physics) {*/
/*physics->collision_poly[0].x = physics->x_pos;*/
/*physics->collision_poly[0].y = physics->y_pos;*/
/*}*/
player_st get_player(int x, int y) {
/* creates player at given postion and zeroes physics */
// player
player_st player;
memset(&player, 0, sizeof(player));
player.max_walking_speed = 100;
// physics settings
get_new_physics(&player.physics);
player.physics->dynamics = true;
player.physics->position.x = x;
player.physics->position.y = y;
// friction (not in use)
player.physics->glob_friction = 40; // drag coef * area
// collisions
player.physics->obj_elasticity = 0.7;
player.physics->collision_poly = calloc(4, sizeof(Vect));
player.physics->collision_shape = calloc(4, sizeof(Vect));
player.physics->collision_poly_size = 4;
Vect *rect = player.physics->collision_shape;
rect[0].x = -10; rect[0].y = -10;
rect[1].x = 10; rect[1].y = -10;
rect[2].x = 10; rect[2].y = 10;
rect[3].x = -10; rect[3].y = 10;
// gravity
set_motor_newtons(player.physics, M_GRAVITY, 0.0, player.physics->obj_mass * 4);
// walking motor
add_motor(player.physics, 0.0, player.physics->obj_mass * 9.81);
set_motor_max_velocity(player.physics, M_PLAYER_WALK, 5); // has to be > grav
set_motor_max_velocity(player.physics, M_GRAVITY, 5);
return (player);
}
bool point_point_colcheck(Vect one, Vect two) {
if (one.x == two.x && one.y == two.y) {
return true;
}
return false;
}
typedef struct {
Vect one;
Vect two;
} Collision;
bool point_line_colcheck(Vect line[2], Vect point) {
// point is outside the rectangle made by the line
if ((point.x > line[0].x && point.x > line[1].x)
|| (point.x < line[0].x && point.x < line[0].x)
|| (point.y > line[0].y && point.y > line[0].y)
|| (point.y < line[0].y && point.y < line[0].y)
){
return false;
}
double m = (double)(line[1].y - line[0].y) / (double)(line[1].x - line[0].x);
double c = (double)(line[0].y - (m * line[0].x));
double y = point.x * m + c;
// point is in the line +- 1
if ((int)y == point.y || ((int)y < point.y && (int)y + 1 > point.y)) {
return true;
}
return false;
}
double *project_col_poly(Body *shape, Vect V) {
double *proj = calloc(shape->collision_poly_size, sizeof(double));
for (int i = 0; i < shape->collision_poly_size; i++) {
Vect point;
point.x = shape->collision_poly[i].x;
point.y = shape->collision_poly[i].y;
//double mag = vect_mag(point);
// printf("point: %f %f mag: %f\n", point.x, point.y, vect_mag(point));
// printf("Vectp: %f %f mag: %f\n", V.x, V.y, vect_mag(V));
proj[i] = vect_scalar_projection(point, V);
}
double min, max;
max = -99999999;
min = 99999999;
for (int i = 0; i < shape->collision_poly_size; i++) {
if (proj[i] > max) {
max = proj[i];
}
if (proj[i] < min) {
min = proj[i];
}
}
double *res = calloc(2, sizeof(double));
res[0] = min;
res[1] = max;
printf("POLY PROJ: %f, %f\n", min, max);
free(proj);
return res;
}
Vect get_normal(Vect start, Vect end) {
Vect norm;
double x = (end.x - start.x);
double y = (end.y - start.y);
norm.x = -y;
norm.y = x;
double len = vect_mag(norm);
norm.x /= len;
norm.y /= len;
return norm;
}
// reference:
// http://www.dyn4j.org/2010/01/sat/#sat-inter
//
// TODO: Maybe avoid calculating the centre of the boxes; have body->position
// be the centre and translate the collision box to be around that. It does
// make placing a little more complex but no bother.
bool sat_collision_check(Body *one, Body *two, Vect *translation) {
int num_axes = one->collision_poly_size + two->collision_poly_size;
Vect *axes = calloc(num_axes, sizeof(Vect));
Vect end;
Vect start = one->collision_poly[0];
Vect one_position;
Vect two_position;
two_position = two->collision_poly[0];
one_position = one->collision_poly[0];
for (int i = 1; i < one->collision_poly_size; i++) {
one_position = vect_add(one_position, one->collision_poly[i]);
end = one->collision_poly[i];
axes[i-1] = get_normal(start, end);
start = end;
}
end = one->collision_poly[0];
axes[one->collision_poly_size - 1] = get_normal(start, end);
start = two->collision_poly[0];
for (int i = 1; i < two->collision_poly_size; i++) {
two_position = vect_add(two_position, two->collision_poly[i]);
end = two->collision_poly[i];
axes[i - 1 + one->collision_poly_size] = get_normal(start, end);
start = end;
}
end = two->collision_poly[0];
axes[two->collision_poly_size + one->collision_poly_size - 1] = get_normal(start, end);
double *proj_one, *proj_two;
proj_one = proj_two = 0;
Vect min_axis;
double min_overlap = 99999999;
for (int i = 0; i < num_axes; i++) {
// project
if (proj_one) {
free(proj_one);
}
if (proj_two) {
free(proj_two);
}
proj_one = project_col_poly(one, axes[i]);
proj_two = project_col_poly(two, axes[i]);
if ((proj_one[0] >= proj_two[1])
|| (proj_two[0] >= proj_one[1])) {
logwrite(INFO, "No collision");
free(axes);
free(proj_one);
free(proj_two);
return false;
} else {
double overlap;
double left = proj_one[1] < proj_two[1] ? proj_one[1] : proj_two[1];
double right = proj_one[0] > proj_two[0] ? proj_one[0] : proj_two[0];
overlap = left - right;
// one of the shapes is contained
if ((overlap > (proj_one[1] - proj_one[0])
|| (overlap > (proj_two[1] - proj_two[0])))) {
logwrite(INFO, "Contained collision");
double min = proj_one[0] - proj_two[0];
double max = proj_one[1] - proj_two[1];
min = min < 0 ? -min : min;
max = max < 0 ? -max : max;
overlap += min < max ? min : max;
}
if (overlap < min_overlap && overlap > 0) {
min_overlap = overlap;
min_axis = axes[i];
}
}
}
// flip the MTV if it is pointing INTO the object
Vect trans;
trans.x = min_overlap * min_axis.x;
trans.y = min_overlap * min_axis.y;
// https://gamedev.stackexchange.com/questions/27596/implementing-separating-axis-theorem-sat-and-minimum-translation-vector-mtv/27629#27629
Vect direction;
one_position.x /= one->collision_poly_size;
one_position.y /= one->collision_poly_size;
two_position.x /= two->collision_poly_size;
two_position.y /= two->collision_poly_size;
printf("ONE POS: %f %f", one_position.x, one_position.y);
printf("TWO POS: %f %f", two_position.x, one_position.y);
direction.x = one_position.x - two_position.x;
direction.y = one_position.y - two_position.y;
double dot = vect_scalar_projection(trans, direction);
printf("DIRECTION: %f %f\n\n", direction.x, direction.y);
printf("DOT: %f\n\n", dot);
if (dot > 0) {
trans = vect_scalar(trans, -1);
}
// return the MTV
*translation = trans;
printf("Trans Vect: %e, %e\n", trans.x, trans.y);
free(axes);
free(proj_one);
free(proj_two);
return true;
}
bool check_collision(Body *one, Body *two, Vect *translation) {
int onesize = one->collision_poly_size;
int twosize = two->collision_poly_size;
// point-point
if (one->collision_poly_size == 1 && two->collision_poly_size == 1) {
if (point_point_colcheck(one->collision_poly[0], two->collision_poly[0])) {
return true;
}
}
// point-line
if ((onesize == 1 || twosize == 1) && (onesize == 2 || twosize == 2)) {
Vect line[2];
Vect point;
if (onesize > twosize) {
line[0] = one->collision_poly[0];
line[1] = one->collision_poly[1];
point = two->collision_poly[0];
} else {
line[0] = two->collision_poly[0];
line[1] = two->collision_poly[1];
point = one->collision_poly[0];
}
return point_line_colcheck(line, point);
}
// line-line
if ((onesize == 2 && twosize == 2)) {
return false;
}
// point-poly
if ((onesize == 1 || twosize == 1) && (onesize > 2 || twosize > 2)) {
return false;
}
// line-poly
if ((onesize == 2 || twosize == 2) && (onesize > 2 || twosize > 2)) {
return false;
}
// poly-poly
if ((onesize > 2 && twosize > 2)) {
return sat_collision_check(one, two, translation);
}
}
Wall *get_long_wall(int numNodes, int *nodes) {
Wall wall;
memset(&wall, 0, sizeof(wall));
wall.numNodes = numNodes;
wall.nodes = calloc(numNodes, sizeof(SDL_Point));
get_new_physics(&wall.physics);
wall.physics->collision_poly = calloc(numNodes, sizeof(Vect));
wall.physics->collision_shape = calloc(numNodes, sizeof(Vect));
wall.physics->collision_poly_size = numNodes;
// collisions
//SDL_Point *collision_poly;
for (int i = 0; i < numNodes; i++) {
wall.nodes[i].x = nodes[2*i];
wall.nodes[i].y = nodes[2*i+1];
wall.physics->collision_shape[i].x = nodes[2*i];
wall.physics->collision_shape[i].y = nodes[2*i+1];
}
Wall *wallwall = malloc(sizeof(wall));
*wallwall = wall;
return wallwall;
}
void accel_thing(Body * thing, double x, double y) {
/* takes acceleration in m/s2 and converts to m/ms adding
* it to physics_thing
*/
// convert to m / millisecond
double x_adj = x / 1000.0;
double y_adj = y / 1000.0;
thing->acc.y += y_adj;
thing->acc.x += x_adj;
}
void set_motor_max_velocity(Body *thing, int motorID, double max) {
thing->motors[motorID].max_velocity = max;
}
void set_motor_timeout(Body *thing, int motorID, uint32_t timeout) {
thing->motors[motorID].timeout = timeout;
}
void set_motor_newtons(Body *thing, int motorID, double x, double y) {
thing->motors[motorID].x =x;
thing->motors[motorID].y =y;
}
void add_motor_newtons(Body *thing, int motorID, double x, double y) {
thing->motors[motorID].x +=x;
thing->motors[motorID].y +=y;
}
// @param thing: the body to apply the motor to
// @param x, y: The initial motor force vector.
void add_motor(Body *thing, double x, double y) {
Motor motor;
memset(&motor, 0, sizeof(Motor));
motor.x = x;
motor.y = y;
motor.timeout = -1;
motor.max_velocity = 999899;
motor.update_motor = default_motor_curve;
if (thing->num_motors == thing->max_motors) {
thing->motors = realloc(thing->motors, sizeof(Motor) * (thing->max_motors *=2));
}
thing->motors[thing->num_motors] = motor;
thing->num_motors += 1;
}
// basic collision handler for testing
//
int process_collisions(Body *thing, Vect *trans) {
Vect translation;
translation.x = translation.y = 0;
Vect temptrans;
int num_cols = 0;
int num = 0;
for (int k = 0; k < things_in_world; k++) {
if (world[k].kind == STATIC_WALL_W
&& world[k].wall->physics->uid != thing->uid) {
if ((world[k].wall->physics->position.x - viewport_pos.x) > (width * 2)
|| world[k].wall->physics->position.x - viewport_pos.x < 0) {
continue;
} else {
num++;
}
if (check_collision(world[k].wall->physics, thing, &temptrans)) {
num++;
thing->colliding = true;
translation = vect_add(translation, temptrans);
}
} else if (world[k].kind == FLOOR) {
for (int i = 0; i < world[k].floor->numPolys; i++) {
if ((world[k].floor->polys[i].physics->position.x - viewport_pos.x) > (2 *width)
|| (world[k].floor->polys[i].physics->position.x - viewport_pos.x) < -width) {
continue;
} else {
num++;
}
if (check_collision(world[k].floor->polys[i].physics, thing, &temptrans)) {
num_cols++;
thing->colliding = true;
translation = vect_add(translation, temptrans);
}
}
}
}
printf("Tested %d collisions\n", num);
if (!num_cols) {
thing->colliding = false;
return false;
} else {
*trans = translation;
return true;
}
}
/* Basic physics works by adding up the acceleratino caused by all the forces on
* the object, converting it to velocity, then doing collision detection and
* applying various reactive forces (friction etc) and finally adjusting the
* object's position.
*/
void advance_thing(Body * thing) {
// TODO: fix ordering of collision detection + physics sim so that collisions
// are less bad.
thing->acc.x = 0;
thing->acc.y = 0;
set_motor_newtons(thing, M_FRICTION, 0,0);
set_motor_max_velocity(thing, M_FRICTION, 0);
if (!thing->collision_poly[0].y) {
thing->updateCollisionPoly(thing);
}
if (!thing->dynamics) {
return;
}
thing->updateCollisionPoly(thing);
Vect translation;
Vect friction;
translation.x = translation.y = 0;
int numcols = 0;
if ((numcols = process_collisions(thing, &translation))) {
double mag = vect_mag(translation);
/*double check = vect_scalar_projection(translation, thing->vel);*/
/*if (check >= 0) {*/
/*translation.x *= -1;*/
/*translation.y *= -1;*/
/*}*/
thing->position.x += translation.x;
thing->position.y += translation.y;
translation.x = translation.x / mag;
translation.y = translation.y / mag;
mag = vect_scalar_projection(thing->vel, translation);
Vect revert_vel;
revert_vel.x = cos(vect_dir(translation)) * mag;
revert_vel.y = sin(vect_dir(translation)) * mag;
thing->vel.x -= revert_vel.x;
thing->vel.y -= revert_vel.y;
// accel
double norm = 9.81 * thing->obj_mass;
double fric_const = 0.52;
Vect x;
x.y = 0; x.x = 1;
if (vect_scalar_projection(thing->vel, translation) > 0) {
friction.x = translation.y;
friction.y = -translation.x;
} else {
friction.x = -translation.y;
friction.y = translation.x;
}
// force
friction.x = friction.x * norm * fric_const;
friction.y = friction.y * norm * fric_const;
printf("friction accel: %e %e\n", friction.x, friction.y);
printf("velocity: %e %e\n", thing->vel.x, thing->vel.y);
set_motor_newtons(thing, M_FRICTION, friction.x, friction.y);
set_motor_max_velocity(thing, M_FRICTION, vect_mag(project_vect(thing->vel, friction)));
// restitution force
/*rest.x = 0;*/
/*rest.y = 0;*/
/*double impulse = thing->obj_mass * vect_mag(thing->vel) * thing->obj_elasticity;*/
/*rest.x = cos(vect_dir(translation)) * impulse;*/
/*rest.y = sin(vect_dir(translation)) * impulse;*/
/*accel_thing(thing, rest.x, rest.y);*/
}
uint32_t now = SDL_GetTicks();
uint32_t time_delta = now - thing->last_advance_time ;
thing->last_advance_time = SDL_GetTicks(); // in milliseconds
if (time_delta > 18) {
logwrite(ERROR, "Simulation too slow\n");
}
// motors
for (int i = 0; i < thing->num_motors; i++) {
if (thing->motors[i].timeout < now) {
continue;
}
Vect F;
Vect V;
F.x = thing->motors[i].x;
F.y = thing->motors[i].y;
V.x = thing->vel.x;
V.y = thing->vel.y;
Vect vel_in_dir = project_vect(V, F);
double dirF = atan2(F.y, F.x);
double dirV = atan2(V.y, V.x);
double diff = dirV > dirF ? dirV - dirF: dirF - dirV;
if (thing->motors[i].max_velocity > vect_mag(vel_in_dir)
|| diff >= M_PI) {
double acc_x = thing->motors[i].x / thing->obj_mass;
double acc_y = thing->motors[i].y / thing->obj_mass;
accel_thing(thing, acc_x, acc_y);
}
// printf("\n diff angle: %f pi: %f \n", dirV - dirF, M_PI);
// printf("Vel: %f, Force: %f\n\n", dirV, dirF);
}
// accelerate based on accel
printf("time: %f", (float)time_delta);
thing->vel.x += thing->acc.x * (float)time_delta;
thing->vel.y += thing->acc.y * (float)time_delta;
double velocity = sqrt((double)(thing->vel.x * thing->vel.x + thing->vel.y * thing->vel.y));
printf("\nvels %e %e\n\n", thing->vel.x, thing->vel.y);
// simple air drag
/*if (velocity > 0.000000000000000) {*/
/*double dir = atan2((double)thing->y_vel, (double)thing->x_vel) + M_PI;*/
/*double absolute_force = 5 * thing->obj_mass * (velocity / time_delta); // 2 * 0.1231 * 5;*/
/*printf("dir %e %e\n\n", dir, dir - M_PI);*/
/*printf("force %e\n\n", absolute_force);*/
/*double f_x = (cos(dir)) * absolute_force;*/
/*double f_y = (sin(dir)) * absolute_force;*/
/*accel_thing(thing, (float)f_x, (float)f_y);*/
/*}*/
if (fabsl((*thing).vel.x) < 0.001) {
(*thing).vel.x = 0;
}
if (fabsl((*thing).vel.y) < 0.001) {
(*thing).vel.y = 0;
}
double oldx = thing->position.x;
double oldy = thing->position.y;
thing->position.x += (thing->vel.x * 1/2 * (double)time_delta);
thing->position.y += (thing->vel.y * 1/2 * (double)time_delta);
// revert if this caused collision
/*if (process_collisions(thing)) {*/
/*thing->x_pos = oldx;*/
/*thing->y_pos = oldy;*/
/*thing->updateCollisionPoly(thing);*/
/*return;*/
/*}*/
// wrap screen
/*if (thing->x_pos > width) {*/
/*thing->x_pos = 0;*/
/*}*/
/*if (thing->y_pos > height) {*/
/*thing->y_pos = 0;*/
/*}*/
/*if (thing->y_pos < 0) {*/
/*thing->y_pos = height;*/
/*}*/
/*if (thing->x_pos < 0) {*/
/*thing->x_pos = width;*/
/*}*/
}
void advance_things(void) {
for (int i = 0; i < things_in_world; i++) {
switch (world[i].kind) {
case PLAYER_W :
logwrite(INFO, "ADVANCE PLAYER\n");
advance_thing((world[i].player->physics));
break;
case STATIC_WALL_W:
logwrite(INFO, "ADVANCE WALL\n");
advance_thing(world[i].wall->physics);
break;
case FLOOR:
for (int k = 0; k < world[i].floor->numPolys; k++) {
advance_thing(world[i].floor->polys[k].physics);
}
}
}
}
// grow array of world things if needed
void add_to_world(world_thing thing) {
if (things_in_world == world_size) {
logwrite(INFO, "Increased world size.");
world = realloc(world, sizeof(world_thing) * (world_size*=2));
}
thing.nid = things_in_world;
memcpy(world + things_in_world, &thing, sizeof(world_thing));
things_in_world += 1;
//printf("Added to world: %d\n", thing.kind);
}
void get_floor(void) {
int floorsize = 1000;
FloorPoly *polys = generate_floor_simple(floorsize);
Floor *floor = calloc(1, sizeof(Floor));
floor->polys = polys;
floor->numPolys = floorsize;
world_thing thing;
thing.kind = FLOOR;
thing.floor = floor;
add_to_world(thing);
}
void startgame(SDL_Renderer * ren) {
logwrite(INFO, "STARTGAME");
things_in_world = 0;
world_size = 100;
world = malloc(sizeof(world_thing) * 100);
memset(world, 0, sizeof(world_thing) * 100);
SDL_GetRendererOutputSize(ren, &width, &height);
player = get_player(20 * width,600);
world_thing player_world;
player_world.kind = PLAYER_W;
player_world.player = malloc(sizeof(player));
*player_world.player = player;
glob_player = player_world.player;
add_to_world(player_world);
world_thing wall_world;
int wall_nodes[] = {500, 100, 200, 100, 100, 200, 900, 200};
wall_world.wall = get_long_wall(4, wall_nodes);
wall_world.kind = STATIC_WALL_W;
add_to_world(wall_world);
viewport_pos.x = 700;
viewport_pos.y = 0;
get_floor();
}
float get_abs(float x,float y) {
return (sqrt(x*x + y*y));
}
void walk_player(int x, int y) {
if (y == -1) {
add_motor_newtons(glob_player->physics, M_PLAYER_WALK, 0, 100 * 10 * y);
return;
}
add_motor_newtons(glob_player->physics, M_PLAYER_WALK, 100 *10* x , 100 * y);
}
void step(int interval) {
const uint8_t * keyboard;
keyboard = SDL_GetKeyboardState(NULL);
set_motor_newtons(player.physics, M_PLAYER_WALK, 0, 0);
if (keyboard[SDL_SCANCODE_W]) {
walk_player(0, -1);
} if (keyboard[SDL_SCANCODE_SPACE]) {
walk_player(0, -1);
} if (keyboard[SDL_SCANCODE_A]) {
walk_player(-1, 0);
} if (keyboard[SDL_SCANCODE_S]) {
walk_player(0, 1);
} if (keyboard[SDL_SCANCODE_D]) {
walk_player(1, 0);
} if (!keyboard[SDL_SCANCODE_W]
&& !keyboard[SDL_SCANCODE_A]
&& !keyboard[SDL_SCANCODE_S]
&& !keyboard[SDL_SCANCODE_D]) {
set_motor_timeout(player.physics, M_PLAYER_WALK, SDL_GetTicks()+1000*2);
}
advance_things();
logwrite(DEBUG, "Updated Physics \n");
}