Compare commits

...

105 Commits

Author SHA1 Message Date
Sara dd0e0d866d added do {..}while(0) to a macro 2023-09-16 20:01:32 +02:00
Sara c7d5706eaa Merge pull request 'fixed indenting style to fit new requirements' (#20) from fixed-indenting into development
Reviewed-on: #20
2023-07-17 14:48:54 +00:00
Sara 092adc18aa Merge branch 'development' into fixed-indenting 2023-07-17 14:48:49 +00:00
Sara a741f3c473 Merge pull request 'Don't require a ':' character after every key in scene files' (#19) from dont-require-colon-in-scene into development
Reviewed-on: #19
2023-07-17 14:48:07 +00:00
Sara bbfedc727a Merge branch 'development' into dont-require-colon-in-scene 2023-07-17 14:48:00 +00:00
Sara 8915e57f9d fixed indenting style to fit new requirements
now using expanded (space) 4-wide tabs
2023-07-17 16:44:10 +02:00
Sara 56956f9330 _parse_key will now return 1 if the key DOES have arguments 2023-07-17 00:43:50 +02:00
Sara 3dd5b82cb0 parse_key now detects if the key has arguments or not, parse config will not parse nonexistent values 2023-07-17 00:41:15 +02:00
Sara 1ae19b5d04 adjusted _parse_key to also allow for configs without arguments 2023-07-17 00:38:49 +02:00
Sara baf75c9c78 no longer requiring at least one colon in a scene config element
This now allows for `player;` instead of requiring `player:;`
2023-07-17 00:36:37 +02:00
Sara b025e81c54 Merge pull request 'key listeners are now only triggered if their value changed' (#18) from key-repeat-events into development
Reviewed-on: #18
2023-07-16 21:41:21 +00:00
Sara 3f5346b0e8 Merge branch 'development' into key-repeat-events 2023-07-16 21:41:15 +00:00
Sara 03a6b65603 Merge pull request 'fixed aabb aabb collision detection and solving' (#17) from broken-physics into development
Reviewed-on: #17
2023-07-16 21:40:44 +00:00
Sara 5ee5292e88 key listeners are now only triggered if their value changed 2023-07-16 23:38:36 +02:00
Sara 41f84bb52c reversed direction of aabb-aabb collision solver terms 2023-07-16 21:32:33 +02:00
Sara 5185f5062b solve_aabb_aabb now incorporates sprite position in all calculations 2023-07-16 21:23:59 +02:00
Sara 7950e45632 reordered terms in _rect_overlap 2023-07-16 21:14:44 +02:00
Sara 0dc35bd9d9 fixed typos and mismatched statements in aabb-aabb collision 2023-07-16 20:53:41 +02:00
Sara 763c43aed6 removed leftover debug log in scene 2023-07-16 18:58:30 +02:00
Sara 415a0d5c97 removed input_disconnect_all from load_scene_additive 2023-07-16 17:42:17 +02:00
Sara bd2e5cbcd0 loading a scene now disconnects all input 2023-07-16 17:40:55 +02:00
Sara 25feb8f95f added input_disconnect_all 2023-07-16 17:40:15 +02:00
Sara deeaad06e1 Merge pull request 'world-format' (#14) from world-format into main
Reviewed-on: #14
2023-07-16 11:56:37 +00:00
Sara 5b91584b00 Merge branch 'main' into world-format 2023-07-16 11:55:24 +00:00
Sara b387dee9c6 first whitespace of a line is now skipped when processing arguments 2023-07-16 13:46:53 +02:00
Sara 19401f1f47 added scene file validation 2023-07-16 13:33:47 +02:00
Sara 25c5f83fe4 added scene parsing 2023-07-16 02:23:15 +02:00
Sara 351c9ba713 removed undefined declaration of physics_update 2023-07-16 00:38:44 +02:00
Sara f9236e4182 moved definitions of INPUT_LISTENER_TYPE_T and input_listener_t to input.c 2023-07-16 00:38:36 +02:00
Sara f0d76713c2 renamed function delegates ending with _t to end with _fn 2023-07-16 00:38:06 +02:00
Sara 6085dbaed0 added scene.h 2023-07-16 00:31:20 +02:00
Sara 33c1a5e9d2 changed the way objects are notified of destruction, simplified game.c preset 2023-07-16 00:31:13 +02:00
Sara 14a81b0f2a removed preset initialization functions 2023-07-15 20:15:25 +02:00
Sara 30defc0db8 Merge pull request 'Allow the world to grow as required by the number of objects being spawned' (#12) from dynamic-size-world into main
Reviewed-on: #12
2023-07-12 00:53:04 +00:00
Sara ec50000130 un-negated uses of object_is_valid that shouldn't be, and negated ones that should be 2023-07-12 02:52:04 +02:00
Sara 0698121853 object_is_valid now confirms that an object is not null and is active 2023-07-12 02:49:36 +02:00
Sara 8a6a0e7493 physics.c: can_collide now checks for object_is_valid before comfirming other variables 2023-07-12 02:47:31 +02:00
Sara 78c0e3a9c0 object_is_valid now takes a constant pointer 2023-07-12 02:46:26 +02:00
Sara 59de57fe3a simplified error checking, out of memory now results in an immediate crash when trying to expand world 2023-07-12 02:44:21 +02:00
Sara 5e001bc345 object_is_valid now confirms if an object is valid, as opposed to if an object is invalid 2023-07-12 02:43:53 +02:00
Sara 3e3f322529 _find_free_object now returns the index of the found object / make_object now allocates the object if the found slot is NULL 2023-07-12 02:38:26 +02:00
Sara 30d4e4c39a replaced initialization code of _world_objects.objects with new_list 2023-07-12 02:29:33 +02:00
Sara a139e4f3bb now initializing newly expanded world space with nullptrs 2023-07-12 02:28:13 +02:00
Sara 6a52f2795d object_is_valid now does not attempt to validate internals if object is NULL 2023-07-12 02:25:32 +02:00
Sara d915047bd1 removed uses of world_num_objects from world.c and replaced them with _world_objects.num 2023-07-12 02:25:11 +02:00
Sara c1fa6e2956 now creating world array with 16 elements if world array length is 0 2023-07-12 02:19:32 +02:00
Sara 544f18e9c6 added error when failing to expand world 2023-07-12 02:16:05 +02:00
Sara e6b66884ec added expandable world objects array 2023-07-12 02:07:29 +02:00
Sara 3ed5755e52 physics.h no longer depends on g_objects being public, or WORLD_NUM_OBJECTS being constant 2023-07-12 02:07:08 +02:00
Sara a5c6a7eceb added objecadded object_is_valid 2023-07-12 02:06:40 +02:00
Sara 1eb8ab2fd9 WIP: world array is now an array of pointers to objects 2023-06-28 08:54:22 +02:00
Sara a18d5599b4 Merge remote-tracking branch 'origin' into dynamic-size-world 2023-06-28 08:52:35 +02:00
Sara ca3b2af703 added math library to CMakeLists 2023-06-27 09:30:00 +02:00
Sara de7d14ae92 added corelib.h include for minimal setup 2023-06-27 08:16:45 +02:00
Sara f395f27532 moved entry to corelib 2023-06-27 08:16:31 +02:00
Sara 528e35537f renamed engine module to entry module 2023-06-26 17:43:15 +02:00
Sara f115334644 moved engine module to corelib 2023-06-26 17:42:02 +02:00
Sara ac722f6e5f Merge pull request 'rework collision_t component into a more wholistic physics_t component' (#11) from object-physics into main
Reviewed-on: #11
2023-06-26 15:28:33 +00:00
Sara 484db1961a reformatting some lines in CMakeLists 2023-06-26 17:24:57 +02:00
Sara 20ca626acf solve_collision_slide no longer modifies rhs argument 2023-06-25 18:18:07 +02:00
Sara 9a732ad82f simplified solve_collision_slide 2023-06-25 18:13:48 +02:00
Sara 812e7eab20 renamed move_and_slide to physics_move 2023-06-25 18:13:28 +02:00
Sara ead22d114c added explicit extern specifiers to physics functions lacking them 2023-06-25 18:13:12 +02:00
Sara 7a2cb3cd5c added solver field to physics_t, exposed solve_collision_slide to make it work 2023-06-25 18:12:38 +02:00
Sara 76bf6e81e5 reworked collision_t component into physics_t component 2023-06-25 12:41:53 +02:00
Sara 8a028dca08 Merge pull request 'improved handling of interpolate_move calls with a distance shorter than minimum delta' (#10) from improve-short-moves into main
Reviewed-on: #10
2023-06-24 20:44:49 +00:00
Sara 4acc18a101 Merge branch 'main' into improve-short-moves 2023-06-24 20:44:43 +00:00
Sara a9ba15f93b improved handling of interpolate_move calls with a distance shorter than minimum delta 2023-06-24 22:42:38 +02:00
Sara 78d1e78dd7 Merge pull request 'split-world-module' (#9) from split-world-module into main
Reviewed-on: #9
2023-06-24 20:09:34 +00:00
Sara aa9dfa3fcd Merge branch 'main' into split-world-module 2023-06-24 20:09:04 +00:00
Sara bf21a388ab Merge pull request 'Add a move_and_slide style interpolation option' (#7) from move-and-slide into main
Reviewed-on: #7
2023-06-24 20:08:56 +00:00
Sara b8c856bf56 split the world module into world, physics, and object 2023-06-24 22:03:28 +02:00
Sara 43fa322409 removed debug prints from _slide_collision solver 2023-06-24 20:34:35 +02:00
Sara 4271ee8f8d fully implemented slide collision for any combination of aabb and circle colliders 2023-06-24 20:32:54 +02:00
Sara daa53c6ef0 movetowards now also works in cases where x == 0 && y != 0 || x != 0 && y == 0 2023-06-24 20:32:31 +02:00
Sara 3df374b3b9 interpolate move will now immediately stop if destination is reached 2023-06-18 22:45:06 +02:00
Sara fd99159b27 Merge pull request 'fix event based axis input giving results that are not in range -1 to 1' (#8) from event-input into move-and-slide
Reviewed-on: #8
2023-06-18 20:24:31 +00:00
Sara fab7338891 axis now keeps a history for both keys 2023-06-18 22:22:16 +02:00
Sara cfcd31cee8 more explicit typing and casting in timespec_to_sec 2023-06-18 22:17:38 +02:00
Sara fc1f3499e3 input events are now still pumped during frame downtime 2023-06-18 22:17:25 +02:00
Sara 14b8b2f380 delta time is now updated before evaluating minimum delta time/ 2023-06-18 22:17:06 +02:00
Sara a93d09c96c reworked most input listener types to use OS events instead of checking state on update
this allows for them to be more reliable with low framerates
2023-06-18 22:14:16 +02:00
Sara b30df338ac no longer zeroing _delta_time if nanosecond element of difference between last and next time is negative 2023-06-18 19:04:22 +02:00
Sara a49ac0db93 timespec_to_sec now first multiplies tv_nsec by 1E-09 to convert to seconds 2023-06-18 18:04:49 +02:00
Sara 56679110e3 added frame limiting 2023-06-18 18:02:22 +02:00
Sara d69b92a464 delta_time is now calculating by converting timespec to seconds 2023-06-18 17:59:50 +02:00
Sara fcb9645dbe added NORMALIZE #define macro
avoids pointers and the accompanying dereference operations of normalize(...)
2023-06-18 16:34:28 +02:00
Sara 6f78731d8f normalize will now normalize if either x or y is non-zero
previously 100,0 would return 100,0 instead of 1,0
2023-06-18 16:33:39 +02:00
Sara 494dbfedb3 clamp_magnitude no longer outputs input x,y regardless of checks 2023-06-18 16:14:30 +02:00
Sara 1df623e669 move_towards now compares absolutes of diff and dir to check if the destination will be reached 2023-06-18 16:09:43 +02:00
Sara fa2a911732 added corelib/math/vec.h single header vector math library 2023-06-18 14:39:04 +02:00
Sara ea3beddfec inverted correction collision_check 2023-06-18 14:27:24 +02:00
Sara dff8e8304d iteration count for interpolate_move now based on number of steps required to fullfill move in theory 2023-06-18 14:24:38 +02:00
Sara 0a8aac2325 implemented slide 2023-06-18 14:18:37 +02:00
Sara 81e242532b added argument to interpolate_move, removed return value from interpolate_move 2023-06-18 14:08:55 +02:00
Sara 96eeb3233f Merge pull request 'implement basic collision and interpolation' (#6) from collision into main
Reviewed-on: #6
2023-06-18 11:51:15 +00:00
Sara bd87bcf8af bbminy in _collision_circle_aabb is now calculated correctly using y 2023-06-18 13:45:53 +02:00
Sara d134df5847 position now resets to last when interpolate_move collides 2023-06-18 13:42:20 +02:00
Sara 4bf759d224 added broadcast collision 2023-06-18 13:22:35 +02:00
Sara e32eb731c2 simplified interpolate move comparison condition 2023-06-18 13:15:03 +02:00
Sara 0b32967c20 removed requirement for a evt_collision listener from _can_collide 2023-06-18 13:13:35 +02:00
Sara 8ae7d302fe inverted _can_collide guard clause in interpolate_move 2023-06-18 11:59:13 +02:00
Sara f8d69e3537 reversed order of operations in interpolate_move 2023-06-18 11:39:16 +02:00
Sara 1748db8412 Merge pull request 'Correctly render sprites' (#5) from fix-render into main
Reviewed-on: #5
2023-06-18 09:24:13 +00:00
Sara d067ea6df8 default sprite origin in is now 0,0 2023-06-16 13:35:33 +02:00
25 changed files with 1639 additions and 1082 deletions

View File

@ -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")

View File

@ -12,136 +12,136 @@ resource_t g_assets[NUM_ASSETS];
resource_t* g_assets_endptr = g_assets; resource_t* g_assets_endptr = g_assets;
static resource_t* insert_asset(const resource_t* resource) { static resource_t* insert_asset(const resource_t* resource) {
*g_assets_endptr = *resource; *g_assets_endptr = *resource;
resource_t* inserted = g_assets_endptr; resource_t* inserted = g_assets_endptr;
++g_assets_endptr; ++g_assets_endptr;
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);
int len = strlen(name_buf); int len = strlen(name_buf);
resource_t res = (resource_t){ resource_t res = (resource_t){
.type = RESOURCETYPE_ARBITRARY, .type = RESOURCETYPE_ARBITRARY,
.hash = hashstr(name_buf), .hash = hashstr(name_buf),
.name = (char*)calloc(len+1, sizeof(char)), .name = (char*)calloc(len+1, sizeof(char)),
.arbitrary_type={ .arbitrary_type={
.memory=memory,.deleter=deleter .memory=memory,.deleter=deleter
} }
}; };
strcpy(res.name, name_buf); strcpy(res.name, name_buf);
insert_asset(&res); insert_asset(&res);
} }
SDL_Texture* load_texture(const char* file) { SDL_Texture* load_texture(const char* file) {
int len = strlen(file); int len = strlen(file);
resource_t res = (resource_t){ resource_t res = (resource_t){
.type = RESOURCETYPE_TEXTURE, .type = RESOURCETYPE_TEXTURE,
.hash = hashstr(file), .hash = hashstr(file),
.name = (char*)calloc(len+1, sizeof(char)), .name = (char*)calloc(len+1, sizeof(char)),
.texture = IMG_LoadTexture(g_context.renderer, file), .texture = IMG_LoadTexture(g_context.renderer, file),
}; };
strcpy(res.name, file); strcpy(res.name, file);
return insert_asset(&res)->texture; return insert_asset(&res)->texture;
} }
TTF_Font* load_font(const char* file, int size) { TTF_Font* load_font(const char* file, int size) {
int len = strlen(file); int len = strlen(file);
resource_t res = (resource_t) { resource_t res = (resource_t) {
.type = RESOURCETYPE_FONT, .type = RESOURCETYPE_FONT,
.hash = hashstr(file), .hash = hashstr(file),
.name = calloc(len+1, sizeof(char)), .name = calloc(len+1, sizeof(char)),
.font = { .font = {
.size = size, .size = size,
.font = TTF_OpenFont(file, size) .font = TTF_OpenFont(file, size)
} }
}; };
strcpy(res.name, file); strcpy(res.name, file);
return insert_asset(&res)->font.font; return insert_asset(&res)->font.font;
} }
SDL_Texture* get_texture(const char* file) { SDL_Texture* get_texture(const char* file) {
resource_t* found = get_asset(file); resource_t* found = get_asset(file);
if(found != NULL && found->type == RESOURCETYPE_TEXTURE) { if(found != NULL && found->type == RESOURCETYPE_TEXTURE) {
return found->texture; return found->texture;
} else { } else {
return load_texture(file); return load_texture(file);
} }
} }
TTF_Font* get_font(const char *file, int size) { TTF_Font* get_font(const char *file, int size) {
uint32_t hash = hashstr(file); uint32_t hash = hashstr(file);
for(resource_t* res = g_assets; res != g_assets_endptr; ++res) { for(resource_t* res = g_assets; res != g_assets_endptr; ++res) {
if(res->hash == hash if(res->hash == hash
&& strcmp(res->name, file) == 0 && strcmp(res->name, file) == 0
&& res->type == RESOURCETYPE_FONT && res->type == RESOURCETYPE_FONT
&& res->font.size == size) { && res->font.size == size) {
return res->font.font; return res->font.font;
} }
} }
return load_font(file, size); return load_font(file, size);
} }
resource_t* get_asset(const char* file) { resource_t* get_asset(const char* file) {
uint32_t hash = hashstr(file); uint32_t hash = hashstr(file);
for(resource_t* res = g_assets; res != g_assets_endptr; ++res) { for(resource_t* res = g_assets; res != g_assets_endptr; ++res) {
if(res->hash == hash if(res->hash == hash
&& strcmp(res->name, file) == 0) { && strcmp(res->name, file) == 0) {
return res; return res;
} }
} }
return NULL; return NULL;
} }
static void _delete_referenced_asset(resource_t* res) { static void _delete_referenced_asset(resource_t* res) {
free(res->name); free(res->name);
switch(res->type) { switch(res->type) {
default: default:
break; break;
case RESOURCETYPE_TEXTURE: case RESOURCETYPE_TEXTURE:
SDL_DestroyTexture(res->texture); SDL_DestroyTexture(res->texture);
break; break;
case RESOURCETYPE_FONT: case RESOURCETYPE_FONT:
TTF_CloseFont(res->font.font); TTF_CloseFont(res->font.font);
break; break;
case RESOURCETYPE_ARBITRARY: case RESOURCETYPE_ARBITRARY:
res->arbitrary_type.deleter(res->arbitrary_type.memory); res->arbitrary_type.deleter(res->arbitrary_type.memory);
break; break;
} }
} }
int asset_exists(const char* name) { int asset_exists(const char* name) {
return get_asset(name) != NULL; return get_asset(name) != NULL;
} }
resourcetype_t asset_type(const char* name) { resourcetype_t asset_type(const char* name) {
resource_t* r = get_asset(name); resource_t* r = get_asset(name);
if(r != NULL) { if(r != NULL) {
return r->type; return r->type;
} else { } else {
return RESOURCETYPE_MIN; return RESOURCETYPE_MIN;
} }
} }
int delete_by_name(const char* name) { int delete_by_name(const char* name) {
resource_t* r = get_asset(name); resource_t* r = get_asset(name);
if(r == NULL) return 0; if(r == NULL) return 0;
_remove_asset(r); _remove_asset(r);
return 1; return 1;
} }
void clean_assets() { void clean_assets() {
for(resource_t* res = g_assets; res != g_assets_endptr; ++res) { for(resource_t* res = g_assets; res != g_assets_endptr; ++res) {
_delete_referenced_asset(res); _delete_referenced_asset(res);
} }
g_assets_endptr = g_assets; g_assets_endptr = g_assets;
} }
static void _remove_asset(resource_t* res) { static void _remove_asset(resource_t* res) {
_delete_referenced_asset(res); _delete_referenced_asset(res);
resource_t* last = g_assets_endptr - 1; resource_t* last = g_assets_endptr - 1;
memmove(res, last, sizeof(resource_t)); memmove(res, last, sizeof(resource_t));
--g_assets_endptr; --g_assets_endptr;
} }

View File

@ -10,34 +10,34 @@ extern "C" {
#include "SDL2/SDL.h" #include "SDL2/SDL.h"
typedef enum resourcetype_t { typedef enum resourcetype_t {
RESOURCETYPE_MIN, RESOURCETYPE_MIN,
RESOURCETYPE_TEXTURE, RESOURCETYPE_TEXTURE,
RESOURCETYPE_FONT, RESOURCETYPE_FONT,
RESOURCETYPE_ARBITRARY, RESOURCETYPE_ARBITRARY,
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;
uintptr_t hash; uintptr_t hash;
char* name; char* name;
union { union {
SDL_Texture* texture; SDL_Texture* texture;
struct { struct {
int size; int size;
TTF_Font* font; TTF_Font* font;
} 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);

View File

@ -5,32 +5,32 @@
#include <SDL2/SDL_messagebox.h> #include <SDL2/SDL_messagebox.h>
context_t g_context = { context_t g_context = {
.window = NULL, .window = NULL,
.renderer = NULL, .renderer = NULL,
.running = 1 .running = 1
}; };
void init_context() { void init_context() {
if(SDL_Init(SDL_INIT_EVERYTHING) != 0) { if(SDL_Init(SDL_INIT_EVERYTHING) != 0) {
SDL_Log("ERROR LOADING SDL %s", SDL_GetError()); SDL_Log("ERROR LOADING SDL %s", SDL_GetError());
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "ERROR LOADING SDL", SDL_GetError(), NULL); SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "ERROR LOADING SDL", SDL_GetError(), NULL);
exit(1); exit(1);
} }
if(IMG_Init(IMG_INIT_JPG | IMG_INIT_PNG) == 0) { if(IMG_Init(IMG_INIT_JPG | IMG_INIT_PNG) == 0) {
SDL_Log("ERROR LOADING IMG %s", IMG_GetError()); SDL_Log("ERROR LOADING IMG %s", IMG_GetError());
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "ERROR LOADING IMG", IMG_GetError(), NULL); SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "ERROR LOADING IMG", IMG_GetError(), NULL);
exit(2); exit(2);
} }
if(TTF_Init() != 0) { if(TTF_Init() != 0) {
SDL_Log("ERROR LOADING TTF %s", TTF_GetError()); SDL_Log("ERROR LOADING TTF %s", TTF_GetError());
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "ERROR LOADING IMG", IMG_GetError(), NULL); SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "ERROR LOADING IMG", IMG_GetError(), NULL);
} }
} }
void close_context() { void close_context() {
SDL_DestroyRenderer(g_context.renderer); SDL_DestroyRenderer(g_context.renderer);
SDL_DestroyWindow(g_context.window); SDL_DestroyWindow(g_context.window);
TTF_Quit(); TTF_Quit();
IMG_Quit(); IMG_Quit();
SDL_Quit(); SDL_Quit();
} }

View File

@ -9,10 +9,10 @@ extern "C" {
#include "SDL2/SDL_events.h" #include "SDL2/SDL_events.h"
typedef struct context_t { typedef struct context_t {
SDL_Window* window; SDL_Window* window;
SDL_Renderer* renderer; SDL_Renderer* renderer;
SDL_Event event; SDL_Event event;
short running; short running;
} context_t; } context_t;
extern context_t g_context; extern context_t g_context;

6
src/corelib/corelib.h Normal file
View File

@ -0,0 +1,6 @@
#include "object.h"
#include "render.h"
#include "assets.h"
#include "physics.h"
#include "world.h"
#include "entry.h"

102
src/corelib/entry.c Normal file
View File

@ -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();
}

View File

@ -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 */

View File

@ -8,18 +8,18 @@ extern "C" {
#include "string.h" #include "string.h"
uintptr_t hashstr(const char* str) { uintptr_t hashstr(const char* str) {
return hashmem(str, strlen(str)); return hashmem(str, strlen(str));
} }
uintptr_t hashmem(const char* mem, size_t size) { uintptr_t hashmem(const char* mem, size_t size) {
const size_t last4shift = sizeof(uintptr_t)*4 - 4; const size_t last4shift = sizeof(uintptr_t)*4 - 4;
uintptr_t hash = 0; uintptr_t hash = 0;
while(size --> 0) { while(size --> 0) {
hash = (hash | *mem) << 1; hash = (hash | *mem) << 1;
hash |= hash >> last4shift; hash |= hash >> last4shift;
++mem; ++mem;
} }
return hash; return hash;
} }
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -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,144 +45,158 @@ 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; g_key_listeners_endptr->axis.last_negative = 0;
++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;
g_key_listeners_endptr->button.button = button; g_key_listeners_endptr->button.button = button;
g_key_listeners_endptr->button.last = 0; g_key_listeners_endptr->button.last = 0;
++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;
++g_key_listeners_endptr; ++g_key_listeners_endptr;
} }
void remove_listener_at(size_t index) { void remove_listener_at(size_t index) {
input_listener_t* listener = g_key_listeners + index; input_listener_t* listener = g_key_listeners + index;
--g_key_listeners_endptr; --g_key_listeners_endptr;
*listener = *g_key_listeners_endptr; *listener = *g_key_listeners_endptr;
} }
void mouse_screen_position(float *ox, float *oy) { void mouse_screen_position(float *ox, float *oy) {
*ox = _last_screen_mouse_x; *ox = _last_screen_mouse_x;
*oy = _last_screen_mouse_y; *oy = _last_screen_mouse_y;
} }
void mouse_world_position(float *ox, float *oy) { void mouse_world_position(float *ox, float *oy) {
mouse_screen_position(ox, oy); mouse_screen_position(ox, oy);
screen_to_view(ox, oy); screen_to_view(ox, oy);
} }
void input_init() { 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) {
listener->mouse.delegate(dx, dy); listener->mouse.delegate(dx, 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;
SDL_GetMouseState(&px, &py); SDL_GetMouseState(&px, &py);
int width, height; int width, height;
SDL_GetRendererOutputSize(g_context.renderer, &width, &height); SDL_GetRendererOutputSize(g_context.renderer, &width, &height);
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_mouse_listener(listener, dx, dy);
_process_axis_listener(listener); }
break; }
case 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;
}
}
_last_mouse_x = px; _last_mouse_y = py; _last_mouse_x = px; _last_mouse_y = py;
_last_screen_mouse_x = (float)px / width; _last_screen_mouse_x = (float)px / width;
_last_screen_mouse_y = (float)py / width; _last_screen_mouse_y = (float)py / width;
_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_MOUSEWHEEL: case SDL_KEYUP:
_scroll_delta = event.wheel.y; case SDL_KEYDOWN:
break; _handle_key_event(event);
} return;
case SDL_MOUSEWHEEL:
_handle_scroll_event(event);
return;
case SDL_MOUSEBUTTONUP:
case SDL_MOUSEBUTTONDOWN:
_handle_mousebutton_event(event);
return;
}
} }
int input_get_keydown(SDL_Scancode scancode) { int input_get_keydown(SDL_Scancode scancode) {
return g_key_states[scancode]; return g_key_states[scancode];
} }
int input_get_mousedown(int mousebtn) { 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;
} }

View File

@ -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
} }

View File

@ -5,9 +5,9 @@
extern "C" { extern "C" {
#endif #endif
#define RLAYER_TILEMAP 100 #define RLAYER_TILEMAP 100
#define RLAYER_SPRITES 0 #define RLAYER_SPRITES 0
#define RLAYER_UI -100 #define RLAYER_UI -100
#ifdef __cplusplus #ifdef __cplusplus
} }

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

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

15
src/corelib/object.c Normal file
View File

@ -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);
}

32
src/corelib/object.h Normal file
View File

@ -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 */

272
src/corelib/physics.c Normal file
View File

@ -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);
}
}

43
src/corelib/physics.h Normal file
View File

@ -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 */

View File

@ -13,9 +13,9 @@
#define NUM_DRAWCMDS 2048 #define NUM_DRAWCMDS 2048
drawcmd_t g_drawdata[NUM_DRAWCMDS]; drawcmd_t g_drawdata[NUM_DRAWCMDS];
drawcmd_t* g_drawdata_endptr = g_drawdata; drawcmd_t* g_drawdata_endptr = g_drawdata;
int _render_mode = 0; int _render_mode = 0;
#define DEFAULT_VIEW (view_t){.x=0.0,.y=0.0,.width=1.0} #define DEFAULT_VIEW (view_t){.x=0.0,.y=0.0,.width=1.0}
@ -27,462 +27,474 @@ view_t g_active_view = DEFAULT_VIEW;
int d_debug_next_frame = 0; int d_debug_next_frame = 0;
void screen_to_view(float *x, float *y) { void screen_to_view(float *x, float *y) {
float xx = *x, yy = *y; float xx = *x, yy = *y;
xx *= g_active_view.width; xx *= g_active_view.width;
yy *= g_active_view.width; yy *= g_active_view.width;
xx += g_active_view.x - g_active_view.width * 0.5f; xx += g_active_view.x - g_active_view.width * 0.5f;
yy += g_active_view.y - g_active_view.width * _aspect_ratio * 0.5f; yy += g_active_view.y - g_active_view.width * _aspect_ratio * 0.5f;
*x = xx; *x = xx;
*y = yy; *y = yy;
} }
void clear_buffer() { void clear_buffer() {
SDL_SetRenderDrawBlendMode(g_context.renderer, SDL_BLENDMODE_BLEND); SDL_SetRenderDrawBlendMode(g_context.renderer, SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(g_context.renderer, 0, 0, 0, 255); SDL_SetRenderDrawColor(g_context.renderer, 0, 0, 0, 255);
SDL_RenderClear(g_context.renderer); SDL_RenderClear(g_context.renderer);
} }
void get_scaling_factors(float* width, float* height, void get_scaling_factors(float* width, float* height,
float* width_mul, float* height_mul, short ui) { float* width_mul, float* height_mul, short ui) {
float aspectr; float aspectr;
(*width)=_render_width; (*height)=_render_height; (*width)=_render_width; (*height)=_render_height;
aspectr = _aspect_ratio; aspectr = _aspect_ratio;
if(ui == 0) { if(ui == 0) {
(*width_mul) = (*width)/g_active_view.width; (*width_mul) = (*width)/g_active_view.width;
(*height_mul) = (*height)/(g_active_view.width*aspectr); (*height_mul) = (*height)/(g_active_view.width*aspectr);
} else { } else {
(*height_mul) = (*width_mul) = (*width); (*height_mul) = (*width_mul) = (*width);
} }
(*width) = g_active_view.width; (*width) = g_active_view.width;
(*height) = (*width)*aspectr; (*height) = (*width)*aspectr;
} }
SDL_FRect get_dest_with_size(SDL_FRect untransformed, int ui) { SDL_FRect get_dest_with_size(SDL_FRect untransformed, int ui) {
float fw, fh, fwm, fhm; float fw, fh, fwm, fhm;
get_scaling_factors(&fw, &fh, &fwm, &fhm, ui); get_scaling_factors(&fw, &fh, &fwm, &fhm, ui);
SDL_FRect r = (SDL_FRect) { SDL_FRect r = (SDL_FRect) {
.x=((-g_active_view.x)+(fw*0.5f)+untransformed.x)*fwm, .x=((-g_active_view.x)+(fw*0.5f)+untransformed.x)*fwm,
.y=((-g_active_view.y)+(fh*0.5f)+untransformed.y)*fwm, .y=((-g_active_view.y)+(fh*0.5f)+untransformed.y)*fwm,
.w=untransformed.w*fwm, .w=untransformed.w*fwm,
.h=untransformed.h*fwm .h=untransformed.h*fwm
}; };
if(ui) { if(ui) {
r.x = untransformed.x * fwm; r.x = untransformed.x * fwm;
r.y = untransformed.y * fhm; r.y = untransformed.y * fhm;
} }
return r; return r;
} }
static static
void _exec_sprite_cmd(const drawcmd_t* cmd) { void _exec_sprite_cmd(const drawcmd_t* cmd) {
const sprite_t* sprite = &cmd->sprite; const sprite_t* sprite = &cmd->sprite;
SDL_FRect untransformed = {sprite->x, sprite->y, sprite->sx, sprite->sy}; SDL_FRect untransformed = {sprite->x, sprite->y, sprite->sx, sprite->sy};
untransformed.x -= sprite->origin.x; untransformed.x -= sprite->origin.x;
untransformed.y -= sprite->origin.y; untransformed.y -= sprite->origin.y;
SDL_FRect destrect = get_dest_with_size(untransformed, cmd->ui); SDL_FRect destrect = get_dest_with_size(untransformed, cmd->ui);
SDL_FPoint origin = {destrect.w * sprite->origin.x, destrect.h * sprite->origin.y}; SDL_FPoint origin = {destrect.w * sprite->origin.x, destrect.h * sprite->origin.y};
SDL_RenderCopyExF(g_context.renderer, sprite->texture, SDL_RenderCopyExF(g_context.renderer, sprite->texture,
&sprite->uv, &destrect, sprite->rot, &sprite->uv, &destrect, sprite->rot,
&origin, sprite->flip); &origin, sprite->flip);
} }
static static
void _exec_rect_cmd(const drawcmd_t* cmd) { void _exec_rect_cmd(const drawcmd_t* cmd) {
float w, h, wm, hm; float w, h, wm, hm;
get_scaling_factors(&w, &h, &wm, &hm, cmd->ui); get_scaling_factors(&w, &h, &wm, &hm, cmd->ui);
SDL_FRect rect = (SDL_FRect) { SDL_FRect rect = (SDL_FRect) {
.x=((-g_active_view.x)+(w*0.5f)+cmd->rect.x)*wm, .x=((-g_active_view.x)+(w*0.5f)+cmd->rect.x)*wm,
.y=((-g_active_view.y)+(h*0.5f)+cmd->rect.y)*hm, .y=((-g_active_view.y)+(h*0.5f)+cmd->rect.y)*hm,
.w=cmd->rect.w*wm, .h=cmd->rect.h*hm .w=cmd->rect.w*wm, .h=cmd->rect.h*hm
}; };
if(cmd->ui) { if(cmd->ui) {
rect.x = cmd->rect.x * wm; rect.x = cmd->rect.x * wm;
rect.y = cmd->rect.y * hm; rect.y = cmd->rect.y * hm;
} }
SDL_Color c = cmd->rect.background; SDL_Color c = cmd->rect.background;
SDL_SetRenderDrawColor(g_context.renderer, c.r, c.g, c.b, c.a); SDL_SetRenderDrawColor(g_context.renderer, c.r, c.g, c.b, c.a);
SDL_RenderFillRectF(g_context.renderer, &rect); SDL_RenderFillRectF(g_context.renderer, &rect);
c = cmd->rect.line; c = cmd->rect.line;
SDL_SetRenderDrawColor(g_context.renderer, c.r, c.g, c.b, c.a); SDL_SetRenderDrawColor(g_context.renderer, c.r, c.g, c.b, c.a);
SDL_FRect r = { SDL_FRect r = {
rect.x, rect.y, rect.x, rect.y,
cmd->rect.line_width*wm, rect.h cmd->rect.line_width*wm, rect.h
}; };
SDL_RenderFillRectF(g_context.renderer, &r); SDL_RenderFillRectF(g_context.renderer, &r);
r = (SDL_FRect){ r = (SDL_FRect){
rect.x, rect.y, rect.x, rect.y,
rect.w, cmd->rect.line_width*hm rect.w, cmd->rect.line_width*hm
}; };
SDL_RenderFillRectF(g_context.renderer, &r); SDL_RenderFillRectF(g_context.renderer, &r);
r = (SDL_FRect){ r = (SDL_FRect){
rect.x, rect.y+rect.h-cmd->rect.line_width*hm, rect.x, rect.y+rect.h-cmd->rect.line_width*hm,
rect.w, cmd->rect.line_width*hm rect.w, cmd->rect.line_width*hm
}; };
SDL_RenderFillRectF(g_context.renderer, &r); SDL_RenderFillRectF(g_context.renderer, &r);
r = (SDL_FRect){ r = (SDL_FRect){
rect.x+rect.w-cmd->rect.line_width*wm, rect.y, rect.x+rect.w-cmd->rect.line_width*wm, rect.y,
cmd->rect.line_width*wm, rect.h cmd->rect.line_width*wm, rect.h
}; };
SDL_RenderFillRectF(g_context.renderer, &r); SDL_RenderFillRectF(g_context.renderer, &r);
} }
static static
void _exec_sliced_cmd(const drawcmd_t* cmd) { void _exec_sliced_cmd(const drawcmd_t* cmd) {
const nineslice_t* sliced = &cmd->sliced; const nineslice_t* sliced = &cmd->sliced;
// target rect in world space // target rect in world space
SDL_FRect rect = sliced->rect; SDL_FRect rect = sliced->rect;
// sliced texture // sliced texture
SDL_Texture* t = sliced->texture; SDL_Texture* t = sliced->texture;
// width and height of sliced texture // width and height of sliced texture
int tw, th; int tw, th;
SDL_QueryTexture(t, NULL, NULL, &tw, &th); SDL_QueryTexture(t, NULL, NULL, &tw, &th);
// top-left // top-left
SDL_Rect srcr = { SDL_Rect srcr = {
0, 0, sliced->corner_size, sliced->corner_size 0, 0, sliced->corner_size, sliced->corner_size
}; };
SDL_FRect dstr = get_dest_with_size((SDL_FRect){ SDL_FRect dstr = get_dest_with_size((SDL_FRect){
rect.x, rect.y, sliced->radius, sliced->radius rect.x, rect.y, sliced->radius, sliced->radius
}, cmd->ui); }, cmd->ui);
SDL_RenderCopyF(g_context.renderer, t, &srcr, &dstr); SDL_RenderCopyF(g_context.renderer, t, &srcr, &dstr);
// top - centre // top - centre
srcr = (SDL_Rect) { srcr = (SDL_Rect) {
sliced->corner_size, 0, sliced->corner_size, 0,
tw - sliced->corner_size*2, sliced->corner_size tw - sliced->corner_size*2, sliced->corner_size
}; };
dstr = get_dest_with_size((SDL_FRect){ dstr = get_dest_with_size((SDL_FRect){
rect.x + sliced->radius, rect.y, sliced->rect.w - sliced->radius * 2, sliced->radius rect.x + sliced->radius, rect.y, sliced->rect.w - sliced->radius * 2, sliced->radius
}, cmd->ui); }, cmd->ui);
SDL_RenderCopyF(g_context.renderer, t, &srcr, &dstr); SDL_RenderCopyF(g_context.renderer, t, &srcr, &dstr);
// top-right // top-right
srcr = (SDL_Rect) { srcr = (SDL_Rect) {
tw - sliced->corner_size, 0, sliced->corner_size, sliced->corner_size tw - sliced->corner_size, 0, sliced->corner_size, sliced->corner_size
}; };
dstr = get_dest_with_size((SDL_FRect){ dstr = get_dest_with_size((SDL_FRect){
rect.x + rect.w - sliced->radius, rect.y, sliced->radius, sliced->radius rect.x + rect.w - sliced->radius, rect.y, sliced->radius, sliced->radius
}, cmd->ui); }, cmd->ui);
SDL_RenderCopyF(g_context.renderer, t, &srcr, &dstr); SDL_RenderCopyF(g_context.renderer, t, &srcr, &dstr);
// centre-left // centre-left
srcr = (SDL_Rect) { srcr = (SDL_Rect) {
0, sliced->corner_size, sliced->corner_size, th - sliced->corner_size * 2 0, sliced->corner_size, sliced->corner_size, th - sliced->corner_size * 2
}; };
dstr = get_dest_with_size((SDL_FRect) { dstr = get_dest_with_size((SDL_FRect) {
rect.x, rect.y + sliced->radius, sliced->radius, rect.h - sliced->radius * 2 rect.x, rect.y + sliced->radius, sliced->radius, rect.h - sliced->radius * 2
}, cmd->ui); }, cmd->ui);
SDL_RenderCopyF(g_context.renderer, t, &srcr, &dstr); SDL_RenderCopyF(g_context.renderer, t, &srcr, &dstr);
// centre-centre // centre-centre
srcr = (SDL_Rect) { srcr = (SDL_Rect) {
sliced->corner_size, sliced->corner_size, tw - sliced->corner_size * 2, th - sliced->corner_size * 2 sliced->corner_size, sliced->corner_size, tw - sliced->corner_size * 2, th - sliced->corner_size * 2
}; };
dstr = get_dest_with_size((SDL_FRect) { dstr = get_dest_with_size((SDL_FRect) {
rect.x + sliced->radius, rect.y + sliced->radius, rect.w - sliced->radius * 2, rect.h - sliced->radius * 2 rect.x + sliced->radius, rect.y + sliced->radius, rect.w - sliced->radius * 2, rect.h - sliced->radius * 2
}, cmd->ui); }, cmd->ui);
SDL_RenderCopyF(g_context.renderer, t, &srcr, &dstr); SDL_RenderCopyF(g_context.renderer, t, &srcr, &dstr);
// centre-right // centre-right
srcr = (SDL_Rect) { srcr = (SDL_Rect) {
tw - sliced->corner_size, sliced->corner_size, sliced->corner_size, th - sliced->corner_size * 2 tw - sliced->corner_size, sliced->corner_size, sliced->corner_size, th - sliced->corner_size * 2
}; };
dstr = get_dest_with_size((SDL_FRect) { dstr = get_dest_with_size((SDL_FRect) {
rect.x + rect.w - sliced->radius, rect.y + sliced->radius, sliced->radius, rect.h - sliced->radius * 2 rect.x + rect.w - sliced->radius, rect.y + sliced->radius, sliced->radius, rect.h - sliced->radius * 2
}, cmd->ui); }, cmd->ui);
SDL_RenderCopyF(g_context.renderer, t, &srcr, &dstr); SDL_RenderCopyF(g_context.renderer, t, &srcr, &dstr);
// bottom-left // bottom-left
srcr = (SDL_Rect) { srcr = (SDL_Rect) {
0, th - sliced->corner_size, sliced->corner_size, sliced->corner_size 0, th - sliced->corner_size, sliced->corner_size, sliced->corner_size
}; };
dstr = get_dest_with_size((SDL_FRect){ dstr = get_dest_with_size((SDL_FRect){
rect.x, rect.y + rect.h - sliced->radius, sliced->radius, sliced->radius rect.x, rect.y + rect.h - sliced->radius, sliced->radius, sliced->radius
}, cmd->ui); }, cmd->ui);
SDL_RenderCopyF(g_context.renderer, t, &srcr, &dstr); SDL_RenderCopyF(g_context.renderer, t, &srcr, &dstr);
// bottom-centre // bottom-centre
srcr = (SDL_Rect) { srcr = (SDL_Rect) {
sliced->corner_size, th - sliced->corner_size, tw - sliced->corner_size * 2, sliced->corner_size sliced->corner_size, th - sliced->corner_size, tw - sliced->corner_size * 2, sliced->corner_size
}; };
dstr = get_dest_with_size((SDL_FRect) { dstr = get_dest_with_size((SDL_FRect) {
rect.x + sliced->radius, rect.y + rect.h - sliced->radius, rect.w - sliced->radius * 2, sliced->radius rect.x + sliced->radius, rect.y + rect.h - sliced->radius, rect.w - sliced->radius * 2, sliced->radius
}, cmd->ui); }, cmd->ui);
SDL_RenderCopyF(g_context.renderer, t, &srcr, &dstr); SDL_RenderCopyF(g_context.renderer, t, &srcr, &dstr);
// bottom-right // bottom-right
srcr = (SDL_Rect) { srcr = (SDL_Rect) {
tw - sliced->corner_size, th - sliced->corner_size, sliced->corner_size, sliced->corner_size tw - sliced->corner_size, th - sliced->corner_size, sliced->corner_size, sliced->corner_size
}; };
dstr = get_dest_with_size((SDL_FRect) { dstr = get_dest_with_size((SDL_FRect) {
rect.x + rect.w - sliced->radius, rect.y + rect.h - sliced->radius, sliced->radius, sliced->radius rect.x + rect.w - sliced->radius, rect.y + rect.h - sliced->radius, sliced->radius, sliced->radius
}, cmd->ui); }, cmd->ui);
SDL_RenderCopyF(g_context.renderer, t, &srcr, &dstr); SDL_RenderCopyF(g_context.renderer, t, &srcr, &dstr);
} }
static static
void _exec_text_cmd(const drawcmd_t* cmd) { void _exec_text_cmd(const drawcmd_t* cmd) {
SDL_FRect r = cmd->text.area; SDL_FRect r = cmd->text.area;
int fh = TTF_FontHeight(cmd->text.style.font); int fh = TTF_FontHeight(cmd->text.style.font);
int wrap = (int)(fh * r.w / cmd->text.style.size); int wrap = (int)(fh * r.w / cmd->text.style.size);
SDL_Surface* s = TTF_RenderText_Solid_Wrapped(cmd->text.style.font, cmd->text.text, cmd->text.style.color, wrap); SDL_Surface* s = TTF_RenderText_Solid_Wrapped(cmd->text.style.font, cmd->text.text, cmd->text.style.color, wrap);
if(s != NULL) { if(s != NULL) {
SDL_Rect srcr = {0,0,s->w, s->h}; SDL_Rect srcr = {0,0,s->w, s->h};
SDL_Texture* t = SDL_CreateTextureFromSurface(g_context.renderer, s); SDL_Texture* t = SDL_CreateTextureFromSurface(g_context.renderer, s);
SDL_FreeSurface(s); SDL_FreeSurface(s);
float asp_dst = r.w / r.h; float asp_dst = r.w / r.h;
float asp_src = (float)srcr.w / (float)srcr.h; float asp_src = (float)srcr.w / (float)srcr.h;
if((float)s->h / fh * cmd->text.style.size > r.h) { if((float)s->h / fh * cmd->text.style.size > r.h) {
srcr.h = srcr.w / asp_dst; srcr.h = srcr.w / asp_dst;
} }
r.w = (float)srcr.w / fh * cmd->text.style.size; r.w = (float)srcr.w / fh * cmd->text.style.size;
r.h = (float)srcr.h / fh * cmd->text.style.size; r.h = (float)srcr.h / fh * cmd->text.style.size;
r = get_dest_with_size(r, cmd->ui); r = get_dest_with_size(r, cmd->ui);
SDL_RenderCopyF(g_context.renderer, t, &srcr, &r); SDL_RenderCopyF(g_context.renderer, t, &srcr, &r);
SDL_DestroyTexture(t); SDL_DestroyTexture(t);
} }
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);
int wrap = (int)(fh * r.w / style.size); int wrap = (int)(fh * r.w / style.size);
SDL_Surface* s = TTF_RenderText_Solid_Wrapped(style.font, str, style.color, wrap); SDL_Surface* s = TTF_RenderText_Solid_Wrapped(style.font, str, style.color, wrap);
if(s != NULL) { if(s != NULL) {
SDL_Rect srcr = {0,0,s->w, s->h}; SDL_Rect srcr = {0,0,s->w, s->h};
SDL_Texture* t = SDL_CreateTextureFromSurface(g_context.renderer, s); SDL_Texture* t = SDL_CreateTextureFromSurface(g_context.renderer, s);
SDL_FreeSurface(s); SDL_FreeSurface(s);
float asp_dst = r.w / r.h; float asp_dst = r.w / r.h;
float asp_src = (float)srcr.w / (float)srcr.h; float asp_src = (float)srcr.w / (float)srcr.h;
if((float)s->h / fh * style.size > r.h) { if((float)s->h / fh * style.size > r.h) {
srcr.h = srcr.w / asp_dst; srcr.h = srcr.w / asp_dst;
} }
r.w = (float)srcr.w / fh * style.size; r.w = (float)srcr.w / fh * style.size;
r.h = (float)srcr.h / fh * style.size; r.h = (float)srcr.h / fh * style.size;
return (sprite_t) { return (sprite_t) {
.depth=RLAYER_SPRITES, .depth=RLAYER_SPRITES,
.origin={0,0}, .origin={0,0},
.rot=0, .rot=0,
.sx=area.w, .sy=area.h, .sx=area.w, .sy=area.h,
.x=area.x, .y=area.y, .x=area.x, .y=area.y,
.texture=t, .texture=t,
.uv=srcr, .uv=srcr,
.flip=SDL_FLIP_NONE, .flip=SDL_FLIP_NONE,
}; };
} }
return (sprite_t) { return (sprite_t) {
.depth=0, .depth=0,
.origin={0,0}, .rot=0, .origin={0,0}, .rot=0,
.sx=0, .sy=0, .sx=0, .sy=0,
.texture=NULL, .texture=NULL,
.uv={0,0,0,0}, .uv={0,0,0,0},
.x=0,.y=0, .x=0,.y=0,
.flip=SDL_FLIP_NONE, .flip=SDL_FLIP_NONE,
}; };
} }
typedef void(*drawcmd_delegate)(const drawcmd_t*); typedef void(*drawcmd_delegate)(const drawcmd_t*);
static drawcmd_delegate const drawcmd_funcs[] = { static drawcmd_delegate const drawcmd_funcs[] = {
&_exec_sprite_cmd, &_exec_sprite_cmd,
&_exec_rect_cmd, &_exec_rect_cmd,
&_exec_sliced_cmd, &_exec_sliced_cmd,
&_exec_text_cmd, &_exec_text_cmd,
}; };
static inline static inline
void _exec_buffer() { void _exec_buffer() {
if(d_debug_next_frame) printf("debug capture of draw buffer\ncount: %zu\n", (size_t)(g_drawdata_endptr - g_drawdata)); if(d_debug_next_frame) printf("debug capture of draw buffer\ncount: %zu\n", (size_t)(g_drawdata_endptr - g_drawdata));
for(const drawcmd_t* cmd = g_drawdata; cmd != g_drawdata_endptr; ++cmd) { for(const drawcmd_t* cmd = g_drawdata; cmd != g_drawdata_endptr; ++cmd) {
if(cmd->type > DRAWCMDTYPE_MIN && cmd->type < DRAWCMDTYPE_MAX) { if(cmd->type > DRAWCMDTYPE_MIN && cmd->type < DRAWCMDTYPE_MAX) {
if(d_debug_next_frame) { if(d_debug_next_frame) {
printf("depth: %d, type: %d\n", cmd->depth, cmd->type); printf("depth: %d, type: %d\n", cmd->depth, cmd->type);
} }
drawcmd_funcs[cmd->type](cmd); drawcmd_funcs[cmd->type](cmd);
} }
} }
} }
void swap_buffer() { void swap_buffer() {
clear_buffer(); clear_buffer();
int iw, ih; int iw, ih;
SDL_GetRendererOutputSize(g_context.renderer, &iw, &ih); SDL_GetRendererOutputSize(g_context.renderer, &iw, &ih);
_render_width = (float)iw; _render_height = (float)ih; _render_width = (float)iw; _render_height = (float)ih;
_aspect_ratio = _render_height/_render_width; _aspect_ratio = _render_height/_render_width;
_exec_buffer(); _exec_buffer();
SDL_RenderPresent(g_context.renderer); SDL_RenderPresent(g_context.renderer);
g_drawdata_endptr = g_drawdata; g_drawdata_endptr = g_drawdata;
} }
static inline static inline
void _insert_drawcmd_at(size_t index, const drawcmd_t* cmd) { void _insert_drawcmd_at(size_t index, const drawcmd_t* cmd) {
drawcmd_t* insertpoint = g_drawdata + index; drawcmd_t* insertpoint = g_drawdata + index;
drawcmd_t* dest = insertpoint + 1; drawcmd_t* dest = insertpoint + 1;
size_t size = (size_t)(g_drawdata_endptr - g_drawdata); size_t size = (size_t)(g_drawdata_endptr - g_drawdata);
++g_drawdata_endptr; ++g_drawdata_endptr;
if(size > index) { if(size > index) {
size_t count = (size - index); size_t count = (size - index);
if(size > 0) if(size > 0)
{ {
memmove(dest, insertpoint, count*sizeof(drawcmd_t)); memmove(dest, insertpoint, count*sizeof(drawcmd_t));
} }
} }
*insertpoint = *cmd; *insertpoint = *cmd;
insertpoint->ui = _render_mode == 1; insertpoint->ui = _render_mode == 1;
} }
static inline static inline
void _draw(const drawcmd_t* cmd) { void _draw(const drawcmd_t* cmd) {
if(g_drawdata_endptr == g_drawdata) { if(g_drawdata_endptr == g_drawdata) {
_insert_drawcmd_at(0, cmd); _insert_drawcmd_at(0, cmd);
return; return;
} }
long top = (size_t)(g_drawdata_endptr - g_drawdata), long top = (size_t)(g_drawdata_endptr - g_drawdata),
bot = 0, bot = 0,
med = 0; med = 0;
if(top != bot) { if(top != bot) {
while(bot <= top) { while(bot <= top) {
med = floor((float)(top + bot) / 2); med = floor((float)(top + bot) / 2);
if(g_drawdata[med].depth > cmd->depth) { if(g_drawdata[med].depth > cmd->depth) {
bot = med+1; bot = med+1;
} else if(g_drawdata[med].depth < cmd->depth) { } else if(g_drawdata[med].depth < cmd->depth) {
top = med-1; top = med-1;
} else { } else {
break; break;
} }
} }
} }
size_t count = (g_drawdata_endptr - g_drawdata); size_t count = (g_drawdata_endptr - g_drawdata);
int diff = g_drawdata[med].depth - cmd->depth; int diff = g_drawdata[med].depth - cmd->depth;
while(diff > 0 && med < count) { while(diff > 0 && med < count) {
med++; med++;
diff = g_drawdata[med].depth - cmd->depth; diff = g_drawdata[med].depth - cmd->depth;
} }
_insert_drawcmd_at(med, cmd); _insert_drawcmd_at(med, cmd);
} }
void draw_sprite(const sprite_t* sprite) { void draw_sprite(const sprite_t* sprite) {
drawcmd_t d = { drawcmd_t d = {
.type=DRAWCMDTYPE_SPRITE, .type=DRAWCMDTYPE_SPRITE,
.depth=sprite->depth, .depth=sprite->depth,
.sprite=*sprite .sprite=*sprite
}; };
_draw(&d); _draw(&d);
} }
void draw_rect(const rectshape_t* rect) { void draw_rect(const rectshape_t* rect) {
drawcmd_t d = { drawcmd_t d = {
.type=DRAWCMDTYPE_RECT, .type=DRAWCMDTYPE_RECT,
.depth=rect->depth, .depth=rect->depth,
.rect=*rect .rect=*rect
}; };
_draw(&d); _draw(&d);
} }
void draw_sliced(const nineslice_t *sliced) { void draw_sliced(const nineslice_t *sliced) {
drawcmd_t d = { drawcmd_t d = {
.type=DRAWCMDTYPE_SLICED, .type=DRAWCMDTYPE_SLICED,
.depth=sliced->depth, .depth=sliced->depth,
.sliced=*sliced .sliced=*sliced
}; };
_draw(&d); _draw(&d);
} }
void draw_text(const char *str, SDL_FRect area, text_style_t style, depth_t depth) { void draw_text(const char *str, SDL_FRect area, text_style_t style, depth_t depth) {
int len = strlen(str); int len = strlen(str);
textarea_t t = { textarea_t t = {
.text = calloc(len+1, sizeof(char)), .text = calloc(len+1, sizeof(char)),
.area = area, .area = area,
.style = style, .style = style,
}; };
strcpy(t.text, str); strcpy(t.text, str);
drawcmd_t d = { drawcmd_t d = {
.type=DRAWCMDTYPE_TEXT, .type=DRAWCMDTYPE_TEXT,
.text=t, .text=t,
.depth=depth, .depth=depth,
}; };
_draw(&d); _draw(&d);
} }
spritesheet_t make_spritesheet(const char *file, int tiles_x, int tiles_y) { spritesheet_t make_spritesheet(const char *file, int tiles_x, int tiles_y) {
spritesheet_t sheet=(spritesheet_t){ spritesheet_t sheet=(spritesheet_t){
.texture=get_texture(file), .texture=get_texture(file),
.w=0,.h=0, .w=0,.h=0,
}; };
SDL_QueryTexture(sheet.texture, NULL, NULL, &sheet.w, &sheet.h); SDL_QueryTexture(sheet.texture, NULL, NULL, &sheet.w, &sheet.h);
sheet.tile_width = sheet.w / tiles_x; sheet.tile_width = sheet.w / tiles_x;
sheet.tile_height = sheet.h / tiles_y; sheet.tile_height = sheet.h / tiles_y;
return sheet; return sheet;
} }
nineslice_t make_nineslice(const char *file, int corner_px, float radius) { nineslice_t make_nineslice(const char *file, int corner_px, float radius) {
nineslice_t sliced = { nineslice_t sliced = {
.depth = RLAYER_UI, .depth = RLAYER_UI,
.corner_size = corner_px, .corner_size = corner_px,
.radius = radius, .radius = radius,
.rect= {0.0, 0.0, 1.0, 1.0}, .rect= {0.0, 0.0, 1.0, 1.0},
.texture=get_texture(file) .texture=get_texture(file)
}; };
return sliced; return sliced;
} }
sprite_t make_sprite(const char* file, float x, float y) { 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,
.uv=(SDL_Rect){0,0,0,0}, .uv=(SDL_Rect){0,0,0,0},
.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;
} }
sprite_t sprite_from_spritesheet(spritesheet_t *sheet, int index) { sprite_t sprite_from_spritesheet(spritesheet_t *sheet, int index) {
SDL_Rect rect = get_srcrect_from(sheet, index); SDL_Rect rect = get_srcrect_from(sheet, index);
return (sprite_t) { return (sprite_t) {
.texture=sheet->texture, .texture=sheet->texture,
.x=0, .y=0, .x=0, .y=0,
.origin=(SDL_FPoint){0,0}, .origin=(SDL_FPoint){0,0},
.sx=1.0, .sy=1.0, .sx=1.0, .sy=1.0,
.rot=0, .rot=0,
.depth=RLAYER_SPRITES, .depth=RLAYER_SPRITES,
.uv=rect, .uv=rect,
.flip=SDL_FLIP_NONE, .flip=SDL_FLIP_NONE,
}; };
} }
text_style_t make_text_style(const char *font, SDL_Color color, int dpi, float size) { text_style_t make_text_style(const char *font, SDL_Color color, int dpi, float size) {
TTF_Font* fnt = get_font(font, dpi); TTF_Font* fnt = get_font(font, dpi);
return (text_style_t){ return (text_style_t){
.font = fnt, .font = fnt,
.size = size, .size = size,
.color = color .color = color
}; };
} }
SDL_Rect get_srcrect_from(spritesheet_t *sheet, int index) { SDL_Rect get_srcrect_from(spritesheet_t *sheet, int index) {
int pixels = index * sheet->tile_width; int pixels = index * sheet->tile_width;
int w = sheet->w / sheet->tile_width; int w = sheet->w / sheet->tile_width;
return (SDL_Rect) { return (SDL_Rect) {
pixels%sheet->w, index/w * sheet->tile_height, pixels%sheet->w, index/w * sheet->tile_height,
sheet->tile_width, sheet->tile_height, sheet->tile_width, sheet->tile_height,
}; };
} }

View File

@ -13,74 +13,74 @@ typedef int depth_t;
extern int d_debug_next_frame; extern int d_debug_next_frame;
typedef enum drawcmdtype_t { typedef enum drawcmdtype_t {
DRAWCMDTYPE_MIN = -1, DRAWCMDTYPE_MIN = -1,
DRAWCMDTYPE_SPRITE = 0, DRAWCMDTYPE_SPRITE = 0,
DRAWCMDTYPE_RECT = 1, DRAWCMDTYPE_RECT = 1,
DRAWCMDTYPE_SLICED = 2, DRAWCMDTYPE_SLICED = 2,
DRAWCMDTYPE_TEXT = 3, DRAWCMDTYPE_TEXT = 3,
DRAWCMDTYPE_MAX DRAWCMDTYPE_MAX
} drawcmdtype_t; } drawcmdtype_t;
typedef struct text_style_t { typedef struct text_style_t {
TTF_Font* font; TTF_Font* font;
float size; float size;
SDL_Color color; SDL_Color color;
} text_style_t; } text_style_t;
typedef struct spritesheet_t { typedef struct spritesheet_t {
SDL_Texture* texture; SDL_Texture* texture;
int w, h; // width and height of texture int w, h; // width and height of texture
int tile_width, tile_height; // the width and height of each tile of each int tile_width, tile_height; // the width and height of each tile of each
} spritesheet_t; // sliced up sheet of sprites } spritesheet_t; // sliced up sheet of sprites
typedef struct nineslice_t { typedef struct nineslice_t {
SDL_Texture* texture; SDL_Texture* texture;
SDL_FRect rect; // the rectangle to fit into SDL_FRect rect; // the rectangle to fit into
depth_t depth; depth_t depth;
float radius; float radius;
int corner_size; int corner_size;
} nineslice_t; // nine-sliced texture for ui } nineslice_t; // nine-sliced texture for ui
typedef struct textarea_t { typedef struct textarea_t {
char* text; char* text;
text_style_t style; text_style_t style;
SDL_FRect area; SDL_FRect area;
} textarea_t; } textarea_t;
typedef struct sprite_t { typedef struct sprite_t {
SDL_Texture* texture; SDL_Texture* texture;
float x, y; // positions of x,y float x, y; // positions of x,y
SDL_FPoint origin; // the origin point on the sprite SDL_FPoint origin; // the origin point on the sprite
float sx, sy; // the x and y scale of the sprite float sx, sy; // the x and y scale of the sprite
float rot; // rotation around origin of the sprite float rot; // rotation around origin of the sprite
depth_t depth; // depth to render at, lower is on top, higher is on bottom depth_t depth; // depth to render at, lower is on top, higher is on bottom
SDL_Rect uv; // the source rect to render from the texture SDL_Rect uv; // the source rect to render from the texture
SDL_RendererFlip flip; // the flipped state of the sprite SDL_RendererFlip flip; // the flipped state of the sprite
} sprite_t; // a drawable and transformable texture sprite } sprite_t; // a drawable and transformable texture sprite
typedef struct rectshape_t { typedef struct rectshape_t {
float x, y, w, h; // the top-left, width and height of a rect float x, y, w, h; // the top-left, width and height of a rect
float line_width; // the width of the lines float line_width; // the width of the lines
int depth; // the depth to render at, lower is on top, higher is on bottom int depth; // the depth to render at, lower is on top, higher is on bottom
SDL_Color background; // the colour of the background SDL_Color background; // the colour of the background
SDL_Color line; // the colour of the line SDL_Color line; // the colour of the line
} rectshape_t; // a drawable rectangle with outline and background } rectshape_t; // a drawable rectangle with outline and background
typedef struct drawcmd_t { typedef struct drawcmd_t {
drawcmdtype_t type; // the thing to render drawcmdtype_t type; // the thing to render
depth_t depth; // the depth to render at depth_t depth; // the depth to render at
short ui; // 0 if this should be rendered in world space, 1 if this should be rendered in ui space short ui; // 0 if this should be rendered in world space, 1 if this should be rendered in ui space
union { union {
sprite_t sprite; // if type is sprite, render this sprite_t sprite; // if type is sprite, render this
rectshape_t rect; // if type is rect, render this rectshape_t rect; // if type is rect, render this
nineslice_t sliced; nineslice_t sliced;
textarea_t text; textarea_t text;
}; };
} drawcmd_t; // an orderable command to render, should not be directly used, use draw_* functions instead } drawcmd_t; // an orderable command to render, should not be directly used, use draw_* functions instead
typedef struct view_t { typedef struct view_t {
float x, y; // the x,y position of the centre of the screen float x, y; // the x,y position of the centre of the screen
float width; // the width of the camera float width; // the width of the camera
} view_t; } view_t;
extern int _render_mode; extern int _render_mode;
@ -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}}

220
src/corelib/scene.c Normal file
View File

@ -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);
}
}

