portfolio prep

main
Sara 2023-12-02 16:54:15 +01:00
commit 6c1a1fbb53
16 changed files with 1823 additions and 0 deletions

14
.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
# build dirs
xcode/
kdev/
[Dd]ebug/
[Rr]elease/
[Bb]uild/
[Bb]in/
# build files
*.exe
*.out
*.o
*.a

44
CMakeLists.txt Normal file
View File

@ -0,0 +1,44 @@
cmake_minimum_required(VERSION 3.17)
project(engine)
project(sim)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/lib")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/lib")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/bin")
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/CMakeModules")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DDEBUG")
find_package(SDL2 REQUIRED)
find_package(SDL2_image REQUIRED)
find_package(SDL2_ttf REQUIRED)
include_directories(SDL2 PRIVATE "${CMAKE_SOURCE_DIR}/src/engine" "${CMAKE_SOURCE_DIR}/include")
if(APPLE)
include_directories("/opt/homebrew/include/")
link_directories("/opt/homebrew/lib/" "./lib/")
else()
link_directories("./lib/")
endif()
file(
GLOB_RECURSE
SIM_SRC
"${CMAKE_SOURCE_DIR}/src/sim/*.c"
)
add_library(sim STATIC ${SIM_SRC})
set_target_properties(sim PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)
target_link_libraries(sim SDL2 SDL2_image SDL2_ttf ecs c m)
file(
GLOB_RECURSE
ENGINE_SRC
"${CMAKE_SOURCE_DIR}/src/engine/*.c"
)
add_executable(engine ${ENGINE_SRC})
target_link_libraries(engine sim SDL2 SDL2_image SDL2_ttf ecs c m)

BIN
Inter-Regular.otf Normal file

Binary file not shown.

BIN
blur.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

BIN
boid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

124
include/ecs.h Normal file
View File

@ -0,0 +1,124 @@
#ifndef ecs_h
#define ecs_h
#include <stddef.h>
#include <memory.h>
#include <stdlib.h>
#if __cplusplus
extern "C" {
#endif
typedef unsigned long long ecsEntityId;
typedef unsigned long long ecsComponentMask;
typedef void (*ecsSystemFn)(ecsEntityId*, ecsComponentMask*, size_t, float);
#define noentity ((ecsEntityId)0x0)
#define nocomponent ((ecsComponentMask)0x0)
#define anycomponent ((ecsComponentMask)~0x0)
typedef enum ECSqueryComparison {
ECS_NOQUERY = 0x0,
ECS_QUERY_ANY,
ECS_QUERY_ALL,
} ecsQueryComparison;
typedef struct ecsComponentQuery {
ecsQueryComparison comparison;
ecsComponentMask mask;
} ecsComponentQuery;
void ecsInit(void);
/**
* \brief Allocates a component list for a component type of stride bytes.
* \param stride The number of bytes to allocate for each component.
*/
ecsComponentMask ecsMakeComponentType(size_t stride);
#define ecsRegisterComponent(__type) ecsMakeComponentType(sizeof(__type))
/**
* \brief Get a pointer to a component attached to entity.
* \param entity The entity to find a component of.
* \param component The component type to find.
* \returns A pointer to a component if found.
* \returns NULL if entity does not contain the given component.
*/
void* ecsGetComponentPtr(ecsEntityId entity, ecsComponentMask component);
/**
* \brief Assigns a new entity id.
* \param components A component query referencing the components to add to the new object.
* \returns The id used to reference the newly created entity.
* \returns NULL if allocation failed
*/
ecsEntityId ecsCreateEntity(ecsComponentMask components);
/**
* \brief Gets the component mask for an entity.
* \param entity the entity to get the mask for.
* \returns the ecsComponentMask for entity.
*/
ecsEntityId ecsGetComponentMask(ecsEntityId entity);
/**
* \brief Destroys an entity and all associated components
* \param entity The id of the entity to destroy.
*/
void ecsDestroyEntity(ecsEntityId entity);
/**
* \brief Attaches one or more components.
* \param entity The entity to attach the new components to.
* \param components Bitmask of the componentId's to attach.
*/
void ecsAttachComponents(ecsEntityId entity, ecsComponentMask components);
/**
* \brief Detaches one or more components.
* \param entity The entity to detach components from.
* \param components Bitmask of the components to detach.
*/
void ecsDetachComponents(ecsEntityId entity, ecsComponentMask components);
/**
* \brief Enables a function to act as a system for entities matching the given query.
* \param func The function to call when query is met.
* \param components The required components to run this system.
* \param comparison The type of requirement components represent. one of { ECS_QUERY_ANY ; ECS_QUERY_ALL }.
* \note
* When comparison=ECS_QUERY_ALL the system will run only when all of the masked components are present on an entity.
* \note
* When comparison=ECS_QUERY_ANY the system will run for all entities where any of the masked components are present.
*/
void ecsEnableSystem(ecsSystemFn func, ecsComponentMask components, ecsQueryComparison comparison, int maxThreads, int executionOrder);
/**
* \brief Disables a function acting as a system.
* \param func Pointer to the function to disable.
*/
void ecsDisableSystem(ecsSystemFn func);
/**
* \brief Run currently enabled systems.
* \note Implicitly calls ecsRunTasks after completion.
*/
void ecsRunSystems(float deltaTime);
/**
* \brief Run queued tasks.
*/
void ecsRunTasks(void);
/**
* \brief Terminate the ECS and clean up allocated resources.
*/
void ecsTerminate(void);
#if __cplusplus
}
#endif
#endif /* ecs_h */

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

