diff --git a/src/corelib/input.c b/src/corelib/input.c index d4b5bf7..df5c666 100644 --- a/src/corelib/input.c +++ b/src/corelib/input.c @@ -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; } } diff --git a/src/corelib/input.h b/src/corelib/input.h index 85d3cc8..9be54d6 100644 --- a/src/corelib/input.h +++ b/src/corelib/input.h @@ -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; diff --git a/src/corelib/math/vec.h b/src/corelib/math/vec.h new file mode 100644 index 0000000..082c589 --- /dev/null +++ b/src/corelib/math/vec.h @@ -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 */ diff --git a/src/corelib/world.c b/src/corelib/world.c index 85fbed4..a46e6bc 100644 --- a/src/corelib/world.c +++ b/src/corelib/world.c @@ -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; } diff --git a/src/corelib/world.h b/src/corelib/world.h index 2c57f31..27de091 100644 --- a/src/corelib/world.h +++ b/src/corelib/world.h @@ -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 */ diff --git a/src/engine.c b/src/engine.c index 8d61289..8a5d514 100644 --- a/src/engine.c +++ b/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; } diff --git a/src/engine.h b/src/engine.h index 87bf672..8667836 100644 --- a/src/engine.h +++ b/src/engine.h @@ -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 */