i somehow managed to forget the fucking source code '~'

main
Sara 2024-01-26 11:32:53 +01:00
parent d0381ce231
commit 449795d4f7
43 changed files with 2678 additions and 0 deletions

333
src/engine/adb.c Normal file
View File

@ -0,0 +1,333 @@
//
// adb.c
// engine
//
// Created by Scott on 14/10/2022.
//
#include "adb.h"
#include <memory.h>
#include <math.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
typedef struct asset_t {
char* filename;
size_t extention_offset;
void* instance;
asset_handle_t handle;
} asset_t;
typedef struct file_handler_t {
uint64_t ext_hash;
char* extention;
asset_load_fn load;
asset_free_fn free;
} file_handler_t;
int adb_is_init = 0;
asset_t* adb_assets_first;
size_t adb_assets_last;
size_t adb_assets_size;
file_handler_t* adb_handlers_first;
size_t adb_handlers_last;
size_t adb_handlers_size;
static inline uint64_t str_hash(const char* str);
static inline void unload_asset(asset_t* asset);
static inline size_t file_extention_offset(const char* filename);
static inline void free_asset_t(asset_t*);
//
// MODULE LIFETIME
//
int adb_init(size_t max_asset_bytes)
{
assert(adb_is_init == 0);
adb_assets_size = max_asset_bytes;
adb_handlers_size = 5;
adb_assets_last = 0;
adb_assets_first = malloc(adb_assets_size * sizeof(asset_t));
adb_handlers_last = 0;
adb_handlers_first = malloc(adb_handlers_size * sizeof(file_handler_t));
adb_is_init = 1;
return 0;
}
int adb_close()
{
for(size_t i = 0; i < adb_assets_last; ++i)
{
free_asset_t(&adb_assets_first[i]);
}
for(size_t i = 0; i < adb_handlers_last; ++i)
{
free(adb_handlers_first[i].extention);
}
free(adb_assets_first);
free(adb_handlers_first);
return 0;
}
//
// FILE HANDLERS
//
void sort_file_handlers()
{
size_t swaps = 0;
file_handler_t tmp;
do
{
swaps = 0;
for(size_t i = 1; i < adb_handlers_last; ++i)
{
if(adb_handlers_first[i-1].ext_hash > adb_handlers_first[i].ext_hash)
{
memcpy(&tmp, &adb_handlers_first[i-1], sizeof(file_handler_t));
memcpy(&adb_handlers_first[i-1], &adb_handlers_first[i], sizeof(file_handler_t));
memcpy(&adb_handlers_first[i], &tmp, sizeof(file_handler_t));
++swaps;
}
}
} while(swaps > 0);
}
void destroy_file_handler(file_handler_t* handler)
{
free(handler->extention);
free(handler);
}
file_handler_t* get_file_handler(const char* extention)
{
if(adb_handlers_last == 0)
return NULL;
uint64_t hash = str_hash(extention);
long int l = 0, r = adb_handlers_last-1, m;
file_handler_t* current;
while(l <= r)
{
m = floorf((float)(l+r)/2.f);
current = &adb_handlers_first[m];
if(current->ext_hash < hash)
l = m + 1;
else if(current->ext_hash > hash)
r = m - 1;
else if(current->ext_hash == hash)
return current;
}
return NULL;
}
int adb_register_file_handler(const char* file_extention, asset_load_fn loadfn, asset_free_fn freefn)
{
file_handler_t* existing = get_file_handler(file_extention);
if(existing == NULL)
{
if(adb_handlers_last + 1 >= adb_handlers_size)
{
adb_handlers_first = realloc(adb_handlers_first, adb_handlers_size * 2);
}
file_handler_t* new = (adb_handlers_first+adb_handlers_last);
new->load = loadfn;
new->free = freefn;
new->extention = malloc(strlen(file_extention));
strcpy(new->extention, file_extention);
new->ext_hash = str_hash(file_extention);
++adb_handlers_last;
}
else
{
if(loadfn != NULL)
existing->load = loadfn;
if(freefn != NULL)
existing->free = freefn;
}
sort_file_handlers();
return 0;
}
//
// ASSETS
//
void sort_assets()
{
size_t swaps = 0;
asset_t tmp;
do
{
swaps = 0;
for(size_t i = 0; i < adb_assets_last; ++i)
{
if(adb_assets_first[i-1].handle > adb_assets_first[i].handle)
{
memcpy(&tmp, &adb_assets_first[i-1], sizeof(asset_t));
memcpy(&adb_assets_first[i-1], &adb_assets_first[i], sizeof(asset_t));
memcpy(&adb_assets_first[i], &tmp, sizeof(asset_t));
++swaps;
}
}
} while(swaps > 0);
}
asset_t* get_asset_t(asset_handle_t handle)
{
if(adb_assets_last == 0)
return NULL;
asset_t* current;
long int l = 0, r = adb_assets_last-1, m;
while(l <= r)
{
m = floorf((float)(l+r)/2.f);
current = &adb_assets_first[m];
if(current->handle < handle)
l = m + 1;
else if(current->handle > handle)
r = m - 1;
else if(current->handle == handle)
return current;
}
return NULL;
}
void* adb_get_asset(asset_handle_t handle)
{
asset_t* asset = get_asset_t(handle);
if(asset == NULL)
return NULL;
else
return asset->instance;
}
int adb_try_get_asset(asset_handle_t handle, void** o_ptr)
{
void* asset = adb_get_asset(handle);
if(o_ptr != NULL)
memcpy(o_ptr, &asset, sizeof(void*));
return asset != NULL;
}
asset_handle_t adb_load_asset(const char* file)
{
// get the full path of the file
char* filename = realpath(file, NULL);
if(filename == NULL)
return 0;
// create asset handle
asset_handle_t handle = str_hash(filename);
// just return handle if asset is loaded
if(adb_try_get_asset(handle, NULL))
return handle;
// get the index of the file extention
size_t ext_offset = file_extention_offset(filename);
// find the handler which is supposed to handle files of this type
file_handler_t* handler = get_file_handler(filename + ext_offset);
// adb has no idea how to handle this file type
if(handler == NULL)
return 0x0;
// add the asset
adb_assets_first[adb_assets_last] = (asset_t){
.filename = filename,
.extention_offset = ext_offset,
.instance = handler->load(filename),
.handle = handle
};
// increment asset offset
++adb_assets_last;
// sort assets by handle
/// TODO: properly insert new assets rather than sorting every time an asset is added
sort_assets();
return handle;
}
void free_asset_t(asset_t* asset)
{
file_handler_t* handler = get_file_handler(asset->filename + asset->extention_offset);
assert(handler != NULL);
handler->free(asset->instance);
free(asset->filename);
}
void adb_free_asset(asset_handle_t handle)
{
asset_t* asset = get_asset_t(handle);
if(asset == NULL)
return;
free_asset_t(asset);
}
//
// FILES
//
asset_handle_t get_file_handle(const char* filename)
{
char* real_path = realpath(filename, NULL);
asset_handle_t handle = str_hash(real_path);
free(real_path);
return adb_try_get_asset(handle, NULL) ? handle : 0;
}
size_t file_extention_offset(const char* filename)
{
const char* cptr = filename + strlen(filename);
while(cptr != filename)
{
if(*cptr == '.')
return cptr - filename;
--cptr;
}
return 0;
}
uint64_t str_hash(const char* str)
{
static const int shift = (8 * sizeof(uint64_t) - 4);
static const uint64_t upper4mask = (uint64_t)0xF << shift;
uint64_t hash = 0;
uint64_t upperfour;
size_t len = 0;
for(const char* c = str; *c != '\0'; ++c)
{
upperfour = hash & upper4mask;
hash = ((hash << 4) ^ (*c));
if(upperfour != 0)
hash ^= (upperfour >> shift);
++len;
}
return hash ^ len;
}

35
src/engine/adb.h Normal file
View File

@ -0,0 +1,35 @@
//
// adb.h
// engine
//
// Created by Scott on 14/10/2022.
//
#ifndef adb_h
#define adb_h
#include <stddef.h>
#include <stdint.h>
typedef uint64_t asset_handle_t;
typedef void* (*asset_load_fn)(const char*);
typedef void (*asset_free_fn)(void*);
typedef struct asset_t asset_t;
typedef struct file_handler_t file_handler_t;
extern int adb_init(size_t max_asset);
extern asset_handle_t adb_get_file_handle(const char* filename);
extern int adb_asset_is_loaded(asset_handle_t handle);
extern void* adb_get_asset(asset_handle_t handle);
#define adb_get_asset_as(__TYPE, handle) ((__TYPE*)get_asset(handle))
extern int adb_try_get_asset(asset_handle_t handle, void** o_ptr);
extern asset_handle_t adb_load_asset(const char* file);
extern void adb_free_asset(asset_handle_t handle);
extern size_t adb_set_asset_memory(size_t max_bytes);
extern int adb_close();
extern int adb_register_file_handler(const char* file_extention, asset_load_fn loadfn, asset_free_fn freefn);
#endif /* adb_h */

