Compare commits
105 Commits
0a6242ef3c
...
developmen
Author | SHA1 | Date |
---|---|---|
|
dd0e0d866d | |
|
c7d5706eaa | |
|
092adc18aa | |
|
a741f3c473 | |
|
bbfedc727a | |
|
8915e57f9d | |
|
56956f9330 | |
|
3dd5b82cb0 | |
|
1ae19b5d04 | |
|
baf75c9c78 | |
|
b025e81c54 | |
|
3f5346b0e8 | |
|
03a6b65603 | |
|
5ee5292e88 | |
|
41f84bb52c | |
|
5185f5062b | |
|
7950e45632 | |
|
0dc35bd9d9 | |
|
763c43aed6 | |
|
415a0d5c97 | |
|
bd2e5cbcd0 | |
|
25feb8f95f | |
|
deeaad06e1 | |
|
5b91584b00 | |
|
b387dee9c6 | |
|
19401f1f47 | |
|
25c5f83fe4 | |
|
351c9ba713 | |
|
f9236e4182 | |
|
f0d76713c2 | |
|
6085dbaed0 | |
|
33c1a5e9d2 | |
|
14a81b0f2a | |
|
30defc0db8 | |
|
ec50000130 | |
|
0698121853 | |
|
8a6a0e7493 | |
|
78c0e3a9c0 | |
|
59de57fe3a | |
|
5e001bc345 | |
|
3e3f322529 | |
|
30d4e4c39a | |
|
a139e4f3bb | |
|
6a52f2795d | |
|
d915047bd1 | |
|
c1fa6e2956 | |
|
544f18e9c6 | |
|
e6b66884ec | |
|
3ed5755e52 | |
|
a5c6a7eceb | |
|
1eb8ab2fd9 | |
|
a18d5599b4 | |
|
ca3b2af703 | |
|
de7d14ae92 | |
|
f395f27532 | |
|
528e35537f | |
|
f115334644 | |
|
ac722f6e5f | |
|
484db1961a | |
|
20ca626acf | |
|
9a732ad82f | |
|
812e7eab20 | |
|
ead22d114c | |
|
7a2cb3cd5c | |
|
76bf6e81e5 | |
|
8a028dca08 | |
|
4acc18a101 | |
|
a9ba15f93b | |
|
78d1e78dd7 | |
|
aa9dfa3fcd | |
|
bf21a388ab | |
|
b8c856bf56 | |
|
43fa322409 | |
|
4271ee8f8d | |
|
daa53c6ef0 | |
|
3df374b3b9 | |
|
fd99159b27 | |
|
fab7338891 | |
|
cfcd31cee8 | |
|
fc1f3499e3 | |
|
14b8b2f380 | |
|
a93d09c96c | |
|
b30df338ac | |
|
a49ac0db93 | |
|
56679110e3 | |
|
d69b92a464 | |
|
fcb9645dbe | |
|
6f78731d8f | |
|
494dbfedb3 | |
|
1df623e669 | |
|
fa2a911732 | |
|
ea3beddfec | |
|
dff8e8304d | |
|
0a8aac2325 | |
|
81e242532b | |
|
96eeb3233f | |
|
bd87bcf8af | |
|
d134df5847 | |
|
4bf759d224 | |
|
e32eb731c2 | |
|
0b32967c20 | |
|
8ae7d302fe | |
|
f8d69e3537 | |
|
1748db8412 | |
|
d067ea6df8 |
|
@ -17,17 +17,20 @@ find_package(SDL2)
|
||||||
find_package(SDL2_image)
|
find_package(SDL2_image)
|
||||||
find_package(SDL2_ttf)
|
find_package(SDL2_ttf)
|
||||||
|
|
||||||
file(GLOB_RECURSE
|
file(
|
||||||
|
GLOB_RECURSE
|
||||||
SOURCE_C
|
SOURCE_C
|
||||||
"${CMAKE_SOURCE_DIR}/src/**.c")
|
"${CMAKE_SOURCE_DIR}/src/**.c")
|
||||||
file(GLOB_RECURSE
|
file(
|
||||||
|
GLOB_RECURSE
|
||||||
SOURCE_CPP
|
SOURCE_CPP
|
||||||
"${CMAKE_SOURCE_DIR}/src/**.c*")
|
"${CMAKE_SOURCE_DIR}/src/**.c*")
|
||||||
|
|
||||||
add_executable(${PROJECT}
|
add_executable(
|
||||||
|
${PROJECT}
|
||||||
${SOURCE_C}
|
${SOURCE_C}
|
||||||
${SOURCE_CPP})
|
${SOURCE_CPP})
|
||||||
|
|
||||||
target_link_libraries(${PROJECT} SDL2 SDL2_image SDL2_ttf)
|
target_link_libraries(${PROJECT} SDL2 SDL2_image SDL2_ttf m)
|
||||||
|
|
||||||
target_include_directories(${PROJECT} PRIVATE "${CMAKE_SOURCE_DIR}/include/" "${CMAKE_SOURCE_DIR}/src/corelib" "${CMAKE_SOURCE_DIR}/src/ui")
|
target_include_directories(${PROJECT} PRIVATE "${CMAKE_SOURCE_DIR}/include/" "${CMAKE_SOURCE_DIR}/src/corelib" "${CMAKE_SOURCE_DIR}/src/ui")
|
||||||
|
|
|
@ -18,7 +18,7 @@ static resource_t* insert_asset(const resource_t* resource) {
|
||||||
return inserted;
|
return inserted;
|
||||||
}
|
}
|
||||||
|
|
||||||
void add_arbitrary_asset(void* memory, deleter_t deleter) {
|
void add_arbitrary_asset(void* memory, deleter_fn deleter) {
|
||||||
char name_buf[99];
|
char name_buf[99];
|
||||||
// generate the name of the arbitrary block based on adresses
|
// generate the name of the arbitrary block based on adresses
|
||||||
sprintf(name_buf, "%p%p", memory, deleter);
|
sprintf(name_buf, "%p%p", memory, deleter);
|
||||||
|
|
|
@ -17,7 +17,7 @@ typedef enum resourcetype_t {
|
||||||
RESOURCETYPE_MAX
|
RESOURCETYPE_MAX
|
||||||
} resourcetype_t;
|
} resourcetype_t;
|
||||||
|
|
||||||
typedef void(*deleter_t)(void* target);
|
typedef void(*deleter_fn)(void* target);
|
||||||
|
|
||||||
typedef struct resource_t {
|
typedef struct resource_t {
|
||||||
resourcetype_t type;
|
resourcetype_t type;
|
||||||
|
@ -31,13 +31,13 @@ typedef struct resource_t {
|
||||||
} font;
|
} font;
|
||||||
struct {
|
struct {
|
||||||
void* memory;
|
void* memory;
|
||||||
deleter_t deleter;
|
deleter_fn deleter;
|
||||||
} arbitrary_type;
|
} arbitrary_type;
|
||||||
};
|
};
|
||||||
} resource_t;
|
} resource_t;
|
||||||
|
|
||||||
extern void add_arbitrary_asset(void* memory, deleter_t deleter);
|
extern void add_arbitrary_asset(void* memory, deleter_fn deleter);
|
||||||
extern void add_arbitrary_asset_by_name(void* memory, deleter_t deleter, const char* name);
|
extern void add_arbitrary_asset_by_name(void* memory, deleter_fn deleter, const char* name);
|
||||||
extern SDL_Texture* load_texture(const char* file);
|
extern SDL_Texture* load_texture(const char* file);
|
||||||
extern TTF_Font* load_font(const char* file, int size);
|
extern TTF_Font* load_font(const char* file, int size);
|
||||||
extern SDL_Texture* get_texture(const char* file);
|
extern SDL_Texture* get_texture(const char* file);
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
#include "object.h"
|
||||||
|
#include "render.h"
|
||||||
|
#include "assets.h"
|
||||||
|
#include "physics.h"
|
||||||
|
#include "world.h"
|
||||||
|
#include "entry.h"
|
|
@ -0,0 +1,102 @@
|
||||||
|
#include "entry.h"
|
||||||
|
#include "world.h"
|
||||||
|
#include "assets.h"
|
||||||
|
#include "render.h"
|
||||||
|
#include "input.h"
|
||||||
|
|
||||||
|
#include "time.h"
|
||||||
|
|
||||||
|
static double _delta_time = 0;
|
||||||
|
static double _min_frame_interval = 0;
|
||||||
|
static struct timespec start_last_frame;
|
||||||
|
|
||||||
|
#define CURRENT_TIME(__out) do { struct timespect ts; timespec_get(&ts, TIME_UTC); __out = ts.tv_sec + tv.nsec * 1E-09; } while(0)
|
||||||
|
|
||||||
|
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 (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
|
||||||
|
int _engine_start() {
|
||||||
|
init_context();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
int _engine_shutdown() {
|
||||||
|
game_exit();
|
||||||
|
clean_assets();
|
||||||
|
close_context();
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
void _handle_events() {
|
||||||
|
while(SDL_PollEvent(&g_context.event)) {
|
||||||
|
input_notify_event(g_context.event);
|
||||||
|
switch(g_context.event.type) {
|
||||||
|
case SDL_QUIT:
|
||||||
|
g_context.running = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
int _engine_run() {
|
||||||
|
SDL_DisplayMode mode;
|
||||||
|
SDL_GetDesktopDisplayMode(0, &mode);
|
||||||
|
SDL_Window* window = SDL_CreateWindow("Tabletop", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, mode.w, mode.h, SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||||
|
|
||||||
|
g_context = (context_t){
|
||||||
|
.window = window,
|
||||||
|
.renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED),
|
||||||
|
.running = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
input_init();
|
||||||
|
world_clear();
|
||||||
|
start_game();
|
||||||
|
|
||||||
|
timespec_get(&start_last_frame, TIME_UTC);
|
||||||
|
struct timespec next_time;
|
||||||
|
while(g_context.running) {
|
||||||
|
timespec_get(&next_time, TIME_UTC);
|
||||||
|
_delta_time = timespec_to_sec(next_time) - timespec_to_sec(start_last_frame);
|
||||||
|
start_last_frame = next_time;
|
||||||
|
_handle_events();
|
||||||
|
update_input();
|
||||||
|
_render_mode = 1;
|
||||||
|
update_ui();
|
||||||
|
_render_mode = 0;
|
||||||
|
update_game();
|
||||||
|
world_update(); // update world objects
|
||||||
|
world_draw(); // 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
_engine_start();
|
||||||
|
_engine_run();
|
||||||
|
_engine_shutdown();
|
||||||
|
}
|
|
@ -1,11 +1,9 @@
|
||||||
#ifndef _engine_h
|
#ifndef _engine_h
|
||||||
#define _engine_h
|
#define _engine_h
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extern float delta_time();
|
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 */
|
/* TO BE DEFINED IN GAME */
|
||||||
|
|
||||||
|
@ -14,8 +12,4 @@ extern void update_game();
|
||||||
extern void update_ui();
|
extern void update_ui();
|
||||||
extern void game_exit();
|
extern void game_exit();
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* _engine_h */
|
#endif /* _engine_h */
|
|
@ -6,6 +6,34 @@
|
||||||
#include "SDL2/SDL_render.h"
|
#include "SDL2/SDL_render.h"
|
||||||
#include <SDL2/SDL_events.h>
|
#include <SDL2/SDL_events.h>
|
||||||
|
|
||||||
|
enum INPUT_LISTENER_TYPE_T {
|
||||||
|
INPUT_LISTENER_MOUSE,
|
||||||
|
INPUT_LISTENER_AXIS,
|
||||||
|
INPUT_LISTENER_SCROLL,
|
||||||
|
INPUT_LISTENER_BUTTON,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct input_listener_t {
|
||||||
|
enum INPUT_LISTENER_TYPE_T type;
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
input_axis_fn delegate;
|
||||||
|
SDL_Scancode positive, negative;
|
||||||
|
int last_positive, last_negative;
|
||||||
|
} axis;
|
||||||
|
struct {
|
||||||
|
input_mouse_fn delegate;
|
||||||
|
} mouse;
|
||||||
|
struct {
|
||||||
|
input_button_fn delegate;
|
||||||
|
uint32_t button;
|
||||||
|
int last;
|
||||||
|
} button;
|
||||||
|
struct {
|
||||||
|
input_scroll_fn delegate;
|
||||||
|
} scroll;
|
||||||
|
};
|
||||||
|
} input_listener_t;
|
||||||
|
|
||||||
const Uint8* g_key_states = NULL;
|
const Uint8* g_key_states = NULL;
|
||||||
input_listener_t g_key_listeners[24];
|
input_listener_t g_key_listeners[24];
|
||||||
|
@ -17,24 +45,25 @@ static uint32_t _mouse_left_seconds = 0;
|
||||||
static float _scroll_delta = 0;
|
static float _scroll_delta = 0;
|
||||||
|
|
||||||
void add_key_listener(SDL_Scancode negative, SDL_Scancode positive,
|
void add_key_listener(SDL_Scancode negative, SDL_Scancode positive,
|
||||||
input_axis_delegate_t delegate) {
|
input_axis_fn delegate) {
|
||||||
memset(g_key_listeners_endptr, 0x0, sizeof(input_listener_t));
|
memset(g_key_listeners_endptr, 0x0, sizeof(input_listener_t));
|
||||||
g_key_listeners_endptr->type = INPUT_LISTENER_AXIS;
|
g_key_listeners_endptr->type = INPUT_LISTENER_AXIS;
|
||||||
g_key_listeners_endptr->axis.delegate = delegate;
|
g_key_listeners_endptr->axis.delegate = delegate;
|
||||||
g_key_listeners_endptr->axis.positive = positive;
|
g_key_listeners_endptr->axis.positive = positive;
|
||||||
g_key_listeners_endptr->axis.negative = negative;
|
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;
|
++g_key_listeners_endptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void add_mouse_listener(input_mouse_delegate_t delegate) {
|
void add_mouse_listener(input_mouse_fn delegate) {
|
||||||
memset(g_key_listeners_endptr, 0x0, sizeof(input_listener_t));
|
memset(g_key_listeners_endptr, 0x0, sizeof(input_listener_t));
|
||||||
g_key_listeners_endptr->type = INPUT_LISTENER_MOUSE;
|
g_key_listeners_endptr->type = INPUT_LISTENER_MOUSE;
|
||||||
g_key_listeners_endptr->mouse.delegate = delegate;
|
g_key_listeners_endptr->mouse.delegate = delegate;
|
||||||
++g_key_listeners_endptr;
|
++g_key_listeners_endptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void add_mouse_button_listener(uint32_t button, input_button_delegate_t delegate) {
|
void add_mouse_button_listener(uint32_t button, input_button_fn delegate) {
|
||||||
memset(g_key_listeners_endptr, 0x0, sizeof(input_listener_t));
|
memset(g_key_listeners_endptr, 0x0, sizeof(input_listener_t));
|
||||||
g_key_listeners_endptr->type = INPUT_LISTENER_BUTTON;
|
g_key_listeners_endptr->type = INPUT_LISTENER_BUTTON;
|
||||||
g_key_listeners_endptr->button.delegate = delegate;
|
g_key_listeners_endptr->button.delegate = delegate;
|
||||||
|
@ -43,7 +72,7 @@ void add_mouse_button_listener(uint32_t button, input_button_delegate_t delegate
|
||||||
++g_key_listeners_endptr;
|
++g_key_listeners_endptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void add_scroll_listener(input_scroll_delegate_t delegate) {
|
void add_scroll_listener(input_scroll_fn delegate) {
|
||||||
memset(g_key_listeners_endptr, 0x0, sizeof(input_listener_t));
|
memset(g_key_listeners_endptr, 0x0, sizeof(input_listener_t));
|
||||||
g_key_listeners_endptr->type = INPUT_LISTENER_SCROLL;
|
g_key_listeners_endptr->type = INPUT_LISTENER_SCROLL;
|
||||||
g_key_listeners_endptr->scroll.delegate = delegate;
|
g_key_listeners_endptr->scroll.delegate = delegate;
|
||||||
|
@ -70,21 +99,6 @@ void input_init() {
|
||||||
g_key_states = SDL_GetKeyboardState(NULL);
|
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
|
static inline
|
||||||
void _process_mouse_listener(input_listener_t* listener, float dx, float dy) {
|
void _process_mouse_listener(input_listener_t* listener, float dx, float dy) {
|
||||||
if(dx != 0.0 && dy != 0.0) {
|
if(dx != 0.0 && dy != 0.0) {
|
||||||
|
@ -92,23 +106,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() {
|
void update_input() {
|
||||||
float dx, dy;
|
float dx, dy;
|
||||||
int px, py;
|
int px, py;
|
||||||
|
@ -118,19 +115,8 @@ void update_input() {
|
||||||
dx = (float)(px - _last_mouse_x)/width; dy = (float)(py - _last_mouse_y)/width;
|
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) {
|
for(input_listener_t* listener = g_key_listeners; listener != g_key_listeners_endptr; ++listener) {
|
||||||
switch(listener->type) {
|
if(listener->type == INPUT_LISTENER_MOUSE) {
|
||||||
case INPUT_LISTENER_AXIS:
|
|
||||||
_process_axis_listener(listener);
|
|
||||||
break;
|
|
||||||
case INPUT_LISTENER_MOUSE:
|
|
||||||
_process_mouse_listener(listener, dx, dy);
|
_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 +126,65 @@ void update_input() {
|
||||||
_scroll_delta = 0;
|
_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;
|
||||||
|
int changed = 0;
|
||||||
|
if(listener->axis.positive == scode) {
|
||||||
|
int new = event.key.state == SDL_PRESSED;
|
||||||
|
changed = new != listener->axis.last_positive;
|
||||||
|
listener->axis.last_positive = new;
|
||||||
|
}
|
||||||
|
else if(listener->axis.negative == scode) {
|
||||||
|
int new = event.key.state == SDL_PRESSED;
|
||||||
|
changed = new != listener->axis.last_negative;
|
||||||
|
listener->axis.last_negative = new;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(changed)
|
||||||
|
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) {
|
void input_notify_event(SDL_Event event) {
|
||||||
switch(event.type) {
|
switch(event.type) {
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
|
case SDL_KEYUP:
|
||||||
|
case SDL_KEYDOWN:
|
||||||
|
_handle_key_event(event);
|
||||||
|
return;
|
||||||
case SDL_MOUSEWHEEL:
|
case SDL_MOUSEWHEEL:
|
||||||
_scroll_delta = event.wheel.y;
|
_handle_scroll_event(event);
|
||||||
break;
|
return;
|
||||||
|
case SDL_MOUSEBUTTONUP:
|
||||||
|
case SDL_MOUSEBUTTONDOWN:
|
||||||
|
_handle_mousebutton_event(event);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,3 +196,7 @@ int input_get_mousedown(int mousebtn) {
|
||||||
uint32_t mask = SDL_BUTTON(mousebtn);
|
uint32_t mask = SDL_BUTTON(mousebtn);
|
||||||
return (SDL_GetMouseState(NULL, NULL) & mask) != 0;
|
return (SDL_GetMouseState(NULL, NULL) & mask) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void input_disconnect_all() {
|
||||||
|
g_key_listeners_endptr = g_key_listeners;
|
||||||
|
}
|
||||||
|
|
|
@ -7,56 +7,30 @@ extern "C" {
|
||||||
|
|
||||||
#include "SDL2/SDL.h"
|
#include "SDL2/SDL.h"
|
||||||
|
|
||||||
typedef void(*input_axis_delegate_t)(int value);
|
typedef void(*input_axis_fn)(int value);
|
||||||
typedef void(*input_mouse_delegate_t)(float dx, float dy);
|
typedef void(*input_mouse_fn)(float dx, float dy);
|
||||||
typedef void(*input_button_delegate_t)(int down);
|
typedef void(*input_button_fn)(int down);
|
||||||
typedef void(*input_scroll_delegate_t)(float delta);
|
typedef void(*input_scroll_fn)(float delta);
|
||||||
|
|
||||||
enum INPUT_LISTENER_TYPE_T {
|
|
||||||
INPUT_LISTENER_MOUSE,
|
|
||||||
INPUT_LISTENER_AXIS,
|
|
||||||
INPUT_LISTENER_SCROLL,
|
|
||||||
INPUT_LISTENER_BUTTON,
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct input_listener_t {
|
|
||||||
enum INPUT_LISTENER_TYPE_T type;
|
|
||||||
union {
|
|
||||||
struct {
|
|
||||||
input_axis_delegate_t delegate;
|
|
||||||
SDL_Scancode positive, negative;
|
|
||||||
int last;
|
|
||||||
} axis;
|
|
||||||
struct {
|
|
||||||
input_mouse_delegate_t delegate;
|
|
||||||
} mouse;
|
|
||||||
struct {
|
|
||||||
input_button_delegate_t delegate;
|
|
||||||
uint32_t button;
|
|
||||||
int last;
|
|
||||||
} button;
|
|
||||||
struct {
|
|
||||||
input_scroll_delegate_t delegate;
|
|
||||||
} scroll;
|
|
||||||
};
|
|
||||||
} input_listener_t;
|
|
||||||
|
|
||||||
extern const Uint8* g_key_states;
|
extern const Uint8* g_key_states;
|
||||||
|
|
||||||
extern void add_key_listener(SDL_Scancode negative, SDL_Scancode positive,
|
extern void add_key_listener(SDL_Scancode negative, SDL_Scancode positive,
|
||||||
input_axis_delegate_t delegate);
|
input_axis_fn delegate);
|
||||||
extern void add_mouse_listener(input_mouse_delegate_t delegate);
|
extern void add_mouse_listener(input_mouse_fn delegate);
|
||||||
extern void add_mouse_button_listener(uint32_t button, input_button_delegate_t delegate);
|
extern void add_mouse_button_listener(uint32_t button, input_button_fn delegate);
|
||||||
extern void add_scroll_listener(input_scroll_delegate_t delegate);
|
extern void add_scroll_listener(input_scroll_fn delegate);
|
||||||
extern void remove_listener_at(size_t index);
|
|
||||||
extern void mouse_screen_position(float* ox, float* oy);
|
extern void mouse_screen_position(float* ox, float* oy);
|
||||||
extern void mouse_world_position(float* ox, float* oy);
|
extern void mouse_world_position(float* ox, float* oy);
|
||||||
|
|
||||||
|
extern int input_get_keydown(SDL_Scancode scancode);
|
||||||
|
extern int input_get_mousedown(int mousebtn);
|
||||||
|
|
||||||
extern void input_init();
|
extern void input_init();
|
||||||
extern void update_input();
|
extern void update_input();
|
||||||
extern void input_notify_event(SDL_Event event);
|
extern void input_notify_event(SDL_Event event);
|
||||||
extern int input_get_keydown(SDL_Scancode scancode);
|
|
||||||
extern int input_get_mousedown(int mousebtn);
|
extern void input_disconnect_all();
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 */
|
|
@ -0,0 +1,15 @@
|
||||||
|
#include "object.h"
|
||||||
|
|
||||||
|
object_t object_default() {
|
||||||
|
return (object_t){
|
||||||
|
.active = 1,
|
||||||
|
.physics = physics_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,32 @@
|
||||||
|
#ifndef _object_h
|
||||||
|
#define _object_h
|
||||||
|
|
||||||
|
#include "render.h"
|
||||||
|
#include "physics.h"
|
||||||
|
|
||||||
|
typedef struct object_t object_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's events should be triggered by the game world update.
|
||||||
|
int wants_to_be_deleted;
|
||||||
|
physics_t physics; // 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);
|
||||||
|
static inline
|
||||||
|
int object_is_valid(const object_t* object) {
|
||||||
|
return object != NULL && object->wants_to_be_deleted == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* _object_h */
|
|
@ -0,0 +1,272 @@
|
||||||
|
#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));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
physics_t physics_default() {
|
||||||
|
return (physics_t) {
|
||||||
|
.type=COLLIDERTYPE_NONE,
|
||||||
|
.velocity_x = 0.f,
|
||||||
|
.velocity_y = 0.f,
|
||||||
|
.solver = &solve_collision_slide
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void object_broadcast_collision(object_t* this, object_t* other) {
|
||||||
|
if(this->physics.evt_collision != NULL) {
|
||||||
|
this->physics.evt_collision(this, other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
short can_collide(const object_t* this) {
|
||||||
|
return object_is_valid(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 > bminx)
|
||||||
|
||
|
||||||
|
(bminx < amaxx && bminx > aminx)
|
||||||
|
) && (
|
||||||
|
(aminy < bmaxy && aminy > bminy)
|
||||||
|
||
|
||||||
|
(bminy < amaxy && bminy > aminy)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
short _collision_aabb_aabb(const object_t* a, const object_t* b) {
|
||||||
|
const float aminx = a->physics.aabb.x + a->sprite.x,
|
||||||
|
aminy = a->physics.aabb.y + a->sprite.y;
|
||||||
|
const float amaxx = aminx + a->physics.aabb.w,
|
||||||
|
amaxy = aminy + a->physics.aabb.h;
|
||||||
|
|
||||||
|
const float bminx = b->physics.aabb.x + b->sprite.x,
|
||||||
|
bminy = b->physics.aabb.y + b->sprite.y;
|
||||||
|
const float bmaxx = bminx + b->physics.aabb.w,
|
||||||
|
bmaxy = bminy + b->physics.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->physics.circle.x,
|
||||||
|
ay = a->sprite.y + a->physics.circle.y,
|
||||||
|
bx = b->sprite.x + b->physics.circle.x,
|
||||||
|
by = b->sprite.y + b->physics.circle.y;
|
||||||
|
const float dx = fabsf(ax-bx), dy = fabsf(ay-by);
|
||||||
|
const float sqrdist = dx*dx+dy*dy;
|
||||||
|
const float mindist = a->physics.circle.radius + b->physics.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->physics.aabb.x + aabb->sprite.x, bbmaxx = bbminx + aabb->physics.aabb.w,
|
||||||
|
bbminy = aabb->physics.aabb.y + aabb->sprite.y, bbmaxy = bbminy + aabb->physics.aabb.h;
|
||||||
|
const float cx = circle->sprite.x + circle->physics.circle.x,
|
||||||
|
cy = circle->sprite.y + circle->physics.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->physics.circle.radius*circle->physics.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->physics.type == COLLIDERTYPE_AABB && b->physics.type == COLLIDERTYPE_AABB) {
|
||||||
|
return _collision_aabb_aabb(a, b);
|
||||||
|
} else if(a->physics.type == COLLIDERTYPE_CIRCLE && b->physics.type == COLLIDERTYPE_CIRCLE) {
|
||||||
|
return _collision_circle_circle(a, b);
|
||||||
|
} else if(a->physics.type == COLLIDERTYPE_CIRCLE && b->physics.type == COLLIDERTYPE_AABB) {
|
||||||
|
return _collision_circle_aabb(a, b);
|
||||||
|
} else if(a->physics.type == COLLIDERTYPE_AABB && b->physics.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->physics.aabb.x + aabb->sprite.x, bbmaxx = bbminx + aabb->physics.aabb.w,
|
||||||
|
bbminy = aabb->physics.aabb.y + aabb->sprite.y, bbmaxy = bbminy + aabb->physics.aabb.h;
|
||||||
|
// the centre of the circle in world space
|
||||||
|
const float cx = circle->sprite.x + circle->physics.circle.x,
|
||||||
|
cy = circle->sprite.y + circle->physics.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->physics.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->physics.circle.x + a->sprite.x, y1 = a->physics.circle.y + a->sprite.y;
|
||||||
|
const float x2 = b->physics.circle.x + b->sprite.x, y2 = b->physics.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->physics.circle.radius + b->physics.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 aminx = a->physics.aabb.x + a->sprite.x;
|
||||||
|
float amaxx = aminx + a->physics.aabb.w;
|
||||||
|
float bminx = b->physics.aabb.x + b->sprite.x;
|
||||||
|
float bmaxx = bminx + b->physics.aabb.w;
|
||||||
|
|
||||||
|
float aminy = a->physics.aabb.y + a->sprite.y;
|
||||||
|
float amaxy = aminy + a->physics.aabb.h;
|
||||||
|
float bminy = b->physics.aabb.y + b->sprite.y;
|
||||||
|
float bmaxy = bminy + b->physics.aabb.h;
|
||||||
|
|
||||||
|
float right = bmaxx - aminx;
|
||||||
|
float left = bminx - amaxx;
|
||||||
|
float top = bminy - amaxy;
|
||||||
|
float bottom = bmaxy - aminy;
|
||||||
|
|
||||||
|
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->physics.type == COLLIDERTYPE_AABB && b->physics.type == COLLIDERTYPE_AABB) {
|
||||||
|
return _solve_aabb_aabb(a, b, out_px, out_py);
|
||||||
|
} else if(a->physics.type == COLLIDERTYPE_AABB && b->physics.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->physics.type == COLLIDERTYPE_CIRCLE && b->physics.type == COLLIDERTYPE_AABB) {
|
||||||
|
return _solve_circle_aabb(a, b, out_px, out_py);
|
||||||
|
} else if(a->physics.type == COLLIDERTYPE_CIRCLE && b->physics.type == COLLIDERTYPE_CIRCLE) {
|
||||||
|
return _solve_circle_circle(a, b, out_px, out_py);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void solve_collision_slide(object_t* left, object_t* right) {
|
||||||
|
float dx, dy;
|
||||||
|
get_solve_force(left, right, &dx, &dy);
|
||||||
|
left->sprite.x += dx;
|
||||||
|
left->sprite.y += dy;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
void _solve_move(object_t* this) {
|
||||||
|
// 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 = world_get_object(i);
|
||||||
|
// check collision, return if found
|
||||||
|
if(can_collide(other) && this != other && _collision_check(other, this)) {
|
||||||
|
object_broadcast_collision(other, this);
|
||||||
|
object_broadcast_collision(this, other);
|
||||||
|
this->physics.solver(this, other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void physics_move(object_t* this, float delta_time) {
|
||||||
|
const float max_step_size = this->physics.max_interpolate_step_size;
|
||||||
|
// calculate step delta
|
||||||
|
float dx = this->physics.velocity_x * delta_time, dy = this->physics.velocity_y * delta_time;
|
||||||
|
const float target_x = this->sprite.x + dx,
|
||||||
|
target_y = this->sprite.y + dy;
|
||||||
|
if(dx == 0 && dy == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// calculate direction x,y
|
||||||
|
float m = sqrtf(dx*dx + dy*dy);
|
||||||
|
dx = dx / m * max_step_size;
|
||||||
|
dy = dy / m * max_step_size;
|
||||||
|
|
||||||
|
const 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(this)) {
|
||||||
|
this->sprite.x = target_x;
|
||||||
|
this->sprite.y = target_y;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(step_count == 0) {
|
||||||
|
this->sprite.x = target_x;
|
||||||
|
this->sprite.y = target_y;
|
||||||
|
_solve_move(this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 1. move towards target
|
||||||
|
* 2. check collision with every other object
|
||||||
|
*/
|
||||||
|
for(int steps = 0; steps <= step_count && (this->sprite.x != target_x || this->sprite.y != target_y); ++steps) {
|
||||||
|
// move towards target, snap to target if distance is too low
|
||||||
|
const float distx = fabsf(this->sprite.x - target_x), disty = fabsf(this->sprite.y - target_y);
|
||||||
|
const float sqdist = distx*distx + disty*disty;
|
||||||
|
if(sqdist > max_step_size*max_step_size) {
|
||||||
|
this->sprite.x += dx;
|
||||||
|
this->sprite.y += dy;
|
||||||
|
} else {
|
||||||
|
this->sprite.x = target_x;
|
||||||
|
this->sprite.y = target_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
_solve_move(this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
#ifndef _physics_h
|
||||||
|
#define _physics_h
|
||||||
|
|
||||||
|
#include "SDL2/SDL_rect.h"
|
||||||
|
|
||||||
|
typedef struct object_t object_t;
|
||||||
|
|
||||||
|
typedef void(*collided_fn)(object_t*, object_t*);
|
||||||
|
typedef void(*solver_fn)(object_t* left, object_t* right);
|
||||||
|
|
||||||
|
typedef enum collider_type_t {
|
||||||
|
COLLIDERTYPE_MIN,
|
||||||
|
COLLIDERTYPE_NONE,
|
||||||
|
COLLIDERTYPE_CIRCLE,
|
||||||
|
COLLIDERTYPE_AABB,
|
||||||
|
COLLIDERTYPE_MAX,
|
||||||
|
} collider_type_t;
|
||||||
|
|
||||||
|
typedef struct circle_t {
|
||||||
|
float x, y;
|
||||||
|
float radius;
|
||||||
|
} circle_t;
|
||||||
|
|
||||||
|
typedef struct physics_t {
|
||||||
|
collider_type_t type;
|
||||||
|
collided_fn evt_collision;
|
||||||
|
solver_fn solver;
|
||||||
|
float velocity_x, velocity_y;
|
||||||
|
float max_interpolate_step_size;
|
||||||
|
union {
|
||||||
|
circle_t circle;
|
||||||
|
SDL_FRect aabb;
|
||||||
|
};
|
||||||
|
} physics_t;
|
||||||
|
|
||||||
|
extern physics_t physics_default();
|
||||||
|
extern void object_broadcast_evt_collision(object_t* this, object_t* other);
|
||||||
|
extern void physics_move(object_t* this, float delta_time);
|
||||||
|
extern short can_collide(const object_t* this);
|
||||||
|
extern void solve_collision_slide(object_t* left, object_t* right);
|
||||||
|
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);
|
||||||
|
@ -443,7 +456,7 @@ sprite_t make_sprite(const char* file, float x, float y) {
|
||||||
sprite_t sprite=(sprite_t){
|
sprite_t sprite=(sprite_t){
|
||||||
.texture=get_texture(file),
|
.texture=get_texture(file),
|
||||||
.x=x,.y=y,
|
.x=x,.y=y,
|
||||||
.origin=(SDL_FPoint){.x=0,.y=0},
|
.origin=(SDL_FPoint){.x=0.0,.y=0.0},
|
||||||
.sx=1.0,.sy=1.0,
|
.sx=1.0,.sy=1.0,
|
||||||
.rot=0,
|
.rot=0,
|
||||||
.depth=RLAYER_SPRITES,
|
.depth=RLAYER_SPRITES,
|
||||||
|
@ -451,7 +464,6 @@ sprite_t make_sprite(const char* file, float x, float y) {
|
||||||
.flip=SDL_FLIP_NONE,
|
.flip=SDL_FLIP_NONE,
|
||||||
};
|
};
|
||||||
SDL_QueryTexture(sprite.texture, NULL, NULL, &sprite.uv.w, &sprite.uv.h);
|
SDL_QueryTexture(sprite.texture, NULL, NULL, &sprite.uv.w, &sprite.uv.h);
|
||||||
sprite.origin.x = -(float)sprite.uv.h/2.f; sprite.origin.y = -(float)sprite.uv.h/2.f;
|
|
||||||
return sprite;
|
return sprite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
@ -102,7 +104,6 @@ extern sprite_t make_sprite(const char* file, float x, float y);
|
||||||
extern sprite_t sprite_from_spritesheet(spritesheet_t* sheet, int index);
|
extern sprite_t sprite_from_spritesheet(spritesheet_t* sheet, int index);
|
||||||
extern text_style_t make_text_style(const char* font, SDL_Color color, int dpi, float size);
|
extern text_style_t make_text_style(const char* font, SDL_Color color, int dpi, float size);
|
||||||
extern SDL_Rect get_srcrect_from(spritesheet_t* sheet, int index);
|
extern SDL_Rect get_srcrect_from(spritesheet_t* sheet, int index);
|
||||||
extern void set_active_view(const view_t* view);
|
|
||||||
|
|
||||||
#define no_sprite (sprite_t){NULL, 0.0, 0.0, {0.0, 0.0}, 0.0, 0.0, 0.0, 0, {0, 0, 0, 0}}
|
#define no_sprite (sprite_t){NULL, 0.0, 0.0, {0.0, 0.0}, 0.0, 0.0, 0.0, 0, {0, 0, 0, 0}}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,220 @@
|
||||||
|
#include "scene.h"
|
||||||
|
|
||||||
|
#include "ctype.h"
|
||||||
|
#include "input.h"
|
||||||
|
#include "stdint.h"
|
||||||
|
#include "stddef.h"
|
||||||
|
#include "stdlib.h"
|
||||||
|
#include "string.h"
|
||||||
|
#include "stdio.h"
|
||||||
|
#include "hash.h"
|
||||||
|
#include "malloc.h"
|
||||||
|
#include "math.h"
|
||||||
|
#include "world.h"
|
||||||
|
|
||||||
|
static
|
||||||
|
struct type_handler_t {
|
||||||
|
uintptr_t hash;
|
||||||
|
char* type;
|
||||||
|
type_handler_fn handler;
|
||||||
|
} _type_handlers[99];
|
||||||
|
|
||||||
|
static
|
||||||
|
int _type_handler_num = 0;
|
||||||
|
|
||||||
|
static
|
||||||
|
struct type_handler_t* _find_handler_for(const char* type) {
|
||||||
|
uintptr_t hash = hashstr(type);
|
||||||
|
for(int i = 0; i < 99; ++i) {
|
||||||
|
if(_type_handlers[i].hash == hash && strcmp(type, _type_handlers[i].type) == 0) {
|
||||||
|
return _type_handlers + i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
struct type_handler_t* _new_handler_for(const char* type) {
|
||||||
|
_type_handlers[_type_handler_num].type = malloc(strlen(type) * sizeof(char));
|
||||||
|
strcpy(_type_handlers[_type_handler_num].type, type);
|
||||||
|
_type_handlers[_type_handler_num].hash = hashstr(type);
|
||||||
|
struct type_handler_t* ptr = _type_handlers + _type_handler_num;
|
||||||
|
++_type_handler_num;
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_type_handler(const char* type, type_handler_fn handler) {
|
||||||
|
struct type_handler_t* ptr = _find_handler_for(type);
|
||||||
|
if(ptr == NULL) {
|
||||||
|
ptr = _new_handler_for(type);
|
||||||
|
}
|
||||||
|
ptr->handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
int fpeekc(FILE* file) {
|
||||||
|
int c = fgetc(file);
|
||||||
|
ungetc(c, file);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void freadto(FILE* file, char c) {
|
||||||
|
char read;
|
||||||
|
do {
|
||||||
|
read = fgetc(file);
|
||||||
|
} while(read != c);
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
int nextnw(FILE* file) {
|
||||||
|
int next;
|
||||||
|
char c;
|
||||||
|
do {
|
||||||
|
next = fgetc(file);
|
||||||
|
c = (char)next;
|
||||||
|
} while(isspace(c));
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
int _parse_key(FILE* file, char* out) {
|
||||||
|
char c;
|
||||||
|
do {
|
||||||
|
c = fgetc(file);
|
||||||
|
if(c == ':' || c == ';') {
|
||||||
|
*out = '\0';
|
||||||
|
} else if(c == '#') {
|
||||||
|
freadto(file, '\n');
|
||||||
|
} else if(!isspace(c)) {
|
||||||
|
*out = c;
|
||||||
|
++out;
|
||||||
|
}
|
||||||
|
} while(c != ':' && c != ';');
|
||||||
|
|
||||||
|
return c == ':';
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void _parse_value(FILE* file, char* out, int* _argc, char** argv) {
|
||||||
|
char c;
|
||||||
|
int argc = 0;
|
||||||
|
argv[argc] = out;
|
||||||
|
++argc;
|
||||||
|
do {
|
||||||
|
c = fgetc(file);
|
||||||
|
switch(c) {
|
||||||
|
case '#':
|
||||||
|
freadto(file, '\n');
|
||||||
|
// fall through
|
||||||
|
case '\n':
|
||||||
|
ungetc(nextnw(file), file);
|
||||||
|
break;
|
||||||
|
case ';':
|
||||||
|
*out = '\0';
|
||||||
|
break;
|
||||||
|
case ',':
|
||||||
|
*out = '\0';
|
||||||
|
++out;
|
||||||
|
argv[argc] = out;
|
||||||
|
++argc;
|
||||||
|
ungetc(nextnw(file), file);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
*out = c;
|
||||||
|
++out;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while(c != ';');
|
||||||
|
*_argc = argc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void _parse_config(FILE* file) {
|
||||||
|
char key[24];
|
||||||
|
char value[128];
|
||||||
|
int argc = 0;
|
||||||
|
char* argv[24];
|
||||||
|
|
||||||
|
char begin = nextnw(file);
|
||||||
|
ungetc(begin, file);
|
||||||
|
|
||||||
|
int has_args = _parse_key(file, key);
|
||||||
|
ungetc(nextnw(file), file);
|
||||||
|
if(has_args) {
|
||||||
|
_parse_value(file, value, &argc, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct type_handler_t* handler = _find_handler_for(key);
|
||||||
|
|
||||||
|
if(handler != NULL) {
|
||||||
|
if(begin == '!') {
|
||||||
|
handler->handler(NULL, argc, argv);
|
||||||
|
} else {
|
||||||
|
handler->handler(make_object(), argc, argv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(fpeekc(file) == EOF) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void _parse_scene(FILE* file) {
|
||||||
|
int next;
|
||||||
|
do {
|
||||||
|
_parse_config(file);
|
||||||
|
next = nextnw(file);
|
||||||
|
ungetc(next, file);
|
||||||
|
} while(next != EOF);
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
int _validate_config(FILE* file) {
|
||||||
|
char c;
|
||||||
|
int colon_count = 0;
|
||||||
|
do {
|
||||||
|
c = nextnw(file);
|
||||||
|
switch(c) {
|
||||||
|
default: break;
|
||||||
|
case ':':
|
||||||
|
colon_count++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while(c != ';');
|
||||||
|
|
||||||
|
return colon_count <= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
int _validate_scene(FILE* file) {
|
||||||
|
if(file == NULL) return 0;
|
||||||
|
|
||||||
|
int next;
|
||||||
|
int validated;
|
||||||
|
do {
|
||||||
|
validated = _validate_config(file);
|
||||||
|
next = nextnw(file);
|
||||||
|
ungetc(next, file);
|
||||||
|
} while(validated == 1 && next != EOF);
|
||||||
|
|
||||||
|
rewind(file);
|
||||||
|
|
||||||
|
return validated;
|
||||||
|
}
|
||||||
|
|
||||||
|
void load_scene(const char* filename) {
|
||||||
|
FILE* file = fopen(filename, "r");
|
||||||
|
if(_validate_scene(file)) {
|
||||||
|
input_disconnect_all();
|
||||||
|
world_clear();
|
||||||
|
_parse_scene(file);
|
||||||
|
fclose(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void load_scene_additive(const char* filename) {
|
||||||
|
FILE* file = fopen(filename, "r");
|
||||||
|
if(_validate_scene(file)) {
|
||||||
|
_parse_scene(file);
|
||||||
|
fclose(file);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
#ifndef _corelib_scene_h
|
||||||
|
#define _corelib_scene_h
|
||||||
|
|
||||||
|
#include "memory.h"
|
||||||
|
|
||||||
|
typedef struct object_t object_t;
|
||||||
|
|
||||||
|
typedef void(*type_handler_fn)(object_t* this, int argc, char** argv);
|
||||||
|
|
||||||
|
// Add or replace the type handler for [Object] entries of a given key.
|
||||||
|
// Will delete the handler entry if handler == NULL.
|
||||||
|
void set_type_handler(const char* type, type_handler_fn handler);
|
||||||
|
|
||||||
|
// Load a scene exclusively. Clears the current scene before parsing file.
|
||||||
|
void load_scene(const char* file);
|
||||||
|
|
||||||
|
// Load a scene additively.
|
||||||
|
// Will not clear the currently active objects before parsing file.
|
||||||
|
void load_scene_additive(const char* file);
|
||||||
|
|
||||||
|
#endif /* _corelib_scene_h */
|
||||||
|
|
|
@ -1,206 +1,122 @@
|
||||||
#include "world.h"
|
#include "world.h"
|
||||||
|
#include "memory.h"
|
||||||
|
#include "assert.h"
|
||||||
|
#include "math/vec.h"
|
||||||
|
#include "object.h"
|
||||||
|
|
||||||
object_t g_objects[WORLD_NUM_OBJECTS];
|
struct {
|
||||||
|
size_t num;
|
||||||
|
object_t** objects;
|
||||||
|
} _world_objects = {
|
||||||
|
.num = 0,
|
||||||
|
.objects = NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline
|
||||||
|
int _expand_world() {
|
||||||
|
size_t new_num = _world_objects.num * 2;
|
||||||
|
if(new_num == 0) {
|
||||||
|
new_num = 16;
|
||||||
|
}
|
||||||
|
object_t** new_list = realloc(_world_objects.objects, new_num * sizeof(object_t*));
|
||||||
|
|
||||||
|
if(new_list == NULL) {
|
||||||
|
assert(!"ERROR: Out of memory");
|
||||||
|
exit(-10);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(size_t i = _world_objects.num; i < new_num; ++i) {
|
||||||
|
new_list[i] = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
_world_objects.objects = new_list;
|
||||||
|
_world_objects.num = new_num;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
size_t _find_free_object() {
|
||||||
|
for(int i = 0; i < _world_objects.num; ++i) {
|
||||||
|
if(!object_is_valid(_world_objects.objects[i])) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size_t num = _world_objects.num;
|
||||||
|
_expand_world();
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
void world_clear() {
|
void world_clear() {
|
||||||
for(int i = 0; i < WORLD_NUM_OBJECTS; ++i) {
|
// free all allocated objects
|
||||||
g_objects[i].active = 0;
|
for(int i = 0; i < _world_objects.num; ++i) {
|
||||||
g_objects[i].enabled = 0;
|
object_t* object = _world_objects.objects[i];
|
||||||
}
|
if(object != NULL) {
|
||||||
}
|
free(object);
|
||||||
|
|
||||||
void object_draw_sprite(object_t* object) {
|
|
||||||
draw_sprite(&object->sprite);
|
|
||||||
}
|
|
||||||
|
|
||||||
object_t* _find_free_object() {
|
|
||||||
for(int i = 0; i < WORLD_NUM_OBJECTS; ++i) {
|
|
||||||
if(g_objects[i].active == 0) {
|
|
||||||
return g_objects + i;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return NULL;
|
// free world array
|
||||||
|
free(_world_objects.objects);
|
||||||
|
// reset world objects struct
|
||||||
|
_world_objects.objects = NULL;
|
||||||
|
_world_objects.num = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
object_t* make_object() {
|
object_t* make_object() {
|
||||||
object_t* o = _find_free_object();
|
// acquire pointer to empty slot
|
||||||
o->active = 1;
|
size_t index = _find_free_object();
|
||||||
o->enabled = 1;
|
// allocate new object
|
||||||
o->collider = collider_default();
|
if(_world_objects.objects[index] == NULL) {
|
||||||
o->evt_draw = &object_draw_sprite;
|
_world_objects.objects[index] = malloc(sizeof(object_t));
|
||||||
o->evt_update = NULL;
|
}
|
||||||
memset(&o->sprite, 0, sizeof(sprite_t));
|
// initialize object to default
|
||||||
|
object_t* o = _world_objects.objects[index];
|
||||||
|
*o = object_default();
|
||||||
|
|
||||||
return o;
|
return o;
|
||||||
}
|
}
|
||||||
|
|
||||||
object_t* instantiate_object(const object_t *original) {
|
object_t* instantiate_object(const object_t *original) {
|
||||||
|
// create new object with default settings
|
||||||
object_t* obj = make_object();
|
object_t* obj = make_object();
|
||||||
*obj = *original;
|
*obj = *original;
|
||||||
obj->active = 1;
|
obj->active = 1;
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
collider_t collider_default() {
|
void world_update() {
|
||||||
return (collider_t) {
|
for(int i = 0; i < _world_objects.num; ++i) {
|
||||||
.type=COLLIDERTYPE_NONE
|
object_t* object = world_get_object(i);
|
||||||
};
|
if(object_is_valid(object)
|
||||||
}
|
&& object->evt_update != NULL) {
|
||||||
|
object->evt_update(object);
|
||||||
void update_objects() {
|
|
||||||
for(int i = 0; i < WORLD_NUM_OBJECTS; ++i) {
|
|
||||||
if(g_objects[i].active == 1
|
|
||||||
&& g_objects[i].enabled == 1
|
|
||||||
&& g_objects[i].evt_update != NULL) {
|
|
||||||
g_objects[i].evt_update(g_objects + i);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw_objects() {
|
void world_draw() {
|
||||||
for(int i = 0; i < WORLD_NUM_OBJECTS; ++i) {
|
for(int i = 0; i < _world_objects.num; ++i) {
|
||||||
if(g_objects[i].active == 1
|
object_t* object = world_get_object(i);
|
||||||
&& g_objects[i].enabled == 1
|
if(object_is_valid(object)
|
||||||
&& g_objects[i].evt_draw != NULL) {
|
&& object->evt_draw != NULL) {
|
||||||
g_objects[i].evt_draw(g_objects + i);
|
object->evt_draw(object);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline
|
object_t* world_get_object(size_t at) {
|
||||||
int _rect_overlap(float aminx, float aminy, float amaxx, float amaxy, float bminx, float bminy, float bmaxx, float bmaxy) {
|
if(at < _world_objects.num) {
|
||||||
return
|
return _world_objects.objects[at];
|
||||||
(
|
|
||||||
(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
|
|
||||||
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.x, 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 && object->collider.evt_collision != NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
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)) {
|
|
||||||
oobject->collider.evt_collision(oobject, iobject);
|
|
||||||
iobject->collider.evt_collision(iobject, oobject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object_t* interpolate_move(object_t* object, float target_x, float target_y, float max_step_size) {
|
|
||||||
// calculate step delta
|
|
||||||
float dx = target_x - object->sprite.x, dy = target_y - object->sprite.y;
|
|
||||||
// calculate direction x,y
|
|
||||||
float m = sqrtf(dx*dx + dy*dy);
|
|
||||||
dx /= m; dy /= m;
|
|
||||||
dx *= max_step_size; dy *= max_step_size;
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 1. check collision with every other object
|
|
||||||
* 2. move towards target
|
|
||||||
*/
|
|
||||||
while(object->sprite.x != target_x || object->sprite.y != target_y) {
|
|
||||||
for(int i = 0; i < WORLD_NUM_OBJECTS; ++i) {
|
|
||||||
object_t* other = g_objects + i;
|
|
||||||
if(!_can_collide(other)) continue;
|
|
||||||
if(object != other && _collision_check(other, object)) {
|
|
||||||
other->collider.evt_collision(other, object);
|
|
||||||
object->collider.evt_collision(object, other);
|
|
||||||
return other;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// move towards target, snap to target if distance is too low
|
|
||||||
float distx = fabsf(object->sprite.x - target_x), disty = fabsf(object->sprite.y - target_y);
|
|
||||||
if(distx < fabsf(dx) && disty < fabsf(dy)) {
|
|
||||||
object->sprite.x += dx; object->sprite.y += dy;
|
|
||||||
} else {
|
} else {
|
||||||
object->sprite.x = target_x;
|
|
||||||
object->sprite.y = target_y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// no collision, return nothing
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t world_num_objects() {
|
||||||
|
return _world_objects.num;
|
||||||
|
}
|
||||||
|
|
||||||
|
void world_reserve_objects(size_t min) {
|
||||||
|
while(_world_objects.num < min) {
|
||||||
|
_expand_world();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,66 +1,23 @@
|
||||||
#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 BASE_WORLD_SIZE 24
|
||||||
|
|
||||||
typedef struct object_t object_t;
|
typedef struct object_t object_t;
|
||||||
|
|
||||||
typedef void(*tick_fn)(struct object_t*);
|
extern object_t* make_object();
|
||||||
typedef void(*draw_fn)(struct object_t*);
|
extern object_t* instantiate_object(const object_t* original);
|
||||||
typedef void(*collided_fn)(struct object_t*, struct object_t*);
|
|
||||||
|
|
||||||
typedef enum collider_type_t {
|
extern void world_clear();
|
||||||
COLLIDERTYPE_MIN,
|
|
||||||
COLLIDERTYPE_NONE,
|
|
||||||
COLLIDERTYPE_CIRCLE,
|
|
||||||
COLLIDERTYPE_AABB,
|
|
||||||
COLLIDERTYPE_MAX,
|
|
||||||
} collider_type_t;
|
|
||||||
|
|
||||||
typedef struct collider_t {
|
extern void world_update();
|
||||||
collider_type_t type;
|
extern void world_draw();
|
||||||
collided_fn evt_collision;
|
|
||||||
union {
|
|
||||||
struct {
|
|
||||||
float x, y;
|
|
||||||
float radius;
|
|
||||||
} circle;
|
|
||||||
SDL_FRect aabb;
|
|
||||||
};
|
|
||||||
} collider_t;
|
|
||||||
|
|
||||||
struct object_t {
|
extern object_t* world_get_object(size_t at);
|
||||||
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
|
extern size_t world_num_objects();
|
||||||
|
extern void world_reserve_objects(size_t min);
|
||||||
tick_fn evt_update;
|
|
||||||
draw_fn evt_draw;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern object_t g_objects[WORLD_NUM_OBJECTS];
|
|
||||||
|
|
||||||
void world_clear();
|
|
||||||
|
|
||||||
|
|
||||||
object_t* make_object();
|
|
||||||
object_t* instantiate_object(const object_t* original);
|
|
||||||
|
|
||||||
collider_t collider_default();
|
|
||||||
|
|
||||||
void object_draw_sprite(object_t* object);
|
|
||||||
|
|
||||||
void update_objects();
|
|
||||||
void draw_objects();
|
|
||||||
|
|
||||||
void update_collision();
|
|
||||||
|
|
||||||
object_t* interpolate_move(object_t* object, float target_x, float target_y, float max_step_size);
|
|
||||||
|
|
||||||
#endif /* _world_h */
|
#endif /* _world_h */
|
||||||
|
|
81
src/engine.c
81
src/engine.c
|
@ -1,81 +0,0 @@
|
||||||
#include "engine.h"
|
|
||||||
#include "world.h"
|
|
||||||
#include "corelib/assets.h"
|
|
||||||
#include "corelib/render.h"
|
|
||||||
#include "corelib/input.h"
|
|
||||||
#include "time.h"
|
|
||||||
|
|
||||||
static float _delta_time = 0;
|
|
||||||
static struct timespec start_last_frame;
|
|
||||||
|
|
||||||
inline float delta_time() {
|
|
||||||
return _delta_time;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline
|
|
||||||
int _engine_start() {
|
|
||||||
init_context();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline
|
|
||||||
int _engine_shutdown() {
|
|
||||||
game_exit();
|
|
||||||
clean_assets();
|
|
||||||
close_context();
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline
|
|
||||||
void _handle_events() {
|
|
||||||
while(SDL_PollEvent(&g_context.event)) {
|
|
||||||
input_notify_event(g_context.event);
|
|
||||||
switch(g_context.event.type) {
|
|
||||||
case SDL_QUIT:
|
|
||||||
g_context.running = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline
|
|
||||||
int _engine_run() {
|
|
||||||
SDL_DisplayMode mode;
|
|
||||||
SDL_GetDesktopDisplayMode(0, &mode);
|
|
||||||
SDL_Window* window = SDL_CreateWindow("Tabletop", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, mode.w, mode.h, SDL_WINDOW_FULLSCREEN_DESKTOP);
|
|
||||||
|
|
||||||
g_context = (context_t){
|
|
||||||
.window = window,
|
|
||||||
.renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED),
|
|
||||||
.running = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
input_init();
|
|
||||||
world_clear();
|
|
||||||
start_game();
|
|
||||||
|
|
||||||
timespec_get(&start_last_frame, TIME_UTC);
|
|
||||||
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;
|
|
||||||
start_last_frame = next_time;
|
|
||||||
_handle_events();
|
|
||||||
update_input();
|
|
||||||
_render_mode = 1;
|
|
||||||
update_ui();
|
|
||||||
_render_mode = 0;
|
|
||||||
update_game();
|
|
||||||
update_objects(); // update world objects
|
|
||||||
draw_objects(); // draw world objects
|
|
||||||
swap_buffer();
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
|
||||||
_engine_start();
|
|
||||||
_engine_run();
|
|
||||||
_engine_shutdown();
|
|
||||||
}
|
|
37
src/game.c
37
src/game.c
|
@ -1,41 +1,12 @@
|
||||||
#include "engine.h"
|
#include "corelib/entry.h"
|
||||||
#include "world.h"
|
#include "corelib/world.h"
|
||||||
#include "corelib/input.h"
|
#include "corelib/input.h"
|
||||||
#include "corelib/render.h"
|
#include "corelib/render.h"
|
||||||
#include "corelib/assets.h"
|
#include "corelib/assets.h"
|
||||||
#include "corelib/layers.h"
|
#include "corelib/layers.h"
|
||||||
#include "SDL2/SDL_mouse.h"
|
|
||||||
#include "SDL2/SDL_scancode.h"
|
|
||||||
|
|
||||||
void on_quit_key(int down) {
|
|
||||||
// Q is pressed or released
|
|
||||||
if(down) {
|
|
||||||
// stop running when Q is pressed
|
|
||||||
g_context.running = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void on_horizontal(int axis) {
|
|
||||||
// when A or D is pressed or released
|
|
||||||
}
|
|
||||||
|
|
||||||
void on_vertical(int axis) {
|
|
||||||
// W or S is pressed or released
|
|
||||||
}
|
|
||||||
|
|
||||||
void on_click(int down) {
|
|
||||||
// when the left mouse button is pressed down
|
|
||||||
// float mouse_x, mouse_y;
|
|
||||||
// mouse_world_position(&mouse_x, &mouse_y);
|
|
||||||
}
|
|
||||||
|
|
||||||
void start_game() {
|
void start_game() {
|
||||||
// called when the game is done initializing the backend and ready to start
|
// called when the game is done initializing the backend and ready to start
|
||||||
g_active_view.width = 21;
|
|
||||||
add_key_listener(SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_Q, &on_quit_key);
|
|
||||||
add_key_listener(SDL_SCANCODE_S, SDL_SCANCODE_W, &on_horizontal);
|
|
||||||
add_key_listener(SDL_SCANCODE_A, SDL_SCANCODE_D, &on_vertical);
|
|
||||||
add_mouse_button_listener(SDL_BUTTON_LMASK, &on_click);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void update_game() {
|
void update_game() {
|
||||||
|
@ -48,4 +19,6 @@ void update_ui() {
|
||||||
// render calls made in this function will be in *screen* space
|
// render calls made in this function will be in *screen* space
|
||||||
}
|
}
|
||||||
|
|
||||||
void game_exit() {}
|
void game_exit() {
|
||||||
|
// called when the game shuts down
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue