portfolio prep
commit
6c1a1fbb53
|
@ -0,0 +1,14 @@
|
|||
# build dirs
|
||||
xcode/
|
||||
kdev/
|
||||
[Dd]ebug/
|
||||
[Rr]elease/
|
||||
[Bb]uild/
|
||||
[Bb]in/
|
||||
|
||||
# build files
|
||||
*.exe
|
||||
*.out
|
||||
*.o
|
||||
*.a
|
||||
|
|
@ -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)
|
Binary file not shown.
|
@ -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 */
|
|
@ -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;
|
||||
}
|
|
@ -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 */
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
@ -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 */
|
|
@ -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);
|
||||
}
|
|
@ -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 */
|
|
@ -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 */
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 */
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
|
Loading…
Reference in New Issue