@ -0,0 +1,313 @@
#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_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 init_asset_database(size_t max_asset_bytes)
{
assert(adb_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_init = 1;
return 0;
}
int close_asset_database()
{
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 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(loadfn != 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* get_asset(asset_handle_t handle)
{
asset_t* asset = get_asset_t(handle);
if(asset == NULL)
return NULL;
else
return asset->instance;
}
int try_get_asset(asset_handle_t handle, void** o_ptr)
{
void* asset = get_asset(handle);
if(o_ptr != NULL)
memcpy(o_ptr, &asset, sizeof(void*));
return asset != NULL;
}
asset_handle_t load_asset(const char* file)
{
char* filename = realpath(file, NULL);
if(filename == NULL)
return 0;
asset_handle_t handle = str_hash(filename);
if(try_get_asset(handle, NULL))
return handle;
size_t ext_offset = file_extention_offset(filename);
file_handler_t* handler = get_file_handler(filename + ext_offset);
adb_assets_first[adb_assets_last] = (asset_t){
.filename = filename,
.extention_offset = ext_offset,
.instance = handler->load(filename),
.handle = handle
};
++adb_assets_last;
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 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 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;
}

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

@ -0,0 +1,28 @@
#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 init_asset_database(size_t max_asset);
extern asset_handle_t get_file_handle(const char* filename);
extern int asset_is_loaded(asset_handle_t handle);
extern void* get_asset(asset_handle_t handle);
#define get_asset_as(__TYPE, handle) ((__TYPE*)get_asset(handle))
extern int try_get_asset(asset_handle_t handle, void** o_ptr);
extern asset_handle_t load_asset(const char* file);
extern void free_asset(asset_handle_t handle);
extern size_t set_asset_memory(size_t max_bytes);
extern int close_asset_database();
extern int register_file_handler(const char* file_extention, asset_load_fn loadfn, asset_free_fn freefn);
#endif /* adb_h */

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

@ -0,0 +1,206 @@
#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 "adb.h"
#include "ui.h"
SDL_Window* window;
SDL_Renderer* renderer;
int engine_wants_to_quit;
float frame_start_time;
float last_frame_time;
short is_render_frame;
float target_frame_time;
float render_frame_time;
void system_window_clear(ecsEntityId* entities, ecsComponentMask* components, size_t size, float delta_time);
void system_window_display(ecsEntityId* entities, ecsComponentMask* components, size_t size, float delta_time);
void engine_init();
void engine_run();
void engine_handle_event(SDL_Event* event);
void engine_clean();
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()
{
// set the frame start time here to ensure there will be time passed when engine_run is called
frame_start_time = (float)clock() / CLOCKS_PER_SEC;
engine_wants_to_quit = 0;
// init asset database for 100 assets
init_asset_database(100);
// register default image file handlers
register_file_handler(".png", &asset_load_sdl_image, &asset_free_sdl_image);
register_file_handler(".jpg", &asset_load_sdl_image, &asset_free_sdl_image);
// default font file handlers
register_file_handler(".ttf", &asset_load_ttf_font, &asset_free_ttf_font);
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);
// allow sim to adjust init settings as needed
sim_config(&init_settings);
target_frame_time = 1.f/(float)init_settings.target_framerate;
// 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("BOIDS!!",
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);
}
// initialize runtime object database
ecsInit();
// initialize imgui renderer
uiInit(renderer);
// enable screen refresh system
ecsEnableSystem(&system_window_clear, nocomponent, ECS_NOQUERY, 0, -100);
// initialize sim
sim_init();
ecsEnableSystem(&system_window_display, nocomponent, ECS_NOQUERY, 0, 10000);
// run created tasks
ecsRunTasks();
}
void engine_run()
{
SDL_Event evt;
float actual_time;
while(!engine_wants_to_quit)
{
last_frame_time = frame_start_time;
frame_start_time = (float)clock() / CLOCKS_PER_SEC;
ecsRunSystems(frame_start_time - last_frame_time);
while(SDL_PollEvent(&evt))
{
engine_handle_event(&evt);
}
}
}
void engine_handle_event(SDL_Event* event)
{
switch(event->type)
{
default: break;
case SDL_QUIT:
engine_wants_to_quit = 1;
break;
}
}
void engine_clean()
{
// quit sim, ui asset database, ecs
sim_quit();
uiTerminate();
close_asset_database();
ecsTerminate();
// delete renderer and window, quit sdl
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
}
void system_window_clear(ecsEntityId* entities, ecsComponentMask* components, size_t size, float delta_time)
{
render_frame_time += delta_time;
is_render_frame = render_frame_time >= target_frame_time;
if(is_render_frame)
{
render_frame_time = 0;
// 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(is_render_frame)
{
// swap buffer
SDL_RenderPresent(renderer);
}
}
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,
.target_framerate = 60
};
}

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

@ -0,0 +1,34 @@
#ifndef _engine_h
#define _engine_h
#include <stdint.h>
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;
int target_framerate;
} engine_init_t;
extern short is_render_frame;
extern void default_engine_init_settings(engine_init_t*);
extern void sim_config(engine_init_t*);
extern void sim_init();
extern void sim_quit();
extern struct SDL_Renderer* renderer;
#if defined(DEBUG)
#define E_LOG(...)\
fprintf(stdout, "%s:%d: ", __FILE__, __LINE__);\
fprintf(stdout, __VA_ARGS__);\
fprintf(stdout, "\n")
#elif defined(NDEBUG)
#define E_LOG(...)
#endif
#endif /* !_engine_h */

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

@ -0,0 +1,380 @@
#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, int* isActive)
{
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();
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)
{
int last = *isActive;
*isActive = !(*isActive);
return last;
}
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()
{
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);
uiNextLine();
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);
}

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

@ -0,0 +1,31 @@
#ifndef ui_h
#define ui_h
#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
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, int* isActive);
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();
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 */

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

@ -0,0 +1,149 @@
#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 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);
float diffm = vmag(&diff);
if(diffm == 0 || isnan(diffm))
{
memmove(r, from, sizeof(fvec));
return;
}
vmulf(&dir, &diff, 1.f/diffm * max_delta);
float dirm = vmag(&dir);
if(dirm >= diffm)
{
memmove(r, from, 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);
}
#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 */

300
src/sim/boid_c.c Normal file
View File

@ -0,0 +1,300 @@
#include "boid_c.h"
#include <SDL2/SDL.h>
#include <engine.h>
#include <assert.h>
ecsComponentMask boid_component;
float boid_acceleration = 75.f;
float boid_max_velocity = 50.f;
SDL_Texture* boid_texture;
behaviour_t alignment = {
.range = 10.f,
.force = .8f
};
behaviour_t separation = {
.range = 4.f,
.force = 50.0f
};
behaviour_t cohesion = {
.range = 100.f,
.force = 0.5f
};
behaviour_t wall_avoid = {
.range = 50.f,
.force = 100.f
};
behaviour_t mouse_interact = {
.range = 70.f,
.force = -1000.f
};
SDL_Rect boid_available_area = {0,0,800, 800};
void system_boid_update_position(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time)
{
boid_c* boid;
fvec force, velocity;
float acceleration = boid_acceleration * delta_time;
int w, h;
for(size_t i = 0; i < count; i++)
{
boid = ecsGetComponentPtr(entities[i], boid_component);
force = boid->force;
vmulf(&force, &force, boid_max_velocity);
vmax(&force, &force, boid_max_velocity);
vmovetowards(&boid->velocity, &boid->velocity, &force, acceleration);
assert(!isnan(boid->position.x) && !isnan(boid->position.y));
assert(!isnan(boid->velocity.x) && !isnan(boid->velocity.y));
boid->force = (fvec){ 0.f, 0.f };
vmulf(&velocity, &boid->velocity, delta_time);
vadd(&boid->position, &boid->position, &velocity);
}
}
void system_boid_update_near(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time)
{
float dist;
boid_c* boid, *other;
float max_range = alignment.range;
max_range = separation.range > max_range ? separation.range : max_range;
max_range = cohesion.range > max_range ? cohesion.range : max_range;
size_t hits = 0;
for(size_t i = 0; i < count; ++i)
{
boid = ecsGetComponentPtr(entities[i], boid_component);
hits = 0;
memset(boid->near, noentity, sizeof(boid->near));
for(size_t j = 0; j < count && hits < BOID_NEAR_COUNT; ++j)
{
other = ecsGetComponentPtr(entities[j], boid_component);
dist = vdist(&boid->position, &other->position);
assert(!isnan(dist));
if(dist < max_range)
{
boid->near[hits++] = entities[j];
}
}
}
}
void system_draw_boids(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time)
{
if(!is_render_frame) return;
boid_c* boid;
int tw, th;
SDL_QueryTexture(boid_texture, NULL, NULL, &tw, &th);
SDL_Rect srcrect = {
.x = 0, .y = 0,
.w = tw, .h = th
};
SDL_FRect dstrect = {
.w = 5, .h = 5
};
int hw = dstrect.w / 2;
int hh = dstrect.h / 2;
for(size_t i = 0; i < count; ++i)
{
boid = ecsGetComponentPtr(entities[i], boid_component);
dstrect.x = boid->position.x - hw;
dstrect.y = boid->position.y - hh;
SDL_RenderCopyExF(renderer, boid_texture,
&srcrect, &dstrect,
(double)vang(&boid->velocity, &VDOWN) * 57.2957795,
&(SDL_FPoint){hw,hh}, SDL_FLIP_NONE);
}
}
void system_boids_cohesion(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time)
{
boid_c* boid, *other;
fvec avrg, force, diff;
float dist;
size_t hit_count;
for(size_t i = 0; i < count; ++i)
{
boid = ecsGetComponentPtr(entities[i], boid_component);
avrg = (fvec){ 0.f, 0.f };
hit_count = 0;
for(size_t j = 0; j < BOID_NEAR_COUNT && boid->near[j] != noentity; ++j)
{
other = ecsGetComponentPtr(boid->near[j], boid_component);
dist = vdist(&other->position, &boid->position);
if(dist < cohesion.range)
{
++hit_count;
vsub(&diff, &other->position, &avrg);
vmulf(&diff, &diff, 1.f/(hit_count));
vadd(&avrg, &avrg, &diff);
}
assert(!isnan(avrg.x) && !isnan(avrg.y));
}
vsub(&force, &avrg, &boid->position);
vmulf(&force, &force, cohesion.force);
vadd(&boid->force, &boid->force, &force);
assert(!isnan(boid->force.x) && !isnan(boid->force.y));
}
}
void system_boids_separation(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time)
{
boid_c* boid, *other;
fvec avrg, force, diff;
float dist;
size_t hit_count;
for(size_t i = 0; i < count; ++i)
{
boid = ecsGetComponentPtr(entities[i], boid_component);
avrg = (fvec){ 0.f, 0.f };
hit_count = 0;
for(size_t j = 0; j < BOID_NEAR_COUNT && boid->near[j] != noentity; ++j)
{
other = ecsGetComponentPtr(boid->near[j], boid_component);
dist = vdist(&other->position, &boid->position);
if(dist < separation.range)
{
++hit_count;
vsub(&diff, &other->position, &avrg);
vmulf(&diff, &diff, 1.f/(hit_count));
vadd(&avrg, &avrg, &diff);
}
assert(!isnan(avrg.x) && !isnan(avrg.y));
}
vsub(&force, &avrg, &boid->position);
vmulf(&force, &force, separation.force);
vsub(&boid->force, &boid->force, &force);
assert(!isnan(boid->force.x) && !isnan(boid->force.y));
}
}
void system_boids_alignment(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time)
{
boid_c* boid, *other;
fvec avrg, diff;
float dist;
size_t hit_count;
for(size_t i = 0; i < count; ++i)
{
boid = ecsGetComponentPtr(entities[i], boid_component);
avrg = (fvec){ 0.f, 0.f };
hit_count = 0;
for(size_t j = 0; j < BOID_NEAR_COUNT && boid->near[j] != noentity; ++j)
{
other = ecsGetComponentPtr(boid->near[j], boid_component);
dist = vdist(&other->position, &boid->position);
if(dist < alignment.range)
{
++hit_count;
vsub(&diff, &other->velocity, &avrg);
vmulf(&diff, &diff, 1.f/(hit_count));
vadd(&avrg, &avrg, &diff);
}
assert(!isnan(avrg.x) && !isnan(avrg.y));
}
vmulf(&avrg, &avrg, alignment.force);
vadd(&boid->force, &boid->force, &avrg);
assert(!isnan(boid->force.x) && !isnan(boid->force.y));
}
}
void system_boid_mouse(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time)
{
boid_c* boid;
fvec mouse, diff;
float m;
int imx, imy;
uint32_t mstate = SDL_GetMouseState(&imx, &imy);
mouse = (fvec){ (float)imx, (float)imy };
if((mstate & SDL_BUTTON_LEFT) == 0) return;
for(size_t i = 0; i < count; ++i)
{
boid = ecsGetComponentPtr(entities[i], boid_component);
vsub(&diff, &mouse, &boid->position);
m = vmag(&diff);
if(m < mouse_interact.range)
{
vmulf(&diff, &diff, (1.f/m)*mouse_interact.force);
vadd(&boid->force, &boid->force, &diff);
}
}
}
void system_boids_wrap(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time)
{
boid_c* boid;
int iw, ih;
float w, h;
SDL_GetRendererOutputSize(renderer, &iw, &ih);
w = (float)iw; h = (float)ih;
for(size_t i = 0; i < count; ++i)
{
boid = ecsGetComponentPtr(entities[i], boid_component);
if(boid->position.x >= w) boid->position.x -= w;
if(boid->position.x < 0) boid->position.x += w;
if(boid->position.y >= h) boid->position.y -= h;
if(boid->position.y < 0) boid->position.y += w;
}
}
void system_boids_wall_avoid(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time)
{
if(wall_avoid.force == 0.f) return;
boid_c* boid;
fvec force;
SDL_Rect limits = boid_available_area;
limits.x += wall_avoid.range;
limits.w -= wall_avoid.range*2;
limits.y += wall_avoid.range;
limits.h -= wall_avoid.range*2;
for(size_t i = 0; i < count; ++i)
{
boid = ecsGetComponentPtr(entities[i], boid_component);
force = (fvec){ 0.f, 0.f };
if(boid->position.x >= limits.x + limits.w)
force.x = -1.f;
if(boid->position.x <= limits.x)
force.x = 1.f;
if(boid->position.y >= limits.y + limits.h)
force.y = -1.f;
if(boid->position.y <= limits.y)
force.y = 1.f;
vnor(&force, &force);
vmulf(&force, &force, wall_avoid.force);
vadd(&boid->force, &boid->force, &force);
}
}

46
src/sim/boid_c.h Normal file
View File

@ -0,0 +1,46 @@
#ifndef boid_c_h
#define boid_c_h
#include <ecs.h>
#include <stdint.h>
#include <vec.h>
#ifndef BOID_NEAR_COUNT
#define BOID_NEAR_COUNT (100)
#endif
extern ecsComponentMask boid_component;
typedef struct boid_c {
fvec position;
fvec velocity;
fvec force;
ecsEntityId near[BOID_NEAR_COUNT];
} boid_c;
typedef struct behaviour_t {
float force;
float range;
} behaviour_t;
extern struct SDL_Texture* boid_texture;
extern float boid_acceleration;
extern float boid_max_velocity;
extern behaviour_t alignment;
extern behaviour_t separation;
extern behaviour_t cohesion;
extern behaviour_t wall_avoid;
extern behaviour_t mouse_interact;
extern struct SDL_Rect boid_available_area;
extern void system_boid_update_position(ecsEntityId*, ecsComponentMask*, size_t, float);
extern void system_boids_cohesion(ecsEntityId*, ecsComponentMask*, size_t, float);
extern void system_boids_separation(ecsEntityId*, ecsComponentMask*, size_t, float);
extern void system_boids_alignment(ecsEntityId*, ecsComponentMask*, size_t, float);
extern void system_boids_wall_avoid(ecsEntityId*, ecsComponentMask*, size_t, float);
extern void system_boids_wrap(ecsEntityId*, ecsComponentMask*, size_t, float);
extern void system_draw_boids(ecsEntityId*, ecsComponentMask*, size_t, float);
extern void system_boid_mouse(ecsEntityId*, ecsComponentMask*, size_t, float);
extern void system_boid_update_near(ecsEntityId*, ecsComponentMask*, size_t, float);
#endif /* boid_c_h */