View File

@ -0,0 +1,58 @@
#include "border_interaction_c.h"
#include <engine.h>
#include <vec.h>
#include <assert.h>
#include <camera.h>
#include "components.h"
ecsComponentMask border_interaction_component = nocomponent;
void _wrap_position(fvec* position, fvec margin, SDL_Rect border)
{
// wrap horizontal
if(position->x < border.x-margin.x)
position->x += border.w + margin.x*2;
else if(position->x > border.x + border.w + margin.x)
position->x -= border.w + margin.x*2;
// wrap vertical
if(position->y < border.y-margin.y)
position->y += border.h + margin.y*2;
else if(position->y > border.y + border.h + margin.x)
position->y -= border.h + margin.y*2;
}
void border_interaction_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time)
{
fvec* position;
border_interaction_t* interaction;
view_t* view = ecsGetComponentPtr(get_main_camera(), view_component);
SDL_Rect border = {-(view->width/2), -(view->height/2), view->width, view->height};
if(query_update_group(&physics_update, &delta_time))
{
for(size_t i = 0; i < count; ++i)
{
position = ecsGetComponentPtr(entities[i], position_component);
interaction = ecsGetComponentPtr(entities[i], border_interaction_component);
switch(interaction->type)
{
default: assert(0); break;
case BORDER_WRAP:
_wrap_position(position, interaction->margin, border);
break;
case BORDER_DESTROY:
{
if(position->x < border.x || position->x > border.x + border.w || position->y < border.y || position->y > border.y + border.h )
{
ecsDestroyEntity(entities[i]);
}
}
break;
}
}
}
}

View File

@ -0,0 +1,23 @@
#ifndef border_interaction_c_h
#define border_interaction_c_h
#include <ecs.h>
#include <stdint.h>
#include <vec.h>
typedef enum border_interaction_e {
BORDER_WRAP,
BORDER_DESTROY
} border_interaction_e;
typedef struct border_interaction_t {
border_interaction_e type;
fvec margin;
} border_interaction_t;
extern ecsComponentMask border_interaction_component;
extern void border_interaction_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time);
#endif /* border_interaction_c_h */

62
src/engine/camera.c Normal file
View File

@ -0,0 +1,62 @@
#include "camera.h"
ecsEntityId camera_main = noentity;
void set_main_camera(ecsEntityId camera)
{
if(get_main_camera() == noentity)
{
camera_main = camera;
}
}
ecsEntityId get_main_camera()
{
if((ecsGetComponentMask(camera_main) & CAMERA_A) == 0)
{
return (camera_main = noentity);
}
else return camera_main;
}
void get_camera_rect(ecsEntityId camera, SDL_Rect* o_rect)
{
view_t* view = ecsGetComponentPtr(camera, view_component);
fvec* position = ecsGetComponentPtr(camera, position_component);
if(view != NULL && position != NULL)
{
get_view_rect(view, o_rect);
return;
o_rect->x += (int)floorf(position->x);
o_rect->y += (int)floorf(position->y);
}
else
{
*o_rect = (SDL_Rect){0,0,0,0};
}
}
ecsEntityId create_default_camera()
{
ecsEntityId camera_id = ecsCreateEntity(CAMERA_A);
if(camera_id != noentity)
{
view_t* view = ecsGetComponentPtr(camera_id, view_component);
int width, height;
SDL_GetRendererOutputSize(renderer, &width, &height);
double asp_ratio = (double)width/height;
view->width = 50;
view->height = view->width * asp_ratio;
fvec* position = ecsGetComponentPtr(camera_id, position_component);
position->x = 0.f;
position->y = 0.f;
if(get_main_camera() == noentity)
set_main_camera(camera_id);
}
return camera_id;
}

18
src/engine/camera.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef camera_h
#define camera_h
#include "view_c.h"
#include "transform_c.h"
extern ecsEntityId get_main_camera();
extern void set_main_camera(ecsEntityId entity);
extern ecsEntityId create_default_camera();
extern void get_camera_rect(ecsEntityId camera, SDL_Rect* o_rect);
#define CAMERA_A position_component | view_component
#endif /* camera_h */

View File

@ -0,0 +1,334 @@
#include "collision_shape_c.h"
#include "transform_c.h"
#include "engine.h"
#include <string.h>
#include <assert.h>
#include <stdint.h>
#include <pthread.h>
ecsComponentMask shape_component = 0x0;
pthread_mutex_t col_reg_mutex = PTHREAD_MUTEX_INITIALIZER;
size_t col_num_registered = 0;
ecsEntityId* col_registered_shapes = NULL;
typedef struct simplex_t {
fvec points[4];
int count;
} simplex_t;
void _simplex_push(simplex_t* simpl, fvec point);
int _next_simplex(simplex_t* simpl, fvec* direction);
fvec _support_point(shape_t* a, shape_t* b, fvec direction);
fvec _shape_furthest_point(shape_t* shape, fvec direction);
int _entities_overlap(ecsEntityId a, ecsEntityId b);
void _remove_from_collision_world(size_t index);
ecsEntityId shape_overlap_test(shape_t* shape, fvec position)
{
shape_t* other;
for(size_t i = 0; i < col_num_registered; ++i)
{
do
{
other = ecsGetComponentPtr(col_registered_shapes[i], shape_component);
if(other == NULL)
_remove_from_collision_world(i);
if(col_num_registered == 0)
return noentity;
} while(other == NULL);
if(shapes_overlap(shape, other))
{
return col_registered_shapes[i];
}
}
return noentity;
}
void _transform_shape(ecsEntityId entity, shape_t* shape)
{
shape_t* a = ecsGetComponentPtr(entity, shape_component);
memcpy(shape, a, sizeof(shape_t));
fvec* position = ecsGetComponentPtr(entity, position_component);
fvec* scale = ecsGetComponentPtr(entity, scale_component);
float* rotation = ecsGetComponentPtr(entity, rotation_component);
transform_point(&shape->position_offset, position, scale, rotation);
shape->scale = *scale;
shape->rotation_offset += *rotation;
}
int _entities_overlap(ecsEntityId entity_a, ecsEntityId entity_b)
{
// make copies of the two shapes so that we can transform them safely
shape_t real_a;
shape_t real_b;
_transform_shape(entity_a, &real_a);
_transform_shape(entity_b, &real_b);
return shapes_overlap(&real_a, &real_b);
}
int shapes_overlap(shape_t* a, shape_t* b)
{
if(a == NULL || b == NULL)
{
return 0;
}
fvec direction = VRIGHT;
fvec support = _support_point(a, b, direction);
simplex_t simplex;
_simplex_push(&simplex, support);
vmulf(&direction, &direction, -1.f);
while(1)
{
support = _support_point(a, b, direction);
if(vdot(&support, &direction) <= 0)
{
return 0;
}
_simplex_push(&simplex, support);
if(_next_simplex(&simplex, &direction))
{
return 1;
}
}
}
void add_to_collision_world(ecsEntityId entity)
{
pthread_mutex_lock(&col_reg_mutex);
size_t new_registered = col_num_registered + 1;
col_registered_shapes = realloc(col_registered_shapes, sizeof(ecsEntityId) * new_registered);
col_registered_shapes[col_num_registered] = entity;
col_num_registered = new_registered;
pthread_mutex_unlock(&col_reg_mutex);
}
void _remove_from_collision_world(size_t index)
{
pthread_mutex_lock(&col_reg_mutex);
if(col_num_registered > 0)
{
size_t length_after = (col_num_registered - index);
ecsEntityId* dest = col_registered_shapes + index;
ecsEntityId* src = dest + 1;
memmove(dest, src, length_after);
col_num_registered -= 1;
if(col_num_registered == 0)
{
free(col_registered_shapes);
col_registered_shapes = NULL;
}
else
{
ecsEntityId* nreg = NULL;
do
{
nreg = realloc(col_registered_shapes, sizeof(ecsEntityId) * col_num_registered);
} while(nreg == NULL);
col_registered_shapes = nreg;
}
}
pthread_mutex_unlock(&col_reg_mutex);
}
ecsEntityId shape_sweep(ecsEntityId entity, fvec move, short trigger_overlap)
{
ecsEntityId hit = noentity;
ecsEntityId newhit;
fvec* position = ecsGetComponentPtr(entity, position_component);
fvec target_position;
vadd(&target_position, position, &move);
shape_t* shape = ecsGetComponentPtr(entity, shape_component);
shape_t* other = NULL;
while(!veq(position, &target_position))
{
vmovetowards(position, position, &target_position, 0.1f);
newhit = shape_overlap_test(shape, *position);
if(newhit)
{
if(trigger_overlap)
{
other = ecsGetComponentPtr(newhit, shape_component);
other->on_overlap(newhit, entity);
shape->on_overlap(entity, newhit);
}
if(hit == noentity)
{
hit = newhit;
return hit;
}
}
}
return hit;
}
// support functions
fvec _circle_furthest_point(shape_t* shape, fvec direction)
{
vnor(&direction, &direction);
vmulf(&direction, &direction, shape->circle.radius);
return direction;
}
fvec _array_furthest_point(fvec* array, size_t count, fvec direction)
{
vnor(&direction, &direction);
fvec point = array[0];
float dot_to_beat = vdot(array, &direction);
for(int i = 0; i < count; ++i)
{
float dot = vdot(array + i, &direction);
if(dot > dot_to_beat)
{
dot_to_beat = dot;
point = array[i];
}
}
return point;
}
fvec _rect_furthest_point(shape_t* shape, fvec direction)
{
fvec points[4] = {
{shape->rect.x, shape->rect.y},
{shape->rect.x + shape->rect.w, shape->rect.y},
{shape->rect.x + shape->rect.w, shape->rect.y + shape->rect.h},
{shape->rect.x, shape->rect.y + shape->rect.h}
};
fvec point = _array_furthest_point(points, 4, direction);
return point;
}
fvec _shape_furthest_point(shape_t* shape, fvec direction)
{
switch(shape->type)
{
default:
return VZERO;
case SHAPE_CIRCLE:
return _circle_furthest_point(shape, direction);
case SHAPE_RECT:
return _circle_furthest_point(shape, direction);
}
}
fvec _support_point(shape_t* a, shape_t* b, fvec direction)
{
fvec inv_dir;
vmulf(&inv_dir, &direction, -1.f);
fvec fpa = _shape_furthest_point(a, direction);
fvec fpb = _shape_furthest_point(b, inv_dir);
transform_point(&fpa, &a->position_offset, &a->scale, &a->rotation_offset);
transform_point(&fpb, &b->position_offset, &b->scale, &b->rotation_offset);
vsub(&direction, &fpa, &fpb);
return direction;
}
void _simplex_push(simplex_t* simpl, fvec point)
{
memcpy(&simpl->points[1], simpl->points, sizeof(fvec) * 3);
simpl->points[0] = point;
}
int _same_direction(fvec direction, fvec ao)
{
return vdot(&direction, &ao) > 0;
}
int _simplex_line(simplex_t* simplex, fvec* direction)
{
fvec a = simplex->points[0];
fvec b = simplex->points[1];
fvec ab, ao;
vmulf(&ao, &a, -1.f);
vsub(&ab, &b, &a);
if(_same_direction(ab, ao))
{
vtriple(direction, &ab, &ao);
}
else
{
simplex->count = 1;
*direction = ao;
}
return 0;
}
int _simplex_triangle(simplex_t* simplex, fvec* direction)
{
fvec a = simplex->points[0];
fvec b = simplex->points[1];
fvec c = simplex->points[2];
fvec ab, ac, ao;
vsub(&ab, &b, &a);
vsub(&ac, &c, &a);
vmulf(&ao, &a, -1.f);
fvec abp, acp;
vtriple(&abp, &ac, &ab);
vtriple(&acp, &ab, &ac);
if(vdot(&abp, &ao) > 0)
{
// remove c
*direction = abp;
simplex->count = 2;
return 0;
}
else if(vdot(&acp, &ao) > 0)
{
// remove b
simplex->points[1] = simplex->points[2];
simplex->count = 2;
return 0;
}
return 1;
}
int _next_simplex(simplex_t* simpl, fvec* direction)
{
switch(simpl->count)
{
case 2:
return _simplex_line(simpl, direction);
case 3:
return _simplex_triangle(simpl, direction);
}
}

