Add a move_and_slide style interpolation option #7

Merged
Sara merged 23 commits from move-and-slide into main 2023-06-24 20:08:56 +00:00
7 changed files with 272 additions and 66 deletions

View File

@ -23,7 +23,8 @@ void add_key_listener(SDL_Scancode negative, SDL_Scancode positive,
g_key_listeners_endptr->axis.delegate = delegate;
g_key_listeners_endptr->axis.positive = positive;
g_key_listeners_endptr->axis.negative = negative;
g_key_listeners_endptr->axis.last = 0;
g_key_listeners_endptr->axis.last_positive =
g_key_listeners_endptr->axis.last_negative = 0;
++g_key_listeners_endptr;
}
@ -70,21 +71,6 @@ void input_init() {
g_key_states = SDL_GetKeyboardState(NULL);
}
static inline
void _process_axis_listener(input_listener_t* listener) {
int val = 0;
if(g_key_states[listener->axis.negative]) {
val = -1;
}
if(g_key_states[listener->axis.positive]) {
val += 1;
}
if(val != listener->axis.last) {
listener->axis.last = val;
listener->axis.delegate(val);
}
}
static inline
void _process_mouse_listener(input_listener_t* listener, float dx, float dy) {
if(dx != 0.0 && dy != 0.0) {
@ -92,23 +78,6 @@ void _process_mouse_listener(input_listener_t* listener, float dx, float dy) {
}
}
static inline
void _process_button_listener(input_listener_t* listener) {
uint32_t state = SDL_GetMouseState(NULL, NULL);
int is_down = (state & (listener->button.button)) != 0;
if(is_down != listener->button.last) {
listener->button.delegate(is_down);
}
listener->button.last = is_down;
}
static inline
void _process_scroll_listener(input_listener_t* listener) {
if(_scroll_delta != 0) {
listener->scroll.delegate(_scroll_delta);
}
}
void update_input() {
float dx, dy;
int px, py;
@ -118,19 +87,8 @@ void update_input() {
dx = (float)(px - _last_mouse_x)/width; dy = (float)(py - _last_mouse_y)/width;
for(input_listener_t* listener = g_key_listeners; listener != g_key_listeners_endptr; ++listener) {
switch(listener->type) {
case INPUT_LISTENER_AXIS:
_process_axis_listener(listener);
break;
case INPUT_LISTENER_MOUSE:
if(listener->type == INPUT_LISTENER_MOUSE) {
_process_mouse_listener(listener, dx, dy);
break;
case INPUT_LISTENER_SCROLL:
_process_scroll_listener(listener);
break;
case INPUT_LISTENER_BUTTON:
_process_button_listener(listener);
break;
}
}
@ -140,13 +98,58 @@ void update_input() {
_scroll_delta = 0;
}
static inline
void _handle_key_event(const SDL_Event event) {
for(input_listener_t* listener = g_key_listeners; listener < g_key_listeners_endptr; ++listener) {
if(listener->type == INPUT_LISTENER_AXIS) {
const SDL_Scancode scode = event.key.keysym.scancode;
if(listener->axis.positive == scode) {
listener->axis.last_positive = event.key.state == SDL_PRESSED;
}
if(listener->axis.negative == scode) {
listener->axis.last_negative = event.key.state == SDL_PRESSED;
}
listener->axis.delegate(listener->axis.last_positive - listener->axis.last_negative);
}
}
}
static inline
void _handle_scroll_event(const SDL_Event event) {
_scroll_delta = event.wheel.y;
for(input_listener_t* listener = g_key_listeners; listener < g_key_listeners_endptr; ++listener) {
if(listener->type == INPUT_LISTENER_SCROLL) {
listener->scroll.delegate(_scroll_delta);
}
}
}
static inline
void _handle_mousebutton_event(const SDL_Event event) {
for(input_listener_t* listener = g_key_listeners; listener < g_key_listeners_endptr; ++listener) {
if(listener->type == INPUT_LISTENER_BUTTON
|| listener->button.button == event.button.button) {
listener->button.last = event.button.state == SDL_PRESSED;
listener->button.delegate(listener->button.last);
}
}
}
void input_notify_event(SDL_Event event) {
switch(event.type) {
default:
return;
case SDL_KEYUP:
case SDL_KEYDOWN:
_handle_key_event(event);
return;
case SDL_MOUSEWHEEL:
_scroll_delta = event.wheel.y;
break;
_handle_scroll_event(event);
return;
case SDL_MOUSEBUTTONUP:
case SDL_MOUSEBUTTONDOWN:
_handle_mousebutton_event(event);
return;
}
}

View File

@ -25,7 +25,7 @@ typedef struct input_listener_t {
struct {
input_axis_delegate_t delegate;
SDL_Scancode positive, negative;
int last;
int last_positive, last_negative;
} axis;
struct {
input_mouse_delegate_t delegate;

54
src/corelib/math/vec.h Normal file
View File

@ -0,0 +1,54 @@
#ifndef _vec_math_h
#define _vec_math_h
#include "math.h"
static inline
void clamp_magnitude(float* xx, float* yy, float max_magnitude) {
float x = *xx, y = *yy;
const float m = sqrtf(x*x + y*y);
if(m > max_magnitude) {
x /= m; y /= m;
x *= max_magnitude; y *= max_magnitude;
} else {
*xx = x; *yy = y;
}
}
static inline
void normalize(float x, float y, float* xx, float* yy) {
if(x != 0 || y != 0) {
const float m = sqrtf(x*x + y*y);
*xx = x / m; *yy = y / m;
} else {
*xx = x; *yy = y;
}
}
#define NORMALIZE(_xx, _yy) \
if(_xx != 0 || _yy != 0) { \
const float m = sqrtf(_xx*_xx + _yy*_yy); \
_xx /= m; _yy /= m; \
} else { \
_xx = 0; _yy = 0; \
}
static inline
int move_towards(float* out_x, float* out_y, float x, float y, float tx, float ty, float max_delta) {
const float diff_x = tx - x,
diff_y = ty - y;
const float m = sqrtf(diff_x*diff_x + diff_y*diff_y);
const float dir_x = diff_x / m * max_delta,
dir_y = diff_y / m * max_delta;
if(fabsf(dir_x) < fabsf(diff_x) || fabsf(dir_y) < fabsf(diff_y)) {
*out_x = x + dir_x;
*out_y = y + dir_y;
return 0;
} else {
*out_x = tx;
*out_y = ty;
return 1;
}
}
#endif /* _vec_math_h */

View File

@ -1,4 +1,5 @@
#include "world.h"
#include "math/vec.h"
object_t g_objects[WORLD_NUM_OBJECTS];
@ -106,6 +107,93 @@ 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
@ -166,36 +254,74 @@ void update_collision() {
}
}
object_t* interpolate_move(object_t* object, float target_x, float target_y, float max_step_size) {
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);
dx /= m; dy /= m;
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 NULL;
return;
}
/*
* 1. move towards target
* 2. check collision with every other object
*/
while(object->sprite.x != target_x || object->sprite.y != target_y) {
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);
if(distx < fabsf(dx) && disty < fabsf(dy)) {
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 {
object->sprite.x = target_x;
object->sprite.y = target_y;
new_x = object->sprite.x = target_x;
new_y = object->sprite.y = target_y;
}
// loop over all objects and check collision if applicable
@ -206,13 +332,14 @@ object_t* interpolate_move(object_t* object, float target_x, float target_y, flo
if(_can_collide(other) && object != other && _collision_check(other, object)) {
object_broadcast_collision(other, object);
object_broadcast_collision(object, other);
object->sprite.x = old_x;
object->sprite.y = old_y;
return 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;
}
}
}
}
// no collision, return nothing
return NULL;
}

View File

@ -62,6 +62,6 @@ void draw_objects();
void update_collision();
object_t* interpolate_move(object_t* object, float target_x, float target_y, float max_step_size);
void interpolate_move(object_t* object, float target_x, float target_y, float max_step_size, int slide);
#endif /* _world_h */

View File

@ -5,11 +5,27 @@
#include "corelib/input.h"
#include "time.h"
static float _delta_time = 0;
static double _delta_time = 0;
static double _min_frame_interval = 0;
static struct timespec start_last_frame;
#define CURRENT_TIME(__out) { struct timespect ts; timespec_get(&ts, TIME_UTC); __out = ts.tv_sec + tv.nsec * 1E-09; }
inline static
double timespec_to_sec(struct timespec spec) {
return (double)spec.tv_sec + (double)spec.tv_nsec * 1E-09;
}
inline float delta_time() {
return _delta_time;
return (float)_delta_time;
}
void set_frame_interval(double frame_interval) {
_min_frame_interval = frame_interval;
}
void set_frame_rate_limit(int fps) {
_min_frame_interval = 1.0/(double)fps;
}
static inline
@ -58,8 +74,7 @@ int _engine_run() {
struct timespec next_time;
while(g_context.running) {
timespec_get(&next_time, TIME_UTC);
_delta_time = (next_time.tv_nsec - start_last_frame.tv_nsec) * 1E-9;
if(next_time.tv_nsec < start_last_frame.tv_nsec) _delta_time = 0;
_delta_time = timespec_to_sec(next_time) - timespec_to_sec(start_last_frame);
start_last_frame = next_time;
_handle_events();
update_input();
@ -70,6 +85,11 @@ int _engine_run() {
update_objects(); // update world objects
draw_objects(); // draw world objects
swap_buffer();
do {
timespec_get(&next_time, TIME_UTC);
_delta_time = timespec_to_sec(next_time) - timespec_to_sec(start_last_frame);
SDL_PumpEvents();
} while(_delta_time < _min_frame_interval);
}
return 0;
}

View File

@ -6,6 +6,8 @@ extern "C" {
#endif
extern float delta_time();
extern void set_frame_interval(double frame_interval);
extern void set_frame_rate_limit(int fps);
/* TO BE DEFINED IN GAME */