split the world module into world, physics, and object
parent
43fa322409
commit
b8c856bf56
|
@ -0,0 +1,16 @@
|
||||||
|
#include "object.h"
|
||||||
|
|
||||||
|
object_t object_default() {
|
||||||
|
return (object_t){
|
||||||
|
.active = 1,
|
||||||
|
.enabled = 1,
|
||||||
|
.collider = collider_default(),
|
||||||
|
.evt_draw = &object_draw_sprite,
|
||||||
|
.evt_update = NULL,
|
||||||
|
.sprite = sprite_default(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void object_draw_sprite(object_t* object) {
|
||||||
|
draw_sprite(&object->sprite);
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
#ifndef _object_h
|
||||||
|
#define _object_h
|
||||||
|
|
||||||
|
#include "render.h"
|
||||||
|
#include "physics.h"
|
||||||
|
|
||||||
|
typedef struct object_t object_t;
|
||||||
|
typedef struct collider_t collider_t;
|
||||||
|
|
||||||
|
typedef void(*tick_fn)(struct object_t*);
|
||||||
|
typedef void(*draw_fn)(struct object_t*);
|
||||||
|
|
||||||
|
struct object_t {
|
||||||
|
sprite_t sprite;
|
||||||
|
int active; // 1 if this object is in use and should not be overriden.
|
||||||
|
int enabled; // 1 if this object's events should be triggered.
|
||||||
|
collider_t collider; // the collider to use for this object's physics interaction.
|
||||||
|
|
||||||
|
uintptr_t timer; // free to use for whatever
|
||||||
|
|
||||||
|
tick_fn evt_update;
|
||||||
|
draw_fn evt_draw;
|
||||||
|
};
|
||||||
|
|
||||||
|
object_t object_default();
|
||||||
|
|
||||||
|
void object_draw_sprite(object_t* object);
|
||||||
|
|
||||||
|
#endif /* _object_h */
|
|
@ -0,0 +1,269 @@
|
||||||
|
#include "physics.h"
|
||||||
|
#include "object.h"
|
||||||
|
#include "world.h"
|
||||||
|
#include "math/vec.h"
|
||||||
|
|
||||||
|
static inline
|
||||||
|
float fclampf(float x, float min_, float max_) {
|
||||||
|
return fminf(max_, fmaxf(min_, x));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
collider_t collider_default() {
|
||||||
|
return (collider_t) {
|
||||||
|
.type=COLLIDERTYPE_NONE
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void object_broadcast_collision(object_t* this, object_t* other) {
|
||||||
|
if(this->collider.evt_collision != NULL) {
|
||||||
|
this->collider.evt_collision(this, other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
short can_collide(const object_t* this) {
|
||||||
|
return this->active && this->enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
int _rect_overlap(float aminx, float aminy, float amaxx, float amaxy, float bminx, float bminy, float bmaxx, float bmaxy) {
|
||||||
|
return
|
||||||
|
(
|
||||||
|
(aminx < bmaxx && aminx > bmaxx)
|
||||||
|
||
|
||||||
|
(amaxx > bminx && amaxx < bmaxx)
|
||||||
|
) && (
|
||||||
|
(aminy < bmaxy && aminy > bmaxy)
|
||||||
|
||
|
||||||
|
(amaxy > bminy && amaxy < bmaxy)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
short _collision_aabb_aabb(const object_t* a, const object_t* b) {
|
||||||
|
const float aminx = a->collider.aabb.x + a->sprite.x, aminy = a->collider.aabb.y + a->sprite.x;
|
||||||
|
const float amaxx = aminx + a->collider.aabb.w, amaxy = aminy + a->collider.aabb.h;
|
||||||
|
const float bminx = b->collider.aabb.x, bminy = b->collider.aabb.y;
|
||||||
|
const float bmaxx = b->collider.aabb.x + b->collider.aabb.w, bmaxy = b->collider.aabb.y + b->collider.aabb.h;
|
||||||
|
|
||||||
|
return _rect_overlap(aminx, aminy, amaxx, amaxy, bminx, bminy, bmaxx, bmaxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
short _collision_circle_circle(const object_t* a, const object_t* b) {
|
||||||
|
const float ax = a->sprite.x + a->collider.circle.x, ay = a->sprite.y + a->collider.circle.y,
|
||||||
|
bx = b->sprite.x + b->collider.circle.x, by = b->sprite.y + b->collider.circle.y;
|
||||||
|
const float dx = fabsf(ax-bx), dy = fabsf(ay-by);
|
||||||
|
const float sqrdist = dx*dx+dy*dy;
|
||||||
|
const float mindist = a->collider.circle.radius + b->collider.circle.radius;
|
||||||
|
const float mindistsqr = mindist*mindist;
|
||||||
|
return sqrdist < mindistsqr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
short _collision_circle_aabb(const object_t* circle, const object_t* aabb) {
|
||||||
|
// generate a point on the edge of the rectangle that is closest to the circle
|
||||||
|
const float bbminx = aabb->collider.aabb.x + aabb->sprite.x, bbmaxx = bbminx + aabb->collider.aabb.w,
|
||||||
|
bbminy = aabb->collider.aabb.y + aabb->sprite.y, bbmaxy = bbminy + aabb->collider.aabb.h;
|
||||||
|
const float cx = circle->sprite.x + circle->collider.circle.x,
|
||||||
|
cy = circle->sprite.y + circle->collider.circle.y;
|
||||||
|
const float x = fclampf(cx, bbminx, bbmaxx),
|
||||||
|
y = fclampf(cy, bbminy, bbmaxy);
|
||||||
|
const float dx = fabsf(cx - x), dy = fabsf(cy - y);
|
||||||
|
|
||||||
|
// calculate the square distance from the centre of the circle to the edge of the aabb
|
||||||
|
const float distsqr = dx*dx+dy*dy;
|
||||||
|
const float rsqr = circle->collider.circle.radius*circle->collider.circle.radius;
|
||||||
|
|
||||||
|
// return if the square distance is larger than the square of the radius
|
||||||
|
return distsqr < rsqr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
short _collision_check(const object_t* a, const object_t* b) {
|
||||||
|
if(a->collider.type == COLLIDERTYPE_AABB && b->collider.type == COLLIDERTYPE_AABB) {
|
||||||
|
return _collision_aabb_aabb(a, b);
|
||||||
|
} else if(a->collider.type == COLLIDERTYPE_CIRCLE && b->collider.type == COLLIDERTYPE_CIRCLE) {
|
||||||
|
return _collision_circle_circle(a, b);
|
||||||
|
} else if(a->collider.type == COLLIDERTYPE_CIRCLE && b->collider.type == COLLIDERTYPE_AABB) {
|
||||||
|
return _collision_circle_aabb(a, b);
|
||||||
|
} else if(a->collider.type == COLLIDERTYPE_AABB && b->collider.type == COLLIDERTYPE_CIRCLE) {
|
||||||
|
return _collision_circle_aabb(b, a);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
float _solve_circle_aabb(const object_t* circle, const object_t* aabb, float* out_px, float* out_py) {
|
||||||
|
// generate a point on the edge of the rectangle that is closest to the circle
|
||||||
|
const float bbminx = aabb->collider.aabb.x + aabb->sprite.x, bbmaxx = bbminx + aabb->collider.aabb.w,
|
||||||
|
bbminy = aabb->collider.aabb.y + aabb->sprite.y, bbmaxy = bbminy + aabb->collider.aabb.h;
|
||||||
|
// the centre of the circle in world space
|
||||||
|
const float cx = circle->sprite.x + circle->collider.circle.x,
|
||||||
|
cy = circle->sprite.y + circle->collider.circle.y;
|
||||||
|
// the point on the rectangle closest to the centre of the circle
|
||||||
|
const float x = fclampf(cx, bbminx, bbmaxx),
|
||||||
|
y = fclampf(cy, bbminy, bbmaxy);
|
||||||
|
// the relative position of the point on the rectangle
|
||||||
|
const float dif_x = cx - x,
|
||||||
|
dif_y = cy - y;
|
||||||
|
// absolute difference for use in calculating euclidean distance
|
||||||
|
const float dist_x = fabsf(dif_x),
|
||||||
|
dist_y = fabsf(dif_y);
|
||||||
|
// euclidean distance
|
||||||
|
const float dist = sqrt(dist_x*dist_x + dist_y*dist_y);
|
||||||
|
const float solve_distance = circle->collider.circle.radius - dist;
|
||||||
|
// distance to solve collision
|
||||||
|
float solve_x, solve_y;
|
||||||
|
normalize(dif_x, dif_y, &solve_x, &solve_y);
|
||||||
|
*out_px = solve_x * solve_distance;
|
||||||
|
*out_py = solve_y * solve_distance;
|
||||||
|
return solve_distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
float _solve_circle_circle(const object_t* a, const object_t* b, float* out_px, float* out_py) {
|
||||||
|
const float x1 = a->collider.circle.x + a->sprite.x, y1 = a->collider.circle.y + a->sprite.y;
|
||||||
|
const float x2 = b->collider.circle.x + b->sprite.x, y2 = b->collider.circle.y + b->sprite.y;
|
||||||
|
const float dif_x = x1 - x2, dif_y = y1 - y2;
|
||||||
|
const float difference = sqrtf(fabsf(dif_x*dif_x) + fabsf(dif_y*dif_y));
|
||||||
|
const float target_difference = a->collider.circle.radius + b->collider.circle.radius;
|
||||||
|
float dir_x, dir_y;
|
||||||
|
normalize(dif_x, dif_y, &dir_x, &dir_y);
|
||||||
|
*out_px = dir_x * target_difference;
|
||||||
|
*out_py = dir_y * target_difference;
|
||||||
|
return target_difference;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
float _solve_aabb_aabb(const object_t* a, const object_t* b, float* out_px, float* out_py) {
|
||||||
|
float right = (a->collider.aabb.x + a->collider.aabb.w + a->sprite.x) - (b->collider.aabb.x + b->sprite.x);
|
||||||
|
float left = (a->collider.aabb.x + a->sprite.x) - (b->collider.aabb.x + b->collider.aabb.w + b->sprite.x);
|
||||||
|
float top = (a->collider.aabb.y + a->sprite.y) - (b->collider.aabb.y + b->collider.aabb.w + b->sprite.y);
|
||||||
|
float bottom = (a->collider.aabb.y + a->collider.aabb.h) - (b->collider.aabb.y + b->sprite.y);
|
||||||
|
|
||||||
|
float ret = right;
|
||||||
|
*out_px = right;
|
||||||
|
*out_py = 0.f;
|
||||||
|
if(fabsf(left) < fabsf(ret)) {
|
||||||
|
*out_px = left;
|
||||||
|
*out_py = 0.f;
|
||||||
|
ret = left;
|
||||||
|
}
|
||||||
|
if(fabsf(top) < fabsf(ret)) {
|
||||||
|
*out_px = 0.f;
|
||||||
|
*out_py = top;
|
||||||
|
ret = top;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(fabsf(bottom) < fabsf(ret)) {
|
||||||
|
*out_px = 0.f;
|
||||||
|
*out_py = bottom;
|
||||||
|
return bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
float get_solve_force(const object_t* a, const object_t* b, float* out_px, float* out_py) {
|
||||||
|
if(a->collider.type == COLLIDERTYPE_AABB && b->collider.type == COLLIDERTYPE_AABB) {
|
||||||
|
return _solve_aabb_aabb(a, b, out_px, out_py);
|
||||||
|
} else if(a->collider.type == COLLIDERTYPE_AABB && b->collider.type == COLLIDERTYPE_CIRCLE) {
|
||||||
|
float penetration_distance = _solve_circle_aabb(b, a, out_px, out_py);
|
||||||
|
*out_px = -(*out_px);
|
||||||
|
*out_py = -(*out_py);
|
||||||
|
} else if(a->collider.type == COLLIDERTYPE_CIRCLE && b->collider.type == COLLIDERTYPE_AABB) {
|
||||||
|
return _solve_circle_aabb(a, b, out_px, out_py);
|
||||||
|
} else if(a->collider.type == COLLIDERTYPE_CIRCLE && b->collider.type == COLLIDERTYPE_CIRCLE) {
|
||||||
|
return _solve_circle_circle(a, b, out_px, out_py);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
void _solve_collision_slide(object_t* this, object_t* other, float old_x, float old_y, float new_x, float new_y) {
|
||||||
|
float dx, dy;
|
||||||
|
const float d = get_solve_force(this, other, &dx, &dy);
|
||||||
|
this->sprite.x += dx;
|
||||||
|
this->sprite.y += dy;
|
||||||
|
|
||||||
|
return;
|
||||||
|
this->sprite.x = old_x;
|
||||||
|
this->sprite.y = new_y;
|
||||||
|
if(!_collision_check(other, this)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->sprite.x = new_x;
|
||||||
|
this->sprite.y = old_y;
|
||||||
|
if(!_collision_check(other, this)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->sprite.x = old_x;
|
||||||
|
this->sprite.y = old_y;
|
||||||
|
if(!_collision_check(other, this)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void interpolate_move(object_t* object, const float target_x, const float target_y, const float max_step_size, const int slide) {
|
||||||
|
// calculate step delta
|
||||||
|
float dx = target_x - object->sprite.x, dy = target_y - object->sprite.y;
|
||||||
|
if(dx == 0 && dy == 0)
|
||||||
|
return;
|
||||||
|
// calculate direction x,y
|
||||||
|
float m = sqrtf(dx*dx + dy*dy);
|
||||||
|
if(dx != 0)
|
||||||
|
dx /= m;
|
||||||
|
if(dy != 0)
|
||||||
|
dy /= m;
|
||||||
|
dx *= max_step_size; dy *= max_step_size;
|
||||||
|
int step_count = max_step_size / m;
|
||||||
|
|
||||||
|
// ensure this object would ever collide
|
||||||
|
// if it wouldn't collide anyway, just set position
|
||||||
|
if(!can_collide(object)) {
|
||||||
|
object->sprite.x = target_x;
|
||||||
|
object->sprite.y = target_y;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 1. move towards target
|
||||||
|
* 2. check collision with every other object
|
||||||
|
*/
|
||||||
|
for(int steps = 0; steps < step_count && (object->sprite.x != target_x || object->sprite.y != target_y); ++steps) {
|
||||||
|
// move towards target, snap to target if distance is too low
|
||||||
|
const float old_x = object->sprite.x, old_y = object->sprite.y;
|
||||||
|
float new_x, new_y;
|
||||||
|
|
||||||
|
const float distx = fabsf(object->sprite.x - target_x), disty = fabsf(object->sprite.y - target_y);
|
||||||
|
const float sqdist = distx*distx + disty*disty;
|
||||||
|
if(sqdist > max_step_size) {
|
||||||
|
object->sprite.x += dx;
|
||||||
|
object->sprite.y += dy;
|
||||||
|
new_x = object->sprite.x;
|
||||||
|
new_y = object->sprite.y;
|
||||||
|
} else {
|
||||||
|
new_x = object->sprite.x = target_x;
|
||||||
|
new_y = object->sprite.y = target_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// loop over all objects and check collision if applicable
|
||||||
|
for(int i = 0; i < WORLD_NUM_OBJECTS; ++i) {
|
||||||
|
// get pointer to other object
|
||||||
|
object_t* other = g_objects + i;
|
||||||
|
// check collision, return if found
|
||||||
|
if(can_collide(other) && object != other && _collision_check(other, object)) {
|
||||||
|
object_broadcast_collision(other, object);
|
||||||
|
object_broadcast_collision(object, other);
|
||||||
|
if(slide) {
|
||||||
|
_solve_collision_slide(object, other, old_x, old_y, new_x, new_y);
|
||||||
|
} else {
|
||||||
|
object->sprite.x = old_x;
|
||||||
|
object->sprite.y = old_y;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
#ifndef _physics_h
|
||||||
|
#define _physics_h
|
||||||
|
|
||||||
|
#include "SDL2/SDL_rect.h"
|
||||||
|
|
||||||
|
typedef struct object_t object_t;
|
||||||
|
|
||||||
|
typedef void(*collided_fn)(object_t*, struct object_t*);
|
||||||
|
|
||||||
|
typedef enum collider_type_t {
|
||||||
|
COLLIDERTYPE_MIN,
|
||||||
|
COLLIDERTYPE_NONE,
|
||||||
|
COLLIDERTYPE_CIRCLE,
|
||||||
|
COLLIDERTYPE_AABB,
|
||||||
|
COLLIDERTYPE_MAX,
|
||||||
|
} collider_type_t;
|
||||||
|
|
||||||
|
typedef struct collider_t {
|
||||||
|
collider_type_t type;
|
||||||
|
collided_fn evt_collision;
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
float x, y;
|
||||||
|
float radius;
|
||||||
|
} circle;
|
||||||
|
SDL_FRect aabb;
|
||||||
|
};
|
||||||
|
} collider_t;
|
||||||
|
|
||||||
|
collider_t collider_default();
|
||||||
|
|
||||||
|
void object_broadcast_evt_collision(object_t* this, object_t* other);
|
||||||
|
|
||||||
|
void physics_update();
|
||||||
|
|
||||||
|
void interpolate_move(object_t* object, float target_x, float target_y, float max_step_size, int slide);
|
||||||
|
|
||||||
|
extern short can_collide(const object_t* this);
|
||||||
|
extern float get_solve_force(const object_t* this, const object_t* other, float* solve_x, float* solve_y);
|
||||||
|
|
||||||
|
#endif /* _physics_h */
|
|
@ -253,6 +253,19 @@ void _exec_text_cmd(const drawcmd_t* cmd) {
|
||||||
free(cmd->text.text);
|
free(cmd->text.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sprite_t sprite_default() {
|
||||||
|
return (sprite_t){
|
||||||
|
.texture = NULL,
|
||||||
|
.x = 0.f, .y = 0.f,
|
||||||
|
.origin = (SDL_FPoint){0.f, 0.f},
|
||||||
|
.sx = 1.f, .sy = 1.f,
|
||||||
|
.rot = 0.f,
|
||||||
|
.depth = RLAYER_SPRITES,
|
||||||
|
.uv = (SDL_Rect){0.f, 0.f, 0.f, 0.f},
|
||||||
|
.flip = SDL_FLIP_NONE,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
sprite_t render_text(const char* str, SDL_FRect area, text_style_t style) {
|
sprite_t render_text(const char* str, SDL_FRect area, text_style_t style) {
|
||||||
SDL_FRect r = area;
|
SDL_FRect r = area;
|
||||||
int fh = TTF_FontHeight(style.font);
|
int fh = TTF_FontHeight(style.font);
|
||||||
|
|
|
@ -91,6 +91,8 @@ extern void screen_to_view(float* x, float* y);
|
||||||
extern void clear_buffer();
|
extern void clear_buffer();
|
||||||
extern void swap_buffer();
|
extern void swap_buffer();
|
||||||
|
|
||||||
|
extern sprite_t sprite_default();
|
||||||
|
|
||||||
extern sprite_t render_text(const char* str, SDL_FRect area, text_style_t style);
|
extern sprite_t render_text(const char* str, SDL_FRect area, text_style_t style);
|
||||||
extern void draw_sprite(const sprite_t* sprite);
|
extern void draw_sprite(const sprite_t* sprite);
|
||||||
extern void draw_rect(const rectshape_t* rect);
|
extern void draw_rect(const rectshape_t* rect);
|
||||||
|
|
|
@ -10,10 +10,6 @@ void world_clear() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void object_draw_sprite(object_t* object) {
|
|
||||||
draw_sprite(&object->sprite);
|
|
||||||
}
|
|
||||||
|
|
||||||
object_t* _find_free_object() {
|
object_t* _find_free_object() {
|
||||||
for(int i = 0; i < WORLD_NUM_OBJECTS; ++i) {
|
for(int i = 0; i < WORLD_NUM_OBJECTS; ++i) {
|
||||||
if(g_objects[i].active == 0) {
|
if(g_objects[i].active == 0) {
|
||||||
|
@ -25,12 +21,7 @@ object_t* _find_free_object() {
|
||||||
|
|
||||||
object_t* make_object() {
|
object_t* make_object() {
|
||||||
object_t* o = _find_free_object();
|
object_t* o = _find_free_object();
|
||||||
o->active = 1;
|
*o = object_default();
|
||||||
o->enabled = 1;
|
|
||||||
o->collider = collider_default();
|
|
||||||
o->evt_draw = &object_draw_sprite;
|
|
||||||
o->evt_update = NULL;
|
|
||||||
memset(&o->sprite, 0, sizeof(sprite_t));
|
|
||||||
return o;
|
return o;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,13 +32,7 @@ object_t* instantiate_object(const object_t *original) {
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
collider_t collider_default() {
|
void world_update() {
|
||||||
return (collider_t) {
|
|
||||||
.type=COLLIDERTYPE_NONE
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void update_objects() {
|
|
||||||
for(int i = 0; i < WORLD_NUM_OBJECTS; ++i) {
|
for(int i = 0; i < WORLD_NUM_OBJECTS; ++i) {
|
||||||
if(g_objects[i].active == 1
|
if(g_objects[i].active == 1
|
||||||
&& g_objects[i].enabled == 1
|
&& g_objects[i].enabled == 1
|
||||||
|
@ -57,7 +42,7 @@ void update_objects() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw_objects() {
|
void world_draw() {
|
||||||
for(int i = 0; i < WORLD_NUM_OBJECTS; ++i) {
|
for(int i = 0; i < WORLD_NUM_OBJECTS; ++i) {
|
||||||
if(g_objects[i].active == 1
|
if(g_objects[i].active == 1
|
||||||
&& g_objects[i].enabled == 1
|
&& g_objects[i].enabled == 1
|
||||||
|
@ -65,281 +50,4 @@ void draw_objects() {
|
||||||
g_objects[i].evt_draw(g_objects + i);
|
g_objects[i].evt_draw(g_objects + i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline
|
|
||||||
int _rect_overlap(float aminx, float aminy, float amaxx, float amaxy, float bminx, float bminy, float bmaxx, float bmaxy) {
|
|
||||||
return
|
|
||||||
(
|
|
||||||
(aminx < bmaxx && aminx > bmaxx)
|
|
||||||
||
|
|
||||||
(amaxx > bminx && amaxx < bmaxx)
|
|
||||||
) && (
|
|
||||||
(aminy < bmaxy && aminy > bmaxy)
|
|
||||||
||
|
|
||||||
(amaxy > bminy && amaxy < bmaxy)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline
|
|
||||||
short _collision_aabb_aabb(const object_t* a, const object_t* b) {
|
|
||||||
const float aminx = a->collider.aabb.x + a->sprite.x, aminy = a->collider.aabb.y + a->sprite.x;
|
|
||||||
const float amaxx = aminx + a->collider.aabb.w, amaxy = aminy + a->collider.aabb.h;
|
|
||||||
const float bminx = b->collider.aabb.x, bminy = b->collider.aabb.y;
|
|
||||||
const float bmaxx = b->collider.aabb.x + b->collider.aabb.w, bmaxy = b->collider.aabb.y + b->collider.aabb.h;
|
|
||||||
|
|
||||||
return _rect_overlap(aminx, aminy, amaxx, amaxy, bminx, bminy, bmaxx, bmaxy);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline
|
|
||||||
short _collision_circle_circle(const object_t* a, const object_t* b) {
|
|
||||||
const float ax = a->sprite.x + a->collider.circle.x, ay = a->sprite.y + a->collider.circle.y,
|
|
||||||
bx = b->sprite.x + b->collider.circle.x, by = b->sprite.y + b->collider.circle.y;
|
|
||||||
const float dx = fabsf(ax-bx), dy = fabsf(ay-by);
|
|
||||||
const float sqrdist = dx*dx+dy*dy;
|
|
||||||
const float mindist = a->collider.circle.radius + b->collider.circle.radius;
|
|
||||||
const float mindistsqr = mindist*mindist;
|
|
||||||
return sqrdist < mindistsqr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline
|
|
||||||
float fclampf(float x, float min_, float max_) {
|
|
||||||
return fminf(max_, fmaxf(min_, x));
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline
|
|
||||||
float _circle_aabb_overlap(const object_t* circle, const object_t* aabb, float* out_px, float* out_py) {
|
|
||||||
// generate a point on the edge of the rectangle that is closest to the circle
|
|
||||||
const float bbminx = aabb->collider.aabb.x + aabb->sprite.x, bbmaxx = bbminx + aabb->collider.aabb.w,
|
|
||||||
bbminy = aabb->collider.aabb.y + aabb->sprite.y, bbmaxy = bbminy + aabb->collider.aabb.h;
|
|
||||||
// the centre of the circle in world space
|
|
||||||
const float cx = circle->sprite.x + circle->collider.circle.x,
|
|
||||||
cy = circle->sprite.y + circle->collider.circle.y;
|
|
||||||
// the point on the rectangle closest to the centre of the circle
|
|
||||||
const float x = fclampf(cx, bbminx, bbmaxx),
|
|
||||||
y = fclampf(cy, bbminy, bbmaxy);
|
|
||||||
// the relative position of the point on the rectangle
|
|
||||||
const float dif_x = cx - x,
|
|
||||||
dif_y = cy - y;
|
|
||||||
// absolute difference for use in calculating euclidean distance
|
|
||||||
const float dist_x = fabsf(dif_x),
|
|
||||||
dist_y = fabsf(dif_y);
|
|
||||||
// euclidean distance
|
|
||||||
const float dist = sqrt(dist_x*dist_x + dist_y*dist_y);
|
|
||||||
const float solve_distance = circle->collider.circle.radius - dist;
|
|
||||||
// distance to solve collision
|
|
||||||
float solve_x, solve_y;
|
|
||||||
normalize(dif_x, dif_y, &solve_x, &solve_y);
|
|
||||||
*out_px = solve_x * solve_distance;
|
|
||||||
*out_py = solve_y * solve_distance;
|
|
||||||
return solve_distance;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline
|
|
||||||
float _circle_circle_overlap(const object_t* a, const object_t* b, float* out_px, float* out_py) {
|
|
||||||
const float x1 = a->collider.circle.x + a->sprite.x, y1 = a->collider.circle.y + a->sprite.y;
|
|
||||||
const float x2 = b->collider.circle.x + b->sprite.x, y2 = b->collider.circle.y + b->sprite.y;
|
|
||||||
const float dif_x = x1 - x2, dif_y = y1 - y2;
|
|
||||||
const float difference = sqrtf(fabsf(dif_x*dif_x) + fabsf(dif_y*dif_y));
|
|
||||||
const float target_difference = a->collider.circle.radius + b->collider.circle.radius;
|
|
||||||
float dir_x, dir_y;
|
|
||||||
normalize(dif_x, dif_y, &dir_x, &dir_y);
|
|
||||||
*out_px = dir_x * target_difference;
|
|
||||||
*out_py = dir_y * target_difference;
|
|
||||||
return target_difference;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline
|
|
||||||
float _aabb_aabb_overlap(const object_t* a, const object_t* b, float* out_px, float* out_py) {
|
|
||||||
float right = (a->collider.aabb.x + a->collider.aabb.w + a->sprite.x) - (b->collider.aabb.x + b->sprite.x);
|
|
||||||
float left = (a->collider.aabb.x + a->sprite.x) - (b->collider.aabb.x + b->collider.aabb.w + b->sprite.x);
|
|
||||||
float top = (a->collider.aabb.y + a->sprite.y) - (b->collider.aabb.y + b->collider.aabb.w + b->sprite.y);
|
|
||||||
float bottom = (a->collider.aabb.y + a->collider.aabb.h) - (b->collider.aabb.y + b->sprite.y);
|
|
||||||
|
|
||||||
float ret = right;
|
|
||||||
*out_px = right;
|
|
||||||
*out_py = 0.f;
|
|
||||||
if(fabsf(left) < fabsf(ret)) {
|
|
||||||
*out_px = left;
|
|
||||||
*out_py = 0.f;
|
|
||||||
ret = left;
|
|
||||||
}
|
|
||||||
if(fabsf(top) < fabsf(ret)) {
|
|
||||||
*out_px = 0.f;
|
|
||||||
*out_py = top;
|
|
||||||
ret = top;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(fabsf(bottom) < fabsf(ret)) {
|
|
||||||
*out_px = 0.f;
|
|
||||||
*out_py = bottom;
|
|
||||||
return bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline
|
|
||||||
float _get_overlap(const object_t* a, const object_t* b, float* out_px, float* out_py) {
|
|
||||||
if(a->collider.type == COLLIDERTYPE_AABB && b->collider.type == COLLIDERTYPE_AABB) {
|
|
||||||
return _aabb_aabb_overlap(a, b, out_px, out_py);
|
|
||||||
} else if(a->collider.type == COLLIDERTYPE_AABB && b->collider.type == COLLIDERTYPE_CIRCLE) {
|
|
||||||
float penetration_distance = _circle_aabb_overlap(b, a, out_px, out_py);
|
|
||||||
*out_px = -(*out_px);
|
|
||||||
*out_py = -(*out_py);
|
|
||||||
} else if(a->collider.type == COLLIDERTYPE_CIRCLE && b->collider.type == COLLIDERTYPE_AABB) {
|
|
||||||
return _circle_aabb_overlap(a, b, out_px, out_py);
|
|
||||||
} else if(a->collider.type == COLLIDERTYPE_CIRCLE && b->collider.type == COLLIDERTYPE_CIRCLE) {
|
|
||||||
return _circle_circle_overlap(a, b, out_px, out_py);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline
|
|
||||||
short _collision_circle_aabb(const object_t* circle, const object_t* aabb) {
|
|
||||||
// generate a point on the edge of the rectangle that is closest to the circle
|
|
||||||
const float bbminx = aabb->collider.aabb.x + aabb->sprite.x, bbmaxx = bbminx + aabb->collider.aabb.w,
|
|
||||||
bbminy = aabb->collider.aabb.y + aabb->sprite.y, bbmaxy = bbminy + aabb->collider.aabb.h;
|
|
||||||
const float cx = circle->sprite.x + circle->collider.circle.x,
|
|
||||||
cy = circle->sprite.y + circle->collider.circle.y;
|
|
||||||
const float x = fclampf(cx, bbminx, bbmaxx),
|
|
||||||
y = fclampf(cy, bbminy, bbmaxy);
|
|
||||||
const float dx = fabsf(cx - x), dy = fabsf(cy - y);
|
|
||||||
|
|
||||||
// calculate the square distance from the centre of the circle to the edge of the aabb
|
|
||||||
const float distsqr = dx*dx+dy*dy;
|
|
||||||
const float rsqr = circle->collider.circle.radius*circle->collider.circle.radius;
|
|
||||||
|
|
||||||
// return if the square distance is larger than the square of the radius
|
|
||||||
return distsqr < rsqr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline
|
|
||||||
short _collision_check(const object_t* a, const object_t* b) {
|
|
||||||
if(a->collider.type == COLLIDERTYPE_AABB && b->collider.type == COLLIDERTYPE_AABB) {
|
|
||||||
return _collision_aabb_aabb(a, b);
|
|
||||||
} else if(a->collider.type == COLLIDERTYPE_CIRCLE && b->collider.type == COLLIDERTYPE_CIRCLE) {
|
|
||||||
return _collision_circle_circle(a, b);
|
|
||||||
} else if(a->collider.type == COLLIDERTYPE_CIRCLE && b->collider.type == COLLIDERTYPE_AABB) {
|
|
||||||
return _collision_circle_aabb(a, b);
|
|
||||||
} else if(a->collider.type == COLLIDERTYPE_AABB && b->collider.type == COLLIDERTYPE_CIRCLE) {
|
|
||||||
return _collision_circle_aabb(b, a);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline
|
|
||||||
short _can_collide(const object_t* object) {
|
|
||||||
return object->active && object->enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
void object_broadcast_collision(object_t* this, object_t* other) {
|
|
||||||
if(this->collider.evt_collision != NULL) {
|
|
||||||
this->collider.evt_collision(this, other);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void update_collision() {
|
|
||||||
for(int outer = 0; outer < WORLD_NUM_OBJECTS; ++outer) {
|
|
||||||
object_t* oobject = g_objects + outer;
|
|
||||||
if(!_can_collide(oobject)) continue;
|
|
||||||
for(int inner = 0; inner < WORLD_NUM_OBJECTS; ++inner) {
|
|
||||||
object_t* iobject = g_objects + inner;
|
|
||||||
if(!_can_collide(oobject)) continue;
|
|
||||||
|
|
||||||
if(outer != inner && _collision_check(iobject, oobject)) {
|
|
||||||
object_broadcast_collision(oobject, iobject);
|
|
||||||
object_broadcast_collision(iobject, oobject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline
|
|
||||||
void _slide_collision(object_t* this, object_t* other, float old_x, float old_y, float new_x, float new_y) {
|
|
||||||
float dx, dy;
|
|
||||||
const float d = _get_overlap(this, other, &dx, &dy);
|
|
||||||
this->sprite.x += dx;
|
|
||||||
this->sprite.y += dy;
|
|
||||||
|
|
||||||
return;
|
|
||||||
this->sprite.x = old_x;
|
|
||||||
this->sprite.y = new_y;
|
|
||||||
if(!_collision_check(other, this)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->sprite.x = new_x;
|
|
||||||
this->sprite.y = old_y;
|
|
||||||
if(!_collision_check(other, this)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->sprite.x = old_x;
|
|
||||||
this->sprite.y = old_y;
|
|
||||||
if(!_collision_check(other, this)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void interpolate_move(object_t* object, const float target_x, const float target_y, const float max_step_size, const int slide) {
|
|
||||||
// calculate step delta
|
|
||||||
float dx = target_x - object->sprite.x, dy = target_y - object->sprite.y;
|
|
||||||
if(dx == 0 && dy == 0)
|
|
||||||
return;
|
|
||||||
// calculate direction x,y
|
|
||||||
float m = sqrtf(dx*dx + dy*dy);
|
|
||||||
if(dx != 0)
|
|
||||||
dx /= m;
|
|
||||||
if(dy != 0)
|
|
||||||
dy /= m;
|
|
||||||
dx *= max_step_size; dy *= max_step_size;
|
|
||||||
int step_count = max_step_size / m;
|
|
||||||
|
|
||||||
// ensure this object would ever collide
|
|
||||||
// if it wouldn't collide anyway, just set position
|
|
||||||
if(!_can_collide(object)) {
|
|
||||||
object->sprite.x = target_x;
|
|
||||||
object->sprite.y = target_y;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 1. move towards target
|
|
||||||
* 2. check collision with every other object
|
|
||||||
*/
|
|
||||||
for(int steps = 0; steps < step_count && (object->sprite.x != target_x || object->sprite.y != target_y); ++steps) {
|
|
||||||
// move towards target, snap to target if distance is too low
|
|
||||||
const float old_x = object->sprite.x, old_y = object->sprite.y;
|
|
||||||
float new_x, new_y;
|
|
||||||
|
|
||||||
const float distx = fabsf(object->sprite.x - target_x), disty = fabsf(object->sprite.y - target_y);
|
|
||||||
const float sqdist = distx*distx + disty*disty;
|
|
||||||
if(sqdist > max_step_size) {
|
|
||||||
object->sprite.x += dx;
|
|
||||||
object->sprite.y += dy;
|
|
||||||
new_x = object->sprite.x;
|
|
||||||
new_y = object->sprite.y;
|
|
||||||
} else {
|
|
||||||
new_x = object->sprite.x = target_x;
|
|
||||||
new_y = object->sprite.y = target_y;
|
|
||||||
}
|
|
||||||
|
|
||||||
// loop over all objects and check collision if applicable
|
|
||||||
for(int i = 0; i < WORLD_NUM_OBJECTS; ++i) {
|
|
||||||
// get pointer to other object
|
|
||||||
object_t* other = g_objects + i;
|
|
||||||
// check collision, return if found
|
|
||||||
if(_can_collide(other) && object != other && _collision_check(other, object)) {
|
|
||||||
object_broadcast_collision(other, object);
|
|
||||||
object_broadcast_collision(object, other);
|
|
||||||
if(slide) {
|
|
||||||
_slide_collision(object, other, old_x, old_y, new_x, new_y);
|
|
||||||
} else {
|
|
||||||
object->sprite.x = old_x;
|
|
||||||
object->sprite.y = old_y;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,67 +1,20 @@
|
||||||
#ifndef _world_h
|
#ifndef _world_h
|
||||||
#define _world_h
|
#define _world_h
|
||||||
|
|
||||||
#include "render.h"
|
#include "object.h"
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#define WORLD_NUM_OBJECTS 255
|
#define WORLD_NUM_OBJECTS 255
|
||||||
|
|
||||||
typedef struct object_t object_t;
|
typedef struct object_t object_t;
|
||||||
|
|
||||||
typedef void(*tick_fn)(struct object_t*);
|
|
||||||
typedef void(*draw_fn)(struct object_t*);
|
|
||||||
typedef void(*collided_fn)(struct object_t*, struct object_t*);
|
|
||||||
|
|
||||||
typedef enum collider_type_t {
|
|
||||||
COLLIDERTYPE_MIN,
|
|
||||||
COLLIDERTYPE_NONE,
|
|
||||||
COLLIDERTYPE_CIRCLE,
|
|
||||||
COLLIDERTYPE_AABB,
|
|
||||||
COLLIDERTYPE_MAX,
|
|
||||||
} collider_type_t;
|
|
||||||
|
|
||||||
typedef struct collider_t {
|
|
||||||
collider_type_t type;
|
|
||||||
collided_fn evt_collision;
|
|
||||||
union {
|
|
||||||
struct {
|
|
||||||
float x, y;
|
|
||||||
float radius;
|
|
||||||
} circle;
|
|
||||||
SDL_FRect aabb;
|
|
||||||
};
|
|
||||||
} collider_t;
|
|
||||||
|
|
||||||
struct object_t {
|
|
||||||
sprite_t sprite;
|
|
||||||
int active; // 1 if this object is in use and should not be overriden.
|
|
||||||
int enabled; // 1 if this object's events should be triggered.
|
|
||||||
collider_t collider; // the collider to use for this object's physics interaction.
|
|
||||||
|
|
||||||
uintptr_t timer; // free to use for whatever
|
|
||||||
|
|
||||||
tick_fn evt_update;
|
|
||||||
draw_fn evt_draw;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern object_t g_objects[WORLD_NUM_OBJECTS];
|
extern object_t g_objects[WORLD_NUM_OBJECTS];
|
||||||
|
|
||||||
void world_clear();
|
|
||||||
|
|
||||||
|
|
||||||
object_t* make_object();
|
object_t* make_object();
|
||||||
object_t* instantiate_object(const object_t* original);
|
object_t* instantiate_object(const object_t* original);
|
||||||
|
|
||||||
collider_t collider_default();
|
void world_clear();
|
||||||
|
|
||||||
void object_draw_sprite(object_t* object);
|
void world_update();
|
||||||
void object_broadcast_evt_collision(object_t* this, object_t* other);
|
void world_draw();
|
||||||
|
|
||||||
void update_objects();
|
|
||||||
void draw_objects();
|
|
||||||
|
|
||||||
void update_collision();
|
|
||||||
|
|
||||||
void interpolate_move(object_t* object, float target_x, float target_y, float max_step_size, int slide);
|
|
||||||
|
|
||||||
#endif /* _world_h */
|
#endif /* _world_h */
|
||||||
|
|
|
@ -82,8 +82,8 @@ int _engine_run() {
|
||||||
update_ui();
|
update_ui();
|
||||||
_render_mode = 0;
|
_render_mode = 0;
|
||||||
update_game();
|
update_game();
|
||||||
update_objects(); // update world objects
|
world_update(); // update world objects
|
||||||
draw_objects(); // draw world objects
|
world_draw(); // draw world objects
|
||||||
swap_buffer();
|
swap_buffer();
|
||||||
do {
|
do {
|
||||||
timespec_get(&next_time, TIME_UTC);
|
timespec_get(&next_time, TIME_UTC);
|
||||||
|
|
Loading…
Reference in New Issue