Merge pull request 'Add a move_and_slide style interpolation option' (#7) from move-and-slide into main
Reviewed-on: #7main
commit
bf21a388ab
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 */
|
|
@ -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);
|
||||
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 other;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no collision, return nothing
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
28
src/engine.c
28
src/engine.c
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
||||
|
|
Loading…
Reference in New Issue