22
src/corelib/scene.h Normal file
View 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 */

View File

@ -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); // free world array
} free(_world_objects.objects);
// reset world objects struct
object_t* _find_free_object() { _world_objects.objects = NULL;
for(int i = 0; i < WORLD_NUM_OBJECTS; ++i) { _world_objects.num = 0;
if(g_objects[i].active == 0) {
return g_objects + i;
}
}
return NULL;
} }
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
return o; object_t* o = _world_objects.objects[index];
*o = object_default();
return o;
} }
object_t* instantiate_object(const object_t *original) { object_t* instantiate_object(const object_t *original) {
object_t* obj = make_object(); // create new object with default settings
*obj = *original; object_t* obj = make_object();
obj->active = 1; *obj = *original;
return obj; obj->active = 1;
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() { 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_update != NULL) { && object->evt_draw != NULL) {
g_objects[i].evt_update(g_objects + i); object->evt_draw(object);
} }
} }
} }
void draw_objects() { object_t* world_get_object(size_t at) {
for(int i = 0; i < WORLD_NUM_OBJECTS; ++i) { if(at < _world_objects.num) {
if(g_objects[i].active == 1 return _world_objects.objects[at];
&& g_objects[i].enabled == 1 } else {
&& g_objects[i].evt_draw != NULL) { return NULL;
g_objects[i].evt_draw(g_objects + i); }
}
}
} }
static inline size_t world_num_objects() {
int _rect_overlap(float aminx, float aminy, float amaxx, float amaxy, float bminx, float bminy, float bmaxx, float bmaxy) { return _world_objects.num;
return
(
(aminx < bmaxx && aminx > bmaxx)
||
(amaxx > bminx && amaxx < bmaxx)
) && (
(aminy < bmaxy && aminy > bmaxy)
||
(amaxy > bminy && amaxy < bmaxy)
);
} }
static inline void world_reserve_objects(size_t min) {
short _collision_aabb_aabb(const object_t* a, const object_t* b) { while(_world_objects.num < min) {
const float aminx = a->collider.aabb.x + a->sprite.x, aminy = a->collider.aabb.y + a->sprite.x; _expand_world();
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 {
object->sprite.x = target_x;
object->sprite.y = target_y;
}
}
// no collision, return nothing
return NULL;
} }

View File

@ -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 */

View File

@ -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();
}

View File

@ -1,51 +1,24 @@
#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() {
// called every frame, // called every frame,
// render calls made in this function will be in *world* space // render calls made in this function will be in *world* space
} }
void update_ui() { void update_ui() {
// called every frame, // called every frame,
// 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
}