commit 6c1a1fbb5382ce49fae7cba648f202f6ccfc6d0e Author: Sara Date: Sat Dec 2 16:54:15 2023 +0100 portfolio prep diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa2cd35 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# build dirs +xcode/ +kdev/ +[Dd]ebug/ +[Rr]elease/ +[Bb]uild/ +[Bb]in/ + +# build files +*.exe +*.out +*.o +*.a + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..6610bff --- /dev/null +++ b/CMakeLists.txt @@ -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) diff --git a/Inter-Regular.otf b/Inter-Regular.otf new file mode 100644 index 0000000..84e6a61 Binary files /dev/null and b/Inter-Regular.otf differ diff --git a/blur.png b/blur.png new file mode 100644 index 0000000..cf6bbfe Binary files /dev/null and b/blur.png differ diff --git a/boid.png b/boid.png new file mode 100644 index 0000000..3a999ea Binary files /dev/null and b/boid.png differ diff --git a/include/ecs.h b/include/ecs.h new file mode 100644 index 0000000..07dbd7d --- /dev/null +++ b/include/ecs.h @@ -0,0 +1,124 @@ +#ifndef ecs_h +#define ecs_h + +#include +#include +#include + + +#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 */ diff --git a/src/engine/adb.c b/src/engine/adb.c new file mode 100644 index 0000000..c525ef1 --- /dev/null +++ b/src/engine/adb.c @@ -0,0 +1,313 @@ +#include "adb.h" +#include +#include +#include +#include +#include +#include + +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; +} diff --git a/src/engine/adb.h b/src/engine/adb.h new file mode 100644 index 0000000..1938cd4 --- /dev/null +++ b/src/engine/adb.h @@ -0,0 +1,28 @@ +#ifndef adb_h +#define adb_h + +#include +#include + +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 */ diff --git a/src/engine/engine.c b/src/engine/engine.c new file mode 100644 index 0000000..c3c870d --- /dev/null +++ b/src/engine/engine.c @@ -0,0 +1,206 @@ +#include "engine.h" + +#include +#include +#include +#include +#include +#include +#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 + }; +} + diff --git a/src/engine/engine.h b/src/engine/engine.h new file mode 100644 index 0000000..998ec6b --- /dev/null +++ b/src/engine/engine.h @@ -0,0 +1,34 @@ +#ifndef _engine_h +#define _engine_h + +#include + +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 */ diff --git a/src/engine/ui.c b/src/engine/ui.c new file mode 100644 index 0000000..88df0aa --- /dev/null +++ b/src/engine/ui.c @@ -0,0 +1,380 @@ +#include "ui.h" +#include +#include + +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); +} diff --git a/src/engine/ui.h b/src/engine/ui.h new file mode 100644 index 0000000..ec665d8 --- /dev/null +++ b/src/engine/ui.h @@ -0,0 +1,31 @@ +#ifndef ui_h +#define ui_h + +#include +#include + +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 */ diff --git a/src/engine/vec.h b/src/engine/vec.h new file mode 100644 index 0000000..600b155 --- /dev/null +++ b/src/engine/vec.h @@ -0,0 +1,149 @@ +#ifndef vec_h +#define vec_h + +#include +#include + +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 */ diff --git a/src/sim/boid_c.c b/src/sim/boid_c.c new file mode 100644 index 0000000..081c1d6 --- /dev/null +++ b/src/sim/boid_c.c @@ -0,0 +1,300 @@ +#include "boid_c.h" +#include +#include +#include + +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); + } +} diff --git a/src/sim/boid_c.h b/src/sim/boid_c.h new file mode 100644 index 0000000..89aad1e --- /dev/null +++ b/src/sim/boid_c.h @@ -0,0 +1,46 @@ +#ifndef boid_c_h +#define boid_c_h + +#include +#include +#include + +#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 */ diff --git a/src/sim/sim.c b/src/sim/sim.c new file mode 100644 index 0000000..1f352f7 --- /dev/null +++ b/src/sim/sim.c @@ -0,0 +1,154 @@ +#include +#include +#include +#include +#include + +#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() +{ +} +