154
src/sim/sim.c Normal file
View File

@ -0,0 +1,154 @@
#include <SDL2/SDL.h>
#include <ecs.h>
#include <engine.h>
#include <adb.h>
#include <ui.h>
#define BOID_NEAR_COUNT (200)
#include "boid_c.h"
int boid_spawn_num;
void system_draw_gui(ecsEntityId* entities, ecsComponentMask* mask, size_t count, float delta_time)
{
static int show_sliders = 1;
int ww, wh;
SDL_GetRendererOutputSize(renderer, &ww, &wh);
boid_available_area.w = ww;
boid_available_area.h = wh;
boid_available_area.x = 0;
SDL_Rect rect = {
0, 0, 500, wh
};
uiBeginFrame();
if(uiBeginWindow(&rect, &show_sliders))
{
// set the area boids will stay in to exclude the area of the ui
boid_available_area.x = rect.w;
boid_available_area.w = ww - rect.w;
// render velocity and acceleration sliders
uiLabelNext("speed", 0.25f);
uiSlider(&boid_max_velocity, 10.f, 100.f, 5.f);
uiLabelNext("acceleration", 0.25f);
uiSlider(&boid_acceleration, 10.f, 100.f, 5.f);
// alignment parameters
uiHeader("alignment");
uiLabelNext("range", 0.25f);
uiSlider(&(alignment.range), 0.1f, 100.f, 1.f);
uiLabelNext("force", 0.25f);
uiSlider(&(alignment.force), 0.0f, 2.f, 0.01f);
// cohesion parameters
uiHeader("cohesion");
uiLabelNext("range", 0.25f);
uiSlider(&(cohesion.range), 0.1f, 100.f, 1.f);
uiLabelNext("force", 0.25f);
uiSlider(&(cohesion.force), 0.0f, 2.f, .01f);
// mouse interaction parameters
uiHeader("mouse");
uiLabelNext("range", 0.25f);
uiSlider(&(mouse_interact.range), 0.1f, 100.f, 1.f);
uiLabelNext("force", 0.25f);
uiSlider(&(mouse_interact.force), -200.f, 200.f, 10.f);
// separation range
uiHeader("separation");
uiSlider(&(separation.range), 0.1f, 100.f, 1.f);
}
}
void sim_config(engine_init_t* config)
{
config->window_width = 1700;
config->window_height = 1000;
config->window_init_flags |= SDL_WINDOW_RESIZABLE;
boid_spawn_num = 500;
config->target_framerate = 24;
}
void spawn_boids()
{
int w, h;
SDL_GetRendererOutputSize(renderer, &w, &h);
fvec position = {9, 0};
ecsEntityId entity;
boid_c* boid;
for(int i = 0; i < boid_spawn_num; i++)
{
position = (fvec){rand() % w, rand() % h};
if((entity = ecsCreateEntity(boid_component)) != noentity)
{
boid = ecsGetComponentPtr(entity, boid_component);
(*boid) = (boid_c){
.position = position,
.velocity = {0,0},
.force = {0,0}
};
}
else
{
exit(2);
}
}
}
void sim_init()
{
// register boid_c as a component type
boid_component = ecsRegisterComponent(boid_c);
// enable the functions that make boids boid
ecsEnableSystem(&system_boid_update_position, boid_component, ECS_QUERY_ALL, 8, 50);
ecsEnableSystem(&system_boid_update_near, boid_component, ECS_QUERY_ALL, 0, 100);
ecsEnableSystem(&system_draw_boids, boid_component, ECS_QUERY_ALL, 0, 200);
ecsEnableSystem(&system_boids_wall_avoid, boid_component, ECS_QUERY_ALL, 8, 300);
ecsEnableSystem(&system_boids_cohesion, boid_component, ECS_QUERY_ALL, 8, 400);
ecsEnableSystem(&system_boids_alignment, boid_component, ECS_QUERY_ALL, 8, 410);
ecsEnableSystem(&system_boids_separation, boid_component, ECS_QUERY_ALL, 8, 420);
ecsEnableSystem(&system_boid_mouse, boid_component, ECS_QUERY_ALL, 8, 430);
// enable the gui system
ecsEnableSystem(&system_draw_gui, nocomponent, ECS_NOQUERY, 0, 500);
int w, h;
SDL_GetRendererOutputSize(renderer, &w, &h);
// load boid image
//asset_handle_t blur_asset = load_asset("blur.png");
asset_handle_t arrow_asset = load_asset("boid.png");
// set boid texture
boid_texture = get_asset(arrow_asset);
// load and set font
asset_handle_t font_asset = load_asset("Inter-Regular.otf");
uiSetFont(get_asset(font_asset));
// set the initially available area
boid_available_area = (SDL_Rect){
.x = 0, .y = 0,
.w = w, .h = h
};
// spawn a bunch of boids
spawn_boids();
}
void sim_quit()
{
}