View File

@ -0,0 +1,39 @@
#ifndef collision_shape_c_h
#define collision_shape_c_h
#include <ecs.h>
#include <vec.h>
#include <SDL2/SDL.h>
typedef void (*on_overlap_fn)(ecsEntityId self, ecsEntityId other);
extern ecsComponentMask shape_component;
typedef enum SHAPE_TYPE {
SHAPE_CIRCLE,
SHAPE_RECT
} SHAPE_TYPE;
typedef struct shape_t {
SHAPE_TYPE type;
on_overlap_fn on_overlap;
float rotation_offset;
fvec position_offset;
fvec scale;
union {
SDL_Rect rect;
struct {
float radius;
} circle;
};
} shape_t;
extern ecsEntityId shape_sweep(ecsEntityId entity, fvec move, short trigger_overlap);
extern int shapes_overlap(shape_t* a, shape_t* b);
extern ecsEntityId shape_overlap_test(shape_t* shape, fvec position);
extern void add_to_collision_world(ecsEntityId entity);
#endif /* collision_shape_c_h */

3
src/engine/components.c Normal file
View File

@ -0,0 +1,3 @@
#include "components.h"
ecsComponentMask collision_shape_component = 0x0;

11
src/engine/components.h Normal file
View File

@ -0,0 +1,11 @@
#ifndef components_h
#define components_h
#include "transform_c.h"
#include "velocity_c.h"
#include "sprite_c.h"
#include "collision_shape_c.h"
#include "border_interaction_c.h"
#include "view_c.h"
#endif /* components_h */

300
src/engine/engine.c Normal file
View File

@ -0,0 +1,300 @@
#include "engine.h"
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <SDL2/SDL_ttf.h>
#include <stdlib.h>
#include <time.h>
#include <ecs.h>
#include "components.h"
#include "adb.h"
#include "input.h"
#include "ui.h"
typedef struct update_group_t {
double last_time;
double time;
double delta_time;
double target_delta;
short is_frame;
} update_group_t;
SDL_Window* window;
SDL_Renderer* renderer;
int engine_wants_to_quit;
update_group_t frame_update;
update_group_t physics_update;
void system_window_clear(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time);
void system_window_display(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time);
void system_update_groups(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time);
void system_poll_events(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time);
void engine_init();
void engine_run();
void engine_handle_event(SDL_Event* event);
void engine_clean();
void engine_update_group(update_group_t* group);
void* asset_load_sdl_image(const char* filename)
{
SDL_Surface* surf = IMG_Load(filename);
SDL_Texture* tex = SDL_CreateTextureFromSurface(renderer, surf);
SDL_FreeSurface(surf);
return tex;
}
void asset_free_sdl_image(void* instance)
{
SDL_Texture* tex = instance;
if(tex != NULL)
{
SDL_DestroyTexture(tex);
}
}
void* asset_load_ttf_font(const char* filename)
{
TTF_Font* font = TTF_OpenFont(filename, 50);
return font;
}
void asset_free_ttf_font(void* instance)
{
TTF_Font* font = instance;
if(instance != NULL)
{
TTF_CloseFont(font);
}
}
int main(int argc, char* argv[])
{
engine_init();
engine_run();
engine_clean();
}
void engine_init()
{
engine_wants_to_quit = 0;
// init asset database for 100 assets
adb_init(100);
// register default image file handlers
adb_register_file_handler(".png", &asset_load_sdl_image, &asset_free_sdl_image);
adb_register_file_handler(".jpg", &asset_load_sdl_image, &asset_free_sdl_image);
// default font file handlers
adb_register_file_handler(".ttf", &asset_load_ttf_font, &asset_free_ttf_font);
adb_register_file_handler(".otf", &asset_load_ttf_font, &asset_free_ttf_font);
// load default init settings
engine_init_t init_settings;
default_engine_init_settings(&init_settings);
// set default target framerates
set_update_group_target_framerate(&frame_update, 60);
physics_update.target_delta = 0.f;
engine_update_group(&frame_update);
engine_update_group(&physics_update);
// allow game to adjust init settings as needed
game_config(&init_settings);
// init sdl, create window and create renderer from window
SDL_Init(init_settings.sdl_init_flags);
TTF_Init();
IMG_Init(IMG_INIT_JPG | IMG_INIT_PNG);
window = SDL_CreateWindow(init_settings.window_name,
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
init_settings.window_width,
init_settings.window_height,
init_settings.window_init_flags);
if(window == NULL)
{
exit(1);
}
renderer = SDL_CreateRenderer(window, init_settings.renderer_index,
init_settings.renderer_init_flags);
if(renderer == NULL)
{
exit(2);
}
// init input system
input_init();
// initialize runtime object database
ecsInit();
// initialize imgui renderer
uiInit(renderer);
position_component = ecsRegisterComponent(fvec);
rotation_component = ecsRegisterComponent(float);
scale_component = ecsRegisterComponent(fvec);
linear_velocity_component = ecsRegisterComponent(fvec);
angular_velocity_component = ecsRegisterComponent(float);
linear_drag_component = ecsRegisterComponent(float);
angular_drag_component = ecsRegisterComponent(float);
sprite_component = ecsRegisterComponent(sprite_t);
border_interaction_component = ecsRegisterComponent(border_interaction_t);
view_component = ecsRegisterComponent(view_t);
shape_component = ecsRegisterComponent(shape_t);
// enable core engine systems
ecsEnableSystem(&system_poll_events, nocomponent, ECS_NOQUERY, 0, -300);
ecsEnableSystem(&system_update_groups, nocomponent, ECS_NOQUERY, 0, -200);
ecsEnableSystem(&system_update_views, view_component, ECS_QUERY_ALL, MAX_THREADS, 499);
// 500-599 is for rendering
ecsEnableSystem(&system_window_clear, nocomponent, ECS_NOQUERY, 0, 500);
ecsEnableSystem(&render_textures_s, sprite_component | position_component, ECS_QUERY_ALL, 0, 550);
ecsEnableSystem(&system_window_display, nocomponent, ECS_NOQUERY, 0, 599);
// 600-999 is for post-render updates
ecsEnableSystem(&linear_velocity_update_s, position_component | linear_velocity_component, ECS_QUERY_ALL, MAX_THREADS, 750);
ecsEnableSystem(&angular_velocity_update_s, rotation_component | angular_velocity_component, ECS_QUERY_ALL, MAX_THREADS, 755);
ecsEnableSystem(&border_interaction_s, border_interaction_component | position_component, ECS_QUERY_ALL, MAX_THREADS, 810);
ecsEnableSystem(&wrap_rotation_s, rotation_component, ECS_QUERY_ALL, 8, 815);
// initialize game
game_init();
// run created tasks
ecsRunTasks();
}
void engine_run()
{
float actual_time;
while(!engine_wants_to_quit)
{
float physics_update_delta;
query_update_group(&physics_update, &physics_update_delta);
ecsRunSystems(physics_update_delta);
}
}
void engine_handle_event(SDL_Event* event)
{
switch(event->type)
{
default: break;
case SDL_QUIT:
engine_wants_to_quit = 1;
break;
case SDL_WINDOWEVENT:
{
switch(event->window.event)
{
default: break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
ecsEnableSystem(&system_update_views, view_component, ECS_QUERY_ALL, 8, -500);
break;
}
break;
}
}
}
void engine_clean()
{
// quit game, ui asset database, ecs
game_quit();
uiTerminate();
adb_close();
ecsTerminate();
input_terminate();
// delete renderer and window, quit sdl
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
}
void engine_update_group(update_group_t* group)
{
group->time = (double)clock() / (double)CLOCKS_PER_SEC;
double delta_time = group->time - group->last_time;
group->is_frame = delta_time >= group->target_delta;
if(group->is_frame)
{
group->last_time = group->time;
group->delta_time = delta_time;
}
}
void system_update_groups(ecsEntityId* entities, ecsComponentMask* components, size_t size, float delta_time)
{
engine_update_group(&frame_update);
engine_update_group(&physics_update);
}
void system_window_clear(ecsEntityId* entities, ecsComponentMask* components, size_t size, float delta_time)
{
if(query_update_group(&frame_update, &delta_time))
{
// clear screen all black
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
}
}
void system_window_display(ecsEntityId* entities, ecsComponentMask* components, size_t size, float delta_time)
{
if(query_update_group(&frame_update, &delta_time))
{
// swap buffer
SDL_RenderPresent(renderer);
}
}
void system_poll_events(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time)
{
SDL_Event evt;
if(query_update_group(&frame_update, &delta_time))
{
while(SDL_PollEvent(&evt))
{
engine_handle_event(&evt);
}
}
}
short query_update_group(update_group_t* update_group, float* out_delta_time)
{
if(out_delta_time != NULL)
(*out_delta_time) = (float)update_group->delta_time;
return update_group->is_frame;
}
void set_update_group_target_framerate(update_group_t* group, int fps)
{
group->target_delta = 1.f / fps;
}
void default_engine_init_settings(engine_init_t* init_settings)
{
(*init_settings) = (engine_init_t){
.window_width = 640, .window_height = 420,
.window_init_flags = SDL_WINDOW_SHOWN,
.sdl_init_flags = SDL_INIT_VIDEO,
.renderer_init_flags = SDL_RENDERER_ACCELERATED,
.renderer_index = -1,
.window_name = "Game"
};
}

48
src/engine/engine.h Normal file
View File

@ -0,0 +1,48 @@
#ifndef _engine_h
#define _engine_h
#include <stdint.h>
#ifndef MAX_THREADS
#define MAX_THREADS 8
#endif /* MAX_THREADS */
typedef struct engine_init_t {
int window_width, window_height;
uint32_t window_init_flags;
uint32_t sdl_init_flags;
uint32_t renderer_init_flags;
int renderer_index;
const char* window_name;
} engine_init_t;
typedef struct update_group_t update_group_t;
extern update_group_t frame_update;
extern update_group_t physics_update;
extern void default_engine_init_settings(engine_init_t*);
extern void game_config(engine_init_t*);
extern void game_init();
extern void game_quit();
extern short query_update_group(update_group_t* group, float* delta_time);
extern void set_update_group_target_framerate(update_group_t* group, int fps);
extern struct SDL_Renderer* renderer;
#if defined(DEBUG)
#include <stdio.h>
#define E_LOG(...)\
fprintf(stdout, "%s:%d: ", __FILE__, __LINE__);\
fprintf(stdout, __VA_ARGS__);\
fprintf(stdout, "\n")
#else
#define E_LOG(...)
#endif
#include "components.h"
#endif /* !_engine_h */

16
src/engine/fmath.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef fmath_h
#define fmath_h
static inline
float fmovetowards(float start, float target, float delta)
{
if(start - delta > target)
start -= delta;
else if(start + delta < target)
start += delta;
else
start = target;
return start;
}
#endif /* fmath_h */

27
src/engine/input.c Normal file
View File

@ -0,0 +1,27 @@
#include "input.h"
#include <SDL2/SDL.h>
const uint8_t* input_key_array;
int input_num_keys;
void input_init()
{
input_key_array = SDL_GetKeyboardState(&input_num_keys);
}
void input_terminate() {}
int8_t input_axis(unsigned key_negative, unsigned key_positive)
{
return input_button(key_positive) - input_button(key_negative);
}
int8_t input_button(unsigned key)
{
if(key < input_num_keys)
{
return input_key_array[key];
}
return 0;
}

11
src/engine/input.h Normal file
View File

@ -0,0 +1,11 @@
#ifndef input_h
#define input_h
#include <stdint.h>
extern void input_init();
extern void input_terminate();
extern int8_t input_axis(unsigned key_negative, unsigned key_positive);
extern int8_t input_button(unsigned key);
#endif /* input_h */

65
src/engine/sprite_c.c Normal file
View File

@ -0,0 +1,65 @@
#include "sprite_c.h"
#include <engine.h>
#include <stdint.h>
#include <vec.h>
#include "transform_c.h"
#include "camera.h"
ecsComponentMask sprite_component = 0x0;
void render_textures_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time)
{
SDL_Rect camera_rect;
ecsEntityId id;
sprite_t* sprite;
fvec* position;
float* rotation;
fvec* scale;
float scale_mul;
float angle;
SDL_FRect dst;
int window_width, window_height;
SDL_GetRendererOutputSize(renderer, &window_width, &window_height);
get_camera_rect(get_main_camera(), &camera_rect);
scale_mul = (float)window_width / camera_rect.w;
SDL_RenderDrawRect(renderer, &camera_rect);
if(query_update_group(&frame_update, &delta_time))
{
for(size_t i = 0; i < count; ++i)
{
id = entities[i];
sprite = ecsGetComponentPtr(id, sprite_component);
position = ecsGetComponentPtr(id, position_component);
rotation = ecsGetComponentPtr(id, rotation_component);
scale = ecsGetComponentPtr(id, scale_component);
if(sprite->texture != NULL)
{
angle = rotation ? (*rotation) : 0;
dst = (SDL_FRect){
.x=(position->x - camera_rect.x) * scale_mul,
.y=(position->y - camera_rect.y) * scale_mul,
.w=(scale ? scale->x : 1.f) * scale_mul,
.h=(scale ? scale->y : 1.f) * scale_mul,
};
dst.x -= dst.w * 0.5f;
dst.y -= dst.h * 0.5f;
SDL_RenderCopyExF(
renderer, sprite->texture,
&sprite->rect, &dst, (double)angle,
&(SDL_FPoint){.x=dst.w*0.5f, .y=dst.h*0.5f},
0x0
);
}
}
}
}

18
src/engine/sprite_c.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef sprite_c_h
#define sprite_c_h
#include <ecs.h>
#include <SDL2/SDL.h>
extern ecsComponentMask sprite_component;
#define SPRITE_A ((ecsComponentMask)(sprite_component | position_component))
typedef struct sprite_t {
SDL_Texture* texture;
SDL_Rect rect;
} sprite_t;
extern void render_textures_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time);
#endif /* sprite_c_h */

28
src/engine/transform_c.c Normal file
View File

@ -0,0 +1,28 @@
#include "transform_c.h"
#include <engine.h>
#include <vec.h>
#include <SDL2/SDL.h>
ecsComponentMask position_component = 0x0;
ecsComponentMask rotation_component = 0x0;
ecsComponentMask scale_component = 0x0;
ecsComponentMask wrap_screen_component = 0x0;
void wrap_rotation_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time)
{
float* rotation;
if(query_update_group(&physics_update, &delta_time))
{
for(size_t i = 0; i < count; ++i)
{
rotation = ecsGetComponentPtr(entities[i], rotation_component);
if(*rotation < -360.f)
*rotation += 360.f;
else if(*rotation > 360.f)
*rotation -= 360.f;
}
}
}

34
src/engine/transform_c.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef transform_c_h
#define transform_c_h
#include <ecs.h>
#include <vec.h>
extern ecsComponentMask position_component;
extern ecsComponentMask rotation_component;
extern ecsComponentMask scale_component;
#define TRANSFORM_A ((ecsComponentMask)(position_component | rotation_component | scale_component))
extern void wrap_screen_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time);
extern void wrap_rotation_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time);
static inline
void transform_point(fvec* r, fvec* position, fvec* scale, float* rotation)
{
r->x *= scale->x;
r->y *= scale->y;
vrot(r, r, *rotation);
vadd(r, r, position);
}
static inline
void inverse_transform_point(fvec* r, fvec* position, fvec* scale, float* rotation)
{
r->x /= scale->x;
r->y /= scale->y;
vrot(r, r, -(*rotation));
vsub(r, r, position);
}
#endif /* transform_c_h */

386
src/engine/ui.c Normal file
View File

@ -0,0 +1,386 @@
//
// ui.c
// engine
//
// Created by Scott on 21/10/2022.
//
#include "ui.h"
#include <math.h>
#include <stdint.h>
SDL_Renderer* uiTarget;
typedef struct ui_selection_t {
uintptr_t item;
short keepSelected;
enum {
UI_SELECT_DEFAULT,
UI_SELECT_TEXT
} selectKind;
union {
struct {
long start, end;
} text_select;
};
} ui_selection_t;
ui_selection_t uiSelection;
uint32_t uiCurrentMouseState;
uint32_t uiLastMouseState;
int uiMouseX, uiMouseY;
SDL_Rect uiCurrentWindow;
int uiIndentLevel;
int uiSingleLineHeight = 30;
int uiLineSpacing = 5;
int uiWindowPadding = 10;
int uiTotalHeight;
int uiRenderSameLine;
SDL_Rect uiNextLineRect;
struct ui_style_t {
TTF_Font* font;
int indentPixels;
} uiStyle;
void uiAddPixels(int n);
void uiUpdateMouseState()
{
uiLastMouseState = uiCurrentMouseState;
uiCurrentMouseState = SDL_GetMouseState(&uiMouseX, &uiMouseY);
}
int uiCanSelect(void* ptr)
{
intptr_t item = (intptr_t)ptr;
return uiSelection.item == item || uiSelection.item == 0;
}
void uiSelect(void* ptr, short keepActive)
{
uiSelection.item = (intptr_t)ptr;
uiSelection.keepSelected = keepActive;
}
short uiIsSelected(void* ptr)
{
return uiSelection.item == (intptr_t)ptr;
}
void uiInit(SDL_Renderer* target)
{
uiTarget = target;
uiSelection.item = 0;
memset(&uiStyle, 0, sizeof(struct ui_style_t));
uiUpdateMouseState();
uiStyle.indentPixels = 10;
}
void uiTerminate()
{
}
void uiSetFont(TTF_Font* font)
{
uiStyle.font = font;
}
// check if a mouse button was pressed on this ui frame
static inline
short uiMouseButtonPressed(uint32_t buttonMask)
{
return
(buttonMask & uiCurrentMouseState) != 0 && // button is down
(buttonMask & uiLastMouseState) == 0; // button was down
}
// check if a mouse button was released on this ui frame
static inline
short uiMouseButtonReleased(uint32_t buttonMask)
{
return
(buttonMask & uiLastMouseState) != 0 && // button was down
(buttonMask & uiCurrentMouseState) == 0; // button is up
}
// check if a mouse button is currently pressed
static inline
short uiMouseButtonDown(uint32_t buttonMask)
{
return uiCurrentMouseState & buttonMask;
}
// change the SDL render target
void uiSetTarget(SDL_Renderer* renderer)
{
uiTarget = renderer;
}
// begin a new frame
void uiBeginFrame()
{
if(uiSelection.keepSelected == 0)
uiSelection.item = 0;
uiUpdateMouseState();
}
// push any number of lines
void uiSkipLines(int n)
{
while(n-- >= 0)
{
uiNextLine();
}
}
// push a new line
void uiNextLine()
{
if(uiRenderSameLine > 0)
{
uiRenderSameLine--;
uiNextLineRect.x += uiNextLineRect.w;
}
else
{
uiAddPixels(uiSingleLineHeight + uiLineSpacing);
}
}
// push the next line down by a number of pixels
void uiAddPixels(int px)
{
uiTotalHeight += px;
uiNextLineRect = uiCurrentWindow;
uiNextLineRect.h = uiSingleLineHeight;
uiNextLineRect.y += uiTotalHeight;
uiNextLineRect.w -= uiIndentLevel * uiStyle.indentPixels * 2;
uiNextLineRect.x += uiIndentLevel * uiStyle.indentPixels;
}
// draw the next n elements on the same line
void uiSameLine(int n)
{
if(n > 1)
{
uiRenderSameLine += n-1;
uiNextLineRect.w /= n;
}
}
// start a window fitting within the given rectangle
int uiBeginWindow(SDL_Rect* rect, const char* label, uiWindowState* state)
{
short isActive = ((*state) & UI_WINDOWSTATE_MINIMIZED) == 0;
uiTotalHeight = 0;
uiIndentLevel = 0;
memcpy(&uiCurrentWindow, rect, sizeof(SDL_Rect));
if(isActive)
{
SDL_SetRenderDrawColor(uiTarget, 20, 20, 20, 255);
SDL_RenderFillRect(uiTarget, rect);
}
uiNextLineRect = uiCurrentWindow;
uiNextLineRect.h = uiSingleLineHeight;
int toggled = uiButton(label);
uiCurrentWindow.x += uiWindowPadding;
uiCurrentWindow.y += uiWindowPadding;
uiCurrentWindow.w -= uiWindowPadding*2;
uiCurrentWindow.h -= uiWindowPadding*2;
uiNextLineRect.x = uiCurrentWindow.x;
uiNextLineRect.w = uiCurrentWindow.w;
uiNextLineRect.y = uiCurrentWindow.y + uiTotalHeight;
uiNextLineRect.h = uiSingleLineHeight;
if(toggled)
{
(*state) ^= UI_WINDOWSTATE_MINIMIZED;
}
return isActive;
}
// check if point (x,y) is in r
int uiInArea(SDL_Rect* r, int x, int y)
{
int x_min = r->x, x_max = r->x + r->w;
int y_min = r->y, y_max = r->y + r->h;
return (x >= x_min && x < x_max && y >= y_min && y < y_max);
}
void uiDrawText(const char* text, SDL_Rect dstrect, SDL_Color colour)
{
int height = TTF_FontHeight(uiStyle.font);
double fontToLineHeight = (double)height/(double)uiSingleLineHeight;
SDL_Rect srcrect = { 0, 0, dstrect.w * fontToLineHeight, dstrect.h * fontToLineHeight };
SDL_Surface* surface = TTF_RenderText_Solid_Wrapped(uiStyle.font, text, colour, srcrect.w);
dstrect.w = surface->w / fontToLineHeight;
if(surface == NULL)
{
SDL_Log("Failed to render text to surface:\n%s", SDL_GetError());
return;
}
SDL_Texture* tex = SDL_CreateTextureFromSurface(uiTarget, surface);
SDL_FreeSurface(surface);
if(tex == NULL)
return;
SDL_RenderCopy(uiTarget, tex, &srcrect, &dstrect);
SDL_DestroyTexture(tex);
}
// draw an interactive slider with a min, max and step
int uiSlider(float* value, float min, float max, float step)
{
const int SLIDER_WIDTH = 10;
const int LINE_THICKNESS = 5;
float startValue = *value;
int valueChanged = 0;
float valuePercentage = ((*value)-min) / (max - min);
SDL_Rect position = uiNextLineRect;
position.x += SLIDER_WIDTH/2;
position.w -= SLIDER_WIDTH;
SDL_Rect lineRect = {
position.x, position.y + uiSingleLineHeight/2,
position.w, LINE_THICKNESS
};
SDL_Color sliderColor = {230, 230, 230, 255};
if(uiMouseButtonPressed(SDL_BUTTON_LEFT))
{
if(uiCanSelect(value) && uiInArea(&position, uiMouseX, uiMouseY))
{
uiSelect(value, 1);
}
}
else if(uiIsSelected(value) && !uiMouseButtonDown(SDL_BUTTON_LEFT))
{
uiSelection.keepSelected = 0;
}
if(uiIsSelected(value))
{
valuePercentage = (float)(uiMouseX - position.x) / (float)(position.w);
if(valuePercentage < 0)
valuePercentage = 0;
else if(valuePercentage > 1)
valuePercentage = 1;
(*value) = valuePercentage * (max - min);
(*value) = roundf((*value) * (1.f/step)) * step + min;
valuePercentage = ((*value) - min) / (max-min);
if(*value != startValue)
{
valueChanged = 1;
}
}
SDL_Rect sliderRect = {
position.x + valuePercentage * position.w - SLIDER_WIDTH/2, position.y,
SLIDER_WIDTH, uiSingleLineHeight
};
SDL_SetRenderDrawColor(uiTarget, 100, 100, 100, 255);
SDL_RenderFillRect(uiTarget, &lineRect);
if(valuePercentage >= 0 && valuePercentage <= 1)
{
SDL_SetRenderDrawColor(uiTarget, sliderColor.r, sliderColor.g, sliderColor.b, sliderColor.a);
SDL_RenderFillRect(uiTarget, &sliderRect);
}
uiNextLine();
return valueChanged;
}
// draw a clickable button
int uiButton(const char* label)
{
SDL_Rect position = uiNextLineRect;
short clicked = 0;
if(uiMouseButtonPressed(SDL_BUTTON_LEFT))
{
if(uiCanSelect(0) && uiInArea(&position, uiMouseX, uiMouseY))
{
uiSelect(0, 0);
clicked = 1;
}
}
SDL_SetRenderDrawColor(uiTarget, 100, 100, 100, 255);
SDL_RenderFillRect(uiTarget, &position);
uiLabel(label);
return clicked;
}
void uiLabel(const char* label)
{
SDL_Colour white = {255, 255, 255, 255};
uiDrawText(label, uiNextLineRect, white);
uiNextLine();
}
void uiLabelNext(const char* label, float ratio)
{
int y = uiNextLineRect.y;
uiNextLineRect.w = uiCurrentWindow.w * ratio;
uiLabel(label);
uiTotalHeight -= uiSingleLineHeight + uiLineSpacing;
uiNextLineRect.y = y;
uiNextLineRect.x += uiCurrentWindow.w * ratio;
uiNextLineRect.w = uiCurrentWindow.w * (1-ratio);
uiNextLineRect.h = uiSingleLineHeight;
}
void uiHeader(const char* label)
{
uiSubIndent();
uiAddPixels(uiSingleLineHeight/2);
uiLabel(label);
uiAddIndent();
}
void uiSetIndent(int n)
{
uiIndentLevel = n;
uiIndentLevel = uiIndentLevel >= 0 ? uiIndentLevel : 0;
// force update of next line rect
uiAddPixels(0);
}
void uiAddIndent()
{
uiSetIndent(uiIndentLevel + 1);
}
void uiSubIndent()
{
uiSetIndent(uiIndentLevel - 1);
}

44
src/engine/ui.h Normal file
View File

@ -0,0 +1,44 @@
//
// ui.h
// engine
//
// Created by Scott on 21/10/2022.
//
#ifndef ui_h
#define ui_h
#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
#include <stdint.h>
typedef uint32_t uiWindowState;
#define UI_WINDOWSTATE_ALLOW_MINIMIZE ((uiWindowState)0x1)
#define UI_WINDOWSTATE_MINIMIZED ((uiWindowState)0x2)
#define UI_WINDOWSTATE_DEFAULT (UI_WINDOWSTATE_ALLOW_MINIMIZE)
extern void uiInit(SDL_Renderer* target);
extern void uiTerminate();
extern void uiSetTarget(SDL_Renderer* renderer);
extern void uiSetFont(TTF_Font* font);
extern void uiBeginFrame();
extern int uiBeginWindow(SDL_Rect* rect, const char* label, uiWindowState* state);
extern void uiSkipLines(int n);
extern void uiNextLine();
extern void uiSameLine(int n);
extern int uiSlider(float* value, float min, float max, float step);
extern int uiButton(const char* label);
extern void uiLabel(const char* label);
extern void uiLabelNext(const char* label, float ratio);
extern void uiHeader(const char* label);
extern void uiAddIndent();
extern void uiSubIndent();
extern void uiSetIndent(int n);
#endif /* ui_h */

203
src/engine/vec.h Normal file
View File

@ -0,0 +1,203 @@
//
// vec.h
// sim
//
// Created by Scott on 11/10/2022.
//
#ifndef vec_h
#define vec_h
#include <math.h>
#include <string.h>
typedef struct fvec {
float x, y;
} fvec;
static inline void vadd(fvec* r, fvec* a, fvec* b)
{ r->x = a->x + b->x; r->y = a->y + b->y; }
static inline void vsub(fvec* r, fvec* a, fvec* b)
{ r->x = a->x - b->x; r->y = a->y - b->y; }
static inline void vmul(fvec* r, fvec* a, fvec* b)
{ r->x = a->x * b->x; r->y = a->y * b->y; }
static inline void vmulf(fvec* r, fvec* a, float b)
{ r->x = a->x * b; r->y = a->y * b; }
static inline void vdiv(fvec* r, fvec* a, fvec* b)
{ r->x = a->x / b->x; r->y = a->y / b->y; }
static inline float vmag(fvec* a)
{ return sqrt(fabsf(a->x*a->x) + fabsf(a->y*a->y)); }
static inline int veq(fvec* a, fvec* b)
{ return(a->x == b->x && a->y == b->y); }
static inline void vnor(fvec* r, fvec* a)
{
float m = vmag(a);
if(m == 0)
{
*r = (fvec){0.f, 0.f};
}
else
{
r->x = a->x / m;
r->y = a->y / m;
}
}
static inline void vabs(fvec* r, fvec* a)
{
r->x = fabsf(a->x);
r->y = fabsf(a->y);
}
static inline float vdist(fvec* a, fvec* b)
{
fvec diff;
vsub(&diff, a, b);
vabs(&diff, &diff);
float m = vmag(&diff);
return isnan(m) ? 0 : m;
}
static inline void vmovetowards(fvec* r, fvec* from, fvec* to, float max_delta)
{
fvec diff, dir;
if(max_delta == 0 || isnan(max_delta))
{
memmove(r, from, sizeof(fvec));
return;
}
vsub(&diff, to, from);
// get the distance between from and to
float diffm = vmag(&diff);
// if the distance is zero, set return to target vector
if(diffm == 0 || isnan(diffm))
{
memmove(r, to, sizeof(fvec));
return;
}
vmulf(&dir, &diff, 1.f/diffm * max_delta);
float dirm = vmag(&dir);
if(dirm >= diffm)
{
memmove(r, to, sizeof(fvec));
}
else
{
vadd(r, from, &dir);
}
}
static inline void vlerp(fvec* r, fvec* from, fvec* to, float t)
{
fvec dir;
vsub(&dir, to, from);
vnor(&dir, &dir);
vmulf(&dir, &dir, t);
vadd(r, from, &dir);
}
static inline void vmin(fvec* r, fvec* a, float min)
{
float m = vmag(a);
if(m < min)
{
vmulf(r, a, (1.f/m)*min);
}
else
{
*r = *a;
}
}
static inline void vmax(fvec* r, fvec* a, float max)
{
float m = vmag(a);
if(m > max)
vmulf(r, a, (1.f/m)*max);
else
*r = *a;
}
static inline void vclamp(fvec* r, fvec* a, float min, float max)
{
float m = vmag(a);
if(m > max)
vmulf(r, a, (1.f/m)*max);
else if(m < min)
vmulf(r, a, (1.f/m)*min);
else
*r = *a;
}
static inline float vdot(fvec* a, fvec* b)
{
return(a->x * b->x + a->y * b->y);
}
static inline float vang(fvec* a, fvec* b)
{
return atan2f(a->y, a->x) - atan2f(b->y, b->x);
}
static inline void vrot(fvec* r, fvec* a, float ang)
{
fvec tmp = *a;
r->x = cosf(ang)*tmp.x - sinf(ang)*tmp.y;
r->y = sinf(ang)*tmp.x + cosf(ang)*tmp.y;
}
static inline void vperpend(fvec* r, fvec* a)
{
r->y = a->x;
r->x = -a->y;
}
static inline void v3cross(float* r, float* a, float* b)
{
float store[3];
memcpy(store, r, sizeof(float) * 3);
store[0] = a[1]*b[2] - a[2]*b[1];
store[1] = a[2]*b[0] - a[0]*b[2];
store[2] = a[0]*b[1] - a[3]*b[0];
memcpy(r, store, sizeof(float) * 3);
}
static inline void vtriple(fvec* r, fvec* a, fvec* b)
{
float va[3];
float vb[3];
float vr[3];
memcpy(va, a, sizeof(float) * 2);
memcpy(vb, b, sizeof(float) * 2);
memset(vr, 0.f, sizeof(float) * 3);
va[2] = vb[2] = 0;
v3cross(vr, va, vb);
v3cross(vr, vr, va);
r->x = vr[0];
r->y = vr[1];
}
#define VZERO ((fvec){0.f,0.f})
#define VUP ((fvec){0.f, 1.f})
#define VDOWN ((fvec){0.f, -1.f})
#define VLEFT ((fvec){-1.f, 0.f})
#define VRIGHT ((fvec){1.f, 0.f})
#endif /* vec_h */

86
src/engine/velocity_c.c Normal file
View File

@ -0,0 +1,86 @@
#include "velocity_c.h"
#include <engine.h>
#include <vec.h>
#include "components.h"
ecsComponentMask linear_velocity_component = 0x0;
ecsComponentMask angular_velocity_component = 0x0;
ecsComponentMask linear_drag_component = 0x0;
ecsComponentMask angular_drag_component = 0x0;
void linear_velocity_update_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time)
{
fvec* position;
fvec* velocity;
float* drag;
shape_t* collision_shape;
fvec actual_velocity;
if(query_update_group(&physics_update, &delta_time))
{
for(size_t i = 0; i < count; ++i)
{
velocity = ecsGetComponentPtr(entities[i], linear_velocity_component);
drag = ecsGetComponentPtr(entities[i], linear_drag_component);
collision_shape = ecsGetComponentPtr(entities[i], shape_component);
// apply velocity
memcpy(&actual_velocity, velocity, sizeof(fvec));
vmulf(&actual_velocity, &actual_velocity, delta_time);
if(collision_shape == NULL)
{
position = ecsGetComponentPtr(entities[i], position_component);
vadd(position, position, &actual_velocity);
}
else
{
shape_sweep(entities[i], actual_velocity, 1);
}
// apply drag
if(drag)
{
vmovetowards(velocity, velocity, &VZERO, (*drag) * delta_time);
}
}
}
}
void angular_velocity_update_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time)
{
float* rotation;
float* velocity;
float* drag;
float max_delta;
if(query_update_group(&physics_update, &delta_time))
{
for(size_t i = 0; i < count; ++i)
{
rotation = ecsGetComponentPtr(entities[i], rotation_component);
velocity = ecsGetComponentPtr(entities[i], angular_velocity_component);
drag = ecsGetComponentPtr(entities[i], angular_drag_component);
// apply current velocity
*rotation += (*velocity) * delta_time;
// apply drag
if(drag)
{
max_delta = *drag * delta_time;
if(*velocity != 0.f)
{
if(*velocity > max_delta)
*velocity -= max_delta;
else if(*velocity < -max_delta)
*velocity += max_delta;
else
*velocity = 0.f;
}
}
}
}
}

16
src/engine/velocity_c.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef velocity_c_h
#define velocity_c_h
#include <ecs.h>
extern ecsComponentMask linear_velocity_component;
extern ecsComponentMask angular_velocity_component;
extern ecsComponentMask linear_drag_component;
extern ecsComponentMask angular_drag_component;
#define PHYSICS_A ((ecsComponentMask)(linear_velocity_component | linear_drag_component | angular_velocity_component | angular_drag_component| TRANSFORM_A))
extern void linear_velocity_update_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time);
extern void angular_velocity_update_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time);
#endif /* velocity_c_h */

32
src/engine/view_c.c Normal file
View File

@ -0,0 +1,32 @@
#include "view_c.h"
#include "engine.h"
double last_aspect_ratio;
ecsComponentMask view_component = 0x0;
void get_view_rect(view_t const* view, SDL_Rect* rect)
{
rect->x = -(view->width / 2);
rect->y = -(view->height / 2);
rect->w = view->width;
rect->h = view->height;
}
void system_update_views(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time)
{
view_t* view;
int width, height; double aspect_ratio;
SDL_GetRendererOutputSize(renderer, &width, &height);
aspect_ratio = (double)height/width;
for(size_t i = 0; i < count; ++i)
{
view = ecsGetComponentPtr(entities[i], view_component);
view->height = view->width * aspect_ratio;
}
ecsDisableSystem(&system_update_views);
}

19
src/engine/view_c.h Normal file
View File

@ -0,0 +1,19 @@
#ifndef view_c_h
#define view_c_h
#include <SDL2/SDL.h>
#include <ecs.h>
#include "engine.h"
extern ecsComponentMask view_component;
typedef struct view_t {
unsigned width;
unsigned height;
} view_t;
void get_view_rect(view_t const* view, SDL_Rect* rect);
extern void system_update_views(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time);
#endif /* view_c_h */

7
src/game/assets.c Normal file
View File

@ -0,0 +1,7 @@
#include "assets.h"
asset_handle_t fnt_inter = 0x0;
asset_handle_t fnt_ibmplex = 0x0;
asset_handle_t spr_player = 0x0;
asset_handle_t spr_bullet = 0x0;
asset_handle_t spr_ufo = 0x0;

12
src/game/assets.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef assets_h
#define assets_h
#include <adb.h>
extern asset_handle_t fnt_inter;
extern asset_handle_t fnt_ibmplex;
extern asset_handle_t spr_player;
extern asset_handle_t spr_bullet;
extern asset_handle_t spr_ufo;
#endif /* !assets_h */

53
src/game/bullet.h Normal file
View File

@ -0,0 +1,53 @@
#ifndef bullet_h
#define bullet_h
#include "components.h"
#include "assets.h"
#include <string.h>
#include <assert.h>
#include "player.h"
#include "bullet_c.h"
#define BULLET_A (PHYSICS_A | SPRITE_A | border_interaction_component | bullet_component | shape_component)
static inline
ecsEntityId spawn_bullet(fvec start_position, fvec start_velocity, ecsEntityId source, int power)
{
ecsEntityId bullet_id = ecsCreateEntity(BULLET_A);
assert(bullet_id != noentity);
{
fvec* position = ecsGetComponentPtr(bullet_id, position_component);
memcpy(position, &start_position, sizeof(fvec));
fvec* velocity = ecsGetComponentPtr(bullet_id, linear_velocity_component);
memcpy(velocity, &start_velocity, sizeof(fvec));
fvec* scale = ecsGetComponentPtr(bullet_id, scale_component);
*scale = (fvec){0.5f,0.5f};
bullet_t* bullet = ecsGetComponentPtr(bullet_id, bullet_component);
bullet->source = source;
bullet->power = power;
float* rotation = ecsGetComponentPtr(bullet_id, rotation_component);
*rotation = vang(&VUP, &start_velocity);
sprite_t* sprite = ecsGetComponentPtr(bullet_id, sprite_component);
SDL_Texture* spr_bullet_ptr = adb_get_asset(spr_bullet);
sprite->texture = spr_bullet_ptr;
SDL_QueryTexture(spr_bullet_ptr, NULL, NULL, &sprite->rect.w, &sprite->rect.h);
shape_t* shape = ecsGetComponentPtr(bullet_id, shape_component);
shape->type = SHAPE_CIRCLE;
shape->circle.radius = 10.f;
border_interaction_t* border_interaction = ecsGetComponentPtr(bullet_id, border_interaction_component);
border_interaction->type = BORDER_DESTROY;
border_interaction->margin = (fvec){0,0};//(fvec){scale->x * sprite->rect.w, scale->y * sprite->rect.h};
add_to_collision_world(bullet_id);
}
return bullet_id;
}
#endif /* bullet_h */

3
src/game/bullet_c.c Normal file
View File

@ -0,0 +1,3 @@
#include "bullet_c.h"
ecsComponentMask bullet_component = 0x0;

13
src/game/bullet_c.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef bullet_c_h
#define bullet_c_h
#include <ecs.h>
extern ecsComponentMask bullet_component;
typedef struct bullet_t {
ecsEntityId source;
int power;
} bullet_t;
#endif /* bullet_c_h */

36
src/game/debug_ui.c Normal file
View File

@ -0,0 +1,36 @@
#include "debug_ui.h"
#include <engine.h>
#include <ui.h>
#include <input.h>
void show_framerate_ui_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time)
{
static uiWindowState window_state = UI_WINDOWSTATE_MINIMIZED;
if(query_update_group(&frame_update, &delta_time))
{
char formatted[100];
uiBeginFrame();
SDL_Rect rect = {0, 0, 300, 300};
if(uiBeginWindow(&rect, "DEBUG INFO", &window_state))
{
if(sprintf(formatted, "delta_time: %f", delta_time))
uiLabel(formatted);
if(sprintf(formatted, "fps: %f", 1.f/delta_time))
uiLabel(formatted);
if(sprintf(formatted, "ax_hor: %d", input_axis(SDL_SCANCODE_A, SDL_SCANCODE_D)))
uiLabel(formatted);
if(sprintf(formatted, "ax_ver: %d", input_axis(SDL_SCANCODE_S, SDL_SCANCODE_W)))
uiLabel(formatted);
if(sprintf(formatted, "count: %zu", count))
uiLabel(formatted);
}
}
}

8
src/game/debug_ui.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef debug_ui_h
#define debug_ui_h
#include <ecs.h>
extern void show_framerate_ui_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time);
#endif /* debug_ui_h */

14
src/game/enemies.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef enemies_h
#define enemies_h
#include <ecs.h>
#include <components.h>
#include "enemy_c.h"
#include "player_c.h"
#include "vec.h"
#define NMI_A PHYSICS_A | player_component | enemy_component;
ecsEntityId spawn_enemy(fvec position, float rotation, ecsEntityId target);
#endif /* enemies_h */

4
src/game/enemy_c.c Normal file
View File

@ -0,0 +1,4 @@
#include "enemy_c.h"
#include "ecs.h"
ecsComponentMask enemy_component = 0x0;

12
src/game/enemy_c.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef enemy_c_h
#define enemy_c_h
#include "ecs.h"
typedef struct enemy_t {
ecsEntityId target;
} enemy_t;
extern ecsComponentMask enemy_component;
#endif /* enemy_c_h */

68
src/game/game.c Normal file
View File

@ -0,0 +1,68 @@
#include <engine.h>
#include <ecs.h>
#include <adb.h>
#include <vec.h>
#include <ui.h>
#include <input.h>
#include <components.h>
#include <camera.h>
#include <memory.h>
#include <string.h>
#include "bullet.h"
#include "assets.h"
#include "player.h"
#include "debug_ui.h"
void game_config(engine_init_t* config)
{
config->window_name = "Asteroids";
config->window_init_flags = SDL_WINDOW_FULLSCREEN_DESKTOP;
config->window_width = 1200;
config->window_height = 800;
set_update_group_target_framerate(&frame_update, 60);
set_update_group_target_framerate(&frame_update, 120);
}
void game_load_assets()
{
fnt_inter = adb_load_asset("Inter-Regular.otf");
fnt_ibmplex = adb_load_asset("IBMPlexMono-Regular.otf");
spr_player = adb_load_asset("ship-player.png");
spr_bullet = adb_load_asset("orb-pink-red.png");
spr_ufo = adb_load_asset("ufo.png");
uiSetFont(adb_get_asset(fnt_ibmplex));
}
void game_load_main_scene()
{
int w,h;
SDL_GetRendererOutputSize(renderer, &w, &h);
// create player entity
ecsEntityId cam = create_default_camera();
spawn_player((fvec){0,0});
fvec* campos = ecsGetComponentPtr(cam, position_component);
}
void game_init()
{
player_component = ecsRegisterComponent(player_t);
input_component = ecsRegisterComponent(input_t);
bullet_component = ecsRegisterComponent(bullet_t);
// the 0-499 block is for pre-render updates
ecsEnableSystem(&input_update_s, input_component, ECS_QUERY_ALL, MAX_THREADS, 250);
ecsEnableSystem(&player_update_s, PLAYER_A, ECS_QUERY_ALL, MAX_THREADS, 255);
#if defined(DEBUG)
ecsEnableSystem(&show_framerate_ui_s, ~noentity, ECS_QUERY_ANY, 0, 550);
#endif
game_load_assets();
game_load_main_scene();
}
void game_quit()
{}

25
src/game/input_c.c Normal file
View File

@ -0,0 +1,25 @@
#include "input_c.h"
#include <engine.h>
#include <input.h>
#include <SDL2/SDL.h>
ecsComponentMask input_component = 0x0;
void input_update_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time)
{
input_t* input;
if(query_update_group(&frame_update, &delta_time))
{
for(size_t i = 0; i < count; ++i)
{
input = ecsGetComponentPtr(entities[i], input_component);
input->axis_horizontal = input_axis(SDL_SCANCODE_A, SDL_SCANCODE_D);
input->axis_vertical = input_axis(SDL_SCANCODE_S, SDL_SCANCODE_W);
if(input_button(SDL_SCANCODE_J))
input->button_fire += 1;
else
input->button_fire = 0;
}
}
}

17
src/game/input_c.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef input_c_h
#define input_c_h
#include <ecs.h>
#include <stdint.h>
extern ecsComponentMask input_component;
typedef struct input_t {
int8_t axis_horizontal;
int8_t axis_vertical;
uint8_t button_fire;
} input_t;
extern void input_update_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time);
#endif /* input_c_h */

55
src/game/player.c Normal file
View File

@ -0,0 +1,55 @@
#include "player.h"
#include <engine.h>
#include <adb.h>
#include <ecs.h>
#include "assets.h"
void player_on_overlap(ecsEntityId self, ecsEntityId other)
{
E_LOG("%zu, %zu", self, other);
}
ecsEntityId spawn_player(fvec start_position)
{
ecsEntityId player_id = ecsCreateEntity(PLAYER_A);
if(player_id)
{
fvec* position = ecsGetComponentPtr(player_id, position_component);
memcpy(position, &start_position, sizeof(fvec));
sprite_t* sprite = ecsGetComponentPtr(player_id, sprite_component);
SDL_Texture* spr_player_ptr = adb_get_asset(spr_player);
sprite->texture = spr_player_ptr;
SDL_QueryTexture(spr_player_ptr, NULL, NULL, &sprite->rect.w, &sprite->rect.h);
fvec* scale = ecsGetComponentPtr(player_id, scale_component);
scale->x = 1.f;
scale->y = 1.f;
player_t* player = ecsGetComponentPtr(player_id, player_component);
player->acceleration = 10.f;
player->speed = 10.f;
player->rotation_speed = 100.f;
player->rotation_acceleration = 120.f;
float* linear_drag = ecsGetComponentPtr(player_id, linear_drag_component);
*linear_drag = 0.5f;
float* angular_drag = ecsGetComponentPtr(player_id, angular_drag_component);
*angular_drag = 1.f;
shape_t* collider = ecsGetComponentPtr(player_id, shape_component);
collider->type = SHAPE_CIRCLE;
collider->circle.radius = 100;
collider->on_overlap = &player_on_overlap;
border_interaction_t* border_interaction = ecsGetComponentPtr(player_id, border_interaction_component);
border_interaction->type = BORDER_WRAP;
border_interaction->margin = (fvec){scale->x, scale->y};
add_to_collision_world(player_id);
}
return player_id;
}

17
src/game/player.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef player_h
#define player_h
#include <ecs.h>
#include <engine.h>
#include <vec.h>
#include "player_c.h"
#include "input_c.h"
#define PLAYER_A (ecsComponentMask)(PHYSICS_A | SPRITE_A | border_interaction_component | player_component | input_component | shape_component)
extern void player_on_overlap(ecsEntityId self, ecsEntityId other);
extern ecsEntityId spawn_player(fvec start_position);
#endif /* player_h */

67
src/game/player_c.c Normal file
View File

@ -0,0 +1,67 @@
#include "player_c.h"
#include <engine.h>
#include <vec.h>
#include <fmath.h>
#include <SDL2/SDL.h>
#include "input_c.h"
#include "transform_c.h"
#include "velocity_c.h"
#include "bullet.h"
ecsComponentMask player_component = 0x0;
void player_update_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time)
{
ecsEntityId id;
player_t* player;
input_t* input;
fvec* lin_velocity;
float* ang_velocity;
float* rotation;
if(query_update_group(&frame_update, &delta_time))
{
for(size_t i = 0; i < count; ++i)
{
id = entities[i];
player = ecsGetComponentPtr(id, player_component);
input = ecsGetComponentPtr(id, input_component);
lin_velocity = ecsGetComponentPtr(id, linear_velocity_component);
ang_velocity = ecsGetComponentPtr(id, angular_velocity_component);
rotation = ecsGetComponentPtr(id, rotation_component);
*ang_velocity = fmovetowards(*ang_velocity, player->rotation_speed * input->axis_horizontal, player->rotation_acceleration * delta_time);
if(input->axis_vertical > 0)
{
fvec input_dir = {
.x = 0,
.y = -(float)(input->axis_vertical)
};
vnor(&input_dir, &input_dir);
vrot(&input_dir, &input_dir, *rotation * 0.017);
vmulf(&input_dir, &input_dir, player->speed);
vmovetowards(lin_velocity, lin_velocity, &input_dir, player->acceleration * delta_time);
}
else if(input->axis_vertical > 0)
{
vmovetowards(lin_velocity, lin_velocity, &VZERO, player->acceleration * delta_time * abs(input->axis_vertical));
}
if(input->button_fire == 1)
{
fvec* position = ecsGetComponentPtr(id, position_component);
fvec velocity;
vrot(&velocity, &VDOWN, *rotation * 0.017);
vmulf(&velocity, &velocity, 50.f);
spawn_bullet(*position, velocity, id, 1);
}
}
}
}

18
src/game/player_c.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef player_c_h
#define player_c_h
#include <ecs.h>
extern ecsComponentMask player_component;
typedef struct player_t {
float speed;
float rotation_speed;
float fire_timer;
float acceleration;
float rotation_acceleration;
} player_t;
extern void player_update_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time);
#endif /* player_c_h */