diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6ec76ed --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.build diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..29a5cfe --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "thirdparty/miniaudio"] + path = thirdparty/miniaudio/repo + url = https://github.com/dr-soft/miniaudio diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..a0f467a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.16) + +project(leftone C) + +if (BUILD_TESTING) + enable_testing() +endif() + +set(LEFTONE_TOOL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tool") + +set(CMAKE_C_FLAGS + "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-missing-field-initializers") +set(CMAKE_C_FLAGS_RELEASE + "${CMAKE_C_FLAGS_RELEASE} -Wno-unused-parameter") + +if (WIN32) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mconsole") +endif() + +find_package(Freetype REQUIRED) +find_package(GLEW REQUIRED) +find_package(OpenGL REQUIRED) +find_package(SDL2 REQUIRED) +find_package(msgpack REQUIRED) + +include_directories(SYSTEM + ${FREETYPE_INCLUDE_DIRS} + ${GLEW_INCLUDE_DIRS} + ${OPENGL_INCLUDE_DIR} +) +include_directories(.) + +include(cmake/anysrc.cmake) +include(cmake/sos.cmake) + +add_subdirectory(app) +add_subdirectory(core) +add_subdirectory(thirdparty) +add_subdirectory(tool) +add_subdirectory(util) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt new file mode 100644 index 0000000..0340b1a --- /dev/null +++ b/app/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(sdl) diff --git a/app/sdl/CMakeLists.txt b/app/sdl/CMakeLists.txt new file mode 100644 index 0000000..a3a7805 --- /dev/null +++ b/app/sdl/CMakeLists.txt @@ -0,0 +1,14 @@ +add_executable(app-sdl + args.c + event.c + main.c +) +target_link_libraries(app-sdl + GLEW::GLEW + SDL2::SDL2 + + conv + parsarg + + loscene +) diff --git a/app/sdl/args.c b/app/sdl/args.c new file mode 100644 index 0000000..86de299 --- /dev/null +++ b/app/sdl/args.c @@ -0,0 +1,104 @@ +#include "./args.h" + +#include +#include +#include +#include +#include +#include + +#include "util/parsarg/parsarg.h" + +#include "core/loscene/param.h" + +void app_args_parse(app_args_t* args, int argc, char** argv) { + assert(args != NULL); + assert(argc > 0); + + parsarg_t pa; + parsarg_initialize(&pa, argc-1, argv+1); + + while (!parsarg_finished(&pa)) { + size_t nlen; + char* n = parsarg_pop_name(&pa, &nlen); + + char* v; + parsarg_pop_value(&pa, &v); + + if (n == NULL && v == NULL) continue; + + bool ok = false; + +# define bool_(name, var) do { \ + if (strncmp(name, n, nlen) == 0 && name[nlen] == 0) { \ + if (v == NULL) { \ + var = true; \ + ok = true; \ + } else { \ + fprintf(stderr, "option '"name"' cannot take any values"); \ + abort(); \ + } \ + } \ + } while (0) + +# define int_(name, var, min, max) do { \ + if (strncmp(name, n, nlen) == 0 && name[nlen] == 0) { \ + char* end; \ + const intmax_t i = strtoimax(v, &end, 0); \ + if (*end == 0 && min <= i && i < max) { \ + var = i; \ + ok = true; \ + } else { \ + fprintf(stderr, \ + "option '"name"' requires " \ + "an integer value (%"PRIdMAX"~%"PRIdMAX")\n", \ + (intmax_t) min, (intmax_t) max); \ + abort(); \ + } \ + continue; \ + } \ + } while (0) + + /* ---- scene parameters ---- */ + int_("width", args->scene.width, 640, INT32_MAX); + int_("height", args->scene.height, 360, INT32_MAX); + int_("dpi-x", args->scene.dpi.x, 1, INT32_MAX); + int_("dpi-y", args->scene.dpi.y, 1, INT32_MAX); + + int_("max-msaa", args->scene.max_msaa, 1, INT32_MAX); + int_("brightness", args->scene.brightness, 0, 2000); + + bool_("disable-heavy-backwall", + args->scene.environment.disable_heavy_backwall); + bool_("disable-heavy-fog", + args->scene.environment.disable_heavy_fog); + + bool_("skip-title", args->scene.skip_title); + + bool_("test-poolset-packing", args->scene.test.loworld_poolset_packing); + bool_("test-player-packing", args->scene.test.loplayer_packing); + + /* ---- app parameters ---- */ + int_("max-fps", args->max_fps, 1, INT32_MAX); + + bool_("override-dpi", args->override_dpi); + + bool_("force-window", args->force_window); + bool_("force-desktop-fullscreen", args->force_desktop_fullscreen); + bool_("force-fullscreen", args->force_fullscreen); + +# undef int_ +# undef bool_ + + if (!ok) { + if (n == NULL) { + fprintf(stderr, "missing option name for the value '%s'\n", v); + } else { + fprintf(stderr, "unknown option '%.*s'\n", (int) nlen, n); + } + abort(); + } + } + + parsarg_deinitialize(&pa); +} diff --git a/app/sdl/args.h b/app/sdl/args.h new file mode 100644 index 0000000..9ae1c65 --- /dev/null +++ b/app/sdl/args.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include "core/loscene/param.h" + +typedef struct { + loscene_param_t scene; + + int32_t max_fps; + + bool override_dpi; + + bool force_window; + bool force_desktop_fullscreen; + bool force_fullscreen; +} app_args_t; + +void +app_args_parse( + app_args_t* args, + int argc, + char** argv +); diff --git a/app/sdl/event.c b/app/sdl/event.c new file mode 100644 index 0000000..05be8bd --- /dev/null +++ b/app/sdl/event.c @@ -0,0 +1,52 @@ +#include "./event.h" + +#include +#include + +#include + +#include "core/locommon/input.h" + +#define APP_EVENT_GET_BUTTON_BIT_FROM_KEY(k) ( \ + (k) == SDLK_a? LOCOMMON_INPUT_BUTTON_LEFT: \ + (k) == SDLK_d? LOCOMMON_INPUT_BUTTON_RIGHT: \ + (k) == SDLK_w? LOCOMMON_INPUT_BUTTON_UP: \ + (k) == SDLK_s? LOCOMMON_INPUT_BUTTON_DOWN: \ + (k) == SDLK_SPACE? LOCOMMON_INPUT_BUTTON_JUMP: \ + (k) == SDLK_LSHIFT? LOCOMMON_INPUT_BUTTON_DASH: \ + (k) == SDLK_ESCAPE? LOCOMMON_INPUT_BUTTON_MENU: \ + 0) + +#define APP_EVENT_GET_BUTTON_BIT_FROM_MOUSE(m) ( \ + (m) == SDL_BUTTON_LEFT? LOCOMMON_INPUT_BUTTON_ATTACK: \ + (m) == SDL_BUTTON_RIGHT? LOCOMMON_INPUT_BUTTON_GUARD: \ + 0) + +bool app_event_handle(locommon_input_t* input, const SDL_Event* e) { + assert(input != NULL); + assert(e != NULL); + + switch (e->type) { + case SDL_MOUSEMOTION: + input->cursor = vec2( + e->motion.x/input->resolution.x, e->motion.y/input->resolution.y); + input->cursor.x = input->cursor.x*2 - 1; + input->cursor.y = 1 - input->cursor.y*2; + break; + case SDL_MOUSEBUTTONDOWN: + input->buttons |= APP_EVENT_GET_BUTTON_BIT_FROM_MOUSE(e->button.button); + break; + case SDL_MOUSEBUTTONUP: + input->buttons &= ~APP_EVENT_GET_BUTTON_BIT_FROM_MOUSE(e->button.button); + break; + case SDL_KEYDOWN: + input->buttons |= APP_EVENT_GET_BUTTON_BIT_FROM_KEY(e->key.keysym.sym); + break; + case SDL_KEYUP: + input->buttons &= ~APP_EVENT_GET_BUTTON_BIT_FROM_KEY(e->key.keysym.sym); + break; + case SDL_QUIT: + return false; + } + return true; +} diff --git a/app/sdl/event.h b/app/sdl/event.h new file mode 100644 index 0000000..9d4c881 --- /dev/null +++ b/app/sdl/event.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#include + +#include "core/locommon/input.h" + +bool +app_event_handle( + locommon_input_t* input, + const SDL_Event* e +); diff --git a/app/sdl/main.c b/app/sdl/main.c new file mode 100644 index 0000000..f0f92ae --- /dev/null +++ b/app/sdl/main.c @@ -0,0 +1,172 @@ +#define SDL_MAIN_HANDLED +#define NO_STDIO_REDIRECT + +#include +#include +#include + +#include +#include + +#include "core/loscene/context.h" + +#include "./args.h" +#include "./event.h" + +static const app_args_t app_default_args_ = { + .scene = { + .width = 960, + .height = 540, + .dpi = vec2(96, 96), + + .max_msaa = 8, + .brightness = 1000, + }, + .max_fps = 60, +}; + +typedef struct { + SDL_Window* win; + SDL_GLContext gl; +} libs_t; + +static void app_initialize_libraries_(libs_t* libs, app_args_t* args) { + assert(libs != NULL); + assert(args != NULL); + + SDL_SetHint(SDL_HINT_VIDEO_HIGHDPI_DISABLED, "0"); + if (SDL_Init(SDL_INIT_VIDEO)) { + fprintf(stderr, "failed to initialize SDL: %s\n", SDL_GetError()); + abort(); + } + + SDL_GL_SetAttribute( + SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); + SDL_GL_SetAttribute( + SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + + uint32_t win_flags = + SDL_WINDOW_ALLOW_HIGHDPI | + SDL_WINDOW_OPENGL | + SDL_WINDOW_SHOWN; + if (args->force_window) { + } else if (args->force_desktop_fullscreen) { + win_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + } else if (args->force_fullscreen) { + win_flags |= SDL_WINDOW_FULLSCREEN; + } else { +# ifdef NDEBUG + win_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; +# endif + } + + libs->win = SDL_CreateWindow( + "LEFTONE", /* title */ + SDL_WINDOWPOS_CENTERED, /* X position */ + SDL_WINDOWPOS_CENTERED, /* Y position */ + args->scene.width, + args->scene.height, + win_flags); + if (libs->win == NULL) { + fprintf(stderr, "failed to create window: %s\n", SDL_GetError()); + abort(); + } + + int w, h; + SDL_GetWindowSize(libs->win, &w, &h); + args->scene.width = w; + args->scene.height = h; + + libs->gl = SDL_GL_CreateContext(libs->win); + + glewExperimental = 1; + if (glewInit() != GLEW_OK) { + fprintf(stderr, "failed to init GLEW\n"); + abort(); + } + + glViewport(0, 0, args->scene.width, args->scene.height); +} +static void app_deinitialize_libraries_(libs_t* libs) { + assert(libs != NULL); + + SDL_GL_DeleteContext(libs->gl); + SDL_DestroyWindow(libs->win); + SDL_Quit(); +} + +static void app_get_dpi_(libs_t* libs, app_args_t* args) { + assert(libs != NULL); + assert(args != NULL); + + if (args->override_dpi) return; + + const int32_t disp = SDL_GetWindowDisplayIndex(libs->win); + + float x, y; + if (SDL_GetDisplayDPI(disp, NULL, &x, &y) == 0) { + args->scene.dpi = vec2(x, y); + } else { + fprintf(stderr, "failed to get display DPI: %s\n", SDL_GetError()); + fprintf(stderr, "Anti-aliasing may not work properly.\n"); + } +} + +int main(int argc, char** argv) { + (void) argc, (void) argv; + + app_args_t args = app_default_args_; + app_args_parse(&args, argc, argv); + + libs_t libs; + app_initialize_libraries_(&libs, &args); + + app_get_dpi_(&libs, &args); + + loscene_context_t* ctx = loscene_context_new(&args.scene); + + locommon_input_t input = { + .resolution = vec2(args.scene.width, args.scene.height), + .dpi = args.scene.dpi, + .cursor = vec2(0, 0), + }; + + glClearColor(0, 0, 0, 0); + + const uint64_t min_frame_time = 1000 / args.max_fps; + for (;;) { + const uint64_t base_time = SDL_GetTicks(); + + SDL_Event e; + while (SDL_PollEvent(&e)) { + if (!app_event_handle(&input, &e)) goto EXIT; + } + + if (!loscene_context_update(ctx, &input, base_time)) goto EXIT; + + glClear(GL_COLOR_BUFFER_BIT); + loscene_context_draw(ctx); + SDL_GL_SwapWindow(libs.win); + +# ifndef NDEBUG + /* for debugging in MSYS */ + fflush(stdout); + fflush(stderr); +# endif + + const uint64_t elapsed = SDL_GetTicks() - base_time; + if (elapsed < min_frame_time) { + SDL_Delay(min_frame_time - elapsed); + } + } + +EXIT: + loscene_context_delete(ctx); + app_deinitialize_libraries_(&libs); + return 0; +} diff --git a/cmake/anysrc.cmake b/cmake/anysrc.cmake new file mode 100644 index 0000000..517d757 --- /dev/null +++ b/cmake/anysrc.cmake @@ -0,0 +1,20 @@ +function(target_any_sources target) + set(bin2c ${LEFTONE_TOOL_DIR}/bin2c.sh) + + target_include_directories(${target} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + + foreach (path ${ARGN}) + get_filename_component(dirname ${path} DIRECTORY) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/anysrc/${dirname}) + + set(name ${target}_${path}_) + set(in ${CMAKE_CURRENT_SOURCE_DIR}/${path}) + set(out ${CMAKE_CURRENT_BINARY_DIR}/anysrc/${path}) + add_custom_command( + OUTPUT ${out}.c ${out}.h + COMMAND cat ${in} | ${bin2c} ${name} ${out} + DEPENDS ${path} ${bin2c} + COMMENT "converting ${path} to C header") + target_sources(${target} PRIVATE ${out}.c ${out}.h) + endforeach() +endfunction() diff --git a/cmake/sos.cmake b/cmake/sos.cmake new file mode 100644 index 0000000..08974e2 --- /dev/null +++ b/cmake/sos.cmake @@ -0,0 +1,20 @@ +function(target_source_of_source target) + target_include_directories(${target} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/sos) + foreach (file ${ARGN}) + get_filename_component(name ${file} NAME_WE) + + set(sos_target sos-${target}-${name}) + set(in ${CMAKE_CURRENT_SOURCE_DIR}/${file}) + set(out ${CMAKE_CURRENT_BINARY_DIR}/sos/${file}) + + add_executable(${sos_target} ${in}) + add_custom_command( + OUTPUT ${out} + COMMAND ${sos_target} > ${out} + DEPENDS ${sos_target} + COMMENT "generating ${file}") + target_sources(${target} PRIVATE ${out}) + endforeach() +endfunction() diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt new file mode 100644 index 0000000..0bba832 --- /dev/null +++ b/core/CMakeLists.txt @@ -0,0 +1,13 @@ +include_directories(.) + +add_subdirectory(lobullet) +add_subdirectory(locharacter) +add_subdirectory(locommon) +add_subdirectory(loeffect) +add_subdirectory(loentity) +add_subdirectory(loground) +add_subdirectory(loplayer) +add_subdirectory(loresource) +add_subdirectory(loscene) +add_subdirectory(loshader) +add_subdirectory(loworld) diff --git a/core/lobullet/CMakeLists.txt b/core/lobullet/CMakeLists.txt new file mode 100644 index 0000000..7c773d8 --- /dev/null +++ b/core/lobullet/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(lobullet + base.c + bomb.c + linear.c + misc.c + pool.c +) +target_link_libraries(lobullet + msgpackc + + math + memory + mpkutil + coly2d + + locommon + loeffect + loentity + loshader +) diff --git a/core/lobullet/base.c b/core/lobullet/base.c new file mode 100644 index 0000000..b01e28d --- /dev/null +++ b/core/lobullet/base.c @@ -0,0 +1,235 @@ +#include "./base.h" + +#include +#include +#include +#include +#include + +#include + +#include "util/math/vector.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/locommon/position.h" +#include "core/locommon/ticker.h" +#include "core/loentity/bullet.h" +#include "core/loentity/character.h" +#include "core/loentity/entity.h" +#include "core/loentity/store.h" +#include "core/loresource/set.h" +#include "core/loshader/bullet.h" + +#include "./bomb.h" +#include "./linear.h" +#include "./misc.h" + +static void lobullet_base_delete_(loentity_t* entity) { + assert(entity != NULL); + + lobullet_base_t* base = (typeof(base)) entity; + if (!base->used) return; + + base->used = false; + +# define each_(NAME, name) do { \ + if (base->type == LOBULLET_TYPE_##NAME) { \ + lobullet_##name##_tear_down(base); \ + return; \ + } \ + } while (0) + + LOBULLET_TYPE_EACH_(each_); + assert(false); + +# undef each_ +} + +static void lobullet_base_die_(loentity_t* entity) { + assert(entity != NULL); + +} + +static bool lobullet_base_update_(loentity_t* entity) { + assert(entity != NULL); + + lobullet_base_t* base = (typeof(base)) entity; + base->cache = (typeof(base->cache)) {0}; + +# define each_(NAME, name) do { \ + if (base->type == LOBULLET_TYPE_##NAME) { \ + return lobullet_##name##_update(base); \ + } \ + } while (0) + + LOBULLET_TYPE_EACH_(each_); + return false; + +# undef each_ +} + +static void lobullet_base_draw_( + loentity_t* entity, const locommon_position_t* basepos) { + assert(entity != NULL); + assert(locommon_position_valid(basepos)); + + lobullet_base_t* base = (typeof(base)) entity; + + vec2_t p; + locommon_position_sub(&p, &base->super.super.pos, basepos); + vec2_addeq(&base->cache.instance.pos, &p); + + loshader_bullet_drawer_add_instance(base->drawer, &base->cache.instance); +} + +static void lobullet_base_pack_( + const loentity_t* entity, msgpack_packer* packer) { + assert(entity != NULL); + assert(packer != NULL); + + const lobullet_base_t* base = (typeof(base)) entity; + + msgpack_pack_map(packer, 4); + + mpkutil_pack_str(packer, "subclass"); + mpkutil_pack_str(packer, "bullet"); + + mpkutil_pack_str(packer, "type"); + mpkutil_pack_str(packer, lobullet_type_stringify(base->type)); + + mpkutil_pack_str(packer, "id"); + msgpack_pack_uint64(packer, base->super.super.id); + + mpkutil_pack_str(packer, "data"); +# define each_(NAME, name) do { \ + if (base->type == LOBULLET_TYPE_##NAME) { \ + lobullet_##name##_pack_data(base, packer); \ + return; \ + } \ + } while (0) + + LOBULLET_TYPE_EACH_(each_); + assert(false); + +# undef each_ +} + +static bool lobullet_base_affect_( + loentity_bullet_t* bullet, loentity_character_t* chara) { + assert(bullet != NULL); + + lobullet_base_t* base = (typeof(base)) bullet; + + vec2_t v = vec2(0, 0); + switch (base->cache.knockback.algorithm) { + case LOBULLET_BASE_KNOCKBACK_ALGORITHM_VELOCITY: + v = base->super.velocity; + break; + case LOBULLET_BASE_KNOCKBACK_ALGORITHM_POSITION: + locommon_position_sub(&v, &chara->super.pos, &base->super.super.pos); + break; + } + const float plen = vec2_pow_length(&v); + if (plen != 0) { + vec2_diveq(&v, sqrtf(plen)); + vec2_muleq(&v, base->cache.knockback.acceleration); + loentity_character_knockback(chara, &v); + } + + if (base->cache.toxic) { + loentity_character_apply_effect(chara, &base->cache.effect); + } + return base->cache.toxic; +} + +void lobullet_base_initialize( + lobullet_base_t* base, + loresource_set_t* res, + loshader_bullet_drawer_t* drawer, + const locommon_ticker_t* ticker, + loentity_store_t* entities) { + assert(base != NULL); + assert(res != NULL); + assert(drawer != NULL); + assert(ticker != NULL); + assert(entities != NULL); + + *base = (typeof(*base)) { + .super = { + .super = { + .vtable = { + .delete = lobullet_base_delete_, + .die = lobullet_base_die_, + .update = lobullet_base_update_, + .draw = lobullet_base_draw_, + .pack = lobullet_base_pack_, + }, + .subclass = LOENTITY_SUBCLASS_BULLET, + }, + .vtable = { + .affect = lobullet_base_affect_, + }, + }, + .res = res, + .drawer = drawer, + .ticker = ticker, + .entities = entities, + }; +} + +void lobullet_base_reinitialize(lobullet_base_t* base, loentity_id_t id) { + assert(base != NULL); + + base->super.super.id = id; +} + +void lobullet_base_deinitialize(lobullet_base_t* base) { + assert(base != NULL); + + lobullet_base_delete_(&base->super.super); +} + +bool lobullet_base_unpack(lobullet_base_t* base, const msgpack_object* obj) { + assert(base != NULL); + + lobullet_base_reinitialize(base, 0); + + const char* v; + size_t vlen; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + +# define streq_(v1, len, v2) \ + (strncmp(v1, v2, len) == 0 && v2[len] == 0) + if (!mpkutil_get_str(item_("subclass"), &v, &vlen) || + !streq_(v, vlen, "bullet")) { + return false; + } +# undef streq_ + + if (!mpkutil_get_str(item_("type"), &v, &vlen) || + !lobullet_type_unstringify(&base->type, v, vlen)) { + return false; + } + + if (!mpkutil_get_uint64(item_("id"), &base->super.super.id)) { + return false; + } + + const msgpack_object* data = item_("data"); +# define each_(NAME, name) do { \ + if (base->type == LOBULLET_TYPE_##NAME) { \ + if (!lobullet_##name##_unpack_data(base, data)) return false; \ + } \ + } while (0) + + LOBULLET_TYPE_EACH_(each_); + +# undef each_ + +# undef item_ + return true; +} diff --git a/core/lobullet/base.h b/core/lobullet/base.h new file mode 100644 index 0000000..5656b47 --- /dev/null +++ b/core/lobullet/base.h @@ -0,0 +1,81 @@ +#pragma once + +#include +#include + +#include + +#include "util/math/vector.h" + +#include "core/locommon/ticker.h" +#include "core/loentity/bullet.h" +#include "core/loentity/entity.h" +#include "core/loentity/store.h" +#include "core/loresource/set.h" +#include "core/loshader/bullet.h" + +#include "./misc.h" + +typedef enum { + LOBULLET_BASE_KNOCKBACK_ALGORITHM_VELOCITY, + LOBULLET_BASE_KNOCKBACK_ALGORITHM_POSITION, +} lobullet_base_knockback_algorithm_t; + +typedef struct { + loentity_bullet_t super; + bool used; + + /* injected deps */ + loresource_set_t* res; + loshader_bullet_drawer_t* drawer; + const locommon_ticker_t* ticker; + loentity_store_t* entities; + + /* params not to be packed */ + struct { + bool toxic; + loeffect_t effect; + /* When toxic is true, apply this effect to characters hit. */ + + struct { + float acceleration; + lobullet_base_knockback_algorithm_t algorithm; + } knockback; + + loshader_bullet_drawer_instance_t instance; + /* instance pos is added to draw pos */ + } cache; + + /* params to be packed (includes id) */ + lobullet_type_t type; + +# define LOBULLET_BASE_DATA_MAX_SIZE 256 + uint8_t data[LOBULLET_BASE_DATA_MAX_SIZE]; + /* pack function for the type is used */ +} lobullet_base_t; + +void +lobullet_base_initialize( + lobullet_base_t* base, + loresource_set_t* res, + loshader_bullet_drawer_t* drawer, + const locommon_ticker_t* ticker, + loentity_store_t* entities +); + +void +lobullet_base_reinitialize( + lobullet_base_t* base, + loentity_id_t id +); + +void +lobullet_base_deinitialize( + lobullet_base_t* base +); + +bool +lobullet_base_unpack( + lobullet_base_t* base, + const msgpack_object* obj +); diff --git a/core/lobullet/bomb.c b/core/lobullet/bomb.c new file mode 100644 index 0000000..5451f44 --- /dev/null +++ b/core/lobullet/bomb.c @@ -0,0 +1,205 @@ +#include "./bomb.h" + +#include +#include +#include +#include +#include + +#include "util/coly2d/shape.h" +#include "util/math/algorithm.h" +#include "util/math/constant.h" +#include "util/math/vector.h" +#include "util/mpkutil/pack.h" + +#include "core/locommon/msgpack.h" +#include "core/locommon/position.h" +#include "core/locommon/ticker.h" +#include "core/loentity/entity.h" +#include "core/loresource/sound.h" +#include "core/loshader/bullet.h" + +#include "./base.h" +#include "./misc.h" + +#define LOBULLET_BOMB_PARAM_TO_PACK_EACH_(PROC) do { \ + PROC("owner", owner); \ + PROC("pos", pos); \ + PROC("size", size); \ + PROC("angle", angle); \ + PROC("color", color); \ + PROC("silent", silent); \ + PROC("beat", beat); \ + PROC("step", step); \ + PROC("knockback", knockback); \ + PROC("effect", effect); \ + PROC("since", since); \ +} while (0) +#define LOBULLET_BOMB_PARAM_TO_PACK_COUNT 11 + +_Static_assert(sizeof(lobullet_bomb_param_t) <= LOBULLET_BASE_DATA_MAX_SIZE); + +static bool lobullet_bomb_update_(lobullet_base_t* base) { + assert(base != NULL); + + const lobullet_bomb_param_t* p = (typeof(p)) base->data; + + base->super.super.pos = p->pos; + base->super.owner = p->owner; + base->super.velocity = vec2(0, 0); + base->super.shape.size = p->size; + base->super.shape.angle = p->angle; + + const uint64_t st = (p->step-1) * p->beat; + const uint64_t ed = st + 100; + + const uint64_t t = base->ticker->time - p->since; + const uint64_t pt = + (int64_t) t >= base->ticker->delta? t - base->ticker->delta: 0; + + if (!p->silent && pt < st && t >= st) { + loresource_sound_play(base->res->sound, "bomb"); + } + + base->cache.toxic = st <= t && t < ed; + base->cache.effect = p->effect; + return t < p->step*p->beat; +} + +bool lobullet_bomb_param_valid(const lobullet_bomb_param_t* param) { + return + param != NULL && + locommon_position_valid(¶m->pos) && + vec2_valid(¶m->size) && + MATH_FLOAT_VALID(param->angle) && + vec4_valid(¶m->color) && + MATH_FLOAT_VALID(param->beat) && + param->step > 0 && + MATH_FLOAT_VALID(param->knockback); +} +void lobullet_bomb_param_pack( + const lobullet_bomb_param_t* p, msgpack_packer* packer) { + assert(lobullet_bomb_param_valid(p)); + assert(packer != NULL); + + msgpack_pack_map(packer, LOBULLET_BOMB_PARAM_TO_PACK_COUNT); + +# define pack_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + LOCOMMON_MSGPACK_PACK_ANY(packer, &p->var); \ + } while (0) + + LOBULLET_BOMB_PARAM_TO_PACK_EACH_(pack_); + +# undef pack_ +} +bool lobullet_bomb_param_unpack( + lobullet_bomb_param_t* p, const msgpack_object* obj) { + assert(p != NULL); + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + +# define unpack_(name, var) do { \ + if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(name), &p->var)) { \ + return false; \ + } \ + } while (0) + + LOBULLET_BOMB_PARAM_TO_PACK_EACH_(unpack_); + return lobullet_bomb_param_valid(p); + +# undef unpack_ + +# undef item_ +} + +void lobullet_bomb_build( + lobullet_base_t* base, + lobullet_type_t type, + const lobullet_bomb_param_t* param) { + assert(base != NULL); + assert(lobullet_bomb_param_valid(param)); + + base->type = type; + + lobullet_bomb_param_t* p = (typeof(p)) base->data; + *p = *param; + p->since = base->ticker->time; +} + +bool lobullet_bomb_square_update(lobullet_base_t* base) { + assert(base != NULL); + + if (!lobullet_bomb_update_(base)) return false; + + const lobullet_bomb_param_t* p = (typeof(p)) base->data; + + /* ---- calculate motion ---- */ + const float beats = (base->ticker->time - p->since) / p->beat; + + float time = 0; + float angle = p->angle; + float alpha = 1; + if (beats < p->step-1) { + time = beats - (int64_t) beats; + alpha = 1-time; + time = time*time; + time = (1-time)*.05f; + } else { + time = 1 - powf(1-(beats - (int64_t) beats), 2); + angle += time * MATH_PI/4; + time = 1-time; + } + + /* ---- apply motion ---- */ + base->super.shape.type = COLY2D_SHAPE_TYPE_RECT; + base->super.shape.angle = angle; + + base->cache.instance = (loshader_bullet_drawer_instance_t) { + .bullet_id = LOSHADER_BULLET_ID_SQUARE, + .size = p->size, + .theta = angle, + .color = p->color, + .time = time, + }; + base->cache.instance.color.w *= alpha; + return true; +} + +bool lobullet_bomb_triangle_update(lobullet_base_t* base) { + assert(base != NULL); + + if (!lobullet_bomb_update_(base)) return false; + + const lobullet_bomb_param_t* p = (typeof(p)) base->data; + + /* ---- calculate motion ---- */ + const float beats = (base->ticker->time - p->since) / p->beat; + + float time = 0; + float alpha = 1; + if (beats < p->step-1) { + time = beats - (int64_t) beats; + alpha = 1-time; + time = time*time; + time = (1-time)*.05f; + } else { + time = 1 - powf(1-(beats - (int64_t) beats), 2); + time = 1-time; + } + + /* ---- apply motion ---- */ + base->super.shape.type = COLY2D_SHAPE_TYPE_TRIANGLE; + + base->cache.instance = (loshader_bullet_drawer_instance_t) { + .bullet_id = LOSHADER_BULLET_ID_TRIANGLE, + .size = p->size, + .theta = base->super.shape.angle, + .color = p->color, + .time = time, + }; + base->cache.instance.color.w *= alpha; + return true; +} diff --git a/core/lobullet/bomb.h b/core/lobullet/bomb.h new file mode 100644 index 0000000..ad2b979 --- /dev/null +++ b/core/lobullet/bomb.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#include + +#include + +#include "util/math/vector.h" + +#include "core/locommon/position.h" +#include "core/loeffect/effect.h" +#include "core/loentity/entity.h" + +#include "./base.h" +#include "./misc.h" + +typedef struct { + loentity_id_t owner; + locommon_position_t pos; + vec2_t size; + float angle; + vec4_t color; + bool silent; + + float beat; + int32_t step; + float knockback; + + loeffect_t effect; + + uint64_t since; /* set by build function */ +} lobullet_bomb_param_t; + +bool +lobullet_bomb_param_valid( + const lobullet_bomb_param_t* param /* NULLABLE */ +); +void +lobullet_bomb_param_pack( + const lobullet_bomb_param_t* param, + msgpack_packer* packer +); +bool +lobullet_bomb_param_unpack( + lobullet_bomb_param_t* param, + const msgpack_object* obj /* NULLABLE */ +); + +void +lobullet_bomb_build( + lobullet_base_t* base, + lobullet_type_t type, + const lobullet_bomb_param_t* param +); + +bool +lobullet_bomb_square_update( + lobullet_base_t* base +); +#define lobullet_bomb_square_build(base, param) \ + lobullet_bomb_build(base, LOBULLET_TYPE_BOMB_SQUARE, param) +#define lobullet_bomb_square_tear_down(base) +#define lobullet_bomb_square_pack_data(base, packer) \ + lobullet_bomb_param_pack( \ + (const lobullet_bomb_param_t*) base->data, packer) +#define lobullet_bomb_square_unpack_data(base, obj) \ + lobullet_bomb_param_unpack( \ + (lobullet_bomb_param_t*) base->data, obj) + +bool +lobullet_bomb_triangle_update( + lobullet_base_t* base +); +#define lobullet_bomb_triangle_build(base, param) \ + lobullet_bomb_build(base, LOBULLET_TYPE_BOMB_TRIANGLE, param) +#define lobullet_bomb_triangle_tear_down(base) +#define lobullet_bomb_triangle_pack_data(base, packer) \ + lobullet_bomb_param_pack( \ + (const lobullet_bomb_param_t*) base->data, packer) +#define lobullet_bomb_triangle_unpack_data(base, obj) \ + lobullet_bomb_param_unpack( \ + (lobullet_bomb_param_t*) base->data, obj) diff --git a/core/lobullet/linear.c b/core/lobullet/linear.c new file mode 100644 index 0000000..67a4b63 --- /dev/null +++ b/core/lobullet/linear.c @@ -0,0 +1,197 @@ +#include "./linear.h" + +#include +#include +#include +#include + +#include + +#include "util/coly2d/shape.h" +#include "util/math/constant.h" +#include "util/math/vector.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/locommon/msgpack.h" +#include "core/locommon/position.h" +#include "core/loeffect/effect.h" +#include "core/loentity/entity.h" + +#include "./base.h" +#include "./misc.h" + +#define LOBULLET_LINEAR_PARAM_TO_PACK_EACH_(PROC) do { \ + PROC("owner", owner); \ + PROC("pos", pos); \ + PROC("size", size); \ + PROC("velocity", velocity); \ + PROC("acceleration", acceleration); \ + PROC("color", color); \ + PROC("duration", duration); \ + PROC("knockback", knockback); \ + PROC("effect", effect); \ + PROC("since", since); \ +} while (0) +#define LOBULLET_LINEAR_PARAM_TO_PACK_COUNT 10 + +_Static_assert(sizeof(lobullet_linear_param_t) <= LOBULLET_BASE_DATA_MAX_SIZE); + +static bool lobullet_linear_update_(lobullet_base_t* base) { + assert(base != NULL); + + const lobullet_linear_param_t* p = (typeof(p)) base->data; + + const float t = (base->ticker->time - p->since)/1000.f; + + base->super.owner = p->owner; + + /* ---- movement ---- */ + vec2_t v1; + vec2_mul(&v1, &p->velocity, t); + + vec2_t v2; + vec2_mul(&v2, &p->acceleration, t*t/2); + + base->super.super.pos = p->pos; + vec2_addeq(&base->super.super.pos.fract, &v1); + vec2_addeq(&base->super.super.pos.fract, &v2); + locommon_position_reduce(&base->super.super.pos); + + /* ---- velocity ---- */ + vec2_mul(&base->super.velocity, &p->acceleration, t); + vec2_addeq(&base->super.velocity, &p->velocity); + + /* ---- angle ---- */ + const float theta = vec2_pow_length(&base->super.velocity) != 0? + atan2f(base->super.velocity.y, base->super.velocity.x): 0; + base->super.shape.size = p->size; + base->super.shape.angle = theta; + + /* ---- parameter update ---- */ + base->cache.toxic = true; + base->cache.effect = p->effect; + + base->cache.knockback = (typeof(base->cache.knockback)) { + .acceleration = p->knockback, + .algorithm = LOBULLET_BASE_KNOCKBACK_ALGORITHM_VELOCITY, + }; + + return p->since + p->duration > base->ticker->time; +} + +bool lobullet_linear_param_valid(const lobullet_linear_param_t* param) { + return + param != NULL && + locommon_position_valid(¶m->pos) && + vec2_valid(¶m->size) && + vec2_valid(¶m->velocity) && + vec2_valid(¶m->acceleration) && + param->duration > 0; +} +void lobullet_linear_param_pack( + const lobullet_linear_param_t* p, msgpack_packer* packer) { + assert(lobullet_linear_param_valid(p)); + assert(packer != NULL); + + msgpack_pack_map(packer, LOBULLET_LINEAR_PARAM_TO_PACK_COUNT); + +# define pack_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + LOCOMMON_MSGPACK_PACK_ANY(packer, &p->var); \ + } while (0) + + LOBULLET_LINEAR_PARAM_TO_PACK_EACH_(pack_); + +# undef pack_ +} +bool lobullet_linear_param_unpack( + lobullet_linear_param_t* p, const msgpack_object* obj) { + assert(p != NULL); + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + +# define unpack_(name, var) do { \ + if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(name), &p->var)) { \ + return false; \ + } \ + } while (0) + + LOBULLET_LINEAR_PARAM_TO_PACK_EACH_(unpack_); + return lobullet_linear_param_valid(p); + +# undef unpack_ + +# undef item_ +} + +void lobullet_linear_build( + lobullet_base_t* base, + lobullet_type_t type, + const lobullet_linear_param_t* param) { + assert(base != NULL); + assert(lobullet_linear_param_valid(param)); + + base->type = type; + + lobullet_linear_param_t* p = (typeof(p)) base->data; + *p = *param; + p->since = base->ticker->time; +} + +bool lobullet_linear_light_update(lobullet_base_t* base) { + assert(base != NULL); + + static const uint64_t fadedur = 500; + + if (!lobullet_linear_update_(base)) return false; + + const lobullet_linear_param_t* p = (typeof(p)) base->data; + + /* ---- calculation ---- */ + vec2_t size = p->size; + vec2_muleq(&size, 1.2f); + + float alpha = 1; + const uint64_t remain = p->duration - (base->ticker->time - p->since); + if (remain <= fadedur) alpha = remain*1.f / fadedur; + + /* ---- apply result ---- */ + base->super.shape.type = COLY2D_SHAPE_TYPE_ELLIPSE; + + base->cache.instance = (loshader_bullet_drawer_instance_t) { + .bullet_id = LOSHADER_BULLET_ID_LIGHT, + .size = size, + .theta = base->super.shape.angle, + .color = p->color, + }; + base->cache.instance.color.w = alpha; + return true; +} + +bool lobullet_linear_triangle_update(lobullet_base_t* base) { + assert(base != NULL); + + if (!lobullet_linear_update_(base)) return false; + + const lobullet_linear_param_t* p = (typeof(p)) base->data; + + /* ---- calculation ---- */ + vec2_t size = p->size; + size.x *= 1-(1-cos(MATH_PI/3))/2; + size.y *= 1-(1-sin(MATH_PI/3))/2; + + /* ---- apply result ---- */ + base->super.shape.type = COLY2D_SHAPE_TYPE_TRIANGLE; + + base->cache.instance = (loshader_bullet_drawer_instance_t) { + .bullet_id = LOSHADER_BULLET_ID_TRIANGLE, + .size = size, + .theta = base->super.shape.angle, + .color = p->color, + .time = 1, + }; + return true; +} diff --git a/core/lobullet/linear.h b/core/lobullet/linear.h new file mode 100644 index 0000000..90dc51d --- /dev/null +++ b/core/lobullet/linear.h @@ -0,0 +1,81 @@ +#pragma once + +#include +#include + +#include + +#include "util/math/vector.h" + +#include "core/locommon/position.h" +#include "core/loeffect/effect.h" +#include "core/loentity/entity.h" + +#include "./base.h" + +typedef struct { + loentity_id_t owner; + + locommon_position_t pos; + vec2_t size; + vec2_t velocity; + vec2_t acceleration; + vec4_t color; + + uint64_t duration; + float knockback; + + loeffect_t effect; + + uint64_t since; /* set by build function */ +} lobullet_linear_param_t; + +bool +lobullet_linear_param_valid( + const lobullet_linear_param_t* param +); +void +lobullet_linear_param_pack( + const lobullet_linear_param_t* param, + msgpack_packer* packer +); +bool +lobullet_linear_param_unpack( + lobullet_linear_param_t* param, + const msgpack_object* obj /* NULLABLE */ +); + +void +lobullet_linear_build( + lobullet_base_t* base, + lobullet_type_t type, + const lobullet_linear_param_t* param +); + +bool +lobullet_linear_light_update( + lobullet_base_t* base +); +#define lobullet_linear_light_build(base, param) \ + lobullet_linear_build(base, LOBULLET_TYPE_LINEAR_LIGHT, param) +#define lobullet_linear_light_tear_down(base) +#define lobullet_linear_light_pack_data(base, packer) \ + lobullet_linear_param_pack( \ + (const lobullet_linear_param_t*) base->data, packer) +#define lobullet_linear_light_unpack_data(base, obj) \ + lobullet_linear_param_unpack( \ + (lobullet_linear_param_t*) base->data, obj) + +bool +lobullet_linear_triangle_update( + lobullet_base_t* base +); +#define lobullet_linear_triangle_build(base, param) \ + lobullet_linear_build(base, LOBULLET_TYPE_LINEAR_TRIANGLE, param) +#define lobullet_linear_triangle_tear_down(base) +#define lobullet_linear_triangle_pack_data(base, packer) \ + lobullet_linear_param_pack( \ + (const lobullet_linear_param_t*) base->data, packer) +#define lobullet_linear_triangle_unpack_data(base, obj) \ + lobullet_linear_param_unpack( \ + (lobullet_linear_param_t*) base->data, obj) diff --git a/core/lobullet/misc.c b/core/lobullet/misc.c new file mode 100644 index 0000000..766f0a9 --- /dev/null +++ b/core/lobullet/misc.c @@ -0,0 +1,37 @@ +#include "./misc.h" + +#include +#include +#include +#include + +const char* lobullet_type_stringify(lobullet_type_t type) { +# define each_(NAME, name) do { \ + if (type == LOBULLET_TYPE_##NAME) return #name; \ + } while (0) + + LOBULLET_TYPE_EACH_(each_); + + assert(false); + return NULL; + +# undef each_ +} + +bool lobullet_type_unstringify( + lobullet_type_t* type, const char* v, size_t len) { + assert(type != NULL); + assert(v != NULL || len == 0); + +# define each_(NAME, name) do { \ + if (strncmp(v, #name, len) == 0 && #name[len] == 0) { \ + *type = LOBULLET_TYPE_##NAME; \ + return true; \ + } \ + } while (0) + + LOBULLET_TYPE_EACH_(each_); + return false; + +# undef each_ +} diff --git a/core/lobullet/misc.h b/core/lobullet/misc.h new file mode 100644 index 0000000..7d56b6b --- /dev/null +++ b/core/lobullet/misc.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +/* dont forget to update EACH macro */ +typedef enum { + LOBULLET_TYPE_LINEAR_LIGHT, + LOBULLET_TYPE_LINEAR_TRIANGLE, + LOBULLET_TYPE_BOMB_SQUARE, + LOBULLET_TYPE_BOMB_TRIANGLE, +} lobullet_type_t; + +#define LOBULLET_TYPE_EACH_(PROC) do { \ + PROC(LINEAR_LIGHT, linear_light); \ + PROC(LINEAR_TRIANGLE, linear_triangle); \ + PROC(BOMB_SQUARE, bomb_square); \ + PROC(BOMB_TRIANGLE, bomb_triangle); \ +} while (0) + +const char* +lobullet_type_stringify( + lobullet_type_t type +); + +bool +lobullet_type_unstringify( + lobullet_type_t* type, + const char* v, + size_t len +); diff --git a/core/lobullet/pool.c b/core/lobullet/pool.c new file mode 100644 index 0000000..a30eb23 --- /dev/null +++ b/core/lobullet/pool.c @@ -0,0 +1,104 @@ +#include "./pool.h" + +#include + +#include + +#include "util/memory/memory.h" + +#include "core/locommon/counter.h" +#include "core/locommon/ticker.h" +#include "core/loentity/store.h" +#include "core/loresource/set.h" +#include "core/loshader/bullet.h" + +#include "./base.h" + +struct lobullet_pool_t { + loresource_set_t* res; + loshader_bullet_drawer_t* drawer; + locommon_counter_t* idgen; + const locommon_ticker_t* ticker; + loentity_store_t* entities; + + size_t length; + lobullet_base_t items[1]; +}; + +static size_t lobullet_pool_find_unused_item_index_( + const lobullet_pool_t* pool) { + assert(pool != NULL); + + for (size_t i = 0; i < pool->length; ++i) { + if (!pool->items[i].used) return i; + } + fprintf(stderr, "bullet pool overflow\n"); + abort(); +} + +lobullet_pool_t* lobullet_pool_new( + loresource_set_t* res, + loshader_bullet_drawer_t* drawer, + locommon_counter_t* idgen, + const locommon_ticker_t* ticker, + loentity_store_t* entities, + size_t length) { + assert(res != NULL); + assert(drawer != NULL); + assert(idgen != NULL); + assert(ticker != NULL); + assert(entities != NULL); + assert(length > 0); + + lobullet_pool_t* pool = + memory_new(sizeof(*pool) + (length-1)*sizeof(pool->items[0])); + *pool = (typeof(*pool)) { + .res = res, + .drawer = drawer, + .idgen = idgen, + .ticker = ticker, + .entities = entities, + .length = length, + }; + for (size_t i = 0; i < pool->length; ++i) { + lobullet_base_initialize( + &pool->items[i], + res, + drawer, + ticker, + entities); + } + return pool; +} + +void lobullet_pool_delete(lobullet_pool_t* pool) { + if (pool == NULL) return; + + for (size_t i = 0; i < pool->length; ++i) { + lobullet_base_deinitialize(&pool->items[i]); + } + memory_delete(pool); +} + +lobullet_base_t* lobullet_pool_create(lobullet_pool_t* pool) { + assert(pool != NULL); + + const size_t i = lobullet_pool_find_unused_item_index_(pool); + + pool->items[i].used = true; + lobullet_base_reinitialize( + &pool->items[i], locommon_counter_count(pool->idgen)); + return &pool->items[i]; +} + +lobullet_base_t* lobullet_pool_unpack_item( + lobullet_pool_t* pool, const msgpack_object* obj) { + assert(pool != NULL); + + const size_t i = lobullet_pool_find_unused_item_index_(pool); + + if (!lobullet_base_unpack(&pool->items[i], obj)) return NULL; + + pool->items[i].used = true; + return &pool->items[i]; +} diff --git a/core/lobullet/pool.h b/core/lobullet/pool.h new file mode 100644 index 0000000..66d9557 --- /dev/null +++ b/core/lobullet/pool.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include + +#include "core/locommon/counter.h" +#include "core/locommon/ticker.h" +#include "core/loentity/store.h" +#include "core/loresource/set.h" +#include "core/loshader/bullet.h" + +#include "./base.h" + +struct lobullet_pool_t; +typedef struct lobullet_pool_t lobullet_pool_t; + +lobullet_pool_t* /* OWNERSHIP */ +lobullet_pool_new( + loresource_set_t* res, + loshader_bullet_drawer_t* drawer, + locommon_counter_t* idgen, + const locommon_ticker_t* ticker, + loentity_store_t* entities, + size_t length +); + +void +lobullet_pool_delete( + lobullet_pool_t* pool /* OWNERSHIP */ +); + +lobullet_base_t* +lobullet_pool_create( + lobullet_pool_t* pool +); + +lobullet_base_t* +lobullet_pool_unpack_item( + lobullet_pool_t* pool, + const msgpack_object* obj +); diff --git a/core/locharacter/CMakeLists.txt b/core/locharacter/CMakeLists.txt new file mode 100644 index 0000000..67c4fad --- /dev/null +++ b/core/locharacter/CMakeLists.txt @@ -0,0 +1,27 @@ +add_library(locharacter + base.c + big_warder.c + cavia.c + encephalon.c + greedy_scientist.c + misc.c + pool.c + scientist.c + theists_child.c + util.c + warder.c +) +target_link_libraries(locharacter + msgpackc + + math + memory + mpkutil + + lobullet + locommon + loeffect + loentity + loplayer + loshader +) diff --git a/core/locharacter/base.c b/core/locharacter/base.c new file mode 100644 index 0000000..bf84cf2 --- /dev/null +++ b/core/locharacter/base.c @@ -0,0 +1,434 @@ +#include "./base.h" + +#include +#include +#include +#include + +#include "util/math/algorithm.h" +#include "util/math/vector.h" + +#include "core/lobullet/pool.h" +#include "core/locommon/easing.h" +#include "core/locommon/msgpack.h" +#include "core/locommon/position.h" +#include "core/locommon/ticker.h" +#include "core/loeffect/recipient.h" +#include "core/loentity/character.h" +#include "core/loentity/store.h" +#include "core/loplayer/player.h" +#include "core/loresource/set.h" +#include "core/loshader/character.h" + +#include "./big_warder.h" +#include "./cavia.h" +#include "./encephalon.h" +#include "./greedy_scientist.h" +#include "./scientist.h" +#include "./theists_child.h" +#include "./misc.h" +#include "./warder.h" + +#define LOCHARACTER_BASE_PARAM_TO_PACK_EACH_( \ + PROC, PROC_type, PROC_state, PROC_str) do { \ + PROC_str ("subclass", "character"); \ + PROC_type ("type", type); \ + PROC ("id", super.super.id); \ + PROC ("ground", ground); \ + PROC ("pos", pos); \ + PROC ("direction", direction); \ + PROC ("knockback", knockback); \ + PROC ("gravity", gravity); \ + PROC ("madness", recipient.madness); \ + PROC ("effects", recipient.effects); \ + PROC_state("state", state); \ + PROC ("since", since); \ + PROC ("last-update-time", last_update_time); \ + PROC ("last-knockback-time", last_knockback_time); \ + PROC ("last-hit-time", last_hit_time); \ +} while (0) +#define LOCHARACTER_BASE_PARAM_TO_PACK_COUNT 15 + +static void locharacter_base_convert_to_world_pos_( + const loentity_ground_t* g, locommon_position_t* wpos, const vec2_t* pos) { + assert(g != NULL); + assert(wpos != NULL); + assert(vec2_valid(pos)); + + vec2_t p = *pos; + p.x *= g->size.x; + p.y += g->size.y; + + *wpos = g->super.pos; + vec2_addeq(&wpos->fract, &p); + locommon_position_reduce(wpos); +} + +static void locharacter_base_convert_from_world_pos_( + const loentity_ground_t* g, vec2_t* pos, const locommon_position_t* wpos) { + assert(g != NULL); + assert(pos != NULL); + assert(locommon_position_valid(wpos)); + + locommon_position_sub(pos, wpos, &g->super.pos); + pos->x /= g->size.x; + pos->y -= g->size.y; +} + +static loentity_ground_t* locharacter_base_get_ground_(locharacter_base_t* base) { + assert(base != NULL); + + loentity_store_iterator_t itr; + if (loentity_store_find_item_by_id(base->entities, &itr, base->ground)) { + return itr.ground; + } + return NULL; +} + +static void locharacter_base_handle_knockback_(locharacter_base_t* base) { + assert(base != NULL); + + vec2_t v = base->knockback; + v.x /= base->cache.ground->size.x; + vec2_muleq(&v, base->ticker->delta_f); + vec2_addeq(&base->pos, &v); + + locommon_easing_linear_float(&base->knockback.x, 0, base->ticker->delta_f/2); + locommon_easing_linear_float(&base->knockback.y, 0, base->ticker->delta_f/2); +} + +static void locharacter_base_calculate_world_position_( + locharacter_base_t* base) { + assert(base != NULL); + + base->pos.x = MATH_CLAMP(base->pos.x, -1, 1); + base->pos.y = MATH_CLAMP(base->pos.y, 0, 1); + + if (base->pos.y < base->cache.height) { + if (base->cache.gravity) base->gravity = 0; + base->pos.y = base->cache.height; + } + locharacter_base_convert_to_world_pos_( + base->cache.ground, &base->super.super.pos, &base->pos); +} + +static void locharacter_base_calculate_velocity_( + locharacter_base_t* base, vec2_t* v, const locommon_position_t* oldpos) { + assert(base != NULL); + assert(v != NULL); + assert(locommon_position_valid(oldpos)); + + locommon_position_sub(v, &base->super.super.pos, oldpos); + vec2_diveq(v, base->ticker->delta_f); +} + +static void locharacter_base_execute_bullet_hittest_( + locharacter_base_t* base, const vec2_t* velocity) { + assert(base != NULL); + assert(vec2_valid(velocity)); + + if (base->last_hit_time + 200 > base->ticker->time) return; + + if (loentity_store_affect_bullets_shot_by_one( + base->entities, + &base->super, + base->player->entity.super.super.id, + velocity, + base->ticker->delta_f)) { + base->last_hit_time = base->ticker->time; + } +} + +static void locharacter_base_delete_(loentity_t* entity) { + assert(entity != NULL); + + locharacter_base_t* base = (typeof(base)) entity; + if (!base->used) return; + + base->used = false; + +# define each_(NAME, name) do { \ + if (base->type == LOCHARACTER_TYPE_##NAME) { \ + locharacter_##name##_tear_down(base); \ + return; \ + } \ + } while (0) + + LOCHARACTER_TYPE_EACH_(each_); + assert(false); + +# undef each_ +} + +static void locharacter_base_die_(loentity_t* entity) { + assert(entity != NULL); + +} + +static bool locharacter_base_update_(loentity_t* entity) { + assert(entity != NULL); + + static const float gravity_acceleration = 2.f; + + locharacter_base_t* base = (typeof(base)) entity; + + base->cache = (typeof(base->cache)) { + .time = base->ticker->time, + }; + + base->cache.ground = locharacter_base_get_ground_(base); + if (base->cache.ground == NULL) return false; + + locharacter_base_convert_from_world_pos_( + base->cache.ground, + &base->cache.player_pos, + &base->player->entity.super.super.pos); + + locharacter_base_handle_knockback_(base); + + locommon_position_t oldpos = base->super.super.pos; + + base->pos.y += base->gravity * base->ticker->delta_f; + +# define each_(NAME, name) do { \ + if (base->type == LOCHARACTER_TYPE_##NAME) { \ + if (!locharacter_##name##_update(base)) return false; \ + } \ + } while (0) + + LOCHARACTER_TYPE_EACH_(each_); + +# undef each_ + + locharacter_base_calculate_world_position_(base); + + if (base->cache.gravity) { + base->gravity -= base->ticker->delta_f * gravity_acceleration; + } else { + base->gravity = 0; + } + if (base->cache.bullet_hittest) { + vec2_t velocity; + locharacter_base_calculate_velocity_(base, &velocity, &oldpos); + locharacter_base_execute_bullet_hittest_(base, &velocity); + } + + base->cache.ground = NULL; + base->last_update_time = base->cache.time; + return true; +} + +static void locharacter_base_draw_( + loentity_t* entity, const locommon_position_t* basepos) { + assert(entity != NULL); + assert(locommon_position_valid(basepos)); + + locharacter_base_t* base = (typeof(base)) entity; + + vec2_t v; + locommon_position_sub(&v, &base->super.super.pos, basepos); + vec2_addeq(&base->cache.instance.pos, &v); + + loshader_character_drawer_add_instance(base->drawer, &base->cache.instance); +} + +static void locharacter_base_apply_effect_( + loentity_character_t* entity, const loeffect_t* effect) { + assert(entity != NULL); + assert(effect != NULL); + + locharacter_base_t* base = (typeof(base)) entity; + loeffect_recipient_apply_effect(&base->recipient, effect); +} + +static void locharacter_base_knockback_( + loentity_character_t* chara, const vec2_t* knockback) { + assert(chara != NULL); + assert(vec2_valid(knockback)); + + locharacter_base_t* base = (typeof(base)) chara; + + static const float r = .05f; + if (vec2_pow_length(knockback) > r*r) { + base->last_knockback_time = base->ticker->time; + } + vec2_addeq(&base->knockback, knockback); +} + +static void locharacter_base_pack_( + const loentity_t* chara, msgpack_packer* packer) { + assert(chara != NULL); + assert(packer != NULL); + + const locharacter_base_t* base = (typeof(base)) chara; + + msgpack_pack_map(packer, LOCHARACTER_BASE_PARAM_TO_PACK_COUNT+1); + +# define pack_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + LOCOMMON_MSGPACK_PACK_ANY(packer, &base->var); \ + } while (0) +# define pack_type_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + mpkutil_pack_str(packer, locharacter_type_stringify(base->var)); \ + } while (0) +# define pack_state_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + mpkutil_pack_str(packer, locharacter_state_stringify(base->var)); \ + } while (0) +# define pack_str_(name, str) do { \ + mpkutil_pack_str(packer, name); \ + mpkutil_pack_str(packer, str); \ + } while (0) + + LOCHARACTER_BASE_PARAM_TO_PACK_EACH_(pack_, pack_type_, pack_state_, pack_str_); + +# undef pack_str_ +# undef pack_state_ +# undef pack_type_ +# undef pack_ + +# define each_(NAME, name) do { \ + if (base->type == LOCHARACTER_TYPE_##NAME) { \ + locharacter_##name##_pack_data(base, packer); \ + return; \ + } \ + } while (0) + + mpkutil_pack_str(packer, "data"); + LOCHARACTER_TYPE_EACH_(each_); + assert(false); + +# undef each_ +} + +void locharacter_base_initialize( + locharacter_base_t* base, + loresource_set_t* res, + loshader_character_drawer_t* drawer, + const locommon_ticker_t* ticker, + lobullet_pool_t* bullets, + loentity_store_t* entities, + loplayer_t* player) { + assert(base != NULL); + assert(res != NULL); + assert(drawer != NULL); + assert(ticker != NULL); + assert(bullets != NULL); + assert(entities != NULL); + assert(player != NULL); + + *base = (typeof(*base)) { + .super = { + .super = { + .vtable = { + .delete = locharacter_base_delete_, + .die = locharacter_base_die_, + .update = locharacter_base_update_, + .draw = locharacter_base_draw_, + .pack = locharacter_base_pack_, + }, + .subclass = LOENTITY_SUBCLASS_CHARACTER, + }, + .vtable = { + .apply_effect = locharacter_base_apply_effect_, + .knockback = locharacter_base_knockback_, + }, + }, + .res = res, + .drawer = drawer, + .ticker = ticker, + .bullets = bullets, + .entities = entities, + .player = player, + }; + loeffect_recipient_initialize(&base->recipient, ticker); +} + +void locharacter_base_reinitialize(locharacter_base_t* base, loentity_id_t id) { + assert(base != NULL); + +# define reset_(name, var) do { \ + base->var = (typeof(base->var)) {0}; \ + } while (0) +# define reset_str_(name, str) + + LOCHARACTER_BASE_PARAM_TO_PACK_EACH_( + reset_, reset_, reset_, reset_str_); + +# undef reset_str_ +# undef reset_ + + loeffect_recipient_reset(&base->recipient); + base->super.super.id = id; +} + +void locharacter_base_deinitialize(locharacter_base_t* base) { + assert(base != NULL); + + if (base->used) locharacter_base_delete_(&base->super.super); + loeffect_recipient_deinitialize(&base->recipient); +} + +bool locharacter_base_unpack( + locharacter_base_t* base, const msgpack_object* obj) { + assert(base != NULL); + assert(obj != NULL); + + locharacter_base_reinitialize(base, 0); + /* id will be overwritten below */ + + const char* v; + size_t vlen; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + +# define unpack_(name, var) do { \ + if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(name), &base->var)) { \ + return NULL; \ + } \ + } while (0) +# define unpack_type_(name, var) do { \ + if (!mpkutil_get_str(item_(name), &v, &vlen) || \ + !locharacter_type_unstringify(&base->var, v, vlen)) { \ + return NULL; \ + } \ + } while (0) +# define unpack_state_(name, var) do { \ + if (!mpkutil_get_str(item_(name), &v, &vlen) || \ + !locharacter_state_unstringify(&base->var, v, vlen)) { \ + return NULL; \ + } \ + } while (0) +# define unpack_str_(name, str) do { \ + if (!mpkutil_get_str(item_(name), &v, &vlen) || \ + !(strncmp(v, str, vlen) == 0 && str[vlen] == 0)) { \ + return NULL; \ + } \ + } while (0) + + LOCHARACTER_BASE_PARAM_TO_PACK_EACH_( + unpack_, unpack_type_, unpack_state_, unpack_str_); + +# undef unpack_str_ +# undef unpack_state_ +# undef unpack_type_ +# undef unpack_ + + const msgpack_object* data = item_("data"); + +# define each_(NAME, name) do { \ + if (base->type == LOCHARACTER_TYPE_##NAME) { \ + return locharacter_##name##_unpack_data(base, data); \ + } \ + } while (0) + + LOCHARACTER_TYPE_EACH_(each_); + return false; + +# undef each_ + +# undef item_ +} diff --git a/core/locharacter/base.h b/core/locharacter/base.h new file mode 100644 index 0000000..7e0e0c5 --- /dev/null +++ b/core/locharacter/base.h @@ -0,0 +1,100 @@ +#pragma once + +#include +#include +#include + +#include "util/math/vector.h" + +#include "core/lobullet/pool.h" +#include "core/locommon/position.h" +#include "core/locommon/ticker.h" +#include "core/loeffect/recipient.h" +#include "core/loentity/character.h" +#include "core/loentity/store.h" +#include "core/loplayer/player.h" +#include "core/loresource/set.h" +#include "core/loshader/character.h" + +#include "./misc.h" + +typedef struct { + loentity_character_t super; + bool used; + + /* injected deps */ + loresource_set_t* res; + loshader_character_drawer_t* drawer; + const locommon_ticker_t* ticker; + lobullet_pool_t* bullets; + loentity_store_t* entities; + loplayer_t* player; + + /* temporary params for update */ + struct { + /* set before calling update function */ + loentity_ground_t* ground; + vec2_t player_pos; + + uint64_t time; + /* Defaultly equals to ticker->time. + But characters who have an event with music + overwrites this value for synchronization */ + + /* set by update function */ + float height; + bool bullet_hittest; + bool gravity; + + loshader_character_drawer_instance_t instance; + } cache; + + /* params to be packed (includes id) */ + locharacter_type_t type; + + loentity_id_t ground; + vec2_t pos; + float direction; + vec2_t knockback; + float gravity; + + loeffect_recipient_t recipient; + + locharacter_state_t state; + uint64_t since; + + uint64_t last_update_time; + uint64_t last_knockback_time; + uint64_t last_hit_time; + +# define LOCHARACTER_BASE_DATA_MAX_SIZE 256 + uint8_t data[LOCHARACTER_BASE_DATA_MAX_SIZE]; +} locharacter_base_t; + +void +locharacter_base_initialize( + locharacter_base_t* base, + loresource_set_t* res, + loshader_character_drawer_t* drawer, + const locommon_ticker_t* ticker, + lobullet_pool_t* bullets, + loentity_store_t* entities, + loplayer_t* player +); + +void +locharacter_base_reinitialize( + locharacter_base_t* base, + loentity_id_t id +); + +void +locharacter_base_deinitialize( + locharacter_base_t* base +); + +bool +locharacter_base_unpack( + locharacter_base_t* base, + const msgpack_object* obj +); diff --git a/core/locharacter/big_warder.c b/core/locharacter/big_warder.c new file mode 100644 index 0000000..3b86a0d --- /dev/null +++ b/core/locharacter/big_warder.c @@ -0,0 +1,787 @@ +#include "./big_warder.h" + +#include +#include +#include + +#include "util/math/algorithm.h" +#include "util/math/vector.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/lobullet/base.h" +#include "core/lobullet/bomb.h" +#include "core/lobullet/linear.h" +#include "core/lobullet/pool.h" +#include "core/locommon/easing.h" +#include "core/locommon/msgpack.h" +#include "core/loentity/entity.h" +#include "core/loplayer/combat.h" +#include "core/loplayer/event.h" +#include "core/loplayer/player.h" +#include "core/loresource/music.h" +#include "core/loresource/text.h" +#include "core/loshader/character.h" + +#include "./base.h" +#include "./misc.h" +#include "./util.h" + +typedef struct { + locharacter_event_holder_t event; + + int32_t phase; + vec2_t from; + vec2_t to; +} locharacter_big_warder_param_t; + +_Static_assert( + sizeof(locharacter_big_warder_param_t) <= LOCHARACTER_BASE_DATA_MAX_SIZE); + +#define LOCHARACTER_BIG_WARDER_PARAM_TO_PACK_EACH_(PROC) do { \ + PROC("event-start-time", event.start_time); \ + PROC("phase", phase); \ + PROC("from", from); \ + PROC("to", to); \ +} while (0) +#define LOCHARACTER_BIG_WARDER_PARAM_TO_PACK_COUNT 4 + +static const vec2_t locharacter_big_warder_size_ = vec2(.04f, .07f); + +static const loeffect_recipient_status_t +locharacter_big_warder_base_status_ = { + .attack = .1f, + .defence = .85f, + .speed = .1f, + .jump = .1f, +}; + +#define LOCHARACTER_BIG_WARDER_BEAT (60000/80.f) /* 80 BPM */ +#define LOCHARACTER_BIG_WARDER_MUSIC_DURATION \ + ((uint64_t) LOCHARACTER_BIG_WARDER_BEAT*144) + +#define LOCHARACTER_BIG_WARDER_MELODY_B_BEAT 80 + +#include "./big_warder.private.h" + +static void +locharacter_big_warder_start_wait_state_( + locharacter_base_t* c +); +static void +locharacter_big_warder_start_walk_state_( + locharacter_base_t* c +); +static void +locharacter_big_warder_start_shoot_state_( + locharacter_base_t* c +); +static void +locharacter_big_warder_start_combo_state_( + locharacter_base_t* c +); +static void +locharacter_big_warder_start_thrust_state_( + locharacter_base_t* c +); +static void +locharacter_big_warder_start_cooldown_state_( + locharacter_base_t* c +); +static void +locharacter_big_warder_start_stunned_state_( + locharacter_base_t* c +); +static void +locharacter_big_warder_start_dead_state_( + locharacter_base_t* c +); + +static void locharacter_big_warder_finalize_event_(locharacter_base_t* c) { + assert(c != NULL); + /* This function must start next state. */ + + locharacter_big_warder_param_t* p = (typeof(p)) c->data; + assert(p != NULL); + + locharacter_event_holder_release_control(&p->event); + + if (c->recipient.madness > 0) { + loentity_character_apply_effect( + &c->player->entity.super, &loeffect_curse_trigger()); + locharacter_big_warder_start_wait_state_(c); + } else { + loplayer_gain_stance(c->player, LOEFFECT_STANCE_ID_UNFINISHER); + locharacter_big_warder_start_dead_state_(c); + } +} + +static bool locharacter_big_warder_reset_if_player_left_( + locharacter_base_t* c) { + assert(c != NULL); + + if (MATH_ABS(c->cache.player_pos.x) < 1 && + 0 < c->cache.player_pos.y && c->cache.player_pos.y < 1) { + return false; + } + + locharacter_big_warder_param_t* p = (typeof(p)) c->data; + locharacter_event_holder_release_control(&p->event); + + locharacter_big_warder_start_wait_state_(c); + return true; +} + +static void locharacter_big_warder_update_wait_state_(locharacter_base_t* c) { + assert(c != NULL); + + static const uint64_t period = 1000; + + c->cache.gravity = true; + + /* ---- motion ---- */ + float t = (c->cache.time - c->since)%period*1.f/period; + t = (t*2) - 1; + t = MATH_ABS(t); + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = t*t*(3-2*t); + + /* ---- state transition ---- */ + if (MATH_ABS(c->cache.player_pos.x) < 1 && + 0 < c->cache.player_pos.y && c->cache.player_pos.y < 1) { + vec2_t diff; + vec2_sub(&diff, &c->cache.player_pos, &c->pos); + if (vec2_pow_length(&diff) < .5f*.5f) { + locharacter_big_warder_start_walk_state_(c); + return; + } + } +} +static void locharacter_big_warder_start_wait_state_(locharacter_base_t* c) { + assert(c != NULL); + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_WAIT; +} + +static void locharacter_big_warder_update_walk_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_BIG_WARDER_BEAT; + static const float linedur = beat*4; + static const uint64_t period = 800; + + locharacter_big_warder_param_t* p = (typeof(p)) c->data; + + const bool event = locharacter_event_holder_has_control(&p->event); + + const uint64_t min_duration = event? LOCHARACTER_BIG_WARDER_BEAT*16: 0; + + /* ---- motion ---- */ + loshader_character_drawer_instance_t* instance = &c->cache.instance; + if (c->pos.x != 0) { + float t = (c->cache.time - c->since)%period*1.f/period; + t = (t*2) - 1; + t = MATH_ABS(t); + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_WALK; + instance->motion_time = t; + } else { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + } + + /* ---- position ---- */ + if (c->pos.x != 0) c->direction = -MATH_SIGN(c->pos.x); + + c->pos.y = 0; + locommon_easing_linear_float(&c->pos.x, 0, c->ticker->delta_f/5); + + /* ---- event ---- */ + if (event) { + p->event.param->cinescope = true; + p->event.param->hide_hud = true; + + if (c->since+(p->phase+1)*linedur < c->cache.time) { + static const char* text[] = { + "boss_big_warder_line0", + "boss_big_warder_line1", + }; + if (p->phase < (int32_t) (sizeof(text)/sizeof(text[0]))) { + const char* v = loresource_text_get( + c->res->lang, text[(size_t) p->phase]); + loplayer_event_param_set_line(p->event.param, v, strlen(v)); + } else { + loplayer_event_param_set_line(p->event.param, "", 0); + } + ++p->phase; + } + } + + /* ---- state transition ---- */ + if (locharacter_big_warder_reset_if_player_left_(c)) return; + + if (c->pos.x == 0 && c->since + min_duration <= c->cache.time) { + if (event) { + p->event.param->hide_hud = false; + p->event.param->cinescope = false; + loplayer_event_param_set_line(p->event.param, "", 0); + } + locharacter_big_warder_start_shoot_state_(c); + return; + } +} +static void locharacter_big_warder_start_walk_state_( + locharacter_base_t* c) { + assert(c != NULL); + + locharacter_big_warder_param_t* p = (typeof(p)) c->data; + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_WALK; + + p->phase = 0; + + loeffect_recipient_reset(&c->recipient); + + if (!loeffect_stance_set_has( + &c->player->status.stances, LOEFFECT_STANCE_ID_UNFINISHER)) { + locharacter_event_holder_take_control(&p->event); + } +} + +static void locharacter_big_warder_update_shoot_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_BIG_WARDER_BEAT; + static const uint64_t duration = beat*3; + + const uint64_t t = c->cache.time - c->since; + + c->cache.bullet_hittest = true; + + /* ---- motion ---- */ + loshader_character_drawer_instance_t* instance = &c->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = t; + + /* ---- shooting ---- */ + locharacter_big_warder_param_t* p = (typeof(p)) c->data; + if (p->phase < 4 && p->phase*beat/2 <= c->cache.time - c->since) { + ++p->phase; + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_linear_light_build(b, (&(lobullet_linear_param_t) { + .owner = c->super.super.id, + .pos = c->super.super.pos, + .size = vec2(.04f, .04f), + .velocity = vec2(c->direction*.5f, 0), + .color = vec4(.6f, .6f, .6f, .8f), + .acceleration = vec2(0, 0), + .duration = 2000, + .knockback = .4f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/2), + })); + loentity_store_add(c->entities, &b->super.super); + } + + /* ---- state transition ---- */ + if (locharacter_big_warder_reset_if_player_left_(c)) return; + + if (c->since + duration <= c->cache.time) { + locharacter_big_warder_start_combo_state_(c); + return; + } +} +static void locharacter_big_warder_start_shoot_state_(locharacter_base_t* c) { + assert(c != NULL); + + locharacter_big_warder_param_t* p = (typeof(p)) c->data; + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_SHOOT; + + c->direction = c->cache.player_pos.x - c->pos.x; + c->direction = c->direction > 0? 1: -1; + + p->phase = 0; +} + +static void locharacter_big_warder_update_combo_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_BIG_WARDER_BEAT; + static const uint64_t step_dur = beat; + static const uint64_t attack_dur = beat*2; + + const locharacter_big_warder_param_t* p = (typeof(p)) c->data; + + c->cache.bullet_hittest = true; + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + if (c->since + step_dur > c->cache.time) { + const float t = (c->cache.time - c->since)*1.f/step_dur; + + /* ---- position ---- */ + vec2_t dist; + vec2_sub(&dist, &p->to, &p->from); + vec2_muleq(&dist, t*t*(3-2*t)); + c->pos = p->from; + vec2_addeq(&c->pos, &dist); + + /* ---- motion ---- */ + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_WALK; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->motion_time = 1-powf(2*t-1, 4); + } else { + float t = (c->cache.time - c->since - step_dur)*1.f/attack_dur; + t *= 4; + t -= (uint64_t) t; + + /* ---- motion ---- */ + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2; + instance->motion_time = t*t; + } + + /* ---- state transition ---- */ + if (locharacter_big_warder_reset_if_player_left_(c)) return; + + if (c->since + step_dur + attack_dur <= c->cache.time) { + locharacter_big_warder_start_thrust_state_(c); + return; + } +} +static void locharacter_big_warder_start_combo_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_BIG_WARDER_BEAT; + static const uint64_t parry = 400; + + locharacter_big_warder_param_t* p = (typeof(p)) c->data; + + if (c->last_knockback_time+parry > c->cache.time) { + locharacter_big_warder_start_stunned_state_(c); + return; + } + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_COMBO; + + c->direction = c->cache.player_pos.x - c->pos.x; + c->direction = c->direction > 0? 1: -1; + + c->gravity = 0; + + p->from = c->pos; + p->to = c->cache.player_pos; + p->to.x -= c->direction * locharacter_big_warder_size_.x; + p->to.y -= locharacter_big_warder_size_.y*.2f; + + for (size_t i = 0; i < 4; ++i) { + loplayer_attack(c->player, &(loplayer_combat_attack_t) { + .attacker = c->super.super.id, + .start = c->ticker->time + (uint64_t) (beat*(i/2.f+1)), + .duration = beat/4, + .knockback = vec2(c->direction*.1f, 0), + .effect = loeffect_immediate_damage(c->recipient.status.attack), + }); + } +} + +static void locharacter_big_warder_update_thrust_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_BIG_WARDER_BEAT; + static const uint64_t duration = beat; + + locharacter_big_warder_param_t* p = (typeof(p)) c->data; + + c->cache.bullet_hittest = true; + + /* ---- motion ---- */ + loshader_character_drawer_instance_t* instance = &c->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + + const uint64_t ti = c->cache.time - c->since; + if (p->phase <= 0) { + /* ---- disappear ---- */ + instance->color.w *= 1 - ti/beat/4; + if (ti > beat/4) { + c->pos = p->to; + if (p->phase < 0) { /* backattack */ + c->direction *= -1; + p->phase = 0; + } + ++p->phase; + } + + } else if (p->phase == 1) { + /* ---- appear ---- */ + float t = (ti/beat/2 - .5f)*2; + if (t > 1) t = 1; + + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->motion_time = t; + instance->color.w *= t; + + if (ti > beat/2) ++p->phase; + + } else { + /* ---- attack ---- */ + float t = (ti/beat - .5f)*2; + if (t > 1) t = 1; + + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2; + instance->motion_time = t; + } + + /* ---- state transition ---- */ + if (locharacter_big_warder_reset_if_player_left_(c)) return; + + if (c->since + duration < c->cache.time) { + locharacter_big_warder_start_cooldown_state_(c); + return; + } +} +static void locharacter_big_warder_start_thrust_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_BIG_WARDER_BEAT; + static const float bmelo = LOCHARACTER_BIG_WARDER_MELODY_B_BEAT; + + locharacter_big_warder_param_t* p = (typeof(p)) c->data; + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_THRUST; + + c->direction = c->cache.player_pos.x - c->pos.x; + c->direction = c->direction > 0? 1: -1; + + c->gravity = 0; + + const bool backattack = + locharacter_event_holder_has_control(&p->event) && + (c->cache.time - p->event.start_time >= beat*bmelo); + const float backattack_f = backattack? 1: -1; + + p->to = c->cache.player_pos; + p->to.x += c->direction*locharacter_big_warder_size_.x*backattack_f; + p->to.y -= locharacter_big_warder_size_.y*.2f; + + p->phase = backattack? -1: 0; + + loplayer_attack(c->player, &(loplayer_combat_attack_t) { + .attacker = c->super.super.id, + .start = c->ticker->time + (uint64_t) (beat/2), + .duration = beat/2, + .knockback = vec2(-backattack_f*c->direction*.1f, 0), + .effect = loeffect_immediate_damage(c->recipient.status.attack), + }); +} + +static void locharacter_big_warder_update_cooldown_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_BIG_WARDER_BEAT; + static const uint64_t duration = beat*4; + + const uint64_t ti = c->cache.time - c->since; + + const locharacter_big_warder_param_t* p = (typeof(p)) c->data; + + c->cache.bullet_hittest = true; + c->cache.gravity = true; + + /* ---- motion ---- */ + float t = ti*1.f/duration; + if (t > 1) t = 1; + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = t; + + /* ---- position ---- */ + if (ti < beat*2) { + t = ti/beat/2; + vec2_sub(&c->pos, &p->to, &p->from); + vec2_muleq(&c->pos, t*t*(3-2*t)); + vec2_addeq(&c->pos, &p->from); + + t = t*2-1; + t = 1-powf(MATH_ABS(t), 2); + c->pos.y += t*c->recipient.status.jump/2; + } else { + c->pos = vec2(0, 0); + } + + /* ---- state transition ---- */ + if (locharacter_big_warder_reset_if_player_left_(c)) return; + + if (c->since + duration <= c->cache.time) { + locharacter_big_warder_start_shoot_state_(c); + return; + } +} +static void locharacter_big_warder_start_cooldown_state_( + locharacter_base_t* c) { + assert(c != NULL); + + locharacter_big_warder_param_t* p = (typeof(p)) c->data; + + if (locharacter_event_holder_has_control(&p->event)) { + static const uint64_t dur = LOCHARACTER_BIG_WARDER_MUSIC_DURATION; + if (p->event.start_time+dur < c->cache.time) { + locharacter_big_warder_finalize_event_(c); + return; + } + } else { + if (c->recipient.madness <= 0) { + locharacter_big_warder_start_dead_state_(c); + return; + } + } + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_COOLDOWN; + + p->from = c->pos; + p->to = vec2(0, 0); +} + +static void locharacter_big_warder_update_stunned_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_BIG_WARDER_BEAT; + static const uint64_t duration = beat*4; + + c->cache.gravity = true; + + /* ---- motion ---- */ + float t = (c->cache.time - c->since)*1.f/duration; + t *= 6; + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + if (t < 1) { + t = 1-powf(1-t, 6); + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN; + instance->motion_time = t; + } else { + t = (t-1)/5; + if (t > 1) t = 1; + t = t*t*(3-2*t); + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = t; + } + + /* ---- state transition ---- */ + if (c->since + duration <= c->cache.time) { + locharacter_big_warder_start_cooldown_state_(c); + return; + } +} +static void locharacter_big_warder_start_stunned_state_( + locharacter_base_t* c) { + assert(c != NULL); + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_STUNNED; + + loeffect_recipient_apply_effect( + &c->recipient, &loeffect_immediate_damage(1.f)); +} + +static void locharacter_big_warder_update_dead_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const uint64_t anime_duration = 4000; + static const uint64_t duration = 30000; + + c->cache.gravity = true; + + /* ---- motion ---- */ + float t = (c->cache.time - c->since)*1.f/anime_duration; + if (t > 1) t = 1; + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN; + instance->motion_time = t*t; + instance->color.w *= 1-t; + + /* ---- state transition ---- */ + if (c->since+duration < c->cache.time) { + locharacter_big_warder_start_wait_state_(c); + return; + } +} +static void locharacter_big_warder_start_dead_state_( + locharacter_base_t* c) { + assert(c != NULL); + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_DEAD; + + loplayer_gain_faith(c->player, .8f); +} + +bool locharacter_big_warder_update(locharacter_base_t* base) { + assert(base != NULL); + + static const vec2_t size = locharacter_big_warder_size_; + static const float height = size.y * 1.4f; + static const float drawsz = MATH_MAX(size.x, size.y); + + locharacter_big_warder_param_t* p = (typeof(p)) base->data; + + loeffect_recipient_update( + &base->recipient, &locharacter_big_warder_base_status_); + + if (!locharacter_event_holder_update(&p->event)) { + locharacter_big_warder_start_wait_state_(base); + } + + base->cache.instance = (loshader_character_drawer_instance_t) { + .character_id = LOSHADER_CHARACTER_ID_WARDER, + .marker_offset = vec2(0, height - drawsz), + .pos = vec2(0, drawsz - height), + .size = vec2(drawsz, drawsz), + .color = vec4(.2f, 0, 0, 1), + }; + + switch (base->state) { + case LOCHARACTER_STATE_WAIT: + locharacter_big_warder_update_wait_state_(base); + break; + case LOCHARACTER_STATE_WALK: + locharacter_big_warder_update_walk_state_(base); + break; + case LOCHARACTER_STATE_SHOOT: + locharacter_big_warder_update_shoot_state_(base); + break; + case LOCHARACTER_STATE_COMBO: + locharacter_big_warder_update_combo_state_(base); + break; + case LOCHARACTER_STATE_THRUST: + locharacter_big_warder_update_thrust_state_(base); + break; + case LOCHARACTER_STATE_COOLDOWN: + locharacter_big_warder_update_cooldown_state_(base); + break; + case LOCHARACTER_STATE_STUNNED: + locharacter_big_warder_update_stunned_state_(base); + break; + case LOCHARACTER_STATE_DEAD: + locharacter_big_warder_update_dead_state_(base); + break; + default: + locharacter_big_warder_start_wait_state_(base); + } + locharacter_big_warder_update_passive_action_(base); + + base->cache.height = height; + + base->cache.instance.marker = !!base->cache.bullet_hittest; + base->cache.instance.size.x *= base->direction; + return true; +} + +void locharacter_big_warder_build( + locharacter_base_t* base, loentity_id_t ground) { + assert(base != NULL); + + base->type = LOCHARACTER_TYPE_BIG_WARDER; + + base->ground = ground; + + base->pos = vec2(.7f, 0); + base->direction = 1; + + base->state = LOCHARACTER_STATE_WAIT; + base->since = base->cache.time; + + locharacter_big_warder_param_t* p = (typeof(p)) base->data; + *p = (typeof(*p)) {0}; + + locharacter_event_holder_initialize( + &p->event, + &base->res->music.boss_big_warder, + base, + LOCHARACTER_BIG_WARDER_MUSIC_DURATION, + 0); +} + +void locharacter_big_warder_tear_down(locharacter_base_t* base) { + assert(base != NULL); + + locharacter_big_warder_param_t* p = (typeof(p)) base->data; + locharacter_event_holder_deinitialize(&p->event); +} + +void locharacter_big_warder_pack_data( + const locharacter_base_t* base, msgpack_packer* packer) { + assert(base != NULL); + assert(packer != NULL); + + const locharacter_big_warder_param_t* p = (typeof(p)) base->data; + + msgpack_pack_map(packer, LOCHARACTER_BIG_WARDER_PARAM_TO_PACK_COUNT); + +# define pack_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + LOCOMMON_MSGPACK_PACK_ANY(packer, &p->var); \ + } while (0) + + LOCHARACTER_BIG_WARDER_PARAM_TO_PACK_EACH_(pack_); + +# undef pack_ +} + +bool locharacter_big_warder_unpack_data( + locharacter_base_t* base, const msgpack_object* obj) { + assert(base != NULL); + + locharacter_big_warder_param_t* p = (typeof(p)) base->data; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + +# define unpack_(name, var) do { \ + if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(name), &p->var)) { \ + return false; \ + } \ + } while (0) + + LOCHARACTER_BIG_WARDER_PARAM_TO_PACK_EACH_(unpack_); + +# undef unpack_ + +# undef item_ + + locharacter_event_holder_initialize( + &p->event, + &base->res->music.boss_big_warder, + base, + LOCHARACTER_BIG_WARDER_MUSIC_DURATION, + p->event.start_time); + return true; +} diff --git a/core/locharacter/big_warder.h b/core/locharacter/big_warder.h new file mode 100644 index 0000000..5e7a02a --- /dev/null +++ b/core/locharacter/big_warder.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include + +#include "core/loentity/entity.h" + +#include "./base.h" + +bool +locharacter_big_warder_update( + locharacter_base_t* base +); + +void +locharacter_big_warder_build( + locharacter_base_t* base, + loentity_id_t ground +); + +void +locharacter_big_warder_tear_down( + locharacter_base_t* base +); + +void +locharacter_big_warder_pack_data( + const locharacter_base_t* base, + msgpack_packer* packer +); + +bool +locharacter_big_warder_unpack_data( + locharacter_base_t* base, + const msgpack_object* obj +); diff --git a/core/locharacter/big_warder.private.h b/core/locharacter/big_warder.private.h new file mode 100644 index 0000000..39d8bb3 --- /dev/null +++ b/core/locharacter/big_warder.private.h @@ -0,0 +1,157 @@ +static void locharacter_big_warder_update_passive_action_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_BIG_WARDER_BEAT; + + const locharacter_big_warder_param_t* p = (typeof(p)) c->data; + if (!locharacter_event_holder_has_control(&p->event)) return; + + const uint64_t dt = c->cache.time - c->last_update_time; + const uint64_t t = c->cache.time - p->event.start_time; + + const float beats = t/beat; + const float last_beats = t > dt? (t-dt)/beat: 0; + +# define name_pos_(name, x, y) \ + locommon_position_t name = c->cache.ground->super.pos; \ + vec2_addeq(&name.fract, &vec2(x, y)); \ + locommon_position_reduce(&name); + + name_pos_(top, 0, .8f); + name_pos_(center, 0, .25f); + name_pos_(left, -.3f, .2f); + name_pos_(right, .3f, .2f); + name_pos_(leftbottom, -.4f, .1f); + name_pos_(rightbottom, .4f, .1f); + name_pos_(leftbottom_off, -.6f, .1f); + name_pos_(rightbottom_off, .6f, .1f); + +# undef name_pos_ + +# define trigger_on_(x) (last_beats < (x) && beats >= (x)) + + /* ---- intro -> A melody ---- */ + if (trigger_on_(12)) { + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = center, + .size = vec2(.4f, .4f), + .angle = MATH_PI/4, + .color = vec4(1, 1, 1, .4f), + .silent = true, + .beat = beat, + .step = 4, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/4), + })); + loentity_store_add(c->entities, &b->super.super); + } + + /* ---- A melody ---- */ + for (size_t i = 48; i < 80; i+=8) { + for (size_t j = 0; j < 2; ++j) { + if (trigger_on_(i-4 + j*4)) { + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_triangle_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = j? leftbottom: rightbottom, + .size = vec2(.05f, .15f), + .angle = j? 0: MATH_PI, + .color = vec4(1, 1, 1, .6f), + .silent = true, + .beat = beat, + .step = 4, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/2), + })); + loentity_store_add(c->entities, &b->super.super); + } + if (trigger_on_(i + j*4)) { + static const float speed = 1.4f; + static const float accel = .7f / (beat*2); + + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_linear_triangle_build(b, (&(lobullet_linear_param_t) { + .owner = c->super.super.id, + .pos = j? leftbottom_off: rightbottom_off, + .size = vec2(.05f, .15f), + .velocity = vec2(j? speed: -speed, 0), + .acceleration = vec2(j? -accel: accel, 0), + .color = vec4(1, 1, 1, .8f), + .duration = beat*2, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/2), + })); + loentity_store_add(c->entities, &b->super.super); + } + } + } + + /* ---- B melody ---- */ + static const int32_t bmelo_trigger_beats[] = {92, 108}; + static const size_t bmelo_trigger_counts = + sizeof(bmelo_trigger_beats)/sizeof(bmelo_trigger_beats[0]); + for (size_t i = 0; i < bmelo_trigger_counts; ++i) { + const int32_t st = bmelo_trigger_beats[i]; + for (int32_t j = 0; j < 4; ++j) { + if (trigger_on_(st + j/2.f)) { + for (int32_t x = -2; x <= 2; ++x) { + locommon_position_t pos = center; + vec2_addeq(&pos.fract, &vec2(x/2.f*.45f, (j-1)/4.f*.3f)); + locommon_position_reduce(&pos); + + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = pos, + .size = vec2(.1f, .1f), + .angle = MATH_PI/4, + .color = vec4(1, 1, 1, .6f), + .silent = true, + .beat = beat*2, + .step = 2, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/2), + })); + loentity_store_add(c->entities, &b->super.super); + } + } + } + } + + /* ---- C melody ---- */ + for (int32_t i = 0; i < 8; ++i) { + for (int32_t x = -10; x <= 10; ++x) { + if (trigger_on_(112 + i*4 + (x+10)/100.f)) { + locommon_position_t pos = center; + pos.fract.x += x/10.f*.47f; + pos.fract.y -= .13f; + locommon_position_reduce(&pos); + + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = pos, + .size = vec2(.06f, .06f), + .angle = MATH_PI/4, + .color = vec4(1, 1, 1, .4f), + .silent = true, + .beat = beat, + .step = 4, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/2), + })); + loentity_store_add(c->entities, &b->super.super); + } + } + } + +# undef trigger_on_ +} diff --git a/core/locharacter/cavia.c b/core/locharacter/cavia.c new file mode 100644 index 0000000..6a8b412 --- /dev/null +++ b/core/locharacter/cavia.c @@ -0,0 +1,265 @@ +#include "./cavia.h" + +#include +#include +#include + +#include "util/math/algorithm.h" +#include "util/math/vector.h" + +#include "core/loeffect/recipient.h" +#include "core/loentity/entity.h" +#include "core/loplayer/event.h" +#include "core/loplayer/player.h" +#include "core/loshader/character.h" + +#include "./base.h" +#include "./misc.h" + +static const vec2_t locharacter_cavia_size_ = vec2(.02f, .05f); + +static const loeffect_recipient_status_t locharacter_cavia_base_status_ = { + .attack = .2f, + .defence = .1f, + .speed = .05f, +}; + +static void +locharacter_cavia_start_walk_state_( + locharacter_base_t* base +); +static bool +locharacter_cavia_start_thrust_state_( + locharacter_base_t* base +); +static void +locharacter_cavia_start_cooldown_state_( + locharacter_base_t* base +); +static void +locharacter_cavia_start_dead_state_( + locharacter_base_t* base +); + +static void locharacter_cavia_update_walk_state_(locharacter_base_t* base) { + assert(base != NULL); + + /* ---- movement ---- */ + const vec2_t* gsize = &base->cache.ground->size; + const float s = base->recipient.status.speed; + base->pos.x += base->ticker->delta_f * base->direction * s / gsize->x; + + if (MATH_ABS(base->pos.x) > 1) base->direction *= -1; + + /* ---- motion ---- */ + loshader_character_drawer_instance_t* instance = &base->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_WALK; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + + const int32_t p = 70/s; + const float t = (base->ticker->time - base->since)%p*2.0f/p - 1; + instance->motion_time = MATH_ABS(t); + + /* ---- dead ---- */ + if (base->recipient.madness <= 0) { + locharacter_cavia_start_dead_state_(base); + return; + } + + /* ---- trigger thrust ---- */ + if (loplayer_event_get_param(base->player->event) == NULL) { + vec2_t dist; + locommon_position_sub( + &dist, &base->player->entity.super.super.pos, &base->super.super.pos); + const float sdist_x = dist.x * base->direction; + if (MATH_ABS(dist.y) < locharacter_cavia_size_.y && + sdist_x >= locharacter_cavia_size_.x && + sdist_x <= locharacter_cavia_size_.x*2) { + if (locharacter_cavia_start_thrust_state_(base)) return; + } + } +} +static void locharacter_cavia_start_walk_state_(locharacter_base_t* base) { + assert(base != NULL); + + base->since = base->ticker->time; + base->state = LOCHARACTER_STATE_WALK; +} + +static void locharacter_cavia_update_thrust_state_(locharacter_base_t* base) { + assert(base != NULL); + + static const uint64_t premotion = 1200; + static const uint64_t duration = 1500; + + /* ---- motion ---- */ + loshader_character_drawer_instance_t* instance = &base->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2; + + float t = (base->ticker->time - base->since)*1.0f/premotion; + if (t > 1) t = 1; + instance->motion_time = t*t*t*t; + + /* ---- cooldown ---- */ + if (base->since+duration <= base->ticker->time) { + /* TODO(catfoot): go to cooldown */ + locharacter_cavia_start_cooldown_state_(base); + return; + } +} +static bool locharacter_cavia_start_thrust_state_(locharacter_base_t* base) { + assert(base != NULL); + + const loplayer_combat_attack_t attack = { + .attacker = base->super.super.id, + .start = base->ticker->time + 1000, + .duration = 500, + .knockback = vec2(base->direction*.1f, 0), + .effect = loeffect_immediate_damage(base->recipient.status.attack), + }; + if (!loplayer_attack(base->player, &attack)) return false; + + base->since = base->ticker->time; + base->state = LOCHARACTER_STATE_THRUST; + return true; +} + +static void locharacter_cavia_update_cooldown_state_(locharacter_base_t* base) { + assert(base != NULL); + + static const uint64_t duration = 500; + + /* ---- motion ---- */ + loshader_character_drawer_instance_t* instance = &base->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + + float t = (base->ticker->time - base->since)*1.0f/duration; + if (t > 1) t = 1; + instance->motion_time = t*t*(3-2*t); + + /* ---- cooldown ---- */ + if (base->since+duration <= base->ticker->time) { + if (base->recipient.madness <= 0) { + locharacter_cavia_start_dead_state_(base); + return; + } else { + locharacter_cavia_start_walk_state_(base); + return; + } + } +} +static void locharacter_cavia_start_cooldown_state_(locharacter_base_t* base) { + assert(base != NULL); + + base->since = base->ticker->time; + base->state = LOCHARACTER_STATE_COOLDOWN; +} + +static void locharacter_cavia_update_dead_state_(locharacter_base_t* base) { + assert(base != NULL); + + static const uint64_t anime = 500; + static const uint64_t duration = 30000; + + /* ---- motion ---- */ + loshader_character_drawer_instance_t* instance = &base->cache.instance; + if (base->since+anime > base->ticker->time) { + const float t = (base->ticker->time - base->since)*1.0f/anime; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT; + instance->motion_time = t*t; + + } else if (base->since+anime*2 > base->ticker->time) { + const float t = (base->ticker->time - anime - base->since)*1.0f/anime; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN; + instance->motion_time = t*t; + + } else if (base->ticker->time+anime > base->since+duration) { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = + 1 - (base->since + duration - base->ticker->time)*1.0f/anime; + + } else { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN; + } + + /* ---- revive ---- */ + if (base->since+duration <= base->ticker->time) { + loeffect_recipient_reset(&base->recipient); + locharacter_cavia_start_walk_state_(base); + return; + } +} +static void locharacter_cavia_start_dead_state_(locharacter_base_t* base) { + assert(base != NULL); + + base->since = base->ticker->time; + base->state = LOCHARACTER_STATE_DEAD; + + loplayer_gain_faith(base->player, .1f); +} + +bool locharacter_cavia_update(locharacter_base_t* base) { + assert(base != NULL); + + static const vec2_t size = locharacter_cavia_size_; + static const vec4_t color = vec4(.1f, .1f, .1f, 1); + static const float height = size.y*1.4f; + static const float drawsz = MATH_MAX(size.x, size.y); + + loeffect_recipient_update(&base->recipient, &locharacter_cavia_base_status_); + + base->cache.instance = (loshader_character_drawer_instance_t) { + .character_id = LOSHADER_CHARACTER_ID_CAVIA, + .marker_offset = vec2(0, height - drawsz), + .pos = vec2(0, drawsz - height), + .size = vec2(drawsz, drawsz), + .color = color, + }; + + switch (base->state) { + case LOCHARACTER_STATE_WALK: + locharacter_cavia_update_walk_state_(base); + break; + case LOCHARACTER_STATE_THRUST: + locharacter_cavia_update_thrust_state_(base); + break; + case LOCHARACTER_STATE_COOLDOWN: + locharacter_cavia_update_cooldown_state_(base); + break; + case LOCHARACTER_STATE_DEAD: + locharacter_cavia_update_dead_state_(base); + break; + default: + locharacter_cavia_start_walk_state_(base); + } + + base->cache.bullet_hittest = base->state != LOCHARACTER_STATE_DEAD; + + base->cache.height = height; + + base->cache.instance.size.x *= base->direction; + base->cache.instance.marker = !!base->cache.bullet_hittest; + return true; +} + +void locharacter_cavia_build( + locharacter_base_t* base, const locharacter_cavia_param_t* param) { + assert(base != NULL); + assert(param != NULL); + + base->type = LOCHARACTER_TYPE_CAVIA; + + base->ground = param->ground; + + base->pos = vec2(param->pos, 0); + base->direction = param->direction == 1? 1: -1; + + base->state = LOCHARACTER_STATE_WALK; + base->since = base->ticker->time; +} diff --git a/core/locharacter/cavia.h b/core/locharacter/cavia.h new file mode 100644 index 0000000..0d65292 --- /dev/null +++ b/core/locharacter/cavia.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include + +#include "util/math/vector.h" + +#include "core/loentity/entity.h" + +#include "./base.h" + +typedef struct { + loentity_id_t ground; + float pos; + float direction; +} locharacter_cavia_param_t; + +bool +locharacter_cavia_update( + locharacter_base_t* base +); + +void +locharacter_cavia_build( + locharacter_base_t* base, + const locharacter_cavia_param_t* param +); + +#define locharacter_cavia_tear_down(base) + +#define locharacter_cavia_pack_data(base, packer) \ + msgpack_pack_nil(packer) + +#define locharacter_cavia_unpack_data(base, obj) \ + (obj != NULL) diff --git a/core/locharacter/encephalon.c b/core/locharacter/encephalon.c new file mode 100644 index 0000000..cfe9ad4 --- /dev/null +++ b/core/locharacter/encephalon.c @@ -0,0 +1,138 @@ +#include "./encephalon.h" + +#include +#include +#include + +#include + +#include "util/chaos/xorshift.h" +#include "util/math/algorithm.h" +#include "util/math/vector.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/locommon/msgpack.h" +#include "core/loentity/entity.h" +#include "core/loplayer/player.h" +#include "core/loresource/sound.h" +#include "core/loshader/character.h" + +#include "./base.h" +#include "./misc.h" + +typedef struct { + float progress; +} locharacter_encephalon_param_t; + +_Static_assert( + sizeof(locharacter_encephalon_param_t) <= LOCHARACTER_BASE_DATA_MAX_SIZE); + +static const vec2_t locharacter_encephalon_size_ = vec2(.1f, .1f); + +#define LOCHARACTER_ENCEPHALON_PARAM_TO_PACK_EACH_(PROC) do { \ + PROC("progress", progress); \ +} while (0) +#define LOCHARACTER_ENCEPHALON_PARAM_TO_PACK_COUNT 1 + +static void locharacter_encephalon_update_progress_(locharacter_base_t* base) { + assert(base != NULL); + + locharacter_encephalon_param_t* p = (typeof(p)) base->data; + + const vec2_t* player = &base->cache.player_pos; + const bool near = + MATH_ABS(player->x) < 1 && + 0 <= player->y && player->y < locharacter_encephalon_size_.y; + + if (near && base->state == LOCHARACTER_STATE_WAIT) { + p->progress += base->ticker->delta_f; + if (p->progress >= 1) { + loplayer_touch_encephalon(base->player); + base->state = LOCHARACTER_STATE_COOLDOWN; + loresource_sound_play(base->res->sound, "touch_gate"); + } + } else { + p->progress -= base->ticker->delta_f * 2; + if (!near) base->state = LOCHARACTER_STATE_WAIT; + } + p->progress = MATH_CLAMP(p->progress, 0, 1); +} + +bool locharacter_encephalon_update(locharacter_base_t* base) { + assert(base != NULL); + + locharacter_encephalon_update_progress_(base); + + const locharacter_encephalon_param_t* p = (typeof(p)) base->data; + + base->pos = vec2(0, locharacter_encephalon_size_.y); + + base->cache.instance = (loshader_character_drawer_instance_t) { + .character_id = LOSHADER_CHARACTER_ID_ENCEPHALON, + .from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1, + .to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1, + .color = vec4(p->progress*.5f, 0, 0, .95f), + .size = locharacter_encephalon_size_, + }; + if (base->state == LOCHARACTER_STATE_COOLDOWN && p->progress > 0) { + base->cache.instance.color.w *= + chaos_xorshift(base->ticker->time)%100/100.f; + } + return true; +} + +void locharacter_encephalon_build( + locharacter_base_t* base, loentity_id_t ground) { + assert(base != NULL); + + base->type = LOCHARACTER_TYPE_ENCEPHALON; + + base->ground = ground; + base->pos = vec2(0, 0); + + base->state = LOCHARACTER_STATE_WAIT; +} + +void locharacter_encephalon_pack_data( + const locharacter_base_t* base, msgpack_packer* packer) { + assert(base != NULL); + assert(packer != NULL); + + const locharacter_encephalon_param_t* p = (typeof(p)) base->data; + + msgpack_pack_map(packer, LOCHARACTER_ENCEPHALON_PARAM_TO_PACK_COUNT); + +# define pack_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + LOCOMMON_MSGPACK_PACK_ANY(packer, &p->var); \ + } while (0) + + LOCHARACTER_ENCEPHALON_PARAM_TO_PACK_EACH_(pack_); + +# undef pack_ +} + +bool locharacter_encephalon_unpack_data( + locharacter_base_t* base, const msgpack_object* obj) { + assert(base != NULL); + + locharacter_encephalon_param_t* p = (typeof(p)) base->data; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + +# define unpack_(name, var) do { \ + if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(name), &p->var)) { \ + return false; \ + } \ + } while (0) + + LOCHARACTER_ENCEPHALON_PARAM_TO_PACK_EACH_(unpack_); + return true; + +# undef unpack_ + +# undef item_ +} diff --git a/core/locharacter/encephalon.h b/core/locharacter/encephalon.h new file mode 100644 index 0000000..d8657a8 --- /dev/null +++ b/core/locharacter/encephalon.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include + +#include "core/loentity/entity.h" + +#include "./base.h" + +bool +locharacter_encephalon_update( + locharacter_base_t* base +); + +void +locharacter_encephalon_build( + locharacter_base_t* base, + loentity_id_t ground +); + +#define locharacter_encephalon_tear_down(base) + +void +locharacter_encephalon_pack_data( + const locharacter_base_t* base, + msgpack_packer* packer +); + +bool +locharacter_encephalon_unpack_data( + locharacter_base_t* base, + const msgpack_object* obj +); diff --git a/core/locharacter/greedy_scientist.c b/core/locharacter/greedy_scientist.c new file mode 100644 index 0000000..838cf1c --- /dev/null +++ b/core/locharacter/greedy_scientist.c @@ -0,0 +1,607 @@ +#include "./greedy_scientist.h" + +#include +#include +#include +#include + +#include "util/chaos/xorshift.h" +#include "util/math/algorithm.h" +#include "util/math/constant.h" +#include "util/math/vector.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/lobullet/base.h" +#include "core/lobullet/bomb.h" +#include "core/lobullet/linear.h" +#include "core/lobullet/pool.h" +#include "core/locommon/easing.h" +#include "core/locommon/msgpack.h" +#include "core/loentity/entity.h" +#include "core/loplayer/combat.h" +#include "core/loplayer/event.h" +#include "core/loplayer/player.h" +#include "core/loresource/music.h" +#include "core/loresource/text.h" +#include "core/loshader/character.h" + +#include "./base.h" +#include "./misc.h" +#include "./util.h" + +typedef struct { + locharacter_event_holder_t event; + + int32_t phase; + vec2_t from; + vec2_t to; +} locharacter_greedy_scientist_param_t; + +_Static_assert( + sizeof(locharacter_greedy_scientist_param_t) <= LOCHARACTER_BASE_DATA_MAX_SIZE); + +#define LOCHARACTER_GREEDY_SCIENTIST_PARAM_TO_PACK_EACH_(PROC) do { \ + PROC("event-start-time", event.start_time); \ + PROC("phase", phase); \ + PROC("from", from); \ + PROC("to", to); \ +} while (0) +#define LOCHARACTER_GREEDY_SCIENTIST_PARAM_TO_PACK_COUNT 4 + +static const vec2_t locharacter_greedy_scientist_size_ = vec2(.03f, .07f); + +static const loeffect_recipient_status_t +locharacter_greedy_scientist_base_status_ = { + .attack = .2f, + .defence = .85f, + .speed = .1f, + .jump = .1f, +}; + +#define LOCHARACTER_GREEDY_SCIENTIST_BEAT (60000/140.f) /* 140 BPM */ +#define LOCHARACTER_GREEDY_SCIENTIST_MUSIC_DURATION \ + ((uint64_t) LOCHARACTER_GREEDY_SCIENTIST_BEAT*128) + +#define LOCHARACTER_GREEDY_SCIENTIST_MELODY_B_BEAT 52 + +#include "./greedy_scientist.private.h" + +static void +locharacter_greedy_scientist_start_wait_state_( + locharacter_base_t* c +); +static void +locharacter_greedy_scientist_start_standup_state_( + locharacter_base_t* c +); +static void +locharacter_greedy_scientist_start_combo_state_( + locharacter_base_t* c +); +static void +locharacter_greedy_scientist_start_cooldown_state_( + locharacter_base_t* c +); +static void +locharacter_greedy_scientist_start_stunned_state_( + locharacter_base_t* c +); +static void +locharacter_greedy_scientist_start_dead_state_( + locharacter_base_t* c +); + +static void locharacter_greedy_scientist_finalize_event_(locharacter_base_t* c) { + assert(c != NULL); + /* This function must start next state. */ + + locharacter_greedy_scientist_param_t* p = (typeof(p)) c->data; + assert(p != NULL); + + locharacter_event_holder_release_control(&p->event); + + if (c->recipient.madness > 0) { + loentity_character_apply_effect( + &c->player->entity.super, &loeffect_curse_trigger()); + locharacter_greedy_scientist_start_wait_state_(c); + } else { + loplayer_gain_stance(c->player, LOEFFECT_STANCE_ID_PHILOSOPHER); + locharacter_greedy_scientist_start_dead_state_(c); + } +} + +static bool locharacter_greedy_scientist_reset_if_player_left_( + locharacter_base_t* c) { + assert(c != NULL); + + if (MATH_ABS(c->cache.player_pos.x) < 1 && + 0 < c->cache.player_pos.y && c->cache.player_pos.y < 1) { + return false; + } + + locharacter_greedy_scientist_param_t* p = (typeof(p)) c->data; + locharacter_event_holder_release_control(&p->event); + + locharacter_greedy_scientist_start_wait_state_(c); + return true; +} + +static void locharacter_greedy_scientist_update_wait_state_(locharacter_base_t* c) { + assert(c != NULL); + + static const uint64_t min_duration = LOCHARACTER_GREEDY_SCIENTIST_BEAT*4; + + c->cache.gravity = true; + + /* ---- motion ---- */ + loshader_character_drawer_instance_t* instance = &c->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = 0; + + /* ---- state transition ---- */ + if (c->since + min_duration <= c->cache.time) { + if (MATH_ABS(c->cache.player_pos.x) < 1 && + 0 < c->cache.player_pos.y && c->cache.player_pos.y < 1) { + vec2_t diff; + vec2_sub(&diff, &c->cache.player_pos, &c->pos); + if (vec2_pow_length(&diff) < .5f*.5f) { + locharacter_greedy_scientist_start_standup_state_(c); + return; + } + } + } +} +static void locharacter_greedy_scientist_start_wait_state_(locharacter_base_t* c) { + assert(c != NULL); + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_WAIT; +} + +static void locharacter_greedy_scientist_update_standup_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_GREEDY_SCIENTIST_BEAT; + static const uint64_t period = beat*4; + + locharacter_greedy_scientist_param_t* p = (typeof(p)) c->data; + + const bool event = locharacter_event_holder_has_control(&p->event); + const uint64_t duration = event? beat*20: beat*8; + + /* ---- motion ---- */ + float t = (c->cache.time - c->since)%period*1.f/period; + t = t*2-1; + t = MATH_ABS(t); + t = t*t*(3-2*t); + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND2; + instance->motion_time = t; + + /* ---- event ---- */ + if (event) { + p->event.param->cinescope = true; + p->event.param->hide_hud = true; + + if (c->since+(p->phase*8+4)*beat < c->cache.time) { + static const char* text[] = { + "boss_greedy_scientist_line0", + "boss_greedy_scientist_line1", + }; + if (p->phase < (int32_t) (sizeof(text)/sizeof(text[0]))) { + const char* v = loresource_text_get( + c->res->lang, text[(size_t) p->phase]); + loplayer_event_param_set_line(p->event.param, v, strlen(v)); + } else { + loplayer_event_param_set_line(p->event.param, "", 0); + } + ++p->phase; + } + } + + /* ---- state transition ---- */ + if (locharacter_greedy_scientist_reset_if_player_left_(c)) return; + + if (c->since + duration <= c->cache.time) { + if (event) { + p->event.param->cinescope = false; + p->event.param->hide_hud = false; + loplayer_event_param_set_line(p->event.param, "", 0); + } + locharacter_greedy_scientist_start_combo_state_(c); + return; + } +} +static void locharacter_greedy_scientist_start_standup_state_( + locharacter_base_t* c) { + assert(c != NULL); + + locharacter_greedy_scientist_param_t* p = (typeof(p)) c->data; + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_STANDUP; + + loeffect_recipient_reset(&c->recipient); + + if (!loeffect_stance_set_has( + &c->player->status.stances, LOEFFECT_STANCE_ID_PHILOSOPHER)) { + locharacter_event_holder_take_control(&p->event); + } + p->phase = 0; +} + +static void locharacter_greedy_scientist_update_combo_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float offset_y = locharacter_greedy_scientist_size_.y*1.5f; + + static const float beat = LOCHARACTER_GREEDY_SCIENTIST_BEAT; + static const uint64_t step_dur = beat; + static const uint64_t attack_dur = beat*3; + + const locharacter_greedy_scientist_param_t* p = (typeof(p)) c->data; + + c->cache.bullet_hittest = true; + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND2; + if (c->since + step_dur > c->cache.time) { + const float t = (c->cache.time - c->since)*1.f/step_dur; + + vec2_t to = p->to; + to.y += offset_y; + + /* ---- position ---- */ + vec2_t dist; + vec2_sub(&dist, &to, &p->from); + vec2_muleq(&dist, t*t*(3-2*t)); + c->pos = p->from; + vec2_addeq(&c->pos, &dist); + + /* ---- motion ---- */ + instance->motion_time = 1-powf(2*t-1, 4); + } else { + float t = (c->cache.time - c->since - step_dur)*1.f/attack_dur; + t *= 3; + t -= (uint64_t) t; + t = t*t; + + /* ---- position ---- */ + c->pos.y -= c->ticker->delta_f*t/3 * offset_y; + + /* ---- motion ---- */ + instance->motion_time = t; + } + + /* ---- state transition ---- */ + if (locharacter_greedy_scientist_reset_if_player_left_(c)) return; + + if (c->since + step_dur + attack_dur <= c->cache.time) { + locharacter_greedy_scientist_start_cooldown_state_(c); + return; + } +} +static void locharacter_greedy_scientist_start_combo_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_GREEDY_SCIENTIST_BEAT; + static const uint64_t parry = 100; + + locharacter_greedy_scientist_param_t* p = (typeof(p)) c->data; + + if (c->last_knockback_time+parry > c->cache.time) { + locharacter_greedy_scientist_start_stunned_state_(c); + return; + } + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_COMBO; + + c->direction = c->cache.player_pos.x - c->pos.x; + c->direction = c->direction > 0? 1: -1; + + c->gravity = 0; + + p->from = c->pos; + p->to = c->cache.player_pos; + + const size_t delay_index = chaos_xorshift(c->cache.time)&1? 2: 3; + for (size_t i = 1; i < 4; ++i) { + const uint64_t delay = i >= delay_index? beat/2: 0; + loplayer_attack(c->player, &(loplayer_combat_attack_t) { + .attacker = c->super.super.id, + .start = c->ticker->time + (uint64_t) (beat*i) + delay, + .duration = beat/2, + .effect = loeffect_immediate_damage(c->recipient.status.attack), + }); + } +} + +static void locharacter_greedy_scientist_update_cooldown_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_GREEDY_SCIENTIST_BEAT; + static const uint64_t duration = beat*4; + + const uint64_t ti = c->cache.time - c->since; + + const locharacter_greedy_scientist_param_t* p = (typeof(p)) c->data; + + c->cache.bullet_hittest = true; + c->cache.gravity = true; + + /* ---- motion ---- */ + float t = ti*1.f/duration; + if (t > 1) t = 1; + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND2; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = t; + + /* ---- state transition ---- */ + if (locharacter_greedy_scientist_reset_if_player_left_(c)) return; + + if (c->since + duration <= c->cache.time) { + locharacter_greedy_scientist_start_combo_state_(c); + return; + } +} +static void locharacter_greedy_scientist_start_cooldown_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_GREEDY_SCIENTIST_BEAT; + static const uint64_t bmelo = LOCHARACTER_GREEDY_SCIENTIST_MELODY_B_BEAT*beat; + + locharacter_greedy_scientist_param_t* p = (typeof(p)) c->data; + + if (locharacter_event_holder_has_control(&p->event)) { + static const uint64_t dur = LOCHARACTER_GREEDY_SCIENTIST_MUSIC_DURATION; + if (p->event.start_time+dur < c->cache.time) { + locharacter_greedy_scientist_finalize_event_(c); + return; + } + } else { + if (c->recipient.madness <= 0) { + locharacter_greedy_scientist_start_dead_state_(c); + return; + } + } + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_COOLDOWN; + + if (locharacter_event_holder_has_control(&p->event)) { + locharacter_greedy_scientist_trigger_chained_mines_(c); + if (c->cache.time - p->event.start_time > bmelo) { + locharacter_greedy_scientist_shoot_amnesia_bullet_(c); + } + } +} + +static void locharacter_greedy_scientist_update_stunned_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_GREEDY_SCIENTIST_BEAT; + static const uint64_t duration = beat*4; + + c->cache.gravity = true; + + /* ---- motion ---- */ + float t = (c->cache.time - c->since)*1.f/duration; + t *= 6; + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + if (t < 1) { + t = 1-powf(1-t, 6); + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT; + instance->motion_time = t; + } else { + t = (t-1)/5; + if (t > 1) t = 1; + t = t*t*(3-2*t); + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = t; + } + + /* ---- state transition ---- */ + if (c->since + duration <= c->cache.time) { + locharacter_greedy_scientist_start_cooldown_state_(c); + return; + } +} +static void locharacter_greedy_scientist_start_stunned_state_( + locharacter_base_t* c) { + assert(c != NULL); + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_STUNNED; + + loeffect_recipient_apply_effect( + &c->recipient, &loeffect_immediate_damage(1.f)); +} + +static void locharacter_greedy_scientist_update_dead_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const uint64_t anime_duration = 4000; + static const uint64_t duration = 30000; + + c->cache.gravity = true; + + /* ---- motion ---- */ + float t = (c->cache.time - c->since)*1.f/anime_duration; + if (t > 1) t = 1; + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT; + instance->motion_time = t*t; + instance->color.w *= 1-t; + + /* ---- state transition ---- */ + if (c->since+duration < c->cache.time) { + locharacter_greedy_scientist_start_wait_state_(c); + return; + } +} +static void locharacter_greedy_scientist_start_dead_state_( + locharacter_base_t* c) { + assert(c != NULL); + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_DEAD; + + loplayer_gain_faith(c->player, .8f); +} + +bool locharacter_greedy_scientist_update(locharacter_base_t* base) { + assert(base != NULL); + + static const vec2_t size = locharacter_greedy_scientist_size_; + static const float height = size.y * 1.4f; + static const float drawsz = MATH_MAX(size.x, size.y); + + locharacter_greedy_scientist_param_t* p = (typeof(p)) base->data; + + loeffect_recipient_update( + &base->recipient, &locharacter_greedy_scientist_base_status_); + + if (!locharacter_event_holder_update(&p->event)) { + locharacter_greedy_scientist_start_wait_state_(base); + } + + base->cache.instance = (loshader_character_drawer_instance_t) { + .character_id = LOSHADER_CHARACTER_ID_SCIENTIST, + .marker_offset = vec2(0, height - drawsz), + .pos = vec2(0, drawsz - height), + .size = vec2(drawsz, drawsz), + .color = vec4(.2f, 0, 0, 1), + }; + + switch (base->state) { + case LOCHARACTER_STATE_WAIT: + locharacter_greedy_scientist_update_wait_state_(base); + break; + case LOCHARACTER_STATE_STANDUP: + locharacter_greedy_scientist_update_standup_state_(base); + break; + case LOCHARACTER_STATE_COMBO: + locharacter_greedy_scientist_update_combo_state_(base); + break; + case LOCHARACTER_STATE_COOLDOWN: + locharacter_greedy_scientist_update_cooldown_state_(base); + break; + case LOCHARACTER_STATE_STUNNED: + locharacter_greedy_scientist_update_stunned_state_(base); + break; + case LOCHARACTER_STATE_DEAD: + locharacter_greedy_scientist_update_dead_state_(base); + break; + default: + locharacter_greedy_scientist_start_wait_state_(base); + } + locharacter_greedy_scientist_update_passive_action_(base); + + base->cache.height = height; + + base->cache.instance.marker = !!base->cache.bullet_hittest; + base->cache.instance.size.x *= base->direction; + return true; +} + +void locharacter_greedy_scientist_build( + locharacter_base_t* base, loentity_id_t ground) { + assert(base != NULL); + + base->type = LOCHARACTER_TYPE_GREEDY_SCIENTIST; + + base->ground = ground; + + base->pos = vec2(.7f, 0); + base->direction = 1; + + base->state = LOCHARACTER_STATE_WAIT; + base->since = base->cache.time; + + locharacter_greedy_scientist_param_t* p = (typeof(p)) base->data; + *p = (typeof(*p)) {0}; + + locharacter_event_holder_initialize( + &p->event, + &base->res->music.boss_greedy_scientist, + base, + LOCHARACTER_GREEDY_SCIENTIST_MUSIC_DURATION, + 0); +} + +void locharacter_greedy_scientist_tear_down(locharacter_base_t* base) { + assert(base != NULL); + + locharacter_greedy_scientist_param_t* p = (typeof(p)) base->data; + locharacter_event_holder_deinitialize(&p->event); +} + +void locharacter_greedy_scientist_pack_data( + const locharacter_base_t* base, msgpack_packer* packer) { + assert(base != NULL); + assert(packer != NULL); + + const locharacter_greedy_scientist_param_t* p = (typeof(p)) base->data; + + msgpack_pack_map(packer, LOCHARACTER_GREEDY_SCIENTIST_PARAM_TO_PACK_COUNT); + +# define pack_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + LOCOMMON_MSGPACK_PACK_ANY(packer, &p->var); \ + } while (0) + + LOCHARACTER_GREEDY_SCIENTIST_PARAM_TO_PACK_EACH_(pack_); + +# undef pack_ +} + +bool locharacter_greedy_scientist_unpack_data( + locharacter_base_t* base, const msgpack_object* obj) { + assert(base != NULL); + + locharacter_greedy_scientist_param_t* p = (typeof(p)) base->data; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + +# define unpack_(name, var) do { \ + if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(name), &p->var)) { \ + return false; \ + } \ + } while (0) + + LOCHARACTER_GREEDY_SCIENTIST_PARAM_TO_PACK_EACH_(unpack_); + +# undef unpack_ + +# undef item_ + + locharacter_event_holder_initialize( + &p->event, + &base->res->music.boss_greedy_scientist, + base, + LOCHARACTER_GREEDY_SCIENTIST_MUSIC_DURATION, + p->event.start_time); + return true; +} diff --git a/core/locharacter/greedy_scientist.h b/core/locharacter/greedy_scientist.h new file mode 100644 index 0000000..85aaded --- /dev/null +++ b/core/locharacter/greedy_scientist.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include + +#include "core/loentity/entity.h" + +#include "./base.h" + +bool +locharacter_greedy_scientist_update( + locharacter_base_t* base +); + +void +locharacter_greedy_scientist_build( + locharacter_base_t* base, + loentity_id_t ground +); + +void +locharacter_greedy_scientist_tear_down( + locharacter_base_t* base +); + +void +locharacter_greedy_scientist_pack_data( + const locharacter_base_t* base, + msgpack_packer* packer +); + +bool +locharacter_greedy_scientist_unpack_data( + locharacter_base_t* base, + const msgpack_object* obj +); diff --git a/core/locharacter/greedy_scientist.private.h b/core/locharacter/greedy_scientist.private.h new file mode 100644 index 0000000..527e65a --- /dev/null +++ b/core/locharacter/greedy_scientist.private.h @@ -0,0 +1,228 @@ +static void locharacter_greedy_scientist_trigger_chained_mines_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_GREEDY_SCIENTIST_BEAT; + + locommon_position_t center = c->cache.ground->super.pos; + center.fract.y += .1f; + locommon_position_reduce(¢er); + + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = center, + .size = vec2(.07f, .07f), + .angle = 0, + .color = vec4(1, .9f, .9f, .8f), + .beat = beat, + .step = 2, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/2), + })); + loentity_store_add(c->entities, &b->super.super); + + for (int32_t i = -6; i <= 6; ++i) { + locommon_position_t pos = center; + pos.fract.x += i/6.f*.5f; + locommon_position_reduce(&pos); + + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = pos, + .size = vec2(.05f, .05f), + .angle = 0, + .color = vec4(1, 1, 1, .4f), + .silent = true, + .beat = beat*1.5f, + .step = 2, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/2), + })); + loentity_store_add(c->entities, &b->super.super); + } +} + +static void locharacter_greedy_scientist_shoot_amnesia_bullet_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_GREEDY_SCIENTIST_BEAT; + + locommon_position_t pos = c->super.super.pos; + pos.fract.y += locharacter_greedy_scientist_size_.y*1.5f; + locommon_position_reduce(&pos); + + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_linear_light_build(b, (&(lobullet_linear_param_t) { + .owner = c->super.super.id, + .pos = pos, + .size = vec2(.06f, .06f), + .velocity = vec2(0, .1f), + .color = vec4(.8f, .8f, .6f, .8f), + .acceleration = vec2(0, -2), + .duration = 1000, + .effect = loeffect_amnesia(c->ticker->time, beat*8), + })); + loentity_store_add(c->entities, &b->super.super); +} + +static void locharacter_greedy_scientist_update_passive_action_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_GREEDY_SCIENTIST_BEAT; + + const locharacter_greedy_scientist_param_t* p = (typeof(p)) c->data; + if (!locharacter_event_holder_has_control(&p->event)) return; + + const uint64_t dt = c->cache.time - c->last_update_time; + const uint64_t t = c->cache.time - p->event.start_time; + + const float beats = t/beat; + const float last_beats = t > dt? (t-dt)/beat: 0; + +# define name_pos_(name, x, y) \ + locommon_position_t name = c->cache.ground->super.pos; \ + vec2_addeq(&name.fract, &vec2(x, y)); \ + locommon_position_reduce(&name); + + name_pos_(top, 0, .8f); + name_pos_(lefttop, -.3f, .8f); + name_pos_(righttop, .3f, .8f); + name_pos_(center, 0, .4f); + name_pos_(left, -.3f, .2f); + name_pos_(right, .3f, .2f); + +# undef name_pos_ + +# define trigger_on_(x) (last_beats < (x) && beats >= (x)) + + /* ---- intro -> A melody ---- */ + for (size_t i = 0; i < 2; ++i) { + if (trigger_on_(16)) { + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_triangle_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = i? left: right, + .size = vec2(.1f*cos(MATH_PI/6), .1f), + .angle = -MATH_PI/2, + .color = vec4(1, 1, 1, .4f), + .silent = true, + .beat = beat*2, + .step = 1, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/4), + })); + loentity_store_add(c->entities, &b->super.super); + } + if (trigger_on_(16.5f)) { + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_triangle_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = i? left: right, + .size = vec2(.1f*cos(MATH_PI/6), .1f), + .angle = MATH_PI/2, + .color = vec4(1, 1, 1, .4f), + .silent = true, + .beat = beat*2, + .step = 1, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/4), + })); + loentity_store_add(c->entities, &b->super.super); + } + if (trigger_on_(17)) { + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = i? left: right, + .size = vec2(.12f, .12f), + .angle = MATH_PI/4, + .color = vec4(1, 1, 1, .4f), + .silent = true, + .beat = beat*2, + .step = 1, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/4), + })); + loentity_store_add(c->entities, &b->super.super); + } + } + + for (size_t i = 0; i < 4; ++i) { + if (trigger_on_(18 + i*.5f)) { + locommon_position_t pos = center; + pos.fract.y -= .1f * i; + locommon_position_reduce(&pos); + + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_triangle_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = pos, + .size = vec2(.05f, .2f), + .angle = -MATH_PI/2, + .color = vec4(1, 1, 1, .4f), + .silent = true, + .beat = beat, + .step = 1, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/2), + })); + loentity_store_add(c->entities, &b->super.super); + } + } + + /* ---- B melody ---- */ + for (size_t i = 52, cnt = 0; i < 84; i+=4, ++cnt) { + if (trigger_on_(i)) { + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_linear_triangle_build(b, (&(lobullet_linear_param_t) { + .owner = c->super.super.id, + .pos = cnt%2? lefttop: righttop, + .size = vec2(.1f, .3f), + .velocity = vec2(0, -1/(beat*4)*1000), + .color = vec4(1, 1, 1, .8f), + .duration = beat*4, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack), + })); + loentity_store_add(c->entities, &b->super.super); + } + } + + /* ---- C melody ---- */ + for (size_t i = 84, cnt = 0; i < 156; i+=8, ++cnt) { + if (trigger_on_(i)) { + for (int32_t x = -1-cnt%2; x <= 2; x+=2) { + locommon_position_t pos = top; + pos.fract.x += .18f*x; + locommon_position_reduce(&pos); + + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_linear_triangle_build(b, (&(lobullet_linear_param_t) { + .owner = c->super.super.id, + .pos = pos, + .size = vec2(.05f, .1f), + .velocity = vec2(0, -1/(beat*4)*1000), + .color = vec4(1, 1, 1, .8f), + .duration = beat*4, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack), + })); + loentity_store_add(c->entities, &b->super.super); + } + } + } + +# undef trigger_on_ +} diff --git a/core/locharacter/misc.c b/core/locharacter/misc.c new file mode 100644 index 0000000..7caeaed --- /dev/null +++ b/core/locharacter/misc.c @@ -0,0 +1,68 @@ +#include "./misc.h" + +#include +#include +#include +#include + +const char* locharacter_type_stringify(locharacter_type_t type) { +# define each_(NAME, name) do { \ + if (type == LOCHARACTER_TYPE_##NAME) return #name; \ + } while(0) + + LOCHARACTER_TYPE_EACH_(each_); + + assert(false); + return NULL; + +# undef each_ +} + +bool locharacter_type_unstringify( + locharacter_type_t* type, const char* v, size_t len) { + assert(type != NULL); + assert(v != NULL || len == 0); + +# define each_(NAME, name) do { \ + if (strncmp(v, #name, len) == 0 && #name[len] == 0) { \ + *type = LOCHARACTER_TYPE_##NAME; \ + return true; \ + } \ + } while(0) + + LOCHARACTER_TYPE_EACH_(each_); + return false; + +# undef each_ +} + +const char* locharacter_state_stringify(locharacter_state_t state) { +# define each_(NAME, name) do { \ + if (state == LOCHARACTER_STATE_##NAME) return #name; \ + } while(0) + + LOCHARACTER_STATE_EACH_(each_); + + assert(false); + return NULL; + +# undef each_ +} + +bool locharacter_state_unstringify( + locharacter_state_t* state, const char* v, size_t len) { + assert(state != NULL); + assert(v != NULL || len == 0); + +# define each_(NAME, name) do { \ + if (strncmp(v, #name, len) == 0 && #name[len] == 0) { \ + *state = LOCHARACTER_STATE_##NAME; \ + return true; \ + } \ + } while(0) + + LOCHARACTER_STATE_EACH_(each_); + return false; + +# undef each_ +} diff --git a/core/locharacter/misc.h b/core/locharacter/misc.h new file mode 100644 index 0000000..7668492 --- /dev/null +++ b/core/locharacter/misc.h @@ -0,0 +1,76 @@ +#pragma once + +#include +#include + +/* dont forget to update EACH macro */ +typedef enum { + LOCHARACTER_TYPE_ENCEPHALON, + LOCHARACTER_TYPE_CAVIA, + LOCHARACTER_TYPE_SCIENTIST, + LOCHARACTER_TYPE_WARDER, + LOCHARACTER_TYPE_THEISTS_CHILD, + LOCHARACTER_TYPE_BIG_WARDER, + LOCHARACTER_TYPE_GREEDY_SCIENTIST, +} locharacter_type_t; + +#define LOCHARACTER_TYPE_EACH_(PROC) do { \ + PROC(ENCEPHALON, encephalon); \ + PROC(CAVIA, cavia); \ + PROC(SCIENTIST, scientist); \ + PROC(WARDER, warder); \ + PROC(THEISTS_CHILD, theists_child); \ + PROC(BIG_WARDER, big_warder); \ + PROC(GREEDY_SCIENTIST, greedy_scientist); \ +} while (0) + +/* dont forget to update EACH macro */ +typedef enum { + LOCHARACTER_STATE_WAIT, + LOCHARACTER_STATE_STANDUP, + LOCHARACTER_STATE_WALK, + LOCHARACTER_STATE_SHOOT, + LOCHARACTER_STATE_RUSH, + LOCHARACTER_STATE_THRUST, + LOCHARACTER_STATE_COMBO, + LOCHARACTER_STATE_COOLDOWN, + LOCHARACTER_STATE_STUNNED, + LOCHARACTER_STATE_DEAD, +} locharacter_state_t; + +#define LOCHARACTER_STATE_EACH_(PROC) do { \ + PROC(WAIT, wait); \ + PROC(STANDUP, standup); \ + PROC(WALK, walk); \ + PROC(SHOOT, shoot); \ + PROC(RUSH, rush); \ + PROC(THRUST, thrust); \ + PROC(COMBO, combo); \ + PROC(COOLDOWN, cooldown); \ + PROC(STUNNED, stunned); \ + PROC(DEAD, dead); \ +} while (0) + +const char* +locharacter_type_stringify( + locharacter_type_t type +); + +bool +locharacter_type_unstringify( + locharacter_type_t* type, + const char* v, + size_t len +); + +const char* +locharacter_state_stringify( + locharacter_state_t state +); + +bool +locharacter_state_unstringify( + locharacter_state_t* state, + const char* v, + size_t len +); diff --git a/core/locharacter/pool.c b/core/locharacter/pool.c new file mode 100644 index 0000000..4b38e7d --- /dev/null +++ b/core/locharacter/pool.c @@ -0,0 +1,122 @@ +#include "./pool.h" + +#include +#include +#include +#include + +#include + +#include "util/math/vector.h" +#include "util/memory/memory.h" + +#include "core/lobullet/pool.h" +#include "core/locommon/counter.h" +#include "core/locommon/ticker.h" +#include "core/loentity/store.h" +#include "core/loplayer/player.h" +#include "core/loresource/set.h" +#include "core/loshader/character.h" + +#include "./base.h" + +struct locharacter_pool_t { + loresource_set_t* res; + loshader_character_drawer_t* drawer; + locommon_counter_t* idgen; + const locommon_ticker_t* ticker; + lobullet_pool_t* bullets; + loentity_store_t* entities; + loplayer_t* player; + + size_t length; + locharacter_base_t items[1]; +}; + +static size_t locharacter_pool_find_unused_item_index_( + const locharacter_pool_t* pool) { + assert(pool != NULL); + + for (size_t i = 0; i < pool->length; ++i) { + if (!pool->items[i].used) return i; + } + fprintf(stderr, "character pool overflow\n"); + abort(); +} + +locharacter_pool_t* locharacter_pool_new( + loresource_set_t* res, + loshader_character_drawer_t* drawer, + locommon_counter_t* idgen, + const locommon_ticker_t* ticker, + lobullet_pool_t* bullets, + loentity_store_t* entities, + loplayer_t* player, + size_t length) { + assert(res != NULL); + assert(drawer != NULL); + assert(idgen != NULL); + assert(ticker != NULL); + assert(bullets != NULL); + assert(entities != NULL); + assert(player != NULL); + assert(length > 0); + + locharacter_pool_t* pool = memory_new( + sizeof(*pool) + (length-1)*sizeof(pool->items[0])); + *pool = (typeof(*pool)) { + .res = res, + .drawer = drawer, + .idgen = idgen, + .ticker = ticker, + .bullets = bullets, + .entities = entities, + .player = player, + .length = length, + }; + + for (size_t i = 0; i < pool->length; ++i) { + locharacter_base_initialize( + &pool->items[i], + res, + drawer, + ticker, + bullets, + entities, + player); + } + return pool; +} + +void locharacter_pool_delete(locharacter_pool_t* pool) { + assert(pool != NULL); + + for (size_t i = 0; i < pool->length; ++i) { + locharacter_base_deinitialize(&pool->items[i]); + } + memory_delete(pool); +} + +locharacter_base_t* locharacter_pool_create(locharacter_pool_t* pool) { + assert(pool != NULL); + + const size_t i = locharacter_pool_find_unused_item_index_(pool); + + locharacter_base_reinitialize( + &pool->items[i], locommon_counter_count(pool->idgen)); + + pool->items[i].used = true; + return &pool->items[i]; +} + +locharacter_base_t* locharacter_pool_unpack_item( + locharacter_pool_t* pool, const msgpack_object* obj) { + assert(pool != NULL); + + const size_t i = locharacter_pool_find_unused_item_index_(pool); + + if (!locharacter_base_unpack(&pool->items[i], obj)) return NULL; + + pool->items[i].used = true; + return &pool->items[i]; +} diff --git a/core/locharacter/pool.h b/core/locharacter/pool.h new file mode 100644 index 0000000..b573f47 --- /dev/null +++ b/core/locharacter/pool.h @@ -0,0 +1,48 @@ +#pragma once + +#include + +#include + +#include "util/math/vector.h" + +#include "core/lobullet/pool.h" +#include "core/locommon/counter.h" +#include "core/locommon/ticker.h" +#include "core/loentity/store.h" +#include "core/loplayer/player.h" +#include "core/loresource/set.h" +#include "core/loshader/character.h" + +#include "./base.h" + +struct locharacter_pool_t; +typedef struct locharacter_pool_t locharacter_pool_t; + +locharacter_pool_t* /* OWNERSHIP */ +locharacter_pool_new( + loresource_set_t* res, + loshader_character_drawer_t* drawer, + locommon_counter_t* idgen, + const locommon_ticker_t* ticker, + lobullet_pool_t* bullets, + loentity_store_t* entities, + loplayer_t* player, + size_t length +); + +void +locharacter_pool_delete( + locharacter_pool_t* pool /* OWNERSHIP */ +); + +locharacter_base_t* +locharacter_pool_create( + locharacter_pool_t* pool +); + +locharacter_base_t* +locharacter_pool_unpack_item( + locharacter_pool_t* pool, + const msgpack_object* obj +); diff --git a/core/locharacter/scientist.c b/core/locharacter/scientist.c new file mode 100644 index 0000000..35aeda3 --- /dev/null +++ b/core/locharacter/scientist.c @@ -0,0 +1,305 @@ +#include "./scientist.h" + +#include +#include +#include + +#include "util/math/algorithm.h" +#include "util/math/vector.h" + +#include "core/lobullet/base.h" +#include "core/lobullet/bomb.h" +#include "core/loeffect/recipient.h" +#include "core/loentity/entity.h" +#include "core/loentity/store.h" +#include "core/loplayer/event.h" +#include "core/loplayer/player.h" +#include "core/loresource/sound.h" +#include "core/loshader/character.h" + +#include "./base.h" +#include "./misc.h" + +static const vec2_t locharacter_scientist_size_ = vec2(.02f, .05f); + +static const loeffect_recipient_status_t locharacter_scientist_base_status_ = { + .attack = .2f, + .defence = .1f, +}; + +static void locharacter_scientist_trigger_bomb_(locharacter_base_t* c) { + assert(c != NULL); + + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = c->player->entity.super.super.pos, + .size = vec2(.15f, .15f), + .angle = 0, + .color = vec4(1, 1, 1, .6f), + .beat = 500, + .step = 2, + .knockback = .1f, + .effect = loeffect_immediate_damage(c->recipient.status.attack), + })); + loentity_store_add(c->entities, &b->super.super); + + loresource_sound_play(c->res->sound, "enemy_trigger"); +} + +static void +locharacter_scientist_start_wait_state_( + locharacter_base_t* base +); +static void +locharacter_scientist_start_shoot_state_( + locharacter_base_t* base +); +static void +locharacter_scientist_start_combo_state_( + locharacter_base_t* base +); +static void +locharacter_scientist_start_dead_state_( + locharacter_base_t* base +); + +static void locharacter_scientist_update_wait_state_(locharacter_base_t* base) { + assert(base != NULL); + + static const uint64_t duration = 1000; + static const uint64_t period = 1000; + + const uint64_t elapsed = base->ticker->time - base->since; + + /* ---- motion ---- */ + float t = elapsed%period*1.f/period; + t = t*2 - 1; + t = MATH_ABS(t); + t = t*t*(3-2*t); + + loshader_character_drawer_instance_t* instance = &base->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND2; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = t; + + /* ---- state transition ---- */ + if (elapsed >= duration) { + if (base->recipient.madness <= 0) { + locharacter_scientist_start_dead_state_(base); + return; + } + + if (loplayer_event_get_param(base->player->event) == NULL) { + vec2_t disp; + vec2_sub(&disp, &base->cache.player_pos, &base->pos); + disp.x *= base->cache.ground->size.x; + + const float pdist = vec2_pow_length(&disp); + if (MATH_ABS(disp.y) < locharacter_scientist_size_.y && + pdist < .2f*.2f) { + static const float r = locharacter_scientist_size_.x*3; + if (pdist < r*r) { + locharacter_scientist_start_combo_state_(base); + } else if (disp.x*base->direction > 0) { + locharacter_scientist_start_shoot_state_(base); + } + } + return; + } + } +} +static void locharacter_scientist_start_wait_state_(locharacter_base_t* base) { + assert(base != NULL); + + base->since = base->ticker->time; + base->state = LOCHARACTER_STATE_WAIT; +} + +static void locharacter_scientist_update_shoot_state_(locharacter_base_t* base) { + assert(base != NULL); + + static const uint64_t duration = 500; + + const uint64_t elapsed = base->ticker->time - base->since; + + /* ---- motion ---- */ + float t = elapsed*1.f / duration; + if (t > 1) t = 1; + t = t*t; + + loshader_character_drawer_instance_t* instance = &base->cache.instance; + if (t < .5f) { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->motion_time = t*2; + } else { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = (t-.5f)*2; + } + + /* ---- state transition ---- */ + if (elapsed >= duration) { + locharacter_scientist_trigger_bomb_(base); + locharacter_scientist_start_wait_state_(base); + return; + } +} +static void locharacter_scientist_start_shoot_state_(locharacter_base_t* base) { + assert(base != NULL); + + base->since = base->ticker->time; + base->state = LOCHARACTER_STATE_SHOOT; +} + +static void locharacter_scientist_update_combo_state_(locharacter_base_t* base) { + assert(base != NULL); + + static const uint64_t duration = 1000; + + const uint64_t elapsed = base->ticker->time - base->since; + + base->cache.gravity = false; + + /* ---- motion ---- */ + float t = elapsed*1.f/duration; + if (t > 1) t = 1; + t = t*t; + + loshader_character_drawer_instance_t* instance = &base->cache.instance; + if (t < .5f) { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->motion_time = t*2; + } else { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = (t-.5f)*2; + } + + /* ---- state transition ---- */ + if (elapsed >= duration) { + locharacter_scientist_start_wait_state_(base); + return; + } +} +static void locharacter_scientist_start_combo_state_(locharacter_base_t* base) { + assert(base != NULL); + + base->since = base->ticker->time; + base->state = LOCHARACTER_STATE_COMBO; + + const float diff = base->cache.player_pos.x - base->pos.x; + base->direction = MATH_SIGN(diff); + + const loplayer_combat_attack_t attack = { + .attacker = base->super.super.id, + .start = base->ticker->time + 600, + .duration = 400, + .knockback = vec2(base->direction*.1f, 0), + .effect = loeffect_immediate_damage(base->recipient.status.attack), + }; + loplayer_attack(base->player, &attack); +} + +static void locharacter_scientist_update_dead_state_(locharacter_base_t* base) { + assert(base != NULL); + + static const uint64_t anime_duration = 500; + static const uint64_t duration = 30000; + + const uint64_t elapsed = base->ticker->time - base->since; + + /* ---- motion ---- */ + loshader_character_drawer_instance_t* instance = &base->cache.instance; + if (elapsed > duration - anime_duration) { /* wake up */ + float t = 1-(duration - elapsed)*1.f/anime_duration; + if (t < 0) t = 0; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = 1-powf(1-t, 2); + } else { /* down */ + float t = elapsed*1.f/anime_duration; + if (t > 1) t = 1; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT; + instance->motion_time = powf(t, 2.); + } + + /* ---- state transition ---- */ + if (elapsed >= duration) { + loeffect_recipient_reset(&base->recipient); + locharacter_scientist_start_wait_state_(base); + return; + } +} +static void locharacter_scientist_start_dead_state_(locharacter_base_t* base) { + assert(base != NULL); + + base->since = base->ticker->time; + base->state = LOCHARACTER_STATE_DEAD; + + loplayer_gain_faith(base->player, .3f); +} + +bool locharacter_scientist_update(locharacter_base_t* base) { + assert(base != NULL); + + static const vec2_t size = locharacter_scientist_size_; + static const vec4_t color = vec4(.1f, .1f, .1f, 1); + static const float height = size.y*1.4f; + static const float drawsz = MATH_MAX(size.x, size.y); + + loeffect_recipient_update(&base->recipient, &locharacter_scientist_base_status_); + + base->cache.instance = (loshader_character_drawer_instance_t) { + .character_id = LOSHADER_CHARACTER_ID_SCIENTIST, + .marker_offset = vec2(0, height - drawsz), + .pos = vec2(0, drawsz - height), + .size = vec2(drawsz, drawsz), + .color = color, + }; + + base->cache.gravity = true; + + switch (base->state) { + case LOCHARACTER_STATE_WAIT: + locharacter_scientist_update_wait_state_(base); + break; + case LOCHARACTER_STATE_SHOOT: + locharacter_scientist_update_shoot_state_(base); + break; + case LOCHARACTER_STATE_COMBO: + locharacter_scientist_update_combo_state_(base); + break; + case LOCHARACTER_STATE_DEAD: + locharacter_scientist_update_dead_state_(base); + break; + default: + locharacter_scientist_start_wait_state_(base); + } + + base->cache.bullet_hittest = base->state != LOCHARACTER_STATE_DEAD; + + base->cache.height = height; + + base->cache.instance.size.x *= base->direction; + base->cache.instance.marker = !!base->cache.bullet_hittest; + return true; +} + +void locharacter_scientist_build( + locharacter_base_t* base, const locharacter_scientist_param_t* param) { + assert(base != NULL); + assert(param != NULL); + + base->type = LOCHARACTER_TYPE_SCIENTIST; + + base->ground = param->ground; + + base->pos = vec2(param->pos, 0); + base->direction = param->direction == 1? 1: -1; + + locharacter_scientist_start_wait_state_(base); +} diff --git a/core/locharacter/scientist.h b/core/locharacter/scientist.h new file mode 100644 index 0000000..5ae2c40 --- /dev/null +++ b/core/locharacter/scientist.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include + +#include "core/loentity/entity.h" + +#include "./base.h" + +typedef struct { + loentity_id_t ground; + float pos; + float direction; +} locharacter_scientist_param_t; + +bool +locharacter_scientist_update( + locharacter_base_t* base +); + +void +locharacter_scientist_build( + locharacter_base_t* base, + const locharacter_scientist_param_t* param +); + +#define locharacter_scientist_tear_down(base) + +#define locharacter_scientist_pack_data(base, packer) \ + msgpack_pack_nil(packer) + +#define locharacter_scientist_unpack_data(base, obj) \ + (obj != NULL) diff --git a/core/locharacter/theists_child.c b/core/locharacter/theists_child.c new file mode 100644 index 0000000..c233027 --- /dev/null +++ b/core/locharacter/theists_child.c @@ -0,0 +1,763 @@ +#include "./theists_child.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "util/chaos/xorshift.h" +#include "util/math/algorithm.h" +#include "util/math/constant.h" +#include "util/math/rational.h" +#include "util/math/vector.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/lobullet/base.h" +#include "core/lobullet/bomb.h" +#include "core/lobullet/linear.h" +#include "core/lobullet/pool.h" +#include "core/locommon/msgpack.h" +#include "core/locommon/ticker.h" +#include "core/loeffect/recipient.h" +#include "core/loeffect/stance.h" +#include "core/loentity/bullet.h" +#include "core/loentity/store.h" +#include "core/loplayer/event.h" +#include "core/loplayer/player.h" +#include "core/loresource/music.h" +#include "core/loresource/set.h" +#include "core/loresource/text.h" +#include "core/loshader/character.h" + +#include "./base.h" +#include "./misc.h" +#include "./util.h" + +typedef struct { + locharacter_event_holder_t event; + + uint64_t phase; + vec2_t from; + vec2_t to; +} locharacter_theists_child_param_t; + +#define LOCHARACTER_THEISTS_CHILD_PARAM_TO_PACK_EACH_(PROC) do { \ + PROC("event-start-time", event.start_time); \ + PROC("phase", phase); \ + PROC("from", from); \ + PROC("to", to); \ +} while (0) +#define LOCHARACTER_THEISTS_CHILD_PARAM_TO_PACK_COUNT 4 + +static const vec2_t locharacter_theists_child_size_ = vec2(.02f, .06f); + +static const loeffect_recipient_status_t +locharacter_theists_child_base_status_ = { + .attack = .1f, + .defence = .9f, + .speed = .1f, + .jump = .2f, +}; + +#define LOCHARACTER_THEISTS_CHILD_BEAT (60000/140.f) /* 140 BPM */ +#define LOCHARACTER_THEISTS_CHILD_MUSIC_DURATION \ + ((uint64_t) LOCHARACTER_THEISTS_CHILD_BEAT*236) + +#define LOCHARACTER_THEISTS_CHILD_MELODY_B_START_BEAT 128 +#define LOCHARACTER_THEISTS_CHILD_MELODY_B_END_BEAT 192 + +#include "./theists_child.private.h" + +static void +locharacter_theists_child_start_wait_state_( + locharacter_base_t* c +); +static void +locharacter_theists_child_start_standup_state_( + locharacter_base_t* c +); +static void +locharacter_theists_child_start_rush_state_( + locharacter_base_t* c +); +static void +locharacter_theists_child_start_combo_state_( + locharacter_base_t* c +); +static void +locharacter_theists_child_start_cooldown_state_( + locharacter_base_t* c +); +static void +locharacter_theists_child_start_stunned_state_( + locharacter_base_t* c +); +static void +locharacter_theists_child_start_dead_state_( + locharacter_base_t* c +); + +static void locharacter_theists_child_finalize_event_(locharacter_base_t* c) { + assert(c != NULL); + /* This function must start next state. */ + + locharacter_theists_child_param_t* p = (typeof(p)) c->data; + assert(p != NULL); + + locharacter_event_holder_release_control(&p->event); + + if (c->recipient.madness > 0) { + loentity_character_apply_effect( + &c->player->entity.super, &loeffect_curse_trigger()); + locharacter_theists_child_start_wait_state_(c); + } else { + loplayer_gain_stance(c->player, LOEFFECT_STANCE_ID_REVOLUTIONER); + locharacter_theists_child_start_dead_state_(c); + } +} + +static bool locharacter_theists_child_reset_if_player_left_( + locharacter_base_t* c) { + assert(c != NULL); + + locharacter_theists_child_param_t* p = (typeof(p)) c->data; + + if (MATH_ABS(c->cache.player_pos.x) < 1 && + 0 < c->cache.player_pos.y && c->cache.player_pos.y < 1) { + return false; + } + locharacter_event_holder_release_control(&p->event); + locharacter_theists_child_start_wait_state_(c); + return true; +} + +static void locharacter_theists_child_fire_bullets_(locharacter_base_t* c) { + assert(c != NULL); + + static const float len = .3f; + static const float accel = .6f; + static const uint64_t dur = 1000; + + for (size_t i = 0; i < 30; ++i) { + const float t = MATH_PI/15*i; + + const vec2_t v = vec2(cos(t), sin(t)); + + locommon_position_t pos = c->super.super.pos; + pos.fract.x += v.x*len; + pos.fract.y += v.y*len; + locommon_position_reduce(&pos); + + lobullet_base_t* bullet = lobullet_pool_create(c->bullets); + lobullet_linear_light_build(bullet, (&(lobullet_linear_param_t) { + .owner = c->super.super.id, + .pos = pos, + .size = vec2(.015f, .015f), + .acceleration = vec2(-v.x*accel, -v.y*accel), + .color = vec4(1, 1, 1, .8f), + .duration = dur, + .knockback = .1f, + .effect = loeffect_immediate_damage(c->recipient.status.attack), + })); + loentity_store_add(c->entities, &bullet->super.super); + } +} + +static void locharacter_theists_child_update_wait_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float standup_range = .5f; + static const int32_t sit_duration = 4000; + + c->cache.gravity = true; + + /* ---- motion ---- */ + float t = (c->cache.time - c->since)*1.f/sit_duration; + if (t > 1) t = 1; + loshader_character_drawer_instance_t* instance = &c->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN; + instance->motion_time = t; + + /* ---- state transition ---- */ + if (c->since+sit_duration <= c->cache.time) { + if (MATH_ABS(c->cache.player_pos.x) < 1 && + 0 < c->cache.player_pos.y && c->cache.player_pos.y < 1) { + vec2_t diff; + vec2_sub(&diff, &c->cache.player_pos, &c->pos); + if (vec2_pow_length(&diff) < standup_range*standup_range) { + locharacter_theists_child_start_standup_state_(c); + return; + } + } + } +} +static void locharacter_theists_child_start_wait_state_(locharacter_base_t* c) { + assert(c != NULL); + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_WAIT; +} + +static void locharacter_theists_child_update_standup_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_THEISTS_CHILD_BEAT; + static const uint64_t line_duration = beat*10; + + locharacter_theists_child_param_t* p = (typeof(p)) c->data; + + const bool event = locharacter_event_holder_has_control(&p->event); + const uint64_t standup_duration = event? beat*64: 1000; + + /* ---- motion ---- */ + float t = (c->cache.time - c->since)*1.0f/standup_duration; + if (t > 1) t = 1; + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + if (t < .5f) { + t *= 2; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT; + instance->motion_time = t*t*(3-2*t); + } else { + t = (t-.5f)*2; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = t*t*(3-2*t); + } + + /* ---- event ---- */ + if (event) { + p->event.param->cinescope = true; + p->event.param->hide_hud = true; + + if (c->since+(p->phase+2)*line_duration < c->cache.time) { + static const char* text[] = { + "boss_theists_child_line0", + "boss_theists_child_line1", + "boss_theists_child_line2", + }; + if (p->phase < sizeof(text)/sizeof(text[0])) { + const char* v = loresource_text_get( + c->res->lang, text[(size_t) p->phase]); + loplayer_event_param_set_line(p->event.param, v, strlen(v)); + } else { + loplayer_event_param_set_line(p->event.param, "", 0); + } + ++p->phase; + } + } + + /* ---- state transition ---- */ + if (locharacter_theists_child_reset_if_player_left_(c)) return; + + if (c->since+standup_duration < c->cache.time) { + if (event) { + p->event.param->hide_hud = false; + p->event.param->cinescope = false; + loplayer_event_param_set_line(p->event.param, "", 0); + } + locharacter_theists_child_start_rush_state_(c); + return; + } +} +static void locharacter_theists_child_start_standup_state_( + locharacter_base_t* c) { + assert(c != NULL); + + locharacter_theists_child_param_t* p = (typeof(p)) c->data; + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_STANDUP; + + p->phase = 0; + loeffect_recipient_reset(&c->recipient); + + if (!loeffect_stance_set_has( + &c->player->status.stances, LOEFFECT_STANCE_ID_REVOLUTIONER)) { + locharacter_event_holder_take_control(&p->event); + } +} + +static void locharacter_theists_child_update_rush_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_THEISTS_CHILD_BEAT; + + static const uint64_t premotion_duration = beat*2; + static const uint64_t whole_duration = beat*4; + + const locharacter_theists_child_param_t* p = (typeof(p)) c->data; + + const uint64_t elapsed = c->cache.time - c->since; + + /* ---- motion ---- */ + float t = elapsed*1.f/premotion_duration; + if (t > 1) t = 1; + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + if (t < .1f) { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_WALK; + instance->motion_time = 1-powf(1-t*10, 2); + } else if (t < .5f) { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_WALK; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->motion_time = 1-powf(1-(t-.1f)/4*10, 2); + } else { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2; + instance->motion_time = powf((t-.5f)*2, 4); + } + + /* ---- position ---- */ + vec2_sub(&c->pos, &p->to, &p->from); + c->direction = MATH_SIGN(c->pos.x); + vec2_muleq(&c->pos, powf(t, 2)); + c->pos.y += (1-MATH_ABS(t*2-1))*c->recipient.status.jump*.1f; + vec2_addeq(&c->pos, &p->from); + + /* ---- state transition ---- */ + if (locharacter_theists_child_reset_if_player_left_(c)) return; + + if (c->since+whole_duration < c->cache.time) { + locharacter_theists_child_start_cooldown_state_(c); + return; + } +} +static void locharacter_theists_child_start_rush_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_THEISTS_CHILD_BEAT; + + static const uint64_t parry = 300; + + locharacter_theists_child_param_t* p = (typeof(p)) c->data; + + if (c->last_knockback_time + parry > c->cache.time) { + locharacter_theists_child_start_stunned_state_(c); + return; + } + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_RUSH; + + const vec2_t* player = &c->cache.player_pos; + + const float diffx = player->x - c->pos.x; + p->from = c->pos; + p->to = vec2( + player->x - MATH_SIGN(diffx)*locharacter_theists_child_size_.x*2, + player->y - .02f); + + const loplayer_combat_attack_t attack = { + .attacker = c->super.super.id, + .start = c->ticker->time + (uint64_t) beat, + .duration = beat*3, + .knockback = vec2(MATH_SIGN(player->x)*.2f, 0), + .effect = loeffect_immediate_damage(c->recipient.status.attack*2), + }; + loplayer_attack(c->player, &attack); +} + +static void locharacter_theists_child_update_combo_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_THEISTS_CHILD_BEAT; + static const uint64_t premotion_duration = beat; + static const uint64_t attack_duration = beat; + static const uint64_t whole_duration = beat*4; + + const locharacter_theists_child_param_t* p = (typeof(p)) c->data; + + const uint64_t elapsed = c->cache.time - c->since; + + /* ---- motion ---- */ + loshader_character_drawer_instance_t* instance = &c->cache.instance; + if (elapsed < premotion_duration) { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->motion_time = elapsed*1.f / premotion_duration; + } else { + const uint64_t attack_elapsed = elapsed - premotion_duration; + + float t = 1; + if (attack_elapsed < attack_duration*p->phase) { + t = attack_elapsed%attack_duration*1.f / attack_duration; + } + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2; + instance->motion_time = 1-powf(1-t, 4); + } + + /* ---- position ---- */ + if (elapsed < premotion_duration) { + const float t = elapsed*1.f/premotion_duration; + vec2_sub(&c->pos, &p->to, &p->from); + vec2_muleq(&c->pos, t*t); + vec2_addeq(&c->pos, &p->from); + } + + /* ---- state transition ---- */ + if (locharacter_theists_child_reset_if_player_left_(c)) return; + + if (elapsed >= whole_duration) { + locharacter_theists_child_start_cooldown_state_(c); + return; + } +} +static void locharacter_theists_child_start_combo_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_THEISTS_CHILD_BEAT; + static const uint64_t parry = 200; + + locharacter_theists_child_param_t* p = (typeof(p)) c->data; + + if (c->last_knockback_time + parry > c->cache.time) { + locharacter_theists_child_start_stunned_state_(c); + return; + } + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_COMBO; + + const float diffx = c->cache.player_pos.x - c->pos.x; + c->direction = MATH_SIGN(diffx); + + p->phase = 2 + chaos_xorshift(c->since)%2; + p->from = c->pos; + p->to = c->cache.player_pos; + p->to.x -= c->direction*locharacter_theists_child_size_.x*2; + p->to.y -= .02f; + + const loplayer_combat_attack_t attack1 = { + .attacker = c->super.super.id, + .start = c->ticker->time + (uint64_t) beat, + .duration = beat/2, + .knockback = vec2(c->direction*.1f, 0), + .effect = loeffect_immediate_damage(c->recipient.status.attack*.8f), + }; + loplayer_attack(c->player, &attack1); + + const loplayer_combat_attack_t attack2 = { + .attacker = c->super.super.id, + .start = c->ticker->time + (uint64_t) (beat*2), + .duration = p->phase == 2? beat*1.5: beat/2, + .knockback = vec2(c->direction*.1f, 0), + .effect = loeffect_immediate_damage(c->recipient.status.attack*1.1f), + }; + loplayer_attack(c->player, &attack2); + + if (p->phase >= 3) { + const loplayer_combat_attack_t attack3 = { + .attacker = c->super.super.id, + .start = c->ticker->time + (uint64_t) (beat*3), + .duration = beat/2, + .knockback = vec2(c->direction*.1f, 0), + .effect = loeffect_immediate_damage(c->recipient.status.attack*1.3f), + }; + loplayer_attack(c->player, &attack3); + } +} + +static void locharacter_theists_child_update_cooldown_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_THEISTS_CHILD_BEAT; + + static const uint64_t duration = beat*4; + + const locharacter_theists_child_param_t* p = (typeof(p)) c->data; + + c->cache.bullet_hittest = true; + c->cache.gravity = true; + + /* ---- motion ---- */ + float t = (c->cache.time - c->since)*1.0f/duration; + if (t > 1) t = 1; + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = t; + + /* ---- state transition ---- */ + if (locharacter_theists_child_reset_if_player_left_(c)) return; + + if (c->since+duration < c->cache.time) { + if (locharacter_event_holder_has_control(&p->event)) { + static const uint64_t dur = LOCHARACTER_THEISTS_CHILD_MUSIC_DURATION; + if (p->event.start_time+dur < c->cache.time) { + locharacter_theists_child_finalize_event_(c); + return; + } + } else { + if (c->recipient.madness <= 0) { + locharacter_theists_child_start_dead_state_(c); + return; + } + } + + vec2_t diff; + vec2_sub(&diff, &c->cache.player_pos, &c->pos); + if (vec2_pow_length(&diff) < .5f*.5f) { + locharacter_theists_child_start_combo_state_(c); + return; + } + + locharacter_theists_child_start_rush_state_(c); + return; + } +} +static void locharacter_theists_child_start_cooldown_state_( + locharacter_base_t* c) { + assert(c != NULL); + + const locharacter_theists_child_param_t* p = (typeof(p)) c->data; + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_COOLDOWN; + + bool skip_firing = false; + if (locharacter_event_holder_has_control(&p->event)) { + const float beat = + (c->cache.time - p->event.start_time)/LOCHARACTER_THEISTS_CHILD_BEAT; + skip_firing = + LOCHARACTER_THEISTS_CHILD_MELODY_B_START_BEAT <= beat && + beat < LOCHARACTER_THEISTS_CHILD_MELODY_B_END_BEAT; + } + if (!skip_firing) locharacter_theists_child_fire_bullets_(c); +} + +static void locharacter_theists_child_update_stunned_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_THEISTS_CHILD_BEAT; + static const uint64_t duration = beat*4; + + /* ---- motion ---- */ + float t = (c->cache.time - c->since)*1.f/duration; + t *= 6; + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + if (t < 1) { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT; + instance->motion_time = 1-powf(1-t, 6); + } else { + t = (t-1)/5; + if (t > 1) t = 1; + t = t*t*(3-2*t); + + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2; + instance->motion_time = t; + } + + /* ---- state transition ---- */ + if (c->since+duration < c->cache.time) { + locharacter_theists_child_start_cooldown_state_(c); + return; + } +} +static void locharacter_theists_child_start_stunned_state_( + locharacter_base_t* c) { + assert(c != NULL); + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_STUNNED; + + loeffect_recipient_apply_effect( + &c->recipient, &loeffect_immediate_damage(1.f)); +} + +static void locharacter_theists_child_update_dead_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const uint64_t anime_duration = 4000; + static const uint64_t duration = 30000; + + c->cache.gravity = true; + + /* ---- motion ---- */ + float t = (c->cache.time - c->since)*1.f/anime_duration; + if (t > 1) t = 1; + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN; + instance->motion_time = t*t; + instance->color.w = 1-t; + + /* ---- state transition ---- */ + if (c->since+duration < c->cache.time) { + c->pos = vec2(0, 0); + locharacter_theists_child_start_wait_state_(c); + return; + } +} +static void locharacter_theists_child_start_dead_state_( + locharacter_base_t* c) { + assert(c != NULL); + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_DEAD; + + loplayer_gain_faith(c->player, .5f); +} + +bool locharacter_theists_child_update(locharacter_base_t* base) { + assert(base != NULL); + + static const vec2_t size = locharacter_theists_child_size_; + static const vec4_t color = vec4(.05f, 0, 0, 1); + static const float height = size.y * 1.4f; + static const float drawsz = MATH_MAX(size.x, size.y); + + locharacter_theists_child_param_t* p = (typeof(p)) base->data; + + loeffect_recipient_update( + &base->recipient, &locharacter_theists_child_base_status_); + + if (!locharacter_event_holder_update(&p->event)) { + locharacter_theists_child_start_wait_state_(base); + } + + base->cache.instance = (loshader_character_drawer_instance_t) { + .character_id = LOSHADER_CHARACTER_ID_CAVIA, + .marker_offset = vec2(0, height - drawsz), + .pos = vec2(0, drawsz - height), + .size = vec2(drawsz, drawsz), + .color = color, + }; + + switch (base->state) { + case LOCHARACTER_STATE_WAIT: + locharacter_theists_child_update_wait_state_(base); + break; + case LOCHARACTER_STATE_STANDUP: + locharacter_theists_child_update_standup_state_(base); + break; + case LOCHARACTER_STATE_RUSH: + locharacter_theists_child_update_rush_state_(base); + break; + case LOCHARACTER_STATE_COMBO: + locharacter_theists_child_update_combo_state_(base); + break; + case LOCHARACTER_STATE_COOLDOWN: + locharacter_theists_child_update_cooldown_state_(base); + break; + case LOCHARACTER_STATE_STUNNED: + locharacter_theists_child_update_stunned_state_(base); + break; + case LOCHARACTER_STATE_DEAD: + locharacter_theists_child_update_dead_state_(base); + break; + default: + locharacter_theists_child_start_wait_state_(base); + } + locharacter_theists_child_update_passive_action_(base); + + base->cache.height = height; + + base->cache.instance.marker = !!base->cache.bullet_hittest; + base->cache.instance.size.x *= base->direction; + return true; +} + +void locharacter_theists_child_build(locharacter_base_t* base, loentity_id_t ground) { + assert(base != NULL); + + base->type = LOCHARACTER_TYPE_THEISTS_CHILD; + + base->ground = ground; + + base->pos = vec2(0, 0); + base->direction = 1; + + base->state = LOCHARACTER_STATE_WAIT; + base->since = base->cache.time; + + locharacter_theists_child_param_t* p = (typeof(p)) base->data; + *p = (typeof(*p)) {0}; + + locharacter_event_holder_initialize( + &p->event, + &base->res->music.boss_theists_child, + base, + LOCHARACTER_THEISTS_CHILD_MUSIC_DURATION, + 0); +} + +void locharacter_theists_child_tear_down(locharacter_base_t* base) { + assert(base != NULL); + + locharacter_theists_child_param_t* p = (typeof(p)) base->data; + locharacter_event_holder_deinitialize(&p->event); +} + +void locharacter_theists_child_pack_data( + const locharacter_base_t* base, msgpack_packer* packer) { + assert(base != NULL); + assert(packer != NULL); + + const locharacter_theists_child_param_t* p = (typeof(p)) base->data; + + msgpack_pack_map(packer, LOCHARACTER_THEISTS_CHILD_PARAM_TO_PACK_COUNT); + +# define pack_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + LOCOMMON_MSGPACK_PACK_ANY(packer, &p->var); \ + } while (0) + + LOCHARACTER_THEISTS_CHILD_PARAM_TO_PACK_EACH_(pack_); + +# undef pack_ +} + +bool locharacter_theists_child_unpack_data( + locharacter_base_t* base, const msgpack_object* obj) { + assert(base != NULL); + + locharacter_theists_child_param_t* p = (typeof(p)) base->data; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + +# define unpack_(name, var) do { \ + if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(name), &p->var)) { \ + return false; \ + } \ + } while (0) + + LOCHARACTER_THEISTS_CHILD_PARAM_TO_PACK_EACH_(unpack_); + +# undef unpack_ + +# undef item_ + + locharacter_event_holder_initialize( + &p->event, + &base->res->music.boss_theists_child, + base, + LOCHARACTER_THEISTS_CHILD_MUSIC_DURATION, + p->event.start_time); + return true; +} diff --git a/core/locharacter/theists_child.h b/core/locharacter/theists_child.h new file mode 100644 index 0000000..3b796b2 --- /dev/null +++ b/core/locharacter/theists_child.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include + +#include "core/loentity/entity.h" + +#include "./base.h" + +bool +locharacter_theists_child_update( + locharacter_base_t* base +); + +void +locharacter_theists_child_build( + locharacter_base_t* base, + loentity_id_t ground +); + +void +locharacter_theists_child_tear_down( + locharacter_base_t* base +); + +void +locharacter_theists_child_pack_data( + const locharacter_base_t* base, + msgpack_packer* packer +); + +bool +locharacter_theists_child_unpack_data( + locharacter_base_t* base, + const msgpack_object* obj +); diff --git a/core/locharacter/theists_child.private.h b/core/locharacter/theists_child.private.h new file mode 100644 index 0000000..5ca34c2 --- /dev/null +++ b/core/locharacter/theists_child.private.h @@ -0,0 +1,150 @@ +static void locharacter_theists_child_update_passive_action_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_THEISTS_CHILD_BEAT; + + const locharacter_theists_child_param_t* p = (typeof(p)) c->data; + if (!locharacter_event_holder_has_control(&p->event)) return; + + const uint64_t dt = c->cache.time - c->last_update_time; + const uint64_t t = c->cache.time - p->event.start_time; + + const float beats = t/beat; + const float last_beats = t > dt? (t-dt)/beat: 0; + +# define name_pos_(name, x, y) \ + locommon_position_t name = c->cache.ground->super.pos; \ + vec2_addeq(&name.fract, &vec2(x, y)); \ + locommon_position_reduce(&name); + + name_pos_(top, 0, .8f); + name_pos_(lefttop, -.25f, .8f); + name_pos_(righttop, .25f, .8f); + name_pos_(center, 0, .25f); + name_pos_(left, -.3f, .2f); + name_pos_(right, .3f, .2f); + +# undef name_pos_ + +# define trigger_on_(x) (last_beats < (x) && beats >= (x)) + + /* ---- intro -> A melody ---- */ + if (trigger_on_(56)) { + for (size_t i = 0; i < 2; ++i) { + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_triangle_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = i? left: right, + .size = vec2(.05f, .15f), + .angle = -MATH_PI/2, + .color = vec4(1, 1, 1, .8f), + .silent = true, + .beat = beat, + .step = 8, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/2), + })); + loentity_store_add(c->entities, &b->super.super); + } + } + if (trigger_on_(64)) { + for (size_t i = 0; i < 2; ++i) { + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_linear_triangle_build(b, (&(lobullet_linear_param_t) { + .owner = c->super.super.id, + .pos = i? lefttop: righttop, + .size = vec2(.05f, .15f), + .velocity = vec2(0, -1.4f/(beat/1000*2)), + .acceleration = vec2(0, 1/(beat/1000*2)), + .color = vec4(1, 1, 1, .8f), + .duration = beat*2, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack), + })); + loentity_store_add(c->entities, &b->super.super); + } + } + + /* ---- B melody ---- */ + for (size_t i = 128, cnt = 0; i < 192; i+=4, ++cnt) { + if (trigger_on_(i)) { + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = cnt%2 == 0? left: right, + .size = vec2(.13f, .13f), + .angle = MATH_PI/4, + .color = vec4(1, 1, 1, .8f), + .silent = true, + .beat = LOCHARACTER_THEISTS_CHILD_BEAT, + .step = 4, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/2), + })); + loentity_store_add(c->entities, &b->super.super); + } + } + for (size_t i = 128; i < 192; i+=4) { + if (trigger_on_(i)) { + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_linear_triangle_build(b, (&(lobullet_linear_param_t) { + .owner = c->super.super.id, + .pos = top, + .size = vec2(.05f, .2f), + .velocity = vec2(0, -1.4f/(beat/1000*2)), + .acceleration = vec2(0, 1/(beat/1000*2)), + .color = vec4(1, 1, 1, .8f), + .duration = beat*2, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack), + })); + loentity_store_add(c->entities, &b->super.super); + } + } + + /* ---- fill-in ---- */ + if (trigger_on_(192)) { + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = center, + .size = vec2(.2f, .2f), + .angle = MATH_PI/4, + .color = vec4(1, 1, .4f, .8f), + .silent = true, + .beat = LOCHARACTER_THEISTS_CHILD_BEAT, + .step = 8, + .knockback = .1f, + .effect = loeffect_amnesia( + c->ticker->time + (uint64_t) (8*beat), beat*4), + })); + loentity_store_add(c->entities, &b->super.super); + } + + /* ---- C melody ---- */ + for (size_t i = 200, cnt = 0; i < 232; i+=2, ++cnt) { + if (trigger_on_(i)) { + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .size = vec2(.16f, .16f), + .pos = cnt%2 == 0? left: right, + .angle = MATH_PI/4, + .color = vec4(1, 1, 1, .8f), + .silent = true, + .beat = LOCHARACTER_THEISTS_CHILD_BEAT, + .step = 2, + .knockback = .1f, + .effect = loeffect_immediate_damage(c->recipient.status.attack), + })); + loentity_store_add(c->entities, &b->super.super); + } + } + +# undef trigger_on_ +} diff --git a/core/locharacter/util.c b/core/locharacter/util.c new file mode 100644 index 0000000..d69acac --- /dev/null +++ b/core/locharacter/util.c @@ -0,0 +1,139 @@ +#include "./util.h" + +#include +#include +#include +#include + +#include "util/math/rational.h" +#include "util/math/vector.h" + +#include "core/locommon/position.h" +#include "core/loresource/music.h" +#include "core/loplayer/event.h" + +#include "./base.h" + +static void locharacter_event_holder_handle_control_lost_( + locharacter_event_holder_t* holder) { + assert(holder != NULL); + + if (holder->music != NULL) { + jukebox_amp_change_volume(&holder->music->amp, 0, &rational(1, 1)); + jukebox_decoder_stop_after(holder->music->decoder, &rational(1, 1)); + } + + holder->param = NULL; + holder->start_time = 0; +} + +void locharacter_event_holder_initialize( + locharacter_event_holder_t* holder, + loresource_music_player_t* music, + locharacter_base_t* owner, + uint64_t duration, + uint64_t start_time) { + assert(holder != NULL); + assert(music != NULL); + assert(owner != NULL); + assert(duration > 0); + + *holder = (typeof(*holder)) { + .music = music, + .owner = owner, + .duration = duration, + .start_time = start_time, + }; +} + +void locharacter_event_holder_deinitialize( + locharacter_event_holder_t* holder) { + assert(holder != NULL); + + locharacter_event_holder_release_control(holder); +} + +bool locharacter_event_holder_take_control( + locharacter_event_holder_t* holder) { + assert(holder != NULL); + assert(holder->owner != NULL); + assert(holder->owner->cache.ground != NULL); + + const locharacter_base_t* owner = holder->owner; + + const bool recover = holder->start_time > 0; + + const uint64_t t = recover? owner->cache.time - holder->start_time: 0; + if (recover && t >= holder->duration) return false; + + holder->param = loplayer_event_take_control( + owner->player->event, owner->super.super.id); + if (holder->param == NULL) return false; + + loplayer_event_param_t* p = holder->param; + + p->area_pos = owner->cache.ground->super.pos; + p->area_pos.fract.y += .4f; + locommon_position_reduce(&p->area_pos); + + p->area_size = vec2(.45f, .45f); + p->music = holder->music; + + if (!recover) { + loentity_character_apply_effect( + &owner->player->entity.super, + &loeffect_curse(owner->ticker->time, holder->duration)); + holder->start_time = owner->cache.time; + } + if (holder->music != NULL) { + jukebox_decoder_play(holder->music->decoder, &rational(t, 1000), false); + jukebox_amp_change_volume(&holder->music->amp, .8f, &rational(1, 1)); + } + return true; +} + +void locharacter_event_holder_release_control( + locharacter_event_holder_t* holder) { + assert(holder != NULL); + + if (holder->param == NULL) return; + + loplayer_event_param_release_control(holder->param); + locharacter_event_holder_handle_control_lost_(holder); +} + +bool locharacter_event_holder_update(locharacter_event_holder_t* holder) { + assert(holder != NULL); + + if (holder->start_time > holder->owner->ticker->time) { + holder->start_time = 0; + } + + loplayer_event_param_t* p = holder->param; + if (p == NULL) { + if (holder->start_time > 0) { + return locharacter_event_holder_take_control(holder); + } + return true; + } + + if (!p->controlled || p->controlled_by != holder->owner->super.super.id) { + locharacter_event_holder_handle_control_lost_(holder); + return false; + } + + if (holder->music != NULL) { + rational_t r; + jukebox_decoder_get_seek_position(holder->music->decoder, &r); + rational_normalize(&r, 1000); + holder->owner->cache.time = r.num + holder->start_time; + } + return true; +} + +bool locharacter_event_holder_has_control( + const locharacter_event_holder_t* holder) { + assert(holder != NULL); + + return holder->param != NULL; +} diff --git a/core/locharacter/util.h b/core/locharacter/util.h new file mode 100644 index 0000000..464ab2a --- /dev/null +++ b/core/locharacter/util.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include + +#include "core/loentity/entity.h" +#include "core/loresource/music.h" +#include "core/loplayer/event.h" + +#include "./base.h" + +typedef struct { + loresource_music_player_t* music; + locharacter_base_t* owner; + + uint64_t duration; + + loplayer_event_param_t* param; + uint64_t start_time; +} locharacter_event_holder_t; + +void +locharacter_event_holder_initialize( + locharacter_event_holder_t* holder, + loresource_music_player_t* music, + locharacter_base_t* owner, + uint64_t duration, + uint64_t start_time +); + +void +locharacter_event_holder_deinitialize( + locharacter_event_holder_t* holder +); + +bool +locharacter_event_holder_take_control( + locharacter_event_holder_t* holder +); + +void +locharacter_event_holder_release_control( + locharacter_event_holder_t* holder +); + +bool /* false: event was aborted by others */ +locharacter_event_holder_update( + locharacter_event_holder_t* holder +); + +bool +locharacter_event_holder_has_control( + const locharacter_event_holder_t* holder +); diff --git a/core/locharacter/warder.c b/core/locharacter/warder.c new file mode 100644 index 0000000..784c17a --- /dev/null +++ b/core/locharacter/warder.c @@ -0,0 +1,299 @@ +#include "./warder.h" + +#include +#include +#include + +#include "util/math/algorithm.h" +#include "util/math/vector.h" + +#include "core/lobullet/base.h" +#include "core/lobullet/linear.h" +#include "core/loeffect/recipient.h" +#include "core/loentity/entity.h" +#include "core/loentity/store.h" +#include "core/loplayer/event.h" +#include "core/loplayer/player.h" +#include "core/loresource/sound.h" +#include "core/loshader/character.h" + +#include "./base.h" +#include "./misc.h" + +static const vec2_t locharacter_warder_size_ = vec2(.02f, .05f); + +static const loeffect_recipient_status_t locharacter_warder_base_status_ = { + .attack = .1f, + .defence = -.8f, +}; + +static void locharacter_warder_shoot_(locharacter_base_t* c) { + assert(c != NULL); + + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_linear_light_build(b, (&(lobullet_linear_param_t) { + .owner = c->super.super.id, + .pos = c->super.super.pos, + .size = vec2(.04f, .04f), + .velocity = vec2(c->direction*.5f, 0), + .color = vec4(.6f, .6f, .6f, .8f), + .acceleration = vec2(0, 0), + .duration = 2000, + .knockback = .4f, + .effect = loeffect_immediate_damage(c->recipient.status.attack), + })); + loentity_store_add(c->entities, &b->super.super); + + loresource_sound_play(c->res->sound, "enemy_shoot"); +} + +static void +locharacter_warder_start_wait_state_( + locharacter_base_t* base +); +static void +locharacter_warder_start_shoot_state_( + locharacter_base_t* base +); +static void +locharacter_warder_start_combo_state_( + locharacter_base_t* base +); +static void +locharacter_warder_start_dead_state_( + locharacter_base_t* base +); + +static void locharacter_warder_update_wait_state_(locharacter_base_t* base) { + assert(base != NULL); + + static const uint64_t duration = 1000; + + const uint64_t elapsed = base->ticker->time - base->since; + + /* ---- motion ---- */ + float t = elapsed*1.f / duration; + if (t > 1) t = 1; + + loshader_character_drawer_instance_t* instance = &base->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = t*t*(3-2*t); + + /* ---- state transition ---- */ + if (elapsed >= duration) { + if (base->recipient.madness <= 0) { + locharacter_warder_start_dead_state_(base); + return; + } + + if (loplayer_event_get_param(base->player->event) == NULL) { + vec2_t disp; + vec2_sub(&disp, &base->cache.player_pos, &base->pos); + disp.x *= base->cache.ground->size.x; + + const float pdist = vec2_pow_length(&disp); + if (MATH_ABS(disp.y) < locharacter_warder_size_.y && pdist < .4f*.4f) { + static const float r = locharacter_warder_size_.x*3; + if (pdist < r*r) { + locharacter_warder_start_combo_state_(base); + } else if (disp.x*base->direction > 0) { + locharacter_warder_start_shoot_state_(base); + } + } + return; + } + } +} +static void locharacter_warder_start_wait_state_(locharacter_base_t* base) { + assert(base != NULL); + + base->since = base->ticker->time; + base->state = LOCHARACTER_STATE_WAIT; +} + +static void locharacter_warder_update_shoot_state_(locharacter_base_t* base) { + assert(base != NULL); + + static const uint64_t duration = 500; + + const uint64_t elapsed = base->ticker->time - base->since; + + /* ---- motion ---- */ + float t = elapsed*1.f / duration; + if (t > 1) t = 1; + t = t*t; + + loshader_character_drawer_instance_t* instance = &base->cache.instance; + if (t < .5f) { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->motion_time = t*2; + } else { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2; + instance->motion_time = (t-.5f)*2; + } + + /* ---- state transition ---- */ + if (elapsed >= duration) { + locharacter_warder_shoot_(base); + locharacter_warder_start_wait_state_(base); + return; + } +} +static void locharacter_warder_start_shoot_state_(locharacter_base_t* base) { + assert(base != NULL); + + base->since = base->ticker->time; + base->state = LOCHARACTER_STATE_SHOOT; +} + +static void locharacter_warder_update_combo_state_(locharacter_base_t* base) { + assert(base != NULL); + + static const uint64_t duration = 1000; + + const uint64_t elapsed = base->ticker->time - base->since; + + /* ---- motion ---- */ + float t = elapsed*1.f/duration; + if (t > 1) t = 1; + t = t*t; + + loshader_character_drawer_instance_t* instance = &base->cache.instance; + if (t < .5f) { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->motion_time = t*2; + } else { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2; + instance->motion_time = (t-.5f)*2; + } + + /* ---- state transition ---- */ + if (elapsed >= duration) { + locharacter_warder_start_wait_state_(base); + return; + } +} +static void locharacter_warder_start_combo_state_(locharacter_base_t* base) { + assert(base != NULL); + + base->since = base->ticker->time; + base->state = LOCHARACTER_STATE_COMBO; + + const float diff = base->cache.player_pos.x - base->pos.x; + base->direction = MATH_SIGN(diff); + + const loplayer_combat_attack_t attack = { + .attacker = base->super.super.id, + .start = base->ticker->time + 500, + .duration = 500, + .knockback = vec2(base->direction*.1f, 0), + .effect = loeffect_immediate_damage(base->recipient.status.attack), + }; + loplayer_attack(base->player, &attack); +} + +static void locharacter_warder_update_dead_state_(locharacter_base_t* base) { + assert(base != NULL); + + static const uint64_t anime_duration = 500; + static const uint64_t duration = 30000; + + const uint64_t elapsed = base->ticker->time - base->since; + + /* ---- motion ---- */ + loshader_character_drawer_instance_t* instance = &base->cache.instance; + if (elapsed > duration - anime_duration) { /* wake up */ + float t = 1-(duration - elapsed)*1.f/anime_duration; + if (t < 0) t = 0; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = 1-powf(1-t, 2); + } else { /* down */ + float t = elapsed*1.f/anime_duration; + if (t > 1) t = 1; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN; + instance->motion_time = t; + } + + /* ---- state transition ---- */ + if (elapsed >= duration) { + loeffect_recipient_reset(&base->recipient); + locharacter_warder_start_wait_state_(base); + return; + } +} +static void locharacter_warder_start_dead_state_(locharacter_base_t* base) { + assert(base != NULL); + + base->since = base->ticker->time; + base->state = LOCHARACTER_STATE_DEAD; + + loplayer_gain_faith(base->player, .1f); +} + +bool locharacter_warder_update(locharacter_base_t* base) { + assert(base != NULL); + + static const vec2_t size = locharacter_warder_size_; + static const vec4_t color = vec4(.1f, .1f, .1f, 1); + static const float height = size.y*1.4f; + static const float drawsz = MATH_MAX(size.x, size.y); + + loeffect_recipient_update(&base->recipient, &locharacter_warder_base_status_); + + base->cache.instance = (loshader_character_drawer_instance_t) { + .character_id = LOSHADER_CHARACTER_ID_WARDER, + .marker_offset = vec2(0, height - drawsz), + .pos = vec2(0, drawsz - height), + .size = vec2(drawsz, drawsz), + .color = color, + }; + + switch (base->state) { + case LOCHARACTER_STATE_WAIT: + locharacter_warder_update_wait_state_(base); + break; + case LOCHARACTER_STATE_SHOOT: + locharacter_warder_update_shoot_state_(base); + break; + case LOCHARACTER_STATE_COMBO: + locharacter_warder_update_combo_state_(base); + break; + case LOCHARACTER_STATE_DEAD: + locharacter_warder_update_dead_state_(base); + break; + default: + locharacter_warder_start_wait_state_(base); + } + + base->cache.bullet_hittest = base->state != LOCHARACTER_STATE_DEAD; + base->cache.gravity = true; + + base->cache.height = height; + + base->cache.instance.size.x *= base->direction; + base->cache.instance.marker = !!base->cache.bullet_hittest; + return true; +} + +void locharacter_warder_build( + locharacter_base_t* base, const locharacter_warder_param_t* param) { + assert(base != NULL); + assert(param != NULL); + + base->type = LOCHARACTER_TYPE_WARDER; + + base->ground = param->ground; + + base->pos = vec2(param->pos, 0); + base->direction = -MATH_SIGN(param->pos); + if (base->direction == 0) base->direction = 1; + + locharacter_warder_start_wait_state_(base); +} diff --git a/core/locharacter/warder.h b/core/locharacter/warder.h new file mode 100644 index 0000000..b73c498 --- /dev/null +++ b/core/locharacter/warder.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include + +#include "core/loentity/entity.h" + +#include "./base.h" + +typedef struct { + loentity_id_t ground; + float pos; +} locharacter_warder_param_t; + +bool +locharacter_warder_update( + locharacter_base_t* base +); + +void +locharacter_warder_build( + locharacter_base_t* base, + const locharacter_warder_param_t* param +); + +#define locharacter_warder_tear_down(base) + +#define locharacter_warder_pack_data(base, packer) \ + msgpack_pack_nil(packer) + +#define locharacter_warder_unpack_data(base, obj) \ + (obj != NULL) diff --git a/core/locommon/CMakeLists.txt b/core/locommon/CMakeLists.txt new file mode 100644 index 0000000..7b282aa --- /dev/null +++ b/core/locommon/CMakeLists.txt @@ -0,0 +1,15 @@ +add_library(locommon + counter.c + easing.c + null.c + physics.c + position.c + ticker.c +) +target_link_libraries(locommon + msgpackc + + coly2d + math + mpkutil +) diff --git a/core/locommon/counter.c b/core/locommon/counter.c new file mode 100644 index 0000000..aaaa571 --- /dev/null +++ b/core/locommon/counter.c @@ -0,0 +1,45 @@ +#include "./counter.h" + +#include +#include + +#include + +#include "util/mpkutil/get.h" + +void locommon_counter_initialize(locommon_counter_t* counter, size_t first) { + assert(counter != NULL); + + *counter = (typeof(*counter)) { + .next = first, + }; +} + +void locommon_counter_deinitialize(locommon_counter_t* counter) { + assert(counter != NULL); + +} + +size_t locommon_counter_count(locommon_counter_t* counter) { + assert(counter != NULL); + + return counter->next++; +} + +void locommon_counter_pack( + const locommon_counter_t* counter, msgpack_packer* packer) { + assert(counter != NULL); + assert(packer != NULL); + + msgpack_pack_uint64(packer, counter->next); +} + +bool locommon_counter_unpack( + locommon_counter_t* counter, const msgpack_object* obj) { + assert(counter != NULL); + + if (!mpkutil_get_uint64(obj, &counter->next)) { + return false; + } + return true; +} diff --git a/core/locommon/counter.h b/core/locommon/counter.h new file mode 100644 index 0000000..38ee923 --- /dev/null +++ b/core/locommon/counter.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +#include + +typedef struct { + size_t next; +} locommon_counter_t; + +void +locommon_counter_initialize( + locommon_counter_t* counter, + size_t first +); + +void +locommon_counter_deinitialize( + locommon_counter_t* counter +); + +size_t +locommon_counter_count( + locommon_counter_t* counter +); + +void +locommon_counter_pack( + const locommon_counter_t* counter, + msgpack_packer* packer +); + +bool +locommon_counter_unpack( + locommon_counter_t* counter, + const msgpack_object* obj /* NULLABLE */ +); diff --git a/core/locommon/easing.c b/core/locommon/easing.c new file mode 100644 index 0000000..dd23010 --- /dev/null +++ b/core/locommon/easing.c @@ -0,0 +1,40 @@ +#include "./easing.h" + +#include +#include + +#include "util/math/algorithm.h" + +void locommon_easing_linear_float(float* v, float ed, float delta) { + assert(v != NULL); + assert(MATH_FLOAT_VALID(*v)); + assert(MATH_FLOAT_VALID(delta)); + assert(delta >= 0); + + const float flag = MATH_SIGN(ed - *v); + *v += flag * delta; + if ((ed-*v)*flag < 0) *v = ed; +} + +void locommon_easing_smooth_float(float* v, float ed, float delta) { + assert(v != NULL); + assert(MATH_FLOAT_VALID(*v)); + assert(MATH_FLOAT_VALID(delta)); + assert(delta >= 0); + + *v += (ed - *v) * MATH_MIN(delta, 1); +} + +void locommon_easing_smooth_position( + locommon_position_t* pos, const locommon_position_t* ed, float delta) { + assert(locommon_position_valid(pos)); + assert(locommon_position_valid(ed)); + assert(MATH_FLOAT_VALID(delta)); + + vec2_t diff; + locommon_position_sub(&diff, ed, pos); + vec2_muleq(&diff, MATH_MIN(delta, 1)); + + vec2_addeq(&pos->fract, &diff); + locommon_position_reduce(pos); +} diff --git a/core/locommon/easing.h b/core/locommon/easing.h new file mode 100644 index 0000000..ecd1073 --- /dev/null +++ b/core/locommon/easing.h @@ -0,0 +1,24 @@ +#pragma once + +#include "./position.h" + +void +locommon_easing_linear_float( + float* v, + float ed, + float delta +); + +void +locommon_easing_smooth_float( + float* v, + float ed, + float delta +); + +void +locommon_easing_smooth_position( + locommon_position_t* pos, + const locommon_position_t* ed, + float delta +); diff --git a/core/locommon/input.h b/core/locommon/input.h new file mode 100644 index 0000000..09edc5a --- /dev/null +++ b/core/locommon/input.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include "util/math/vector.h" + +typedef enum { + LOCOMMON_INPUT_BUTTON_LEFT = 1 << 0, + LOCOMMON_INPUT_BUTTON_RIGHT = 1 << 1, + LOCOMMON_INPUT_BUTTON_UP = 1 << 2, + LOCOMMON_INPUT_BUTTON_DOWN = 1 << 3, + LOCOMMON_INPUT_BUTTON_DASH = 1 << 4, + LOCOMMON_INPUT_BUTTON_JUMP = 1 << 5, + LOCOMMON_INPUT_BUTTON_ATTACK = 1 << 6, + LOCOMMON_INPUT_BUTTON_GUARD = 1 << 7, + LOCOMMON_INPUT_BUTTON_MENU = 1 << 8, +} locommon_input_button_t; + +typedef uint16_t locommon_input_buttons_t; + +typedef struct { + locommon_input_buttons_t buttons; + + vec2_t resolution; /* in pixels */ + vec2_t dpi; + vec2_t cursor; /* -1~1 */ +} locommon_input_t; diff --git a/core/locommon/msgpack.h b/core/locommon/msgpack.h new file mode 100644 index 0000000..cd380df --- /dev/null +++ b/core/locommon/msgpack.h @@ -0,0 +1,118 @@ +#pragma once + +#include +#include + +#include + +#include "util/math/vector.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/locommon/counter.h" +#include "core/locommon/null.h" + +/* THE FOLLOWING INCLUDES DESTROY DEPENDENCY STRUCTURE BETWEEN MODULES. :( */ +#include "core/loeffect/effect.h" +#include "core/loeffect/generic.h" +#include "core/loeffect/recipient.h" +#include "core/loeffect/stance.h" +#include "core/loentity/entity.h" + +#define LOCOMMON_MSGPACK_PACK_ANY_(packer, v) _Generic((v), \ + const int32_t*: locommon_msgpack_pack_int32_, \ + const uint64_t*: locommon_msgpack_pack_uint64_, \ + const float*: locommon_msgpack_pack_float_, \ + const bool*: locommon_msgpack_pack_bool_, \ + const char*: locommon_msgpack_pack_str_, \ + const vec2_t*: locommon_msgpack_pack_vec2_, \ + const vec4_t*: locommon_msgpack_pack_vec4_, \ + \ + const locommon_counter_t*: locommon_counter_pack, \ + const locommon_null_t*: locommon_null_pack, \ + const locommon_position_t*: locommon_position_pack, \ + const locommon_ticker_t*: locommon_ticker_pack, \ + \ + const loeffect_t*: loeffect_pack, \ + const loeffect_generic_immediate_param_t*: loeffect_generic_immediate_param_pack, \ + const loeffect_generic_lasting_param_t*: loeffect_generic_lasting_param_pack, \ + const loeffect_recipient_effect_param_t*: loeffect_recipient_effect_param_pack, \ + const loeffect_stance_set_t*: loeffect_stance_set_pack \ + )(v, packer) + +#define LOCOMMON_MSGPACK_PACK_ANY(packer, v) \ + LOCOMMON_MSGPACK_PACK_ANY_(packer, (const typeof(*v)*) (v)) + +#define LOCOMMON_MSGPACK_UNPACK_ANY(obj, v) _Generic((v), \ + int32_t*: locommon_msgpack_unpack_int32_, \ + uint64_t*: locommon_msgpack_unpack_uint64_, \ + float*: locommon_msgpack_unpack_float_, \ + bool*: locommon_msgpack_unpack_bool_, \ + vec2_t*: locommon_msgpack_unpack_vec2_, \ + vec4_t*: locommon_msgpack_unpack_vec4_, \ + \ + locommon_counter_t*: locommon_counter_unpack, \ + locommon_null_t*: locommon_null_unpack, \ + locommon_position_t*: locommon_position_unpack, \ + locommon_ticker_t*: locommon_ticker_unpack, \ + \ + loeffect_t*: loeffect_unpack, \ + loeffect_generic_immediate_param_t*: loeffect_generic_immediate_param_unpack, \ + loeffect_generic_lasting_param_t*: loeffect_generic_lasting_param_unpack, \ + loeffect_recipient_effect_param_t*: loeffect_recipient_effect_param_unpack, \ + loeffect_stance_set_t*: loeffect_stance_set_unpack \ + )(v, obj) + +static inline void locommon_msgpack_pack_int32_( + const int32_t* v, msgpack_packer* packer) { + msgpack_pack_int32(packer, *v); +} +static inline void locommon_msgpack_pack_uint64_( + const uint64_t* v, msgpack_packer* packer) { + msgpack_pack_uint64(packer, *v); +} +static inline void locommon_msgpack_pack_float_( + const float* v, msgpack_packer* packer) { + msgpack_pack_double(packer, *v); +} +static inline void locommon_msgpack_pack_bool_( + const bool* v, msgpack_packer* packer) { + mpkutil_pack_bool(packer, *v); +} +static inline void locommon_msgpack_pack_str_( + const char* str, msgpack_packer* packer) { + mpkutil_pack_str(packer, str); +} +static inline void locommon_msgpack_pack_vec2_( + const vec2_t* v, msgpack_packer* packer) { + mpkutil_pack_vec2(packer, v); +} +static inline void locommon_msgpack_pack_vec4_( + const vec4_t* v, msgpack_packer* packer) { + mpkutil_pack_vec4(packer, v); +} + +static inline bool locommon_msgpack_unpack_int32_( + int32_t* v, const msgpack_object* obj) { + return mpkutil_get_int32(obj, v); +} +static inline bool locommon_msgpack_unpack_uint64_( + uint64_t* v, const msgpack_object* obj) { + return mpkutil_get_uint64(obj, v); +} +static inline bool locommon_msgpack_unpack_float_( + float* v, const msgpack_object* obj) { + return mpkutil_get_float(obj, v); +} +static inline bool locommon_msgpack_unpack_bool_( + bool* v, const msgpack_object* obj) { + return mpkutil_get_bool(obj, v); +} +static inline bool locommon_msgpack_unpack_vec2_( + vec2_t* v, const msgpack_object* obj) { + return mpkutil_get_vec2(obj, v); +} +static inline bool locommon_msgpack_unpack_vec4_( + vec4_t* v, const msgpack_object* obj) { + return mpkutil_get_vec4(obj, v); +} diff --git a/core/locommon/null.c b/core/locommon/null.c new file mode 100644 index 0000000..ee4ec61 --- /dev/null +++ b/core/locommon/null.c @@ -0,0 +1,20 @@ +#include "./null.h" + +#include +#include +#include + +#include + +void locommon_null_pack(const locommon_null_t* null, msgpack_packer* packer) { + assert(null != NULL); + assert(null != NULL); + + msgpack_pack_nil(packer); +} + +bool locommon_null_unpack(locommon_null_t* null, const msgpack_object* obj) { + assert(null != NULL); + + return obj != NULL; +} diff --git a/core/locommon/null.h b/core/locommon/null.h new file mode 100644 index 0000000..8317114 --- /dev/null +++ b/core/locommon/null.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include + +/* used in some macro templates */ +typedef struct {} locommon_null_t; + +void +locommon_null_pack( + const locommon_null_t* null, + msgpack_packer* packer +); + +bool +locommon_null_unpack( + locommon_null_t* null, + const msgpack_object* obj +); diff --git a/core/locommon/physics.c b/core/locommon/physics.c new file mode 100644 index 0000000..7a22e06 --- /dev/null +++ b/core/locommon/physics.c @@ -0,0 +1,94 @@ +#include "./physics.h" + +#include +#include +#include + +#include "util/coly2d/hittest.h" +#include "util/math/algorithm.h" +#include "util/math/vector.h" + +#include "./position.h" + +bool locommon_physics_entity_valid(const locommon_physics_entity_t* e) { + return + e != NULL && + locommon_position_valid(&e->pos) && + vec2_valid(&e->velocity) && + vec2_valid(&e->size); +} + +bool locommon_physics_solve_collision_with_fixed_one( + locommon_physics_entity_t* e1, + const locommon_physics_entity_t* e2, + float dt) { + assert(locommon_physics_entity_valid(e1)); + assert(locommon_physics_entity_valid(e2)); + assert(vec2_pow_length(&e2->velocity) == 0); + assert(MATH_FLOAT_VALID(dt)); + + vec2_t size; + vec2_add(&size, &e1->size, &e2->size); + + vec2_t pos; + locommon_position_sub(&pos, &e1->pos, &e2->pos); + pos.x /= size.x; + pos.y /= size.y; + + vec2_t velocity = e1->velocity; + velocity.x /= size.x; + velocity.y /= size.y; + + vec2_t disp; + vec2_mul(&disp, &velocity, dt); + + vec2_t spos; + vec2_sub(&spos, &pos, &disp); + + static const vec2_t origin = vec2(0, 0); + static const vec2_t sz = vec2(1, 1); + if (!coly2d_hittest_lineseg_and_rect(&spos, &pos, &origin, &sz)) { + return false; + } + + if (MATH_ABS(spos.x) < 1 && MATH_ABS(spos.y) < 1) { + float* f = MATH_ABS(spos.x) > MATH_ABS(spos.y)? &spos.x: &spos.y; + *f = MATH_SIGN(*f); + } + + vec2_t vt = vec2(MATH_INF, MATH_INF); + if (velocity.x != 0) { + vt.x = -(MATH_SIGN(velocity.x)+spos.x) / velocity.x; + } + if (velocity.y != 0) { + vt.y = -(MATH_SIGN(velocity.y)+spos.y) / velocity.y; + } + + /* ---- simulation ---- */ + float t = MATH_MIN(vt.x, vt.y); + if (t < 0) t = MATH_MAX(vt.x, vt.y); + if (t < 0 || t >= dt) return false; + + vec2_t d, v = velocity, p = spos; + vec2_mul(&d, &v, t); + vec2_addeq(&p, &d); + + if (t == vt.x) v.x = 0; + if (t == vt.y) v.y = 0; + vec2_mul(&d, &v, dt-t); + vec2_addeq(&p, &d); + + /* ---- return result ---- */ + p.x *= size.x; + p.y *= size.y; + v.x *= size.x; + v.y *= size.y; + + e1->pos = e2->pos; + vec2_addeq(&e1->pos.fract, &p); + locommon_position_reduce(&e1->pos); + + e1->velocity = v; + + return true; +} diff --git a/core/locommon/physics.h b/core/locommon/physics.h new file mode 100644 index 0000000..771a8c1 --- /dev/null +++ b/core/locommon/physics.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include "util/math/vector.h" + +#include "./position.h" + +typedef struct { + /* input */ + vec2_t size; + + /* input/output */ + locommon_position_t pos; + vec2_t velocity; +} locommon_physics_entity_t; + +bool +locommon_physics_entity_valid( + const locommon_physics_entity_t* e +); + +bool /* whether they were collided */ +locommon_physics_solve_collision_with_fixed_one( + locommon_physics_entity_t* e1, + const locommon_physics_entity_t* e2, + float dt +); diff --git a/core/locommon/position.c b/core/locommon/position.c new file mode 100644 index 0000000..728348a --- /dev/null +++ b/core/locommon/position.c @@ -0,0 +1,83 @@ +#include "./position.h" + +#include +#include +#include + +#include + +#include "util/math/algorithm.h" +#include "util/math/vector.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +int32_t locommon_position_get_district_from_chunk(int32_t x) { + static const int32_t cpd = LOCOMMON_POSITION_CHUNKS_PER_DISTRICT; + return x >= 0? x/cpd: x/cpd-1; +} + +int32_t locommon_position_get_chunk_index_in_district(int32_t x) { + static const int32_t cpd = LOCOMMON_POSITION_CHUNKS_PER_DISTRICT; + return x >= 0? x%cpd: cpd-1-(-x-1)%cpd; +} + +bool locommon_position_valid(const locommon_position_t* a) { + return a != NULL && vec2_valid(&a->fract); +} + +void locommon_position_sub( + vec2_t* a, const locommon_position_t* b, const locommon_position_t* c) { + assert(a != NULL); + assert(locommon_position_valid(b)); + assert(locommon_position_valid(c)); + + vec2_sub(a, &b->fract, &c->fract); + a->x += b->chunk.x - c->chunk.x; + a->y += b->chunk.y - c->chunk.y; +} + +void locommon_position_reduce(locommon_position_t* a) { + assert(locommon_position_valid(a)); + +# define reduce_(e) do { \ + a->chunk.e += (int) a->fract.e; \ + a->fract.e -= (int) a->fract.e; \ + if (a->fract.e < 0) { \ + --a->chunk.e; \ + ++a->fract.e; \ + } \ + } while (0) + + reduce_(x); + reduce_(y); + +# undef reduce_ +} + +void locommon_position_pack( + const locommon_position_t* pos, msgpack_packer* packer) { + assert(locommon_position_valid(pos)); + assert(packer != NULL); + + msgpack_pack_array(packer, 4); + + msgpack_pack_int32(packer, pos->chunk.x); + msgpack_pack_int32(packer, pos->chunk.y); + msgpack_pack_double(packer, pos->fract.x); + msgpack_pack_double(packer, pos->fract.y); +} + +bool locommon_position_unpack( + locommon_position_t* pos, const msgpack_object* obj) { + assert(pos != NULL); + + const msgpack_object_array* root = mpkutil_get_array(obj); + if (root == NULL || root->size != 4 || + !mpkutil_get_int32(&root->ptr[0], &pos->chunk.x) || + !mpkutil_get_int32(&root->ptr[1], &pos->chunk.y) || + !mpkutil_get_float(&root->ptr[2], &pos->fract.x) || + !mpkutil_get_float(&root->ptr[3], &pos->fract.y)) { + return false; + } + return true; +} diff --git a/core/locommon/position.h b/core/locommon/position.h new file mode 100644 index 0000000..c2a01fc --- /dev/null +++ b/core/locommon/position.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include + +#include + +#include "util/math/vector.h" + +typedef struct { + struct { + int32_t x, y; + } chunk; + vec2_t fract; +} locommon_position_t; + +#define locommon_position(chunk_x, chunk_y, fract) \ + ((locommon_position_t) { {chunk_x, chunk_y, }, fract }) + +#define LOCOMMON_POSITION_CHUNKS_PER_DISTRICT 7 + +int32_t +locommon_position_get_district_from_chunk( + int32_t x +); + +int32_t +locommon_position_get_chunk_index_in_district( + int32_t x +); + +bool +locommon_position_valid( + const locommon_position_t* a +); + +void +locommon_position_sub( + vec2_t* a, + const locommon_position_t* b, + const locommon_position_t* c +); + +void +locommon_position_reduce( + locommon_position_t* a +); + +void +locommon_position_pack( + const locommon_position_t* pos, + msgpack_packer* packer +); + +bool +locommon_position_unpack( + locommon_position_t* pos, + const msgpack_object* obj +); diff --git a/core/locommon/ticker.c b/core/locommon/ticker.c new file mode 100644 index 0000000..92957c8 --- /dev/null +++ b/core/locommon/ticker.c @@ -0,0 +1,46 @@ +#include "./ticker.h" + +#include +#include +#include + +#include + +#include "util/mpkutil/get.h" + +void locommon_ticker_initialize(locommon_ticker_t* ticker, uint64_t time) { + assert(ticker != NULL); + + *ticker = (typeof(*ticker)) { .time = time, }; +} + +void locommon_ticker_deinitialize(locommon_ticker_t* ticker) { + (void) ticker; +} + +void locommon_ticker_tick(locommon_ticker_t* ticker, uint64_t time) { + assert(ticker != NULL); + assert(ticker->time <= time); + + ticker->delta = time - ticker->time; + ticker->delta_f = ticker->delta*1.f / LOCOMMON_TICKER_UNIT; + ticker->time = time; +} + +void locommon_ticker_pack( + const locommon_ticker_t* ticker, msgpack_packer* packer) { + assert(ticker != NULL); + assert(packer != NULL); + + msgpack_pack_uint64(packer, ticker->time); +} + +bool locommon_ticker_unpack( + locommon_ticker_t* ticker, const msgpack_object* obj) { + assert(ticker != NULL); + + if (!mpkutil_get_uint64(obj, &ticker->time)) { + return false; + } + return true; +} diff --git a/core/locommon/ticker.h b/core/locommon/ticker.h new file mode 100644 index 0000000..7c79e1a --- /dev/null +++ b/core/locommon/ticker.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +#include + +#define LOCOMMON_TICKER_UNIT 1000 + +typedef struct { + uint64_t time; + + int64_t delta; + float delta_f; +} locommon_ticker_t; + +void +locommon_ticker_initialize( + locommon_ticker_t* ticker, + uint64_t time +); + +void +locommon_ticker_deinitialize( + locommon_ticker_t* ticker +); + +void +locommon_ticker_tick( + locommon_ticker_t* ticker, + uint64_t time +); + +void +locommon_ticker_pack( + const locommon_ticker_t* ticker, + msgpack_packer* packer +); + +bool +locommon_ticker_unpack( + locommon_ticker_t* ticker, + const msgpack_object* obj +); diff --git a/core/loeffect/CMakeLists.txt b/core/loeffect/CMakeLists.txt new file mode 100644 index 0000000..66f415e --- /dev/null +++ b/core/loeffect/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(loeffect + effect.c + generic.c + recipient.c + stance.c +) +target_link_libraries(loeffect + msgpackc + + mpkutil + + locommon + loshader +) diff --git a/core/loeffect/effect.c b/core/loeffect/effect.c new file mode 100644 index 0000000..ea6557d --- /dev/null +++ b/core/loeffect/effect.c @@ -0,0 +1,99 @@ +#include "./effect.h" + +#include +#include +#include +#include + +#include + +#include "core/locommon/msgpack.h" + +#include "./generic.h" + +#define LOEFFECT_ID_EACH_(PROC) do { \ + PROC(IMMEDIATE_DAMAGE, "imm-damage", imm); \ + PROC(CURSE, "curse", lasting); \ + PROC(CURSE_TRIGGER, "curse-trigger", null); \ + PROC(AMNESIA, "amnesia", lasting); \ +} while (0) + +const char* loeffect_id_stringify(loeffect_id_t id) { +# define each_(NAME, s, d) do { \ + if (LOEFFECT_ID_##NAME == id) return s; \ + } while(0) + + LOEFFECT_ID_EACH_(each_); + + assert(false); + return NULL; + +# undef each_ +} + +bool loeffect_id_unstringify(loeffect_id_t* id, const char* str, size_t len) { + assert(id != NULL); + assert(str != NULL || len == 0); + +# define each_(NAME, s, d) do { \ + if (strncmp(str, s, len) == 0 && s[len] == 0) { \ + *id = LOEFFECT_ID_##NAME; \ + return true; \ + } \ + } while (0) + + LOEFFECT_ID_EACH_(each_); + return false; + +# undef each_ +} + +void loeffect_pack(const loeffect_t* effect, msgpack_packer* packer) { + assert(effect != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, 2); + + mpkutil_pack_str(packer, "id"); + mpkutil_pack_str(packer, loeffect_id_stringify(effect->id)); + + mpkutil_pack_str(packer, "data"); + +# define each_(NAME, s, d) do { \ + if (effect->id == LOEFFECT_ID_##NAME) { \ + LOCOMMON_MSGPACK_PACK_ANY(packer, &effect->data.d); \ + } \ + } while (0) + + LOEFFECT_ID_EACH_(each_); + +# undef each_ +} + +bool loeffect_unpack(loeffect_t* effect, const msgpack_object* obj) { + assert(effect != NULL); + + if (obj == NULL) return false; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + + const char* idstr; + size_t idstr_len; + if (!mpkutil_get_str(item_("id"), &idstr, &idstr_len) || + !loeffect_id_unstringify(&effect->id, idstr, idstr_len)) { + return false; + } + +# define each_(NAME, s, d) do { \ + if (effect->id == LOEFFECT_ID_##NAME) { \ + return LOCOMMON_MSGPACK_UNPACK_ANY(item_("data"), &effect->data.d); \ + } \ + } while (0) + + LOEFFECT_ID_EACH_(each_); + +# undef each_ + return false; +} diff --git a/core/loeffect/effect.h b/core/loeffect/effect.h new file mode 100644 index 0000000..e4c2256 --- /dev/null +++ b/core/loeffect/effect.h @@ -0,0 +1,87 @@ +#pragma once + +#include +#include +#include + +#include + +#include "core/locommon/null.h" + +#include "./generic.h" + +typedef enum { + LOEFFECT_ID_IMMEDIATE_DAMAGE, + + LOEFFECT_ID_CURSE, + /* The curse effect actually does nothing and is just for HUD. + * To kill player immediately, use curse trigger effect.*/ + LOEFFECT_ID_CURSE_TRIGGER, + + LOEFFECT_ID_AMNESIA, + LOEFFECT_ID_LOST, +} loeffect_id_t; + +typedef struct { + loeffect_id_t id; + union { + locommon_null_t null; + loeffect_generic_immediate_param_t imm; + loeffect_generic_lasting_param_t lasting; + } data; +} loeffect_t; + +#define loeffect_immediate_damage(a) \ + ((loeffect_t) { \ + .id = LOEFFECT_ID_IMMEDIATE_DAMAGE, \ + .data = { .imm = { \ + .amount = a, \ + }, }, \ + } ) + +#define loeffect_curse(b, dur) \ + ((loeffect_t) { \ + .id = LOEFFECT_ID_CURSE, \ + .data = { .lasting = { \ + .begin = b, \ + .duration = dur, \ + }, }, \ + } ) + +#define loeffect_curse_trigger() \ + ((loeffect_t) { \ + .id = LOEFFECT_ID_CURSE_TRIGGER, \ + } ) + +#define loeffect_amnesia(b, dur) \ + ((loeffect_t) { \ + .id = LOEFFECT_ID_AMNESIA, \ + .data = { .lasting = { \ + .begin = b, \ + .duration = dur, \ + }, }, \ + } ) + +const char* +loeffect_id_stringify( + loeffect_id_t id +); + +bool +loeffect_id_unstringify( + loeffect_id_t* id, + const char* str, + size_t len +); + +void +loeffect_pack( + const loeffect_t* effect, + msgpack_packer* packer +); + +bool +loeffect_unpack( + loeffect_t* effect, + const msgpack_object* obj +); diff --git a/core/loeffect/generic.c b/core/loeffect/generic.c new file mode 100644 index 0000000..4aaeb99 --- /dev/null +++ b/core/loeffect/generic.c @@ -0,0 +1,81 @@ +#include "./generic.h" + +#include +#include +#include +#include + +#include + +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +void loeffect_generic_immediate_param_pack( + const loeffect_generic_immediate_param_t* param, + msgpack_packer* packer) { + assert(param != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, 1); + + mpkutil_pack_str(packer, "amount"); + msgpack_pack_double(packer, param->amount); +} + +bool loeffect_generic_immediate_param_unpack( + loeffect_generic_immediate_param_t* param, + const msgpack_object* obj) { + assert(param != NULL); + + if (obj == NULL) return false; + + const msgpack_object_map* root = mpkutil_get_map(obj); + + const msgpack_object* amount = mpkutil_get_map_item_by_str(root, "amount"); + if (!mpkutil_get_float(amount, ¶m->amount)) { + return false; + } + return true; +} + +void loeffect_generic_lasting_param_pack( + const loeffect_generic_lasting_param_t* param, msgpack_packer* packer) { + assert(param != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, 3); + + mpkutil_pack_str(packer, "begin"); + msgpack_pack_uint64(packer, param->begin); + + mpkutil_pack_str(packer, "duration"); + msgpack_pack_uint64(packer, param->duration); + + mpkutil_pack_str(packer, "amount"); + msgpack_pack_double(packer, param->amount); +} + +bool loeffect_generic_lasting_param_unpack( + loeffect_generic_lasting_param_t* param, const msgpack_object* obj) { + assert(param != NULL); + assert(obj != NULL); + + if (obj == NULL) return false; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + + if (!mpkutil_get_uint64(item_("begin"), ¶m->begin)) { + return false; + } + if (!mpkutil_get_uint64(item_("duration"), ¶m->duration)) { + return false; + } + if (!mpkutil_get_float(item_("amount"), ¶m->amount)) { + return false; + } + +# undef item_ + return true; +} diff --git a/core/loeffect/generic.h b/core/loeffect/generic.h new file mode 100644 index 0000000..a66f0da --- /dev/null +++ b/core/loeffect/generic.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +#include + +typedef struct { + float amount; +} loeffect_generic_immediate_param_t; + +typedef struct { + uint64_t begin; + uint64_t duration; + float amount; +} loeffect_generic_lasting_param_t; + +void +loeffect_generic_immediate_param_pack( + const loeffect_generic_immediate_param_t* param, + msgpack_packer* packer +); + +bool +loeffect_generic_immediate_param_unpack( + loeffect_generic_immediate_param_t* param, + const msgpack_object* obj +); + +void +loeffect_generic_lasting_param_pack( + const loeffect_generic_lasting_param_t* param, + msgpack_packer* packer +); + +bool +loeffect_generic_lasting_param_unpack( + loeffect_generic_lasting_param_t* param, + const msgpack_object* obj +); diff --git a/core/loeffect/recipient.c b/core/loeffect/recipient.c new file mode 100644 index 0000000..5b7bccd --- /dev/null +++ b/core/loeffect/recipient.c @@ -0,0 +1,132 @@ +#include "./recipient.h" + +#include +#include +#include +#include + +#include + +#include "util/math/algorithm.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/locommon/msgpack.h" +#include "core/locommon/ticker.h" + +#include "./generic.h" + +#define LOEFFECT_RECIPIENT_EFFECT_PARAM_EACH_(PROC) do { \ + PROC(curse); \ +} while (0) +#define LOEFFECT_RECIPIENT_EFFECT_PARAM_COUNT 1 + +void loeffect_recipient_initialize( + loeffect_recipient_t* recipient, const locommon_ticker_t* ticker) { + assert(recipient != NULL); + assert(ticker != NULL); + + *recipient = (typeof(*recipient)) { + .ticker = ticker, + }; + loeffect_recipient_reset(recipient); +} + +void loeffect_recipient_deinitialize(loeffect_recipient_t* recipient) { + assert(recipient != NULL); + +} + +void loeffect_recipient_reset(loeffect_recipient_t* recipient) { + assert(recipient != NULL); + + recipient->madness = 1; + recipient->faith = 1; + + recipient->effects = (typeof(recipient->effects)) {0}; +} + +void loeffect_recipient_apply_effect( + loeffect_recipient_t* recipient, const loeffect_t* effect) { + assert(recipient != NULL); + assert(effect != NULL); + + if (recipient->madness <= 0) return; + + switch (effect->id) { + case LOEFFECT_ID_IMMEDIATE_DAMAGE: + recipient->madness -= + effect->data.imm.amount * (1-recipient->status.defence); + recipient->last_damage = LOEFFECT_ID_IMMEDIATE_DAMAGE; + break; + case LOEFFECT_ID_CURSE: + recipient->effects.curse = effect->data.lasting; + break; + case LOEFFECT_ID_CURSE_TRIGGER: + recipient->madness = 0; + recipient->last_damage = LOEFFECT_ID_CURSE; + break; + case LOEFFECT_ID_AMNESIA: + recipient->effects.amnesia = effect->data.lasting; + break; + default: + ; + } +} + +void loeffect_recipient_update( + loeffect_recipient_t* recipient, const loeffect_recipient_status_t* base) { + assert(recipient != NULL); + assert(base != NULL); + + recipient->status = *base; + + if (recipient->madness > 0 && recipient->faith <= 0) { + recipient->madness -= recipient->ticker->delta_f / 30; + recipient->last_damage = LOEFFECT_ID_LOST; + } + + recipient->madness = MATH_CLAMP(recipient->madness, 0, 1); + recipient->faith = MATH_CLAMP(recipient->faith, 0, 1); +} + +void loeffect_recipient_effect_param_pack( + const loeffect_recipient_effect_param_t* param, + msgpack_packer* packer) { + assert(param != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, LOEFFECT_RECIPIENT_EFFECT_PARAM_COUNT); + +# define each_(name) do { \ + mpkutil_pack_str(packer, #name); \ + LOCOMMON_MSGPACK_PACK_ANY(packer, ¶m->name); \ + } while (0) + + LOEFFECT_RECIPIENT_EFFECT_PARAM_EACH_(each_); + +# undef each_ +} + +bool loeffect_recipient_effect_param_unpack( + loeffect_recipient_effect_param_t* param, const msgpack_object* obj) { + assert(param != NULL); + + if (obj == NULL) return false; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + +# define each_(name) do { \ + if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(#name), ¶m->name)) { \ + param->name = (typeof(param->name)) {0}; \ + } \ + } while (0) + + LOEFFECT_RECIPIENT_EFFECT_PARAM_EACH_(each_); + +# undef each_ +# undef item_ + return true; +} diff --git a/core/loeffect/recipient.h b/core/loeffect/recipient.h new file mode 100644 index 0000000..57f0397 --- /dev/null +++ b/core/loeffect/recipient.h @@ -0,0 +1,76 @@ +#pragma once + +#include + +#include + +#include "core/locommon/ticker.h" + +#include "./effect.h" +#include "./generic.h" + +typedef struct { + float attack; + float defence; + + float speed; /* [chunks/sec] */ + float jump; /* [chunks/sec^2] */ +} loeffect_recipient_status_t; + +typedef struct { + loeffect_generic_lasting_param_t curse; + loeffect_generic_lasting_param_t amnesia; +} loeffect_recipient_effect_param_t; + +typedef struct { + const locommon_ticker_t* ticker; + + float madness; + float faith; + loeffect_id_t last_damage; + + loeffect_recipient_effect_param_t effects; + + loeffect_recipient_status_t status; + +} loeffect_recipient_t; + +void +loeffect_recipient_initialize( + loeffect_recipient_t* recipient, + const locommon_ticker_t* ticker +); + +void +loeffect_recipient_deinitialize( + loeffect_recipient_t* recipient +); + +void +loeffect_recipient_reset( + loeffect_recipient_t* recipient +); + +void +loeffect_recipient_apply_effect( + loeffect_recipient_t* recipient, + const loeffect_t* effect +); + +void +loeffect_recipient_update( + loeffect_recipient_t* recipient, + const loeffect_recipient_status_t* base +); + +void +loeffect_recipient_effect_param_pack( + const loeffect_recipient_effect_param_t* recipient, + msgpack_packer* packer +); + +bool +loeffect_recipient_effect_param_unpack( + loeffect_recipient_effect_param_t* recipient, + const msgpack_object* obj /* NULLABLE */ +); diff --git a/core/loeffect/stance.c b/core/loeffect/stance.c new file mode 100644 index 0000000..3d7a340 --- /dev/null +++ b/core/loeffect/stance.c @@ -0,0 +1,143 @@ +#include "./stance.h" + +#include +#include +#include +#include +#include + +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/loshader/menu_stance.h" + +#include "./recipient.h" + +const char* loeffect_stance_stringify(loeffect_stance_id_t id) { +# define each_(NAME, name) \ + if (id == LOEFFECT_STANCE_ID_##NAME) return #name; + + LOEFFECT_STANCE_EACH(each_); + + assert(false); + return NULL; + +# undef each_ +} + +bool loeffect_stance_unstringify( + loeffect_stance_id_t* id, const char* str, size_t len) { + assert(id != NULL); + assert(str != NULL || len == 0); + +# define each_(NAME, name) do {\ + if (strncmp(str, #name, len) == 0 && #name[len] == 0) { \ + *id = LOEFFECT_STANCE_ID_##NAME; \ + return true; \ + } \ + } while (0) + + LOEFFECT_STANCE_EACH(each_); + return false; + +# undef each_ +} + +loshader_menu_stance_id_t loeffect_stance_get_id_for_menu_shader( + loeffect_stance_id_t id) { +# define each_(NAME, name) do {\ + if (id == LOEFFECT_STANCE_ID_##NAME) { \ + return LOSHADER_MENU_STANCE_ID_##NAME; \ + } \ + } while (0) + + LOEFFECT_STANCE_EACH(each_); + + assert(false); + return LOSHADER_MENU_STANCE_ID_EMPTY; + +# undef each_ +} + +void loeffect_stance_set_initialize(loeffect_stance_set_t* set) { + assert(set != NULL); + + *set = 1 << LOEFFECT_STANCE_ID_MISSIONARY; +} + +void loeffect_stance_set_deinitialize(loeffect_stance_set_t* set) { + assert(set != NULL); + +} + +void loeffect_stance_set_add( + loeffect_stance_set_t* set, loeffect_stance_id_t id) { + assert(set != NULL); + + *set |= 1 << id; +} + +void loeffect_stance_set_remove( + loeffect_stance_set_t* set, loeffect_stance_id_t id) { + assert(set != NULL); + + *set &= ~(1 << id); +} + +bool loeffect_stance_set_has( + const loeffect_stance_set_t* set, loeffect_stance_id_t id) { + assert(set != NULL); + + return *set & (1 << id); +} + +void loeffect_stance_set_affect_base_status( + const loeffect_stance_set_t* set, + loeffect_recipient_status_t* status) { + assert(set != NULL); + assert(status != NULL); + +} + +void loeffect_stance_set_pack( + const loeffect_stance_set_t* set, msgpack_packer* packer) { + assert(set != NULL); + assert(packer != NULL); + + loeffect_stance_id_t mask = 1; + size_t len = 0; + while (mask <= *set) { + len += !!(*set & mask); + mask <<= 1; + } + msgpack_pack_array(packer, len); + + mask = 1; + size_t i = 0; + while (*set >= mask) { + if (*set & mask) { + mpkutil_pack_str(packer, loeffect_stance_stringify(i)); + } + ++i; + mask <<= 1; + } +} + +bool loeffect_stance_set_unpack( + loeffect_stance_set_t* set, const msgpack_object* obj) { + assert(set != NULL); + + const msgpack_object_array* array = mpkutil_get_array(obj); + if (array == NULL) return false; + + for (size_t i = 0; i < array->size; ++i) { + size_t len; + const char* name; + if (!mpkutil_get_str(&array->ptr[i], &name, &len)) continue; + + loeffect_stance_id_t stance; + if (!loeffect_stance_unstringify(&stance, name, len)) continue; + *set |= 1 << stance; + } + return true; +} diff --git a/core/loeffect/stance.h b/core/loeffect/stance.h new file mode 100644 index 0000000..d9bd933 --- /dev/null +++ b/core/loeffect/stance.h @@ -0,0 +1,84 @@ +#pragma once + +#include +#include +#include + +#include + +#include "core/loshader/menu_stance.h" + +typedef enum { + LOEFFECT_STANCE_ID_MISSIONARY, + LOEFFECT_STANCE_ID_REVOLUTIONER, + LOEFFECT_STANCE_ID_UNFINISHER, + LOEFFECT_STANCE_ID_PHILOSOPHER, + LOEFFECT_STANCE_ID_LENGTH_, +} loeffect_stance_id_t; +_Static_assert(LOEFFECT_STANCE_ID_LENGTH_ < 16); + +typedef uint16_t loeffect_stance_set_t; + +#define LOEFFECT_STANCE_EACH(PROC) do { \ + PROC(MISSIONARY, missionary); \ + PROC(REVOLUTIONER, revolutioner); \ + PROC(UNFINISHER, unfinisher); \ + PROC(PHILOSOPHER, philosopher); \ +} while (0) + +const char* +loeffect_stance_stringify( + loeffect_stance_id_t id +); + +bool +loeffect_stance_unstringify( + loeffect_stance_id_t* id, + const char* str, + size_t len +); + +loshader_menu_stance_id_t +loeffect_stance_get_id_for_menu_shader( + loeffect_stance_id_t id +); + +void +loeffect_stance_set_initialize( + loeffect_stance_set_t* set +); + +void +loeffect_stance_set_deinitialize( + loeffect_stance_set_t* set +); + +void +loeffect_stance_set_add( + loeffect_stance_set_t* set, + loeffect_stance_id_t id +); + +void +loeffect_stance_set_remove( + loeffect_stance_set_t* set, + loeffect_stance_id_t id +); + +bool +loeffect_stance_set_has( + const loeffect_stance_set_t* set, + loeffect_stance_id_t id +); + +void +loeffect_stance_set_pack( + const loeffect_stance_set_t* set, + msgpack_packer* packer +); + +bool +loeffect_stance_set_unpack( + loeffect_stance_set_t* set, + const msgpack_object* obj +); diff --git a/core/loentity/CMakeLists.txt b/core/loentity/CMakeLists.txt new file mode 100644 index 0000000..1e7272a --- /dev/null +++ b/core/loentity/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(loentity + bullet.c + character.c + entity.c + store.c +) +target_link_libraries(loentity + msgpackc + + coly2d + container + math + memory + + locommon +) diff --git a/core/loentity/bullet.c b/core/loentity/bullet.c new file mode 100644 index 0000000..759ebbc --- /dev/null +++ b/core/loentity/bullet.c @@ -0,0 +1,43 @@ +#include "./bullet.h" + +#include +#include +#include + +#include "util/coly2d/shape.h" +#include "util/math/algorithm.h" +#include "util/math/vector.h" + +#include "core/locommon/position.h" + +#include "./decl.private.h" + +bool loentity_bullet_affect( + loentity_bullet_t* bullet, loentity_character_t* chara) { + assert(bullet != NULL); + assert(chara != NULL); + + assert(bullet->vtable.affect != NULL); + return bullet->vtable.affect(bullet, chara); +} + +bool loentity_bullet_hittest( + const loentity_bullet_t* bullet, + const locommon_position_t* point, + const vec2_t* velocity, + float dt) { + assert(bullet != NULL); + assert(locommon_position_valid(point)); + assert(vec2_valid(velocity)); + assert(MATH_FLOAT_VALID(dt)); + + vec2_t st; + locommon_position_sub(&st, point, &bullet->super.pos); + + vec2_t ed; + vec2_sub(&ed, velocity, &bullet->velocity); + vec2_muleq(&ed, dt); + vec2_addeq(&ed, &st); + + return coly2d_shape_hittest_lineseg(&bullet->shape, &st, &ed); +} diff --git a/core/loentity/bullet.h b/core/loentity/bullet.h new file mode 100644 index 0000000..b353597 --- /dev/null +++ b/core/loentity/bullet.h @@ -0,0 +1,45 @@ +#pragma once + +#include + +#include "util/coly2d/shape.h" +#include "util/math/vector.h" + +#include "core/locommon/position.h" + +#include "./entity.h" + +#include "./decl.private.h" + +typedef struct { + bool + (*affect)( + loentity_bullet_t* bullet, + loentity_character_t* chara + ); +} loentity_bullet_vtable_t; + +struct loentity_bullet_t { + loentity_t super; + + loentity_bullet_vtable_t vtable; + + loentity_id_t owner; + vec2_t velocity; + + coly2d_shape_t shape; +}; + +bool +loentity_bullet_affect( + loentity_bullet_t* bullet, + loentity_character_t* chara +); + +bool +loentity_bullet_hittest( + const loentity_bullet_t* bullet, + const locommon_position_t* point, + const vec2_t* velocity, + float dt +); diff --git a/core/loentity/character.c b/core/loentity/character.c new file mode 100644 index 0000000..c72d873 --- /dev/null +++ b/core/loentity/character.c @@ -0,0 +1,28 @@ +#include "./character.h" + +#include +#include + +#include "core/loeffect/effect.h" + +#include "./entity.h" + +#include "./decl.private.h" + +void loentity_character_apply_effect( + loentity_character_t* chara, const loeffect_t* effect) { + assert(chara != NULL); + assert(effect != NULL); + + assert(chara->vtable.apply_effect != NULL); + chara->vtable.apply_effect(chara, effect); +} + +void loentity_character_knockback( + loentity_character_t* chara, const vec2_t* v) { + assert(chara != NULL); + assert(v != NULL); + + assert(chara->vtable.knockback != NULL); + chara->vtable.knockback(chara, v); +} diff --git a/core/loentity/character.h b/core/loentity/character.h new file mode 100644 index 0000000..650e063 --- /dev/null +++ b/core/loentity/character.h @@ -0,0 +1,40 @@ +#pragma once + +#include "util/math/vector.h" + +#include "core/loeffect/effect.h" + +#include "./entity.h" + +#include "./decl.private.h" + +typedef struct { + void + (*apply_effect)( + loentity_character_t* chara, + const loeffect_t* effect + ); + void + (*knockback)( + loentity_character_t* chara, + const vec2_t* v + ); +} loentity_character_vtable_t; + +struct loentity_character_t { + loentity_t super; + + loentity_character_vtable_t vtable; +}; + +void +loentity_character_apply_effect( + loentity_character_t* chara, + const loeffect_t* effect +); + +void +loentity_character_knockback( + loentity_character_t* chara, + const vec2_t* v +); diff --git a/core/loentity/decl.private.h b/core/loentity/decl.private.h new file mode 100644 index 0000000..8e09592 --- /dev/null +++ b/core/loentity/decl.private.h @@ -0,0 +1,13 @@ +#pragma once + +struct loentity_t; +typedef struct loentity_t loentity_t; + +struct loentity_ground_t; +typedef struct loentity_ground_t loentity_ground_t; + +struct loentity_bullet_t; +typedef struct loentity_bullet_t loentity_bullet_t; + +struct loentity_character_t; +typedef struct loentity_character_t loentity_character_t; diff --git a/core/loentity/entity.c b/core/loentity/entity.c new file mode 100644 index 0000000..026797f --- /dev/null +++ b/core/loentity/entity.c @@ -0,0 +1,44 @@ +#include "./entity.h" + +#include +#include +#include + +#include "./decl.private.h" + +void loentity_delete(loentity_t* entity) { + assert(entity != NULL); + + assert(entity->vtable.delete != NULL); + entity->vtable.delete(entity); +} + +void loentity_die(loentity_t* entity) { + assert(entity != NULL); + + assert(entity->vtable.die != NULL); + entity->vtable.die(entity); +} + +bool loentity_update(loentity_t* entity) { + assert(entity != NULL); + + assert(entity->vtable.update != NULL); + return entity->vtable.update(entity); +} + +void loentity_draw(loentity_t* entity, const locommon_position_t* basepos) { + assert(entity != NULL); + assert(locommon_position_valid(basepos)); + + assert(entity->vtable.draw != NULL); + entity->vtable.draw(entity, basepos); +} + +void loentity_pack(const loentity_t* entity, msgpack_packer* packer) { + assert(entity != NULL); + assert(packer != NULL); + + assert(entity->vtable.pack != NULL); + entity->vtable.pack(entity, packer); +} diff --git a/core/loentity/entity.h b/core/loentity/entity.h new file mode 100644 index 0000000..c6d280e --- /dev/null +++ b/core/loentity/entity.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include + +#include + +#include "util/math/vector.h" + +#include "core/locommon/position.h" + +#include "./decl.private.h" + +typedef struct { + void + (*delete)( + loentity_t* entity + ); + + void + (*die)( + loentity_t* entity + ); + + bool + (*update)( + loentity_t* entity + ); + void + (*draw)( + loentity_t* entity, + const locommon_position_t* basepos + ); + + void + (*pack)( + const loentity_t* entity, + msgpack_packer* packer + ); +} loentity_vtable_t; + +typedef enum { + LOENTITY_SUBCLASS_NONE, + LOENTITY_SUBCLASS_GROUND, + LOENTITY_SUBCLASS_BULLET, + LOENTITY_SUBCLASS_CHARACTER, +} loentity_subclass_t; + +typedef uint64_t loentity_id_t; + +struct loentity_t { + loentity_vtable_t vtable; + loentity_subclass_t subclass; + + loentity_id_t id; + + locommon_position_t pos; + + bool dont_save; +}; + +void +loentity_delete( + loentity_t* entity /* OWNERSHIP */ +); + +void +loentity_die( + loentity_t* entity +); + +bool +loentity_update( + loentity_t* entity +); + +void +loentity_draw( + loentity_t* entity, + const locommon_position_t* basepos +); + +void +loentity_pack( + const loentity_t* entity, + msgpack_packer* packer +); diff --git a/core/loentity/ground.h b/core/loentity/ground.h new file mode 100644 index 0000000..12e79f1 --- /dev/null +++ b/core/loentity/ground.h @@ -0,0 +1,18 @@ +#pragma once + +#include "util/math/vector.h" + +#include "./entity.h" + +#include "./decl.private.h" + +typedef struct { +} loentity_ground_vtable_t; + +struct loentity_ground_t { + loentity_t super; + + loentity_ground_vtable_t vtable; + + vec2_t size; +}; diff --git a/core/loentity/store.c b/core/loentity/store.c new file mode 100644 index 0000000..2716816 --- /dev/null +++ b/core/loentity/store.c @@ -0,0 +1,211 @@ +#include "./store.h" + +#include +#include +#include + +#include "util/container/array.h" +#include "util/math/algorithm.h" +#include "util/math/vector.h" +#include "util/memory/memory.h" + +#include "core/locommon/physics.h" +#include "core/locommon/position.h" + +#include "./bullet.h" +#include "./character.h" +#include "./entity.h" +#include "./ground.h" + +struct loentity_store_t { + CONTAINER_ARRAY loentity_t** items; +}; + +static void loentity_store_iterator_assign_by_index_( + loentity_store_t* store, loentity_store_iterator_t* itr) { + assert(store != NULL); + assert(itr != NULL); + assert(itr->index < container_array_get_length(store->items)); + + itr->entity = NULL; + itr->ground = NULL; + itr->bullet = NULL; + itr->character = NULL; + + itr->entity = store->items[itr->index]; + switch (itr->entity->subclass) { + case LOENTITY_SUBCLASS_GROUND: + itr->ground = (loentity_ground_t*) itr->entity; + break; + case LOENTITY_SUBCLASS_BULLET: + itr->bullet = (loentity_bullet_t*) itr->entity; + break; + case LOENTITY_SUBCLASS_CHARACTER: + itr->character = (loentity_character_t*) itr->entity; + break; + default: + assert(false); + } +} + +loentity_store_t* loentity_store_new(size_t reserve) { + loentity_store_t* store = memory_new(sizeof(*store)); + *store = (typeof(*store)) {0}; + + container_array_reserve(store->items, reserve); + return store; +} + +void loentity_store_delete(loentity_store_t* store) { + if (store == NULL) return; + + container_array_delete(store->items); + memory_delete(store); +} + +void loentity_store_add( + loentity_store_t* store, loentity_t* entity) { + assert(store != NULL); + assert(entity != NULL); + + const size_t len = container_array_get_length(store->items); + size_t index = 0; + for (; index < len; ++index) { + if (store->items[index] == NULL) break; + } + if (index == len) container_array_insert(store->items, index); + store->items[index] = entity; +} + +loentity_t* loentity_store_remove( + loentity_store_t* store, const loentity_store_iterator_t* itr) { + assert(store != NULL); + assert(itr != NULL); + + assert(itr->index < container_array_get_length(store->items)); + assert(itr->entity != NULL); + assert(itr->entity == store->items[itr->index]); + + store->items[itr->index] = NULL; + return itr->entity; +} + +void loentity_store_clear(loentity_store_t* store) { + assert(store != NULL); + + const size_t len = container_array_get_length(store->items); + for (size_t i = 0; i < len; ++i) { + loentity_t** e = &store->items[i]; + if (*e == NULL) continue; + loentity_delete(*e); + *e = NULL; + } +} + +bool loentity_store_iterate_next( + loentity_store_t* store, loentity_store_iterator_t* itr) { + assert(store != NULL); + assert(itr != NULL); + + ++itr->index; + if (itr->entity == NULL) { + itr->index = 0; + } + itr->entity = NULL; + itr->ground = NULL; + itr->bullet = NULL; + itr->character = NULL; + + const size_t len = container_array_get_length(store->items); + for (; itr->index < len; ++itr->index) { + if (store->items[itr->index] != NULL) break; + } + if (itr->index >= len) return false; + + loentity_store_iterator_assign_by_index_(store, itr); + return true; +} + +bool loentity_store_find_item_by_id( + loentity_store_t* store, loentity_store_iterator_t* itr, loentity_id_t id) { + assert(store != NULL); + assert(itr != NULL); + + const size_t len = container_array_get_length(store->items); + for (itr->index = 0; itr->index < len; ++itr->index) { + loentity_t* e = store->items[itr->index]; + if (e != NULL && e->id == id) { + loentity_store_iterator_assign_by_index_(store, itr); + return true; + } + } + return false; +} + +bool loentity_store_solve_collision_between_ground( + loentity_store_t* store, + locommon_physics_entity_t* e, + float dt) { + assert(store != NULL); + assert(e != NULL); + + bool solved = false; + + loentity_store_iterator_t itr = {0}; + while (loentity_store_iterate_next(store, &itr)) { + if (itr.ground == NULL) continue; + + const locommon_physics_entity_t g = { + .pos = itr.entity->pos, + .velocity = vec2(0, 0), + .size = itr.ground->size, + }; + if (locommon_physics_solve_collision_with_fixed_one(e, &g, dt)) { + solved = true; + } + } + return solved; +} + +bool loentity_store_affect_bullets_shot_by_others( + loentity_store_t* store, + loentity_character_t* chara, + const vec2_t* velocity, + float dt) { + assert(store != NULL); + assert(chara != NULL); + assert(vec2_valid(velocity)); + assert(MATH_FLOAT_VALID(dt)); + + loentity_store_iterator_t itr = {0}; + while (loentity_store_iterate_next(store, &itr)) { + if (itr.bullet == NULL || itr.bullet->owner == chara->super.id) continue; + + if (loentity_bullet_hittest(itr.bullet, &chara->super.pos, velocity, dt)) { + if (loentity_bullet_affect(itr.bullet, chara)) return true; + } + } + return false; +} + +bool loentity_store_affect_bullets_shot_by_one( + loentity_store_t* store, + loentity_character_t* chara, + loentity_id_t shooter, + const vec2_t* velocity, + float dt) { + assert(store != NULL); + assert(chara != NULL); + assert(vec2_valid(velocity)); + assert(MATH_FLOAT_VALID(dt)); + + loentity_store_iterator_t itr = {0}; + while (loentity_store_iterate_next(store, &itr)) { + if (itr.bullet == NULL || itr.bullet->owner != shooter) continue; + + if (loentity_bullet_hittest(itr.bullet, &chara->super.pos, velocity, dt)) { + if (loentity_bullet_affect(itr.bullet, chara)) return true; + } + } + return false; +} diff --git a/core/loentity/store.h b/core/loentity/store.h new file mode 100644 index 0000000..f92edd4 --- /dev/null +++ b/core/loentity/store.h @@ -0,0 +1,90 @@ +#pragma once + +#include +#include + +#include "util/math/vector.h" + +#include "core/locommon/physics.h" +#include "core/locommon/position.h" + +#include "./bullet.h" +#include "./character.h" +#include "./entity.h" +#include "./ground.h" + +struct loentity_store_t; +typedef struct loentity_store_t loentity_store_t; + +typedef struct { + loentity_t* entity; + loentity_ground_t* ground; + loentity_bullet_t* bullet; + loentity_character_t* character; + + size_t index; +} loentity_store_iterator_t; + +loentity_store_t* /* OWNERSHIP */ +loentity_store_new( + size_t reserve +); + +void +loentity_store_delete( + loentity_store_t* store /* OWNERSHIP */ +); + +void +loentity_store_add( + loentity_store_t* store, + loentity_t* entity /* OWNERSHIP */ +); + +loentity_t* /* OWNERSHIP */ +loentity_store_remove( + loentity_store_t* store, + const loentity_store_iterator_t* itr +); + +void +loentity_store_clear( + loentity_store_t* store +); + +bool +loentity_store_iterate_next( + loentity_store_t* store, + loentity_store_iterator_t* itr +); + +bool +loentity_store_find_item_by_id( + loentity_store_t* store, + loentity_store_iterator_t* itr, + loentity_id_t id +); + +bool /* whether the entitiy was collided */ +loentity_store_solve_collision_between_ground( + loentity_store_t* store, + locommon_physics_entity_t* e, + float dt +); + +bool +loentity_store_affect_bullets_shot_by_others( + loentity_store_t* store, + loentity_character_t* chara, + const vec2_t* velocity, + float dt +); + +bool +loentity_store_affect_bullets_shot_by_one( + loentity_store_t* store, + loentity_character_t* chara, + loentity_id_t shooter, + const vec2_t* velocity, + float dt +); diff --git a/core/loground/CMakeLists.txt b/core/loground/CMakeLists.txt new file mode 100644 index 0000000..e0f0c4f --- /dev/null +++ b/core/loground/CMakeLists.txt @@ -0,0 +1,17 @@ +add_library(loground + base.c + island.c + misc.c + pool.c +) +target_link_libraries(loground + msgpackc + + math + memory + mpkutil + + locommon + loentity + loshader +) diff --git a/core/loground/base.c b/core/loground/base.c new file mode 100644 index 0000000..b2a84ea --- /dev/null +++ b/core/loground/base.c @@ -0,0 +1,228 @@ +#include "./base.h" + +#include +#include +#include +#include + +#include + +#include "util/math/vector.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/locommon/msgpack.h" +#include "core/locommon/position.h" +#include "core/loentity/entity.h" +#include "core/loentity/ground.h" +#include "core/loshader/ground.h" + +#include "./island.h" +#include "./misc.h" + +#define LOGROUND_BASE_PARAM_TO_PACK_EACH_(PROC, PROC_str, PROC_type) do { \ + PROC_str ("subclass", "ground"); \ + PROC_type ("type", type); \ + PROC ("id", super.super.id); \ + PROC ("pos", super.super.pos); \ + PROC ("size", super.size); \ +} while (0) +#define LOGROUND_BASE_PARAM_TO_PACK_COUNT 5 + +static void loground_base_delete_(loentity_t* entity) { + assert(entity != NULL); + + loground_base_t* base = (typeof(base)) entity; + if (!base->used) return; + + base->used = false; + +# define each_(NAME, name) do { \ + if (base->type == LOGROUND_TYPE_##NAME) { \ + loground_##name##_tear_down(base); \ + return; \ + } \ + } while (0) + + LOGROUND_TYPE_EACH_(each_); + assert(false); + +# undef each_ +} + +static void loground_base_die_(loentity_t* entity) { + assert(entity != NULL); + +} + +static bool loground_base_update_(loentity_t* entity) { + assert(entity != NULL); + + loground_base_t* base = (typeof(base)) entity; + base->cache = (typeof(base->cache)) {0}; + +# define each_(NAME, name) do { \ + if (base->type == LOGROUND_TYPE_##NAME) { \ + return loground_##name##_update(base); \ + } \ + } while (0) + + LOGROUND_TYPE_EACH_(each_); + return false; + +# undef each_ +} + +static void loground_base_draw_( + loentity_t* entity, const locommon_position_t* basepos) { + assert(entity != NULL); + assert(locommon_position_valid(basepos)); + + loground_base_t* base = (typeof(base)) entity; + + vec2_t p; + locommon_position_sub(&p, &base->super.super.pos, basepos); + vec2_addeq(&base->cache.instance.pos, &p); + + loshader_ground_drawer_add_instance(base->drawer, &base->cache.instance); +} + +static void loground_base_pack_( + const loentity_t* entity, msgpack_packer* packer) { + assert(entity != NULL); + assert(packer != NULL); + + const loground_base_t* base = (typeof(base)) entity; + + msgpack_pack_map(packer, LOGROUND_BASE_PARAM_TO_PACK_COUNT+1); + +# define pack_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + LOCOMMON_MSGPACK_PACK_ANY(packer, &base->var); \ + } while (0) +# define pack_str_(name, str) do { \ + mpkutil_pack_str(packer, name); \ + mpkutil_pack_str(packer, str); \ + } while (0) +# define pack_type_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + mpkutil_pack_str(packer, loground_type_stringify(base->var)); \ + } while (0) + + + LOGROUND_BASE_PARAM_TO_PACK_EACH_(pack_, pack_str_, pack_type_); + +# undef pack_type_ +# undef pack_str_ +# undef pack_ + +# define each_(NAME, name) do { \ + if (base->type == LOGROUND_TYPE_##NAME) { \ + loground_##name##_pack_data(base, packer); \ + return; \ + } \ + } while (0) + + mpkutil_pack_str(packer, "data"); + LOGROUND_TYPE_EACH_(each_); + assert(false); + +# undef each_ +} + +void loground_base_initialize( + loground_base_t* base, loshader_ground_drawer_t* drawer) { + assert(base != NULL); + assert(drawer != NULL); + + *base = (typeof(*base)) { + .super = { + .super = { + .vtable = { + .delete = loground_base_delete_, + .die = loground_base_die_, + .update = loground_base_update_, + .draw = loground_base_draw_, + .pack = loground_base_pack_, + }, + .subclass = LOENTITY_SUBCLASS_GROUND, + }, + }, + .drawer = drawer, + }; +} + +void loground_base_reinitialize(loground_base_t* base, loentity_id_t id) { + assert(base != NULL); + +# define reset_(name, var) do { \ + base->var = (typeof(base->var)) {0}; \ + } while (0) +# define reset_str_(name, str) + + LOGROUND_BASE_PARAM_TO_PACK_EACH_(reset_, reset_str_, reset_); + +# undef reset_str_ +# undef reset_ + + base->super.super.id = id; +} + +void loground_base_deinitialize(loground_base_t* base) { + assert(base != NULL); + + loground_base_delete_(&base->super.super); +} + +bool loground_base_unpack(loground_base_t* base, const msgpack_object *obj) { + assert(base != NULL); + assert(obj != NULL); + + loground_base_reinitialize(base, 0); + /* id will be overwritten below */ + + const char* v; + size_t vlen; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + +# define unpack_(name, var) do { \ + if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(name), &base->var)) { \ + return NULL; \ + } \ + } while (0) +# define unpack_type_(name, var) do { \ + if (!mpkutil_get_str(item_(name), &v, &vlen) || \ + !loground_type_unstringify(&base->var, v, vlen)) { \ + return NULL; \ + } \ + } while (0) +# define unpack_str_(name, str) do { \ + if (!mpkutil_get_str(item_(name), &v, &vlen) || \ + !(strncmp(v, str, vlen) == 0 && str[vlen] == 0)) { \ + return NULL; \ + } \ + } while (0) + + LOGROUND_BASE_PARAM_TO_PACK_EACH_(unpack_, unpack_str_, unpack_type_); + +# undef unpack_str_ +# undef unpack_type_ +# undef unpack_ + + const msgpack_object* data = item_("data"); +# define each_(NAME, name) do { \ + if (base->type == LOGROUND_TYPE_##NAME) { \ + return loground_##name##_unpack_data(base, data); \ + } \ + } while (0) + + LOGROUND_TYPE_EACH_(each_); + return false; + +# undef each_ + +# undef item_ +} diff --git a/core/loground/base.h b/core/loground/base.h new file mode 100644 index 0000000..8353174 --- /dev/null +++ b/core/loground/base.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include + +#include + +#include "core/loentity/entity.h" +#include "core/loentity/ground.h" +#include "core/loshader/ground.h" + +#include "./misc.h" + +typedef struct { + loentity_ground_t super; + bool used; + + /* injected deps */ + loshader_ground_drawer_t* drawer; + + /* params not to be packed */ + struct { + loshader_ground_drawer_instance_t instance; + } cache; + + /* params to be packed (includes id, pos, and size) */ + loground_type_t type; + +# define LOGROUND_BASE_DATA_MAX_SIZE 256 + uint8_t data[LOGROUND_BASE_DATA_MAX_SIZE]; +} loground_base_t; + +void +loground_base_initialize( + loground_base_t* base, + loshader_ground_drawer_t* drawer +); + +void +loground_base_reinitialize( + loground_base_t* base, + loentity_id_t id +); + +void +loground_base_deinitialize( + loground_base_t* base +); + +bool +loground_base_unpack( + loground_base_t* base, + const msgpack_object *obj +); diff --git a/core/loground/island.c b/core/loground/island.c new file mode 100644 index 0000000..f814afe --- /dev/null +++ b/core/loground/island.c @@ -0,0 +1,35 @@ +#include + +#include + +#include "util/math/vector.h" + +#include "core/locommon/position.h" + +#include "./base.h" +#include "./misc.h" + +bool loground_island_update(loground_base_t* base) { + assert(base != NULL); + + base->cache.instance = (loshader_ground_drawer_instance_t) { + .ground_id = LOSHADER_GROUND_ID_ISLAND, + .size = base->super.size, + }; + return true; +} + +void loground_island_build( + loground_base_t* base, + const locommon_position_t* pos, + const vec2_t* size) { + assert(base != NULL); + assert(locommon_position_valid(pos)); + assert(vec2_valid(size)); + assert(size->x >= 0 && size->y >= 0); + + base->type = LOGROUND_TYPE_ISLAND; + + base->super.super.pos = *pos; + base->super.size = *size; +} diff --git a/core/loground/island.h b/core/loground/island.h new file mode 100644 index 0000000..43cce41 --- /dev/null +++ b/core/loground/island.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include + +#include "util/math/vector.h" + +#include "core/locommon/position.h" + +#include "./base.h" + +bool +loground_island_update( + loground_base_t* base +); + +void +loground_island_build( + loground_base_t* base, + const locommon_position_t* pos, + const vec2_t* size +); + +#define loground_island_tear_down(base) + +#define loground_island_pack_data(base, packer) \ + msgpack_pack_nil(packer) + +#define loground_island_unpack_data(base, obj) \ + (obj != NULL) diff --git a/core/loground/misc.c b/core/loground/misc.c new file mode 100644 index 0000000..1d990bd --- /dev/null +++ b/core/loground/misc.c @@ -0,0 +1,37 @@ +#include "./misc.h" + +#include +#include +#include +#include + +const char* loground_type_stringify(loground_type_t type) { +# define each_(NAME, name) do { \ + if (type == LOGROUND_TYPE_##NAME) return #name; \ + } while (0) + + LOGROUND_TYPE_EACH_(each_); + + assert(false); + return NULL; + +# undef each_ +} + +bool loground_type_unstringify( + loground_type_t* type, const char* v, size_t len) { + assert(type != NULL); + assert(v != NULL || len == 0); + +# define each_(NAME, name) do { \ + if (strncmp(v, #name, len) == 0 && #name[len] == 0) { \ + *type = LOGROUND_TYPE_##NAME; \ + return true; \ + } \ + } while (0) + + LOGROUND_TYPE_EACH_(each_); + return false; + +# undef each_ +} diff --git a/core/loground/misc.h b/core/loground/misc.h new file mode 100644 index 0000000..d839899 --- /dev/null +++ b/core/loground/misc.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +/* dont forget to update EACH macro */ +typedef enum { + LOGROUND_TYPE_ISLAND, +} loground_type_t; + +#define LOGROUND_TYPE_EACH_(PROC) do { \ + PROC(ISLAND, island); \ +} while (0) + +const char* +loground_type_stringify( + loground_type_t type +); + +bool +loground_type_unstringify( + loground_type_t* type, + const char* v, + size_t len +); diff --git a/core/loground/pool.c b/core/loground/pool.c new file mode 100644 index 0000000..e704444 --- /dev/null +++ b/core/loground/pool.c @@ -0,0 +1,89 @@ +#include "./pool.h" + +#include +#include +#include +#include + +#include + +#include "util/memory/memory.h" + +#include "core/locommon/counter.h" +#include "core/loshader/ground.h" + +#include "./base.h" + +struct loground_pool_t { + loshader_ground_drawer_t* drawer; + locommon_counter_t* idgen; + + size_t length; + loground_base_t items[1]; +}; + +static size_t loground_pool_find_unused_item_index_( + const loground_pool_t* pool) { + assert(pool != NULL); + + for (size_t i = 0; i < pool->length; ++i) { + if (!pool->items[i].used) return i; + } + fprintf(stderr, "ground pool overflow\n"); + abort(); +} + +loground_pool_t* loground_pool_new( + loshader_ground_drawer_t* drawer, + locommon_counter_t* idgen, + size_t length) { + assert(drawer != NULL); + assert(idgen != NULL); + assert(length > 0); + + loground_pool_t* pool = memory_new( + sizeof(*pool) + (length-1)*sizeof(pool->items[0])); + *pool = (typeof(*pool)) { + .drawer = drawer, + .idgen = idgen, + .length = length, + }; + + for (size_t i = 0; i < pool->length; ++i) { + loground_base_initialize(&pool->items[i], drawer); + } + return pool; +} + +void loground_pool_delete(loground_pool_t* pool) { + assert(pool != NULL); + + for (size_t i = 0; i < pool->length; ++i) { + loground_base_deinitialize(&pool->items[i]); + } + memory_delete(pool); +} + +loground_base_t* loground_pool_create(loground_pool_t* pool) { + assert(pool != NULL); + + const size_t i = loground_pool_find_unused_item_index_(pool); + + loground_base_reinitialize( + &pool->items[i], locommon_counter_count(pool->idgen)); + + pool->items[i].used = true; + return &pool->items[i]; +} + +loground_base_t* loground_pool_unpack_item( + loground_pool_t* pool, const msgpack_object* obj) { + assert(pool != NULL); + + const size_t i = loground_pool_find_unused_item_index_(pool); + + if (!loground_base_unpack(&pool->items[i], obj)) return NULL; + + pool->items[i].used = true; + return &pool->items[i]; +} diff --git a/core/loground/pool.h b/core/loground/pool.h new file mode 100644 index 0000000..b16c734 --- /dev/null +++ b/core/loground/pool.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include + +#include "util/math/vector.h" + +#include "core/lobullet/pool.h" +#include "core/locommon/counter.h" +#include "core/loshader/ground.h" + +#include "./base.h" + +struct loground_pool_t; +typedef struct loground_pool_t loground_pool_t; + +loground_pool_t* /* OWNERSHIP */ +loground_pool_new( + loshader_ground_drawer_t* drawer, + locommon_counter_t* idgen, + size_t length +); + +void +loground_pool_delete( + loground_pool_t* pool /* OWNERSHIP */ +); + +loground_base_t* /* OWNERSHIP */ +loground_pool_create( + loground_pool_t* pool +); + +loground_base_t* /* OWNERSHIP/NULLABLE */ +loground_pool_unpack_item( + loground_pool_t* pool, + const msgpack_object* obj +); diff --git a/core/loplayer/CMakeLists.txt b/core/loplayer/CMakeLists.txt new file mode 100644 index 0000000..0a5b3f6 --- /dev/null +++ b/core/loplayer/CMakeLists.txt @@ -0,0 +1,28 @@ +add_library(loplayer + action.c + camera.c + combat.c + controller.c + entity.c + event.c + hud.c + menu.c + player.c + status.c +) +target_link_libraries(loplayer + msgpackc + + conv + glyphas + math + memory + mpkutil + + lobullet + locommon + loeffect + loentity + loresource + loshader +) diff --git a/core/loplayer/action.c b/core/loplayer/action.c new file mode 100644 index 0000000..1f1baea --- /dev/null +++ b/core/loplayer/action.c @@ -0,0 +1,880 @@ +#include "./action.h" + +#include +#include +#include +#include +#include + +#include + +#include "util/math/algorithm.h" +#include "util/memory/memory.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/lobullet/base.h" +#include "core/lobullet/linear.h" +#include "core/lobullet/pool.h" +#include "core/locommon/easing.h" +#include "core/locommon/ticker.h" +#include "core/loeffect/effect.h" +#include "core/loeffect/stance.h" +#include "core/loentity/store.h" +#include "core/loresource/set.h" +#include "core/loresource/sound.h" +#include "core/loresource/text.h" + +#include "./camera.h" +#include "./combat.h" +#include "./controller.h" +#include "./entity.h" +#include "./event.h" +#include "./hud.h" +#include "./menu.h" +#include "./status.h" + +struct loplayer_action_t { + loresource_set_t* res; + const locommon_ticker_t* ticker; + lobullet_pool_t* bullets; + loentity_store_t* entities; + + loplayer_event_t* event; + loplayer_status_t* status; + loplayer_entity_t* entity; + const loplayer_controller_t* controller; + loplayer_combat_t* combat; + loplayer_camera_t* camera; + loplayer_hud_t* hud; + loplayer_menu_t* menu; + + union { + struct { + } stand; + struct { + float direction; + } moving; + struct { + float direction; + } dodge; + struct { + } combat; + struct { + } shoot; + struct { + } dead; + struct { + bool invincible; + } menu; + } state; + uint64_t since; + + void + (*execute)( + loplayer_action_t* action + ); + void + (*pack)( + const loplayer_action_t* action, + msgpack_packer* packer + ); +}; + +#define LOPLAYER_ACTION_STATE_EACH_(PROC) do { \ + PROC(stand); \ + PROC(moving); \ + PROC(dodge); \ + PROC(combat); \ + PROC(shoot); \ + PROC(dead); \ + PROC(menu); \ +} while (0) + +static void +loplayer_action_start_stand_state_( + loplayer_action_t* action +); +static void +loplayer_action_start_moving_state_( + loplayer_action_t* action, + float direction +); +static void +loplayer_action_start_dodge_state_( + loplayer_action_t* action, + float direction +); +static void +loplayer_action_start_combat_state_( + loplayer_action_t* action +); +static bool +loplayer_action_start_shoot_state_( + loplayer_action_t* action +); +static void +loplayer_action_start_dead_state_( + loplayer_action_t* action +); +static void +loplayer_action_start_menu_state_( + loplayer_action_t* action, + bool invincible +); + +static void loplayer_action_affect_bullet_(loplayer_action_t* action) { + assert(action != NULL); + + if (action->status->bullet_immune_until > action->ticker->time) { + return; + } + if (loplayer_entity_affect_bullet(action->entity)) { + action->status->bullet_immune_until = action->ticker->time + 200; + } +} +static bool loplayer_action_shoot_bullet_(loplayer_action_t* action) { + assert(action != NULL); + + static const float consume = .05f; + + float* f = &action->status->recipient.faith; + if (*f <= 0) return false; + + vec2_t v; + locommon_position_sub( + &v, &action->controller->looking, &action->entity->super.super.pos); + const float vlen = vec2_length(&v); + if (vlen == 0) { + v = vec2(action->entity->direction, 0); + } else { + vec2_diveq(&v, vec2_length(&v)); + } + + /* TODO(catfoot): diffusion */ + vec2_muleq(&v, 1.f); + vec2_addeq(&v, &action->entity->last_velocity); + + lobullet_base_t* b = lobullet_pool_create(action->bullets); + lobullet_linear_light_build(b, (&(lobullet_linear_param_t) { + .owner = action->entity->super.super.id, + .pos = action->entity->super.super.pos, + .size = vec2(.015f, .015f), + .velocity = v, + .acceleration = vec2(0, -.1f), + .color = vec4(.8f, .8f, .8f, .8f), + .duration = 2000, + .knockback = .1f, + .effect = loeffect_immediate_damage( + action->status->recipient.status.attack/2), + })); + loentity_store_add(action->entities, &b->super.super); + + *f -= consume; + if (*f < 0) *f = 0; + return true; +} + +static void loplayer_action_show_tutorial_after_death_(loplayer_action_t* action) { + assert(action != NULL); + +# define text_(name) loresource_text_get(action->res->lang, name) +# define popup_(name) \ + loplayer_menu_popup( \ + action->menu, \ + text_("tutorial_title_"name), \ + text_("tutorial_text_" name)) + + switch (action->status->recipient.last_damage) { + case LOEFFECT_ID_IMMEDIATE_DAMAGE: + popup_("dead_by_combat"); + break; + case LOEFFECT_ID_CURSE: + popup_("dead_by_curse"); + break; + case LOEFFECT_ID_LOST: + popup_("dead_by_lost"); + break; + default: + return; + } + +# undef popup_ +# undef text_ + + loplayer_action_start_menu_popup_state(action); +} + +static void loplayer_action_execute_stand_state_(loplayer_action_t* action) { + assert(action != NULL); + + const float max_acceleration_ = action->entity->on_ground? 2.0f: 0.5f; + + loplayer_entity_aim(action->entity, &action->controller->looking); + + if (action->status->recipient.madness <= 0) { + loplayer_action_start_dead_state_(action); + return; + } + + loplayer_action_affect_bullet_(action); + if (loplayer_combat_accept_all_attacks(action->combat)) { + loplayer_action_start_combat_state_(action); + return; + } + + switch (action->controller->action) { + case LOPLAYER_CONTROLLER_ACTION_NONE: + break; + case LOPLAYER_CONTROLLER_ACTION_ATTACK: + if (loplayer_action_start_shoot_state_(action)) return; + break; + case LOPLAYER_CONTROLLER_ACTION_DODGE: + loplayer_action_start_dodge_state_(action, action->entity->direction); + return; + case LOPLAYER_CONTROLLER_ACTION_GUARD: + break; + case LOPLAYER_CONTROLLER_ACTION_UNGUARD: + break; + case LOPLAYER_CONTROLLER_ACTION_MENU: + if (action->entity->movement.x == 0) { + loplayer_menu_show_status(action->menu); + loplayer_action_start_menu_state_(action, false /* INVINCIBLE */); + return; + } + break; + } + + switch (action->controller->movement) { + case LOPLAYER_CONTROLLER_MOVEMENT_NONE: + break; + case LOPLAYER_CONTROLLER_MOVEMENT_JUMP: + if (action->entity->on_ground) { + action->entity->gravity += action->status->recipient.status.jump; + } + break; + case LOPLAYER_CONTROLLER_MOVEMENT_WALK_LEFT: + case LOPLAYER_CONTROLLER_MOVEMENT_DASH_LEFT: + loplayer_action_start_moving_state_(action, -1); + return; + case LOPLAYER_CONTROLLER_MOVEMENT_WALK_RIGHT: + case LOPLAYER_CONTROLLER_MOVEMENT_DASH_RIGHT: + loplayer_action_start_moving_state_(action, 1); + return; + } + + const float t = (action->ticker->time - action->since)%2000/1000.0f - 1; + action->entity->motion.time = t*t*(3-2*MATH_ABS(t)); + action->entity->motion.from = LOSHADER_CHARACTER_MOTION_ID_STAND1; + action->entity->motion.to = LOSHADER_CHARACTER_MOTION_ID_STAND2; + + locommon_easing_linear_float( + &action->entity->movement.x, + 0, + max_acceleration_ * action->ticker->delta_f); +} +static void loplayer_action_pack_stand_state_( + const loplayer_action_t* action, msgpack_packer* packer) { + assert(action != NULL); + + msgpack_pack_map(packer, 1); + + mpkutil_pack_str(packer, "name"); + mpkutil_pack_str(packer, "stand"); +} +static bool loplayer_action_unpack_stand_state_( + loplayer_action_t* action, const msgpack_object_map* root) { + assert(action != NULL); + + return root != NULL; +} +static void loplayer_action_start_stand_state_(loplayer_action_t* action) { + assert(action != NULL); + + action->since = action->ticker->time; + action->execute = loplayer_action_execute_stand_state_; + action->pack = loplayer_action_pack_stand_state_; + + action->camera->state = LOPLAYER_CAMERA_STATE_DEFAULT; + loplayer_hud_show(action->hud); +} + +static void loplayer_action_execute_moving_state_(loplayer_action_t* action) { + assert(action != NULL); + + static const float backwalk_attenuation_ = 0.8f; + static const float dash_speed_ = 1.4f; + + const float max_acceleration_ = action->entity->on_ground? 2.4f: 0.8f; + + const float dir = action->state.moving.direction; + + loplayer_entity_aim(action->entity, &action->controller->looking); + + if (action->status->recipient.madness <= 0) { + loplayer_action_start_dead_state_(action); + return; + } + + loplayer_action_affect_bullet_(action); + if (loplayer_combat_accept_all_attacks(action->combat)) { + loplayer_action_start_combat_state_(action); + return; + } + + switch (action->controller->action) { + case LOPLAYER_CONTROLLER_ACTION_NONE: + break; + case LOPLAYER_CONTROLLER_ACTION_ATTACK: + if (loplayer_action_start_shoot_state_(action)) return; + break; + case LOPLAYER_CONTROLLER_ACTION_DODGE: + loplayer_action_start_dodge_state_(action, dir); + return; + case LOPLAYER_CONTROLLER_ACTION_GUARD: + break; + case LOPLAYER_CONTROLLER_ACTION_UNGUARD: + break; + case LOPLAYER_CONTROLLER_ACTION_MENU: + break; + } + + float max_speed = action->status->recipient.status.speed; + float control_dir = dir; + + switch (action->controller->movement) { + case LOPLAYER_CONTROLLER_MOVEMENT_NONE: + loplayer_action_start_stand_state_(action); + return; + case LOPLAYER_CONTROLLER_MOVEMENT_JUMP: + if (action->entity->on_ground) { + action->entity->gravity += action->status->recipient.status.jump; + } + return; + case LOPLAYER_CONTROLLER_MOVEMENT_WALK_LEFT: + control_dir = -1; + break; + case LOPLAYER_CONTROLLER_MOVEMENT_WALK_RIGHT: + control_dir = 1; + break; + case LOPLAYER_CONTROLLER_MOVEMENT_DASH_LEFT: + max_speed *= dash_speed_; + control_dir = -1; + break; + case LOPLAYER_CONTROLLER_MOVEMENT_DASH_RIGHT: + max_speed *= dash_speed_; + control_dir = 1; + break; + } + if (control_dir * dir < 0) { + loplayer_action_start_stand_state_(action); + return; + } + + if (dir * action->entity->direction < 0) { + max_speed *= backwalk_attenuation_; + } + + if (action->entity->on_ground) { + const int32_t p = 70/max_speed; + const float t = (action->ticker->time - action->since)%p*2.0f/p - 1; + action->entity->motion.time = MATH_ABS(t); + action->entity->motion.from = LOSHADER_CHARACTER_MOTION_ID_STAND1; + action->entity->motion.to = LOSHADER_CHARACTER_MOTION_ID_WALK; + } + + locommon_easing_linear_float( + &action->entity->movement.x, + max_speed*dir, + max_acceleration_ * action->ticker->delta_f); +} +static void loplayer_action_pack_moving_state_( + const loplayer_action_t* action, msgpack_packer* packer) { + assert(action != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, 2); + + mpkutil_pack_str(packer, "name"); + mpkutil_pack_str(packer, "moving"); + + mpkutil_pack_str(packer, "direction"); + msgpack_pack_double(packer, action->state.moving.direction); +} +static bool loplayer_action_unpack_moving_state_( + loplayer_action_t* action, const msgpack_object_map* root) { + assert(action != NULL); + + const msgpack_object* direction = + mpkutil_get_map_item_by_str(root, "direction"); + if (!mpkutil_get_float(direction, &action->state.moving.direction)) { + return false; + } + return true; +} +static void loplayer_action_start_moving_state_( + loplayer_action_t* action, float dir) { + assert(action != NULL); + assert(MATH_FLOAT_VALID(dir)); + + action->since = action->ticker->time; + action->execute = loplayer_action_execute_moving_state_; + action->pack = loplayer_action_pack_moving_state_; + + action->state = (typeof(action->state)) { + .moving = { + .direction = dir, + }, + }; + action->camera->state = LOPLAYER_CAMERA_STATE_DEFAULT; + loplayer_hud_show(action->hud); +} + +static void loplayer_action_execute_dodge_state_(loplayer_action_t* action) { + assert(action != NULL); + + static const uint64_t duration_ = 200; + static const float start_speed_ = 0.6f; + static const float end_speed_ = 0.1f; + + if (action->since + duration_ <= action->ticker->time) { + loplayer_combat_drop_all_attacks(action->combat); + loplayer_action_start_stand_state_(action); + return; + } + + const float dir = action->state.dodge.direction; + + vec2_t* v = &action->entity->movement; + + const float t = (action->ticker->time - action->since)*1.0f/duration_; + const float r = 1 - powf(1-t, 1.5); + v->x = (r * (start_speed_-end_speed_) + end_speed_) * dir; + v->y = 0; + + action->entity->motion.time = 1-powf(1-t, 2); + action->entity->motion.from = LOSHADER_CHARACTER_MOTION_ID_WALK; + action->entity->motion.to = LOSHADER_CHARACTER_MOTION_ID_STAND1; +} +static void loplayer_action_pack_dodge_state_( + const loplayer_action_t* action, msgpack_packer* packer) { + assert(action != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, 2); + + mpkutil_pack_str(packer, "name"); + mpkutil_pack_str(packer, "dodge"); + + mpkutil_pack_str(packer, "direction"); + msgpack_pack_double(packer, action->state.dodge.direction); +} +static bool loplayer_action_unpack_dodge_state_( + loplayer_action_t* action, const msgpack_object_map* root) { + assert(action != NULL); + + const msgpack_object* direction = + mpkutil_get_map_item_by_str(root, "direction"); + if (!mpkutil_get_float(direction, &action->state.moving.direction)) { + return false; + } + return true; +} +static void loplayer_action_start_dodge_state_( + loplayer_action_t* action, float dir) { + assert(action != NULL); + assert(MATH_FLOAT_VALID(dir)); + + action->since = action->ticker->time; + action->state = (typeof(action->state)) { + .moving = { + .direction = dir, + }, + }; + action->execute = loplayer_action_execute_dodge_state_; + action->pack = loplayer_action_pack_dodge_state_; + + action->camera->state = LOPLAYER_CAMERA_STATE_DEFAULT; + loplayer_hud_show(action->hud); + loplayer_combat_drop_all_attacks(action->combat); + + loresource_sound_play(action->res->sound, "dodge"); +} + +static void loplayer_action_execute_combat_state_(loplayer_action_t* action) { + assert(action != NULL); + + if (action->status->recipient.madness <= 0) { + loplayer_action_start_dead_state_(action); + return; + } + + loplayer_action_affect_bullet_(action); + if (!loplayer_combat_accept_all_attacks(action->combat)) { + loplayer_action_start_stand_state_(action); + return; + } + + switch (action->controller->action) { + case LOPLAYER_CONTROLLER_ACTION_NONE: + break; + case LOPLAYER_CONTROLLER_ACTION_ATTACK: + break; + case LOPLAYER_CONTROLLER_ACTION_DODGE: + loplayer_action_start_dodge_state_(action, action->entity->direction); + return; + case LOPLAYER_CONTROLLER_ACTION_GUARD: + loplayer_combat_guard(action->combat); + break; + case LOPLAYER_CONTROLLER_ACTION_UNGUARD: + loplayer_combat_unguard(action->combat); + break; + case LOPLAYER_CONTROLLER_ACTION_MENU: + break; + } + + action->entity->gravity = 0; + + const float klen = vec2_length(&action->entity->knockback); + if (klen > .1f) vec2_muleq(&action->entity->knockback, .1f/klen); +} +static void loplayer_action_pack_combat_state_( + const loplayer_action_t* action, msgpack_packer* packer) { + assert(action != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, 1); + + mpkutil_pack_str(packer, "name"); + mpkutil_pack_str(packer, "combat"); +} +static bool loplayer_action_unpack_combat_state_( + loplayer_action_t* action, const msgpack_object_map* root) { + assert(action != NULL); + + return root != NULL; +} +static void loplayer_action_start_combat_state_(loplayer_action_t* action) { + assert(action != NULL); + + action->since = action->ticker->time; + action->execute = loplayer_action_execute_combat_state_; + action->pack = loplayer_action_pack_combat_state_; + + action->entity->movement = vec2(0, 0); + + action->camera->state = LOPLAYER_CAMERA_STATE_COMBAT; + loplayer_hud_show(action->hud); +} + +static void loplayer_action_execute_shoot_state_(loplayer_action_t* action) { + assert(action != NULL); + + static const uint64_t duration = 300; + static const float max_acceleration = 1.f; + + loplayer_entity_aim(action->entity, &action->controller->looking); + + if (action->status->recipient.madness <= 0) { + loplayer_action_start_dead_state_(action); + return; + } + + loplayer_action_affect_bullet_(action); + if (loplayer_combat_accept_all_attacks(action->combat)) { + loplayer_action_start_combat_state_(action); + return; + } + + if (action->since+duration <= action->ticker->time) { + if (loplayer_action_shoot_bullet_(action)) { + loresource_sound_play(action->res->sound, "player_shoot"); + } + loplayer_action_start_stand_state_(action); + return; + } + + const float a = max_acceleration * action->ticker->delta_f; + locommon_easing_linear_float(&action->entity->movement.x, 0, a); + locommon_easing_linear_float(&action->entity->movement.y, 0, a); +} +static void loplayer_action_pack_shoot_state_( + const loplayer_action_t* action, msgpack_packer* packer) { + assert(action != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, 1); + + mpkutil_pack_str(packer, "name"); + mpkutil_pack_str(packer, "shoot"); +} +static bool loplayer_action_unpack_shoot_state_( + loplayer_action_t* action, const msgpack_object_map* root) { + assert(action != NULL); + + return root != NULL; +} +static bool loplayer_action_start_shoot_state_(loplayer_action_t* action) { + assert(action != NULL); + + if (!loeffect_stance_set_has( + &action->status->stances, LOEFFECT_STANCE_ID_REVOLUTIONER)) { + return false; + } + + action->since = action->ticker->time; + action->execute = loplayer_action_execute_shoot_state_; + action->pack = loplayer_action_pack_shoot_state_; + + action->camera->state = LOPLAYER_CAMERA_STATE_DEFAULT; + loplayer_hud_show(action->hud); + + loresource_sound_play(action->res->sound, "player_trigger"); + return true; +} + +static void loplayer_action_execute_dead_state_(loplayer_action_t* action) { + assert(action != NULL); + + static const uint64_t duration_ = 3000; + + if (action->since + duration_ <= action->ticker->time) { + loplayer_entity_move(action->entity, &action->status->respawn_pos); + loplayer_status_reset(action->status); + loplayer_combat_drop_all_attacks(action->combat); + + loplayer_action_start_stand_state_(action); + loplayer_action_show_tutorial_after_death_(action); + loplayer_event_abort(action->event); + return; + } +} +static void loplayer_action_pack_dead_state_( + const loplayer_action_t* action, msgpack_packer* packer) { + assert(action != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, 1); + + mpkutil_pack_str(packer, "name"); + mpkutil_pack_str(packer, "dead"); +} +static bool loplayer_action_unpack_dead_state_( + loplayer_action_t* action, const msgpack_object_map* root) { + assert(action != NULL); + + return root != NULL; +} +static void loplayer_action_start_dead_state_(loplayer_action_t* action) { + assert(action != NULL); + + action->since = action->ticker->time; + action->execute = loplayer_action_execute_dead_state_; + action->pack = loplayer_action_pack_dead_state_; + + action->entity->movement = vec2(0, 0); + + action->camera->state = LOPLAYER_CAMERA_STATE_DEAD; + loplayer_hud_hide(action->hud); + loplayer_combat_drop_all_attacks(action->combat); + + /* Deny all event requests. */ + loplayer_event_abort(action->event); + loplayer_event_take_control(action->event, action->entity->super.super.id); +} + +static void loplayer_action_execute_menu_state_(loplayer_action_t* action) { + assert(action != NULL); + + if (action->status->recipient.madness <= 0) { + loplayer_menu_hide(action->menu); + loplayer_action_start_dead_state_(action); + return; + } + + loplayer_action_affect_bullet_(action); + if (!action->state.menu.invincible && + loplayer_combat_accept_all_attacks(action->combat)) { + loplayer_menu_hide(action->menu); + loplayer_action_start_combat_state_(action); + return; + } + + switch (action->controller->action) { + case LOPLAYER_CONTROLLER_ACTION_NONE: + break; + case LOPLAYER_CONTROLLER_ACTION_ATTACK: + break; + case LOPLAYER_CONTROLLER_ACTION_DODGE: + break; + case LOPLAYER_CONTROLLER_ACTION_GUARD: + break; + case LOPLAYER_CONTROLLER_ACTION_UNGUARD: + break; + case LOPLAYER_CONTROLLER_ACTION_MENU: + loplayer_menu_hide(action->menu); + loplayer_action_start_stand_state_(action); + return; + } +} +static void loplayer_action_pack_menu_state_( + const loplayer_action_t* action, msgpack_packer* packer) { + assert(action != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, 2); + + mpkutil_pack_str(packer, "name"); + mpkutil_pack_str(packer, "menu"); + + mpkutil_pack_str(packer, "invincible"); + mpkutil_pack_bool(packer, action->state.menu.invincible); +} +static bool loplayer_action_unpack_menu_state_( + loplayer_action_t* action, const msgpack_object_map* root) { + assert(action != NULL); + + const msgpack_object* invincible = + mpkutil_get_map_item_by_str(root, "invincible"); + if (!mpkutil_get_bool(invincible, &action->state.menu.invincible)) { + return false; + } + return true; +} +static void loplayer_action_start_menu_state_( + loplayer_action_t* action, bool invincible) { + assert(action != NULL); + + action->since = action->ticker->time; + action->execute = loplayer_action_execute_menu_state_; + action->pack = loplayer_action_pack_menu_state_; + + action->entity->movement = vec2(0, 0); + + action->state.menu = (typeof(action->state.menu)) { + .invincible = invincible, + }; + + action->camera->state = LOPLAYER_CAMERA_STATE_MENU; +} + +loplayer_action_t* loplayer_action_new( + loresource_set_t* res, + const locommon_ticker_t* ticker, + lobullet_pool_t* bullets, + loentity_store_t* entities, + loplayer_event_t* event, + loplayer_status_t* status, + loplayer_entity_t* entity, + loplayer_combat_t* combat, + const loplayer_controller_t* controller, + loplayer_camera_t* camera, + loplayer_hud_t* hud, + loplayer_menu_t* menu) { + assert(res != NULL); + assert(ticker != NULL); + assert(bullets != NULL); + assert(entities != NULL); + assert(event != NULL); + assert(status != NULL); + assert(entity != NULL); + assert(combat != NULL); + assert(controller != NULL); + assert(camera != NULL); + assert(hud != NULL); + assert(menu != NULL); + + loplayer_action_t* action = memory_new(sizeof(*action)); + *action = (typeof(*action)) { + .res = res, + .ticker = ticker, + .bullets = bullets, + .entities = entities, + .event = event, + .status = status, + .entity = entity, + .combat = combat, + .controller = controller, + .camera = camera, + .hud = hud, + .menu = menu, + }; + loplayer_action_start_stand_state_(action); + return action; +} + +void loplayer_action_delete(loplayer_action_t* action) { + if (action == NULL) return; + + memory_delete(action); +} + +void loplayer_action_start_menu_popup_state(loplayer_action_t* action) { + assert(action != NULL); + + loplayer_action_start_menu_state_(action, true /* invincible */); +} + +void loplayer_action_execute(loplayer_action_t* action) { + assert(action != NULL); + + assert(action->execute != NULL); + action->execute(action); +} + +void loplayer_action_pack(const loplayer_action_t* action, msgpack_packer* packer) { + assert(action != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, 2); + + mpkutil_pack_str(packer, "since"); + msgpack_pack_uint64(packer, action->since); + + assert(action->pack != NULL); + + mpkutil_pack_str(packer, "state"); + action->pack(action, packer); +} + +bool loplayer_action_unpack( + loplayer_action_t* action, const msgpack_object* obj) { + assert(action != NULL); + + if (obj == NULL) return false; + + const msgpack_object_map* root = mpkutil_get_map(obj); + + const msgpack_object* since = mpkutil_get_map_item_by_str(root, "since"); + if (!mpkutil_get_uint64(since, &action->since)) return false; + + const msgpack_object_map* state = mpkutil_get_map( + mpkutil_get_map_item_by_str(root, "state")); + + bool state_loaded = false; + + const msgpack_object* name = mpkutil_get_map_item_by_str(state, "name"); + const char* v; + size_t len; + if (!mpkutil_get_str(name, &v, &len)) { + loplayer_action_start_stand_state_(action); + state_loaded = true; + } + +# define unpack_state_(name_) do { \ + if (!state_loaded && strncmp(v, #name_, len) == 0 && #name_[len] == 0) { \ + action->execute = loplayer_action_execute_##name_##_state_; \ + action->pack = loplayer_action_pack_##name_##_state_; \ + if (!loplayer_action_unpack_##name_##_state_(action, state)) { \ + loplayer_action_start_stand_state_(action); \ + } \ + state_loaded = true; \ + } \ + } while (0) + + LOPLAYER_ACTION_STATE_EACH_(unpack_state_); + +# undef unpack_state_ + + return true; +} diff --git a/core/loplayer/action.h b/core/loplayer/action.h new file mode 100644 index 0000000..1eb4817 --- /dev/null +++ b/core/loplayer/action.h @@ -0,0 +1,64 @@ +#pragma once + +#include + +#include + +#include "core/lobullet/pool.h" +#include "core/locommon/ticker.h" +#include "core/loresource/set.h" + +#include "./camera.h" +#include "./combat.h" +#include "./controller.h" +#include "./entity.h" +#include "./event.h" +#include "./hud.h" +#include "./menu.h" +#include "./status.h" + +struct loplayer_action_t; +typedef struct loplayer_action_t loplayer_action_t; + +loplayer_action_t* /* OWNERSHIP */ +loplayer_action_new( + loresource_set_t* res, + const locommon_ticker_t* ticker, + lobullet_pool_t* bullets, + loentity_store_t* entities, + loplayer_event_t* event, + loplayer_status_t* status, + loplayer_entity_t* entity, + loplayer_combat_t* combat, + const loplayer_controller_t* controller, + loplayer_camera_t* camera, + loplayer_hud_t* hud, + loplayer_menu_t* menu +); + +void +loplayer_action_delete( + loplayer_action_t* action /* OWNERSHIP */ +); + +void +loplayer_action_start_menu_popup_state( + loplayer_action_t* action +); + +void +loplayer_action_execute( + loplayer_action_t* action +); + +void +loplayer_action_pack( + const loplayer_action_t* action, + msgpack_packer* packer +); + +bool +loplayer_action_unpack( + loplayer_action_t* action, + const msgpack_object* obj /* NULLABLE */ +); diff --git a/core/loplayer/camera.c b/core/loplayer/camera.c new file mode 100644 index 0000000..f442bff --- /dev/null +++ b/core/loplayer/camera.c @@ -0,0 +1,273 @@ +#include "./camera.h" + +#include +#include +#include + +#include "util/math/algorithm.h" +#include "util/math/matrix.h" +#include "util/math/vector.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/locommon/easing.h" +#include "core/locommon/position.h" +#include "core/locommon/ticker.h" + +#include "./entity.h" +#include "./event.h" +#include "./status.h" + +#define LOPLAYER_CAMERA_STATE_EACH_(PROC) do { \ + PROC(DEFAULT, default); \ + PROC(COMBAT, combat); \ + PROC(DEAD, dead); \ + PROC(MENU, menu); \ +} while (0) + +static void loplayer_camera_bind_position_in_area_( + const loplayer_camera_t* camera, + locommon_position_t* pos, + const locommon_position_t* areapos, + const vec2_t* areasize) { + assert(camera != NULL); + assert(locommon_position_valid(pos)); + assert(locommon_position_valid(areapos)); + assert(vec2_valid(areasize)); + + vec2_t szoffset = camera->display_chunksz; + vec2_diveq(&szoffset, camera->scale); + + vec2_t sz; + vec2_sub(&sz, areasize, &szoffset); + + vec2_t v; + locommon_position_sub(&v, pos, areapos); + +# define fix_coordinate_(axis) do { \ + if (sz.axis > 0) { \ + if (MATH_ABS(v.axis) > sz.axis) v.axis = MATH_SIGN(v.axis)*sz.axis; \ + } else { \ + v.axis = 0; \ + } \ + } while (0) + + fix_coordinate_(x); + fix_coordinate_(y); + +# undef fix_coordinate_ + + *pos = *areapos; + vec2_addeq(&pos->fract, &v); + locommon_position_reduce(pos); +} + +const char* loplayer_camera_state_stringify(loplayer_camera_state_t state) { +# define each_(NAME, name) do { \ + if (state == LOPLAYER_CAMERA_STATE_##NAME) return #name; \ + } while (0) + + LOPLAYER_CAMERA_STATE_EACH_(each_); + + assert(false); + return NULL; + +# undef each_ +} + +bool loplayer_camera_state_unstringify( + loplayer_camera_state_t* state, const char* str, size_t len) { + assert(state != NULL); + +# define each_(NAME, name) do { \ + if (strncmp(str, #name, len) == 0 && #name[len] == 0) { \ + *state = LOPLAYER_CAMERA_STATE_##NAME; \ + return true; \ + } \ + } while (0) + + LOPLAYER_CAMERA_STATE_EACH_(each_); + return false; + +# undef each_ +} + +void loplayer_camera_initialize( + loplayer_camera_t* camera, + loshader_set_t* shaders, + const locommon_ticker_t* ticker, + const loplayer_event_t* event, + const loplayer_status_t* status, + const loplayer_entity_t* entity, + const mat4_t* proj) { + assert(camera != NULL); + assert(shaders != NULL); + assert(ticker != NULL); + assert(event != NULL); + assert(status != NULL); + assert(entity != NULL); + assert(mat4_valid(proj)); + + mat4_t inv_proj; + mat4_inv(&inv_proj, proj); + + static const vec4_t chunk = vec4(1, 1, 0, 0); + vec4_t chunksz; + mat4_mul_vec4(&chunksz, &inv_proj, &chunk); + + *camera = (typeof(*camera)) { + .shaders = shaders, + .ticker = ticker, + .event = event, + .status = status, + .entity = entity, + + .display_chunksz = chunksz.xy, + + .matrix = mat4_scale(1, 1, 1), + .scale = 1.0f, + + .cinesco = { + .color = vec4(0, 0, 0, 1), + }, + + .state = LOPLAYER_CAMERA_STATE_DEFAULT, + .brightness = 1, + }; +} + +void loplayer_camera_deinitialize(loplayer_camera_t* camera) { + assert(camera != NULL); + +} + +void loplayer_camera_update(loplayer_camera_t* camera) { + assert(camera != NULL); + + const float d = camera->ticker->delta_f; + const loplayer_status_t* stat = camera->status; + + locommon_position_t target = camera->entity->super.super.pos; + + /* ---- movement ---- */ + const loplayer_event_param_t* e = loplayer_event_get_param(camera->event); + if (e != NULL && e->area_size.x > 0 && e->area_size.y > 0) { + loplayer_camera_bind_position_in_area_( + camera, &target, &e->area_pos, &e->area_size); + } + + vec2_t dist; + locommon_position_sub(&dist, &target, &camera->pos); + if (vec2_pow_length(&dist) > 2) camera->pos = target; + + locommon_easing_smooth_position(&camera->pos, &target, d*10); + +# define ease_float_(name, ed, speed) \ + locommon_easing_smooth_float(&camera->name, ed, d*(speed)) + + /* ---- cinema scope ---- */ + ease_float_(cinesco.size, !!(e != NULL && e->cinescope)*.3f, 2); + + /* ---- damage effect ---- */ + const bool damaged = + stat->last_damage_time > 0 && + stat->last_damage_time+500 > camera->ticker->time; + ease_float_(pe.raster, !!damaged*.5f, damaged? 5: 3); + + /* ---- amnesia effect ---- */ + const uint64_t amnesia_st = stat->recipient.effects.amnesia.begin; + const uint64_t amnesia_dur = stat->recipient.effects.amnesia.duration; + ease_float_( + pe.amnesia_displacement, + !!(amnesia_st+amnesia_dur > camera->ticker->time), 5); + + /* ---- dying effect ---- */ + const float dying = stat->dying_effect; + camera->pixsort = dying < .1f? dying/.1f: powf(1-(dying-.1f), 1.5f); + if (camera->pixsort < 0) camera->pixsort = 0; + + /* ---- switch by current state ---- */ + switch (camera->state) { + case LOPLAYER_CAMERA_STATE_DEFAULT: + ease_float_(scale, 1.0f, 10); + ease_float_(pe.whole_blur, 0.0f, 1); + ease_float_(pe.radial_displacement, 0.0f, 5); + ease_float_(pe.radial_fade, 0.5f, 3); + break; + case LOPLAYER_CAMERA_STATE_COMBAT: + ease_float_(scale, 1.5f, 8); + ease_float_(pe.whole_blur, 0.0f, 1); + ease_float_(pe.radial_displacement, 0.6f, 3); + ease_float_(pe.radial_fade, 0.8f, 1); + break; + case LOPLAYER_CAMERA_STATE_DEAD: + ease_float_(scale, 2.0f, 1); + ease_float_(pe.whole_blur, 1.0f, 1); + ease_float_(pe.radial_displacement, 0.3f, .7f); + ease_float_(pe.radial_fade, 1.5f, .7f); + break; + case LOPLAYER_CAMERA_STATE_MENU: + ease_float_(scale, 1.0f, 10); + ease_float_(pe.whole_blur, 0.9f, 1); + ease_float_(pe.radial_displacement, 0.0f, 10); + ease_float_(pe.radial_fade, 0.6f, 1); + break; + } + +# undef ease_float_ + + /* ---- fixed params ---- */ + camera->pe.brightness = camera->brightness; + + /* ---- matrix ---- */ + camera->matrix = mat4_scale(camera->scale, camera->scale, 1); +} + +void loplayer_camera_draw(const loplayer_camera_t* camera) { + assert(camera != NULL); + + loshader_pixsort_drawer_set_intensity( + camera->shaders->drawer.pixsort, camera->pixsort); + + loshader_posteffect_drawer_set_param( + camera->shaders->drawer.posteffect, &camera->pe); + + loshader_cinescope_drawer_set_param( + camera->shaders->drawer.cinescope, &camera->cinesco); +} + +void loplayer_camera_pack( + const loplayer_camera_t* camera, msgpack_packer* packer) { + assert(camera != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, 2); + + mpkutil_pack_str(packer, "state"); + mpkutil_pack_str(packer, loplayer_camera_state_stringify(camera->state)); + + mpkutil_pack_str(packer, "pos"); + locommon_position_pack(&camera->pos, packer); +} + +bool loplayer_camera_unpack( + loplayer_camera_t* camera, const msgpack_object* obj) { + assert(camera != NULL); + + if (obj == NULL) return false; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + const char* v; + size_t len; + if (!mpkutil_get_str(item_("state"), &v, &len) || + !loplayer_camera_state_unstringify(&camera->state, v, len)) { + return false; + } + if (!locommon_position_unpack(&camera->pos, item_("pos"))) { + return false; + } +# undef item_ + return true; +} diff --git a/core/loplayer/camera.h b/core/loplayer/camera.h new file mode 100644 index 0000000..b5c58a2 --- /dev/null +++ b/core/loplayer/camera.h @@ -0,0 +1,101 @@ +#pragma once + +#include + +#include + +#include "util/math/matrix.h" +#include "util/math/vector.h" + +#include "core/locommon/position.h" +#include "core/locommon/ticker.h" +#include "core/loshader/cinescope.h" +#include "core/loshader/posteffect.h" +#include "core/loshader/set.h" + +#include "./entity.h" +#include "./event.h" +#include "./status.h" + +typedef enum { + LOPLAYER_CAMERA_STATE_DEFAULT, + LOPLAYER_CAMERA_STATE_COMBAT, + LOPLAYER_CAMERA_STATE_DEAD, + LOPLAYER_CAMERA_STATE_MENU, +} loplayer_camera_state_t; + +typedef struct { + /* injected deps */ + loshader_set_t* shaders; + const locommon_ticker_t* ticker; + const loplayer_event_t* event; + const loplayer_status_t* status; + const loplayer_entity_t* entity; + + /* immutable params */ + vec2_t display_chunksz; + + /* read-only mutable params */ + locommon_position_t pos; + mat4_t matrix; + + float scale; + float pixsort; + loshader_posteffect_drawer_param_t pe; + loshader_cinescope_drawer_param_t cinesco; + + /* public params */ + loplayer_camera_state_t state; + float brightness; + +} loplayer_camera_t; + +const char* +loplayer_camera_state_stringify( + loplayer_camera_state_t state +); + +bool +loplayer_camera_state_unstringify( + loplayer_camera_state_t* state, + const char* str, + size_t len +); + +void +loplayer_camera_initialize( + loplayer_camera_t* camera, + loshader_set_t* shaders, + const locommon_ticker_t* ticker, + const loplayer_event_t* event, + const loplayer_status_t* status, + const loplayer_entity_t* entity, + const mat4_t* proj +); + +void +loplayer_camera_deinitialize( + loplayer_camera_t* camera +); + +void +loplayer_camera_update( + loplayer_camera_t* camera +); + +void +loplayer_camera_draw( + const loplayer_camera_t* camera +); + +void +loplayer_camera_pack( + const loplayer_camera_t* camera, + msgpack_packer* packer +); + +bool +loplayer_camera_unpack( + loplayer_camera_t* camera, + const msgpack_object* packer +); diff --git a/core/loplayer/combat.c b/core/loplayer/combat.c new file mode 100644 index 0000000..d1dece5 --- /dev/null +++ b/core/loplayer/combat.c @@ -0,0 +1,503 @@ +#include "./combat.h" + +#include +#include +#include +#include + +#include "util/math/algorithm.h" +#include "util/memory/memory.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/locommon/easing.h" +#include "core/locommon/msgpack.h" +#include "core/locommon/ticker.h" +#include "core/loeffect/effect.h" +#include "core/loentity/character.h" +#include "core/loentity/store.h" +#include "core/loresource/sound.h" +#include "core/loshader/combat_ring.h" + +#include "./entity.h" +#include "./status.h" + +struct loplayer_combat_t { + /* injected deps */ + loresource_sound_t* sound; + const locommon_ticker_t* ticker; + loshader_combat_ring_drawer_t* drawer; + loentity_store_t* entities; + + loplayer_status_t* status; + loplayer_entity_t* entity; + + /* temporary cache for drawing */ + uint64_t ring_start; + uint64_t ring_end; + + uint64_t guard_start; + uint64_t guard_end; + + uint64_t attack_end; + float alpha; + + /* params */ + bool accepted; + + size_t length; + loplayer_combat_attack_t attacks[1]; +}; + +#define LOPLAYER_COMBAT_GUARD_ERROR_THRESHOLD 0.55f + +#define LOPLAYER_COMBAT_ATTACK_PARAM_TO_PACK_EACH_(PROC) do { \ + PROC("attacker", attacker); \ + PROC("start", start); \ + PROC("duration", duration); \ + PROC("knockback", knockback); \ + PROC("effect", effect); \ +} while (0) +#define LOPLAYER_COMBAT_ATTACK_PARAM_TO_PACK_COUNT 5 + +static bool loplayer_combat_find_attack_index_in_period_( + loplayer_combat_t* combat, size_t* index, uint64_t st, uint64_t ed) { + assert(combat != NULL); + assert(st <= ed); + + static size_t dummy_; + if (index == NULL) index = &dummy_; + + for (size_t i = 0; i < combat->length; ++i) { + const uint64_t ist = combat->attacks[i].start; + const uint64_t ied = combat->attacks[i].duration + ist; + + if (ist < ed && ied > st) { + *index = i; + return true; + } + } + return false; +} + +static bool loplayer_combat_find_unused_attack_index_( + loplayer_combat_t* combat, size_t *index) { + assert(combat != NULL); + + for (size_t i = 0; i < combat->length; ++i) { + const uint64_t ed = combat->attacks[i].start + combat->attacks[i].duration; + if (ed <= combat->ticker->time) { + *index = i; + return true; + } + } + return false; +} + +static void loplayer_combat_execute_reflective_attack_( + loplayer_combat_t* combat, + loplayer_combat_attack_t* attack, + loentity_character_t* chara) { + assert(combat != NULL); + assert(attack != NULL); + + if (chara == NULL) return; + + const loeffect_t dmg = + loeffect_immediate_damage(combat->status->recipient.status.attack); + loentity_character_apply_effect(chara, &dmg); + + vec2_t knockback = attack->knockback; + vec2_muleq(&knockback, -1); + loentity_character_knockback(chara, &knockback); + + loresource_sound_play(combat->sound, "reflection"); +} + +static void loplayer_combat_execute_enemy_attack_( + loplayer_combat_t* combat, + loplayer_combat_attack_t* attack, + loentity_character_t* chara) { + assert(combat != NULL); + assert(attack != NULL); + + /* chara can be NULL */ + (void) chara; + + loplayer_status_apply_effect(combat->status, &attack->effect); + loentity_character_knockback(&combat->entity->super, &attack->knockback); +} + +static void loplayer_combat_handle_attack_( + loplayer_combat_t* combat, + loplayer_combat_attack_t* attack, + loentity_character_t* chara) { + assert(combat != NULL); + assert(attack != NULL); + + if (combat->guard_start < combat->guard_end) { + const uint64_t atked = attack->start + attack->duration; + + const uint64_t stdiff = + MATH_MAX(attack->start, combat->guard_start) - + MATH_MIN(attack->start, combat->guard_start); + const uint64_t eddiff = + MATH_MAX(atked, combat->guard_end) - + MATH_MIN(atked, combat->guard_end); + + const float guard_error = (stdiff + eddiff)*1.0f / attack->duration; + + const bool back_attack = + attack->knockback.x * combat->entity->direction > 0; + + float guard_error_thresh = LOPLAYER_COMBAT_GUARD_ERROR_THRESHOLD; + if (back_attack) guard_error_thresh /= 10; + + if (guard_error < guard_error_thresh) { + loplayer_combat_execute_reflective_attack_(combat, attack, chara); + return; + } + } + loplayer_combat_execute_enemy_attack_(combat, attack, chara); +} + +static void loplayer_combat_draw_ring_base_( + const loplayer_combat_t* combat) { + assert(combat != NULL); + + loshader_combat_ring_drawer_add_instance( + combat->drawer, &(loshader_combat_ring_drawer_instance_t) { + .range = -1, /* = draw ring base */ + .color = vec4(0, 0, 0, .8f*combat->alpha), + }); +} + +static void loplayer_combat_draw_attacks_(const loplayer_combat_t* combat) { + assert(combat != NULL); + + const uint64_t ring_st = combat->ring_start; + const uint64_t ring_ed = combat->ring_end; + assert(ring_st <= ring_ed); + + const uint64_t ring_dur = ring_ed - ring_st; + + for (size_t i = 0; i < combat->length; ++i) { + const uint64_t st = combat->attacks[i].start; + const uint64_t ed = combat->attacks[i].duration + st; + + if (st < ring_ed && ring_st < ed) { + const uint64_t rst = st - MATH_MIN(ring_st, st); + const uint64_t red = ed - ring_st; + assert(rst <= red); + + loshader_combat_ring_drawer_add_instance( + combat->drawer, &(loshader_combat_ring_drawer_instance_t) { + .range = .8f, + .start = rst*1.f/ring_dur, + .end = MATH_MIN(red, ring_dur)*1.f/ring_dur, + .color = vec4(.7f, .1f, .1f, combat->alpha), + }); + } + } +} + +static void loplayer_combat_draw_guard_(const loplayer_combat_t* combat) { + assert(combat != NULL); + + const bool now_guarding = (combat->guard_start > combat->guard_end); + if (!now_guarding && combat->guard_end <= combat->ring_start) { + return; + } + + const float ring_dur = combat->ring_end - combat->ring_start; + assert(ring_dur > 0); + + const uint64_t st = combat->guard_start - + MATH_MIN(combat->ring_start, combat->guard_start); + const uint64_t ed = + (now_guarding? combat->ticker->time: combat->guard_end) - + combat->ring_start; + + loshader_combat_ring_drawer_add_instance( + combat->drawer, &(loshader_combat_ring_drawer_instance_t) { + .range = .7f, + .start = st/ring_dur, + .end = ed/ring_dur, + .color = vec4(.1f, .1f, .7f, combat->alpha), + }); +} + +static void loplayer_combat_draw_clockhand_( + const loplayer_combat_t* combat) { + assert(combat != NULL); + + const uint64_t ring_dur = combat->ring_end - combat->ring_start; + assert(ring_dur > 0); + + assert(combat->ticker->time >= combat->ring_start); + const uint64_t cur = combat->ticker->time - combat->ring_start; + const float curf = cur*1.f/ring_dur; + + loshader_combat_ring_drawer_add_instance( + combat->drawer, &(loshader_combat_ring_drawer_instance_t) { + .range = 0, /* = draw clockhand */ + .start = curf, + .color = vec4(1, 1, 1, combat->alpha), + }); +} + +loplayer_combat_t* loplayer_combat_new( + loresource_sound_t* sound, + loshader_combat_ring_drawer_t* drawer, + const locommon_ticker_t* ticker, + loentity_store_t* entities, + loplayer_status_t* status, + loplayer_entity_t* entity, + size_t length) { + assert(sound != NULL); + assert(drawer != NULL); + assert(ticker != NULL); + assert(entities != NULL); + assert(status != NULL); + assert(entity != NULL); + assert(length > 0); + + loplayer_combat_t* combat = memory_new( + sizeof(*combat) + (length-1)*sizeof(combat->attacks[0])); + *combat = (typeof(*combat)) { + .sound = sound, + .drawer = drawer, + .ticker = ticker, + .entities = entities, + .status = status, + .entity = entity, + .length = length, + }; + + for (size_t i = 0; i < combat->length; ++i) { + combat->attacks[i] = (typeof(combat->attacks[0])) {0}; + } + return combat; +} + +void loplayer_combat_delete(loplayer_combat_t* combat) { + if (combat == NULL) return; + + memory_delete(combat); +} + +bool loplayer_combat_add_attack( + loplayer_combat_t* combat, const loplayer_combat_attack_t* attack) { + assert(combat != NULL); + assert(attack != NULL); + + if (loplayer_combat_find_attack_index_in_period_( + combat, NULL, attack->start, attack->start + attack->duration)) { + return false; + } + + size_t index; + if (!loplayer_combat_find_unused_attack_index_(combat, &index)) { + return false; + } + + combat->attacks[index] = *attack; + return true; +} + +bool loplayer_combat_accept_all_attacks(loplayer_combat_t* combat) { + assert(combat != NULL); + + if (combat->accepted) return true; + + if (!loplayer_combat_find_attack_index_in_period_( + combat, NULL, combat->ticker->time, SIZE_MAX)) { + return false; + } + + combat->accepted = true; + return true; +} + +void loplayer_combat_drop_all_attacks(loplayer_combat_t* combat) { + assert(combat != NULL); + + for (size_t i = 0; i < combat->length; ++i) { + combat->attacks[i] = (typeof(combat->attacks[0])) {0}; + } + combat->accepted = false; + combat->ring_start = 0; + combat->ring_end = 0; + combat->guard_start = 0; + combat->guard_end = 0; + combat->attack_end = 0; +} + +void loplayer_combat_guard(loplayer_combat_t* combat) { + assert(combat != NULL); + + if (combat->ring_end <= combat->ticker->time || + combat->guard_start > combat->guard_end) { + return; + } + combat->guard_start = combat->ticker->time; + + loresource_sound_play(combat->sound, "guard"); +} + +void loplayer_combat_unguard(loplayer_combat_t* combat) { + assert(combat != NULL); + + if (combat->guard_start <= combat->guard_end) return; + + combat->guard_end = combat->ticker->time; +} + +void loplayer_combat_update(loplayer_combat_t* combat) { + assert(combat != NULL); + + if (!combat->accepted) return; + + const uint64_t cur = combat->ticker->time; + const uint64_t pre = cur - combat->ticker->delta; + + combat->ring_end = 0; + for (size_t i = 0; i < combat->length; ++i) { + loentity_store_iterator_t itr = (typeof(itr)) {0}; + + if (!loentity_store_find_item_by_id( + combat->entities, &itr, combat->attacks[i].attacker)) { + combat->attacks[i].start = 0; + combat->attacks[i].duration = 0; + continue; + } + + const uint64_t st = combat->attacks[i].start; + const uint64_t ed = combat->attacks[i].duration + st; + + const bool pre_active = st <= pre && pre < ed; + const bool cur_active = st <= cur && cur < ed; + if (!pre_active && cur_active) { + combat->attack_end = ed; + } else if (pre_active && !cur_active) { + loplayer_combat_handle_attack_( + combat, &combat->attacks[i], itr.character); + } + combat->ring_end = MATH_MAX(combat->ring_end, ed); + } + if (combat->ring_end > cur) { + if (combat->ring_start == 0) { + combat->ring_start = cur; + } + locommon_easing_smooth_float( + &combat->alpha, 1, combat->ticker->delta_f*10); + } else { + combat->alpha = 0; + loplayer_combat_drop_all_attacks(combat); + } +} + +void loplayer_combat_draw_ui(const loplayer_combat_t* combat) { + assert(combat != NULL); + + if (!combat->accepted || combat->ring_end <= combat->ticker->time) { + return; + } + loplayer_combat_draw_ring_base_(combat); + loplayer_combat_draw_attacks_(combat); + loplayer_combat_draw_guard_(combat); + loplayer_combat_draw_clockhand_(combat); +} + +void loplayer_combat_pack( + const loplayer_combat_t* combat, msgpack_packer* packer) { + assert(combat != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, 1); + + mpkutil_pack_str(packer, "attacks"); + + size_t len = 0; + for (size_t i = 0; i < combat->length; ++i) { + const uint64_t st = combat->attacks[i].start; + const uint64_t ed = combat->attacks[i].duration + st; + const uint64_t cur = combat->ticker->time; + + if (st <= cur && cur < ed) ++len; + } + msgpack_pack_array(packer, len); + + for (size_t i = 0; i < combat->length; ++i) { + const uint64_t st = combat->attacks[i].start; + const uint64_t ed = combat->attacks[i].duration + st; + const uint64_t cur = combat->ticker->time; + + if (st <= cur && cur < ed) { + loplayer_combat_attack_pack(&combat->attacks[i], packer); + } + } +} + +bool loplayer_combat_unpack( + loplayer_combat_t* combat, const msgpack_object* obj) { + assert(combat != NULL); + + loplayer_combat_drop_all_attacks(combat); + + if (obj == NULL) return false; + + const msgpack_object_map* root = mpkutil_get_map(obj); + + const msgpack_object_array* array = + mpkutil_get_array(mpkutil_get_map_item_by_str(root, "attacks")); + + size_t src = 0, dst = 0; + while (src < array->size && dst < combat->length) { + if (loplayer_combat_attack_unpack( + &combat->attacks[dst], &array->ptr[src++])) { + ++dst; + } + } + return true; +} + +void loplayer_combat_attack_pack( + const loplayer_combat_attack_t* attack, + msgpack_packer* packer) { + assert(attack != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, LOPLAYER_COMBAT_ATTACK_PARAM_TO_PACK_COUNT); + +# define pack_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + LOCOMMON_MSGPACK_PACK_ANY(packer, &attack->var); \ + } while (0) + + LOPLAYER_COMBAT_ATTACK_PARAM_TO_PACK_EACH_(pack_); + +# undef pack_ +} + +bool loplayer_combat_attack_unpack( + loplayer_combat_attack_t* attack, + const msgpack_object* obj) { + assert(attack != NULL); + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + +# define unpack_(name, var) do { \ + if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(name), &attack->var)) { \ + return false; \ + } \ + } while (0) + + LOPLAYER_COMBAT_ATTACK_PARAM_TO_PACK_EACH_(unpack_); + return true; + +# undef unpack_ +# undef item_ +} diff --git a/core/loplayer/combat.h b/core/loplayer/combat.h new file mode 100644 index 0000000..5d83f35 --- /dev/null +++ b/core/loplayer/combat.h @@ -0,0 +1,107 @@ +#pragma once + +#include +#include + +#include + +#include "util/math/vector.h" + +#include "core/locommon/ticker.h" +#include "core/loeffect/effect.h" +#include "core/loentity/store.h" +#include "core/loresource/sound.h" +#include "core/loshader/combat_ring.h" + +#include "./entity.h" +#include "./status.h" + +struct loplayer_combat_t; +typedef struct loplayer_combat_t loplayer_combat_t; + +typedef struct { + loentity_id_t attacker; + + uint64_t start; + uint64_t duration; + + vec2_t knockback; + + loeffect_t effect; +} loplayer_combat_attack_t; + +loplayer_combat_t* /* OWNERSHIP */ +loplayer_combat_new( + loresource_sound_t* sound, + loshader_combat_ring_drawer_t* drawer, + const locommon_ticker_t* ticker, + loentity_store_t* entities, + loplayer_status_t* status, + loplayer_entity_t* entity, + size_t length +); + +void +loplayer_combat_delete( + loplayer_combat_t* combat /* OWNERSHIP */ +); + +bool +loplayer_combat_add_attack( + loplayer_combat_t* combat, + const loplayer_combat_attack_t* attack +); + +bool +loplayer_combat_accept_all_attacks( + loplayer_combat_t* combat +); + +void +loplayer_combat_drop_all_attacks( + loplayer_combat_t* combat +); + +void +loplayer_combat_guard( + loplayer_combat_t* combat +); + +void +loplayer_combat_unguard( + loplayer_combat_t* combat +); + +void +loplayer_combat_update( + loplayer_combat_t* combat +); + +void +loplayer_combat_draw_ui( + const loplayer_combat_t* combat +); + +void +loplayer_combat_pack( + const loplayer_combat_t* combat, + msgpack_packer* packer +); + +bool +loplayer_combat_unpack( + loplayer_combat_t* combat, + const msgpack_object* obj /* NULLABLE */ +); + +void +loplayer_combat_attack_pack( + const loplayer_combat_attack_t* attack, + msgpack_packer* packer +); + +bool +loplayer_combat_attack_unpack( + loplayer_combat_attack_t* attack, + const msgpack_object* obj /* NULLABLE */ +); diff --git a/core/loplayer/controller.c b/core/loplayer/controller.c new file mode 100644 index 0000000..b065143 --- /dev/null +++ b/core/loplayer/controller.c @@ -0,0 +1,79 @@ +#include "./controller.h" + +#include +#include +#include + +#include "core/locommon/input.h" +#include "core/locommon/position.h" + +void loplayer_controller_initialize(loplayer_controller_t* controller) { + assert(controller != NULL); + + *controller = (typeof(*controller)) {0}; +} + +void loplayer_controller_deinitialize(loplayer_controller_t* controller) { + assert(controller != NULL); + +} + +void loplayer_controller_update( + loplayer_controller_t* controller, + const locommon_input_t* input, + const locommon_position_t* cursor) { + assert(controller != NULL); + assert(input != NULL); + assert(locommon_position_valid(cursor)); + + controller->looking = *cursor; + controller->cursor = input->cursor; + + controller->movement = LOPLAYER_CONTROLLER_MOVEMENT_NONE; + controller->action = LOPLAYER_CONTROLLER_ACTION_NONE; + + const bool prev_jump = + controller->prev.buttons & LOCOMMON_INPUT_BUTTON_JUMP; + const bool prev_guarding = + controller->prev.buttons & LOCOMMON_INPUT_BUTTON_GUARD; + const bool prev_dash = + controller->prev.buttons & LOCOMMON_INPUT_BUTTON_DASH; + const bool prev_menu = + controller->prev.buttons & LOCOMMON_INPUT_BUTTON_MENU; + + if (input->buttons & LOCOMMON_INPUT_BUTTON_JUMP && !prev_jump) { + controller->movement = LOPLAYER_CONTROLLER_MOVEMENT_JUMP; + + } else if (input->buttons & LOCOMMON_INPUT_BUTTON_LEFT) { + controller->movement = LOPLAYER_CONTROLLER_MOVEMENT_WALK_LEFT; + if (input->buttons & LOCOMMON_INPUT_BUTTON_DASH) { + controller->movement = LOPLAYER_CONTROLLER_MOVEMENT_DASH_LEFT; + } + + } else if (input->buttons & LOCOMMON_INPUT_BUTTON_RIGHT) { + controller->movement = LOPLAYER_CONTROLLER_MOVEMENT_WALK_RIGHT; + if (input->buttons & LOCOMMON_INPUT_BUTTON_DASH) { + controller->movement = LOPLAYER_CONTROLLER_MOVEMENT_DASH_RIGHT; + } + } + + if (input->buttons & LOCOMMON_INPUT_BUTTON_ATTACK) { + controller->action = LOPLAYER_CONTROLLER_ACTION_ATTACK; + } + + if (input->buttons & LOCOMMON_INPUT_BUTTON_GUARD) { + if (!prev_guarding) controller->action = LOPLAYER_CONTROLLER_ACTION_GUARD; + } else { + if (prev_guarding) controller->action = LOPLAYER_CONTROLLER_ACTION_UNGUARD; + } + + if (input->buttons & LOCOMMON_INPUT_BUTTON_DASH && !prev_dash) { + controller->action = LOPLAYER_CONTROLLER_ACTION_DODGE; + } + + if (input->buttons & LOCOMMON_INPUT_BUTTON_MENU && !prev_menu) { + controller->action = LOPLAYER_CONTROLLER_ACTION_MENU; + } + + controller->prev = *input; +} diff --git a/core/loplayer/controller.h b/core/loplayer/controller.h new file mode 100644 index 0000000..7ff7bc8 --- /dev/null +++ b/core/loplayer/controller.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +#include "core/locommon/input.h" +#include "core/locommon/position.h" + +typedef enum { + LOPLAYER_CONTROLLER_MOVEMENT_NONE, + LOPLAYER_CONTROLLER_MOVEMENT_JUMP, + LOPLAYER_CONTROLLER_MOVEMENT_WALK_LEFT, + LOPLAYER_CONTROLLER_MOVEMENT_WALK_RIGHT, + LOPLAYER_CONTROLLER_MOVEMENT_DASH_LEFT, + LOPLAYER_CONTROLLER_MOVEMENT_DASH_RIGHT, +} loplayer_controller_movement_t; + +typedef enum { + LOPLAYER_CONTROLLER_ACTION_NONE, + LOPLAYER_CONTROLLER_ACTION_ATTACK, + LOPLAYER_CONTROLLER_ACTION_GUARD, + LOPLAYER_CONTROLLER_ACTION_UNGUARD, + LOPLAYER_CONTROLLER_ACTION_DODGE, + LOPLAYER_CONTROLLER_ACTION_MENU, +} loplayer_controller_action_t; + +typedef struct { + locommon_position_t looking; + vec2_t cursor; /* display coordinate (-1~1) */ + + loplayer_controller_movement_t movement; + loplayer_controller_action_t action; + + locommon_input_t prev; +} loplayer_controller_t; + +void +loplayer_controller_initialize( + loplayer_controller_t* controller +); + +void +loplayer_controller_deinitialize( + loplayer_controller_t* controller +); + +void +loplayer_controller_update( + loplayer_controller_t* controller, + const locommon_input_t* input, + const locommon_position_t* cursor +); diff --git a/core/loplayer/entity.c b/core/loplayer/entity.c new file mode 100644 index 0000000..f01e00e --- /dev/null +++ b/core/loplayer/entity.c @@ -0,0 +1,311 @@ +#include "./entity.h" + +#include +#include +#include +#include + +#include + +#include "util/math/algorithm.h" +#include "util/math/vector.h" +#include "util/memory/memory.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/locommon/easing.h" +#include "core/locommon/msgpack.h" +#include "core/locommon/physics.h" +#include "core/locommon/ticker.h" +#include "core/loentity/character.h" +#include "core/loentity/entity.h" +#include "core/loentity/store.h" +#include "core/loresource/sound.h" +#include "core/loshader/character.h" + +#include "./event.h" +#include "./status.h" + +#define LOPLAYER_ENTITY_WIDTH .02f +#define LOPLAYER_ENTITY_HEIGHT .05f +#define LOPLAYER_ENTITY_DRAW_SIZE LOPLAYER_ENTITY_HEIGHT +#define LOPLAYER_ENTITY_SHIFT_Y .03f + +#define LOPLAYER_ENTITY_GRAVITY_ACCELARATION 2.2f +#define LOPLAYER_ENTITY_RECOVERY_ACCELARATION 1.f + +#define LOPLAYER_ENTITY_MAX_GRAVITY 2.f + +#define LOPLAYER_ENTITY_DIRECTION_EPSILON .05f + +#define LOPLAYER_ENTITY_PARAM_TO_PACK_EACH_(PROC) do { \ + PROC("pos", super.super.pos); \ + PROC("movement", movement); \ + PROC("knockback", knockback); \ + PROC("gravity", gravity); \ +} while (0) +#define LOPLAYER_ENTITY_PARAM_TO_PACK_COUNT 4 + +static void loplayer_entity_update_position_( + loplayer_entity_t* p, vec2_t* velocity) { + assert(p != NULL); + assert(vec2_valid(velocity)); + + vec2_t disp = *velocity; + vec2_muleq(&disp, p->ticker->delta_f); + + vec2_addeq(&p->super.super.pos.fract, &disp); + p->super.super.pos.fract.y -= LOPLAYER_ENTITY_SHIFT_Y; + locommon_position_reduce(&p->super.super.pos); + + locommon_physics_entity_t e = { + .size = vec2(LOPLAYER_ENTITY_WIDTH, LOPLAYER_ENTITY_HEIGHT), + .pos = p->super.super.pos, + .velocity = *velocity, + }; + + loentity_store_solve_collision_between_ground( + p->entities, &e, p->ticker->delta_f); + + p->super.super.pos = e.pos; + + p->super.super.pos.fract.y += LOPLAYER_ENTITY_SHIFT_Y; + locommon_position_reduce(&p->super.super.pos); + + p->on_ground = false; + if (e.velocity.y == 0) { + if (velocity->y <= 0) { + p->on_ground = true; + } + if (p->gravity*velocity->y >= 0) p->gravity = 0; + if (p->knockback.y*velocity->y >= 0) p->knockback.y = 0; + } + if (e.velocity.x == 0 && velocity->x != 0) { + if (p->knockback.x*velocity->x >= 0) p->knockback.x = 0; + } + p->last_velocity = *velocity = e.velocity; +} + +static void loplayer_entity_bind_in_event_area_(loplayer_entity_t* p) { + assert(p != NULL); + + const loplayer_event_param_t* e = loplayer_event_get_param(p->event); + if (e == NULL || e->area_size.x <= 0 || e->area_size.y <= 0) return; + + vec2_t v; + locommon_position_sub(&v, &p->super.super.pos, &e->area_pos); + + if (MATH_ABS(v.x) > e->area_size.x) { + v.x = MATH_SIGN(v.x) * e->area_size.x; + } + if (MATH_ABS(v.y) > e->area_size.y) { + v.y = MATH_SIGN(v.y) * e->area_size.y; + } + + p->super.super.pos = e->area_pos; + vec2_addeq(&p->super.super.pos.fract, &v); + locommon_position_reduce(&p->super.super.pos); +} + +static void loplayer_entity_delete_(loentity_t* entity) { + assert(entity != NULL); + + /* does not anything */ +} + +static bool loplayer_entity_update_(loentity_t* entity) { + assert(entity != NULL); + + loplayer_entity_t* p = (typeof(p)) entity; + + /* ---- position ---- */ + vec2_t velocity = p->movement; + vec2_addeq(&velocity, &p->knockback); + velocity.y += p->gravity; + loplayer_entity_update_position_(p, &velocity); + loplayer_entity_bind_in_event_area_(p); + + /* ---- gravity ---- */ + const float dt = p->ticker->delta_f; + p->gravity -= LOPLAYER_ENTITY_GRAVITY_ACCELARATION*dt; + p->gravity = MATH_MAX(p->gravity, -LOPLAYER_ENTITY_MAX_GRAVITY); + + /* ---- recovery from knockback ---- */ + locommon_easing_linear_float( + &p->knockback.x, 0, LOPLAYER_ENTITY_RECOVERY_ACCELARATION*dt); + locommon_easing_linear_float( + &p->knockback.y, 0, LOPLAYER_ENTITY_RECOVERY_ACCELARATION*dt); + return true; +} + +static void loplayer_entity_draw_( + loentity_t* entity, const locommon_position_t* basepos) { + assert(entity != NULL); + assert(basepos != NULL); + + loplayer_entity_t* p = (typeof(p)) entity; + + locommon_position_t center = p->super.super.pos; + center.fract.y -= LOPLAYER_ENTITY_SHIFT_Y; + locommon_position_reduce(¢er); + + loshader_character_drawer_instance_t instance = { + .character_id = LOSHADER_CHARACTER_ID_PLAYER, + .from_motion_id = p->motion.from, + .to_motion_id = p->motion.to, + .motion_time = p->motion.time, + .marker = p->status->bullet_immune_until < p->ticker->time, + .marker_offset = vec2(0, LOPLAYER_ENTITY_SHIFT_Y), + + .size = vec2( + LOPLAYER_ENTITY_DRAW_SIZE*p->direction, LOPLAYER_ENTITY_DRAW_SIZE), + .color = vec4(0, 0, 0, 1), + }; + locommon_position_sub(&instance.pos, ¢er, basepos); + + loshader_character_drawer_add_instance(p->drawer, &instance); +} + +static void loplayer_entity_apply_effect_( + loentity_character_t* chara, const loeffect_t* effect) { + assert(chara != NULL); + assert(effect != NULL); + + loplayer_entity_t* p = (typeof(p)) chara; + loplayer_status_apply_effect(p->status, effect); +} + +static void loplayer_entity_knockback_( + loentity_character_t* chara, const vec2_t* v) { + assert(chara != NULL); + assert(vec2_valid(v)); + + loplayer_entity_t* p = (typeof(p)) chara; + vec2_addeq(&p->knockback, v); +} + +void loplayer_entity_initialize( + loplayer_entity_t* player, + loentity_id_t id, + loresource_sound_t* sound, + loshader_character_drawer_t* drawer, + const locommon_ticker_t* ticker, + loentity_store_t* entities, + const loplayer_event_t* event, + loplayer_status_t* status) { + assert(player != NULL); + assert(sound != NULL); + assert(drawer != NULL); + assert(ticker != NULL); + assert(entities != NULL); + assert(event != NULL); + assert(status != NULL); + + *player = (typeof(*player)) { + .super = { + .super = { + .vtable = { + .delete = loplayer_entity_delete_, + .update = loplayer_entity_update_, + .draw = loplayer_entity_draw_, + }, + .subclass = LOENTITY_SUBCLASS_CHARACTER, + .id = id, + .pos = locommon_position(0, 0, vec2(0.5, 0.5)), + .dont_save = true, + }, + .vtable = { + .apply_effect = loplayer_entity_apply_effect_, + .knockback = loplayer_entity_knockback_, + }, + }, + .sound = sound, + .drawer = drawer, + .ticker = ticker, + .entities = entities, + .event = event, + .status = status, + + .direction = 1, + }; +} + +void loplayer_entity_deinitialize(loplayer_entity_t* player) { + assert(player != NULL); + +} + +void loplayer_entity_move( + loplayer_entity_t* player, const locommon_position_t* pos) { + assert(player != NULL); + assert(locommon_position_valid(pos)); + + player->super.super.pos = *pos; + player->on_ground = false; + player->movement = vec2(0, 0); + player->knockback = vec2(0, 0); + player->gravity = 0; +} + +void loplayer_entity_aim( + loplayer_entity_t* player, const locommon_position_t* pos) { + assert(player != NULL); + assert(locommon_position_valid(pos)); + + vec2_t dir; + locommon_position_sub(&dir, pos, &player->super.super.pos); + + if (MATH_ABS(dir.x) > LOPLAYER_ENTITY_DIRECTION_EPSILON) { + player->direction = MATH_SIGN(dir.x); + } +} + +bool loplayer_entity_affect_bullet(loplayer_entity_t* player) { + assert(player != NULL); + + return loentity_store_affect_bullets_shot_by_others( + player->entities, + &player->super, + &player->last_velocity, + player->ticker->delta_f); +} + +void loplayer_entity_pack( + const loplayer_entity_t* player, msgpack_packer* packer) { + assert(player != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, LOPLAYER_ENTITY_PARAM_TO_PACK_COUNT); + +# define pack_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + LOCOMMON_MSGPACK_PACK_ANY(packer, &player->var); \ + } while (0) + + LOPLAYER_ENTITY_PARAM_TO_PACK_EACH_(pack_); + +# undef pack_ +} + +bool loplayer_entity_unpack( + loplayer_entity_t* player, const msgpack_object* obj) { + assert(player != NULL); + + if (obj == NULL) return false; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + +# define unpack_(name, var) do { \ + if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(name), &player->var)) { \ + return false; \ + } \ + } while (0) + + LOPLAYER_ENTITY_PARAM_TO_PACK_EACH_(unpack_); + return true; + +# undef unpack_ +# undef item_ +} diff --git a/core/loplayer/entity.h b/core/loplayer/entity.h new file mode 100644 index 0000000..37c1d6a --- /dev/null +++ b/core/loplayer/entity.h @@ -0,0 +1,93 @@ +#pragma once + +#include + +#include + +#include "util/math/vector.h" + +#include "core/locommon/ticker.h" +#include "core/loentity/character.h" +#include "core/loentity/store.h" +#include "core/loresource/sound.h" +#include "core/loshader/character.h" + +#include "./event.h" +#include "./status.h" + +typedef struct { + loentity_character_t super; + + /* injected deps */ + const locommon_ticker_t* ticker; + loresource_sound_t* sound; + loshader_character_drawer_t* drawer; + loentity_store_t* entities; + const loplayer_event_t* event; + loplayer_status_t* status; + + /* read-only mutable params */ + float direction; + + /* public params */ + vec2_t movement; + vec2_t knockback; + float gravity; + + /* temporary cache for update */ + bool on_ground; + vec2_t last_velocity; + + /* temporary cache for draw */ + struct { + loshader_character_motion_id_t from; + loshader_character_motion_id_t to; + float time; + } motion; +} loplayer_entity_t; + +void +loplayer_entity_initialize( + loplayer_entity_t* player, + loentity_id_t id, + loresource_sound_t* sound, + loshader_character_drawer_t* drawer, + const locommon_ticker_t* ticker, + loentity_store_t* entities, + const loplayer_event_t* event, + loplayer_status_t* status +); + +void +loplayer_entity_deinitialize( + loplayer_entity_t* player +); + +void +loplayer_entity_move( + loplayer_entity_t* player, + const locommon_position_t* pos +); + +void +loplayer_entity_aim( + loplayer_entity_t* player, + const locommon_position_t* pos +); + +bool +loplayer_entity_affect_bullet( + loplayer_entity_t* player +); + +void +loplayer_entity_pack( + const loplayer_entity_t* player, + msgpack_packer* packer +); + +bool +loplayer_entity_unpack( + loplayer_entity_t* player, + const msgpack_object* obj +); diff --git a/core/loplayer/event.c b/core/loplayer/event.c new file mode 100644 index 0000000..2039473 --- /dev/null +++ b/core/loplayer/event.c @@ -0,0 +1,146 @@ +#include "./event.h" + +#include +#include +#include + +#include "util/glyphas/block.h" +#include "util/glyphas/cache.h" +#include "util/math/vector.h" +#include "util/memory/memory.h" + +#include "core/locommon/position.h" +#include "core/loresource/set.h" +#include "core/loshader/set.h" + +struct loplayer_event_t { + loplayer_event_param_t param; + /* convertible between loplayer_event_t* and loplayer_event_param_t* */ + + /* injected deps */ + loresource_set_t* res; + loshader_set_t* shaders; + + /* owned objects */ + glyphas_cache_t* font; + glyphas_block_t* text; + + /* immutable params */ + struct { + vec2_t fontsz; + } geometry; +}; + +static void loplayer_event_calculate_geometry_(loplayer_event_t* event) { + assert(event != NULL); + + typeof(event->geometry)* geo = &event->geometry; + + geo->fontsz = event->shaders->dpi; + vec2_muleq(&geo->fontsz, .15f); +} + +loplayer_event_t* loplayer_event_new( + loresource_set_t* res, loshader_set_t* shaders) { + assert(res != NULL); + assert(shaders != NULL); + + loplayer_event_t* event = memory_new(sizeof(*event)); + *event = (typeof(*event)) { + .res = res, + .shaders = shaders, + }; + loplayer_event_calculate_geometry_(event); + + event->font = glyphas_cache_new( + shaders->tex.event_line, + &res->font.serif, + event->geometry.fontsz.x, + event->geometry.fontsz.y); + + event->text = glyphas_block_new( + GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL, + -event->geometry.fontsz.y, + INT32_MAX, + 256); + + return event; +} + +void loplayer_event_delete(loplayer_event_t* event) { + if (event == NULL) return; + + glyphas_cache_delete(event->font); + glyphas_block_delete(event->text); + + memory_delete(event); +} + +loplayer_event_param_t* loplayer_event_take_control( + loplayer_event_t* event, loentity_id_t id) { + assert(event != NULL); + + if (event->param.controlled) return NULL; + + event->param = (typeof(event->param)) { + .controlled = true, + .controlled_by = id, + }; + return &event->param; +} + +void loplayer_event_abort(loplayer_event_t* event) { + assert(event != NULL); + + if (!event->param.controlled) return; + + loplayer_event_param_release_control(&event->param); +} + +void loplayer_event_draw(const loplayer_event_t* event) { + assert(event != NULL); + + if (!event->param.controlled) return; + + loshader_event_line_drawer_add_block( + &event->shaders->drawer.event_line, event->text); +} + +const loplayer_event_param_t* loplayer_event_get_param( + const loplayer_event_t* event) { + assert(event != NULL); + + if (!event->param.controlled) return NULL; + + return &event->param; +} + +void loplayer_event_param_set_line( + loplayer_event_param_t* param, const char* str, size_t len) { + assert(param != NULL); + + loplayer_event_t* event = (typeof(event)) param; + + glyphas_block_clear(event->text); + + static const vec4_t white = vec4(1, 1, 1, 1); + glyphas_block_add_characters(event->text, event->font, &white, str, len); + + static const vec2_t center = vec2(.5f, -.5f); + glyphas_block_set_origin(event->text, ¢er); + + const vec2_t scale = vec2( + 2/event->shaders->resolution.x, 2/event->shaders->resolution.y); + glyphas_block_scale(event->text, &scale); + + static const vec2_t trans = vec2(0, -.85f); + glyphas_block_translate(event->text, &trans); +} + +void loplayer_event_param_release_control(loplayer_event_param_t* param) { + assert(param != NULL); + assert(param->controlled); + + loplayer_event_param_set_line(param, "", 0); + param->controlled = false; +} diff --git a/core/loplayer/event.h b/core/loplayer/event.h new file mode 100644 index 0000000..01057f4 --- /dev/null +++ b/core/loplayer/event.h @@ -0,0 +1,74 @@ +#pragma once + +#include +#include + +#include "util/math/vector.h" + +#include "core/locommon/position.h" +#include "core/loentity/entity.h" +#include "core/loresource/music.h" +#include "core/loresource/set.h" +#include "core/loshader/set.h" + +struct loplayer_event_t; +typedef struct loplayer_event_t loplayer_event_t; + +typedef struct { + bool controlled; + /* You can return the control by assigning false. */ + + loentity_id_t controlled_by; + + locommon_position_t area_pos; + vec2_t area_size; + + bool cinescope; + bool hide_hud; + + loresource_music_player_t* music; +} loplayer_event_param_t; + +loplayer_event_t* /* OWNERSHIP */ +loplayer_event_new( + loresource_set_t* res, + loshader_set_t* shaders +); + +void +loplayer_event_delete( + loplayer_event_t* event /* OWNERSHIP*/ +); + +loplayer_event_param_t* /* NULLABLE */ +loplayer_event_take_control( + loplayer_event_t* event, + loentity_id_t id +); + +void +loplayer_event_abort( + loplayer_event_t* event +); + +void +loplayer_event_draw( + const loplayer_event_t* event +); + +const loplayer_event_param_t* /* NULLABLE */ +loplayer_event_get_param( + const loplayer_event_t* event +); + +void +loplayer_event_param_set_line( + loplayer_event_param_t* param, + const char* str, + size_t len +); + +void +loplayer_event_param_release_control( + loplayer_event_param_t* param +); diff --git a/core/loplayer/hud.c b/core/loplayer/hud.c new file mode 100644 index 0000000..7ccaff7 --- /dev/null +++ b/core/loplayer/hud.c @@ -0,0 +1,409 @@ +#include "./hud.h" + +#include +#include +#include +#include + +#include "util/conv/charcode.h" +#include "util/glyphas/block.h" +#include "util/glyphas/cache.h" +#include "util/math/algorithm.h" +#include "util/memory/memory.h" + +#include "core/locommon/easing.h" +#include "core/locommon/position.h" +#include "core/locommon/ticker.h" +#include "core/loresource/set.h" +#include "core/loresource/text.h" +#include "core/loshader/hud_bar.h" +#include "core/loshader/set.h" + +#include "./entity.h" +#include "./event.h" +#include "./status.h" + +typedef struct { + float alpha; + float remain; + + glyphas_block_t* text; +} loplayer_hud_effect_t; + +struct loplayer_hud_t { + /* injected deps */ + loshader_set_t* shaders; + const locommon_ticker_t* ticker; + const loplayer_event_t* event; + const loplayer_status_t* status; + const loplayer_entity_t* entity; + + /* owned objects */ + struct { + glyphas_cache_t* sans; + glyphas_cache_t* serif; + } font; + + /* immutable params */ + struct { + vec2_t fontsz_normal_px; + vec2_t fontsz_normal; + vec2_t fontsz_small_px; + vec2_t fontsz_small; + + vec2_t padding; + + vec2_t madness_bar_pos; + vec2_t madness_bar_size; + + vec2_t faith_bar_pos; + vec2_t faith_bar_size; + + vec2_t effect_bar_pos; + vec2_t effect_bar_size; + } geometry; + + /* mutable params */ + float alpha; + + float prev_madness; + float prev_faith; + + bool shown; + + glyphas_block_t* biome_text; + + union { + struct { + loplayer_hud_effect_t curse; + loplayer_hud_effect_t amnesia; + loplayer_hud_effect_t lost; + }; + loplayer_hud_effect_t array[3]; + } effects; +}; + +static void loplayer_hud_calculate_geometry_(loplayer_hud_t* hud) { + assert(hud != NULL); + + const vec2_t* dpi = &hud->shaders->dpi; + const vec2_t* reso = &hud->shaders->resolution; + + typeof(hud->geometry)* geo = &hud->geometry; + +# define px_to_disp_(v) vec2((v).x/reso->x*2, (v).y/reso->y*2) + + geo->fontsz_normal_px = *dpi; + vec2_muleq(&geo->fontsz_normal_px, .4f); + + geo->fontsz_normal = px_to_disp_(geo->fontsz_normal_px); + + geo->fontsz_small_px = *dpi; + vec2_muleq(&geo->fontsz_small_px, .3f); + + geo->fontsz_small = px_to_disp_(geo->fontsz_small_px); + + geo->padding = *dpi; + vec2_muleq(&geo->padding, .4f); + geo->padding = px_to_disp_(geo->padding); + + geo->madness_bar_size = vec2(.5f, .1f*dpi->y); + geo->madness_bar_size.y /= reso->y/2; + + geo->madness_bar_pos = vec2( + -1 + geo->padding.x + geo->madness_bar_size.x, + 1 - geo->padding.y - geo->madness_bar_size.y); + + geo->faith_bar_size = vec2(.4f, .05f*dpi->y); + geo->faith_bar_size.y /= reso->y/2; + + geo->faith_bar_pos = vec2( + -1 + geo->padding.x + geo->faith_bar_size.x, + 1 - geo->padding.y - geo->madness_bar_size.y*2 - geo->faith_bar_size.y); + + geo->effect_bar_size = vec2(3*dpi->x/reso->x, 4/reso->y); + + geo->effect_bar_pos = vec2(1-geo->padding.x, -1+geo->padding.y); + vec2_subeq(&geo->effect_bar_pos, &geo->effect_bar_size); + +# undef px_to_disp_ +} + +static glyphas_block_t* loplayer_hud_create_effect_text_block_( + const loplayer_hud_t* hud, const char* text) { + assert(hud != NULL); + + glyphas_block_t* block = glyphas_block_new( + GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL, + -hud->geometry.fontsz_normal_px.y, + INT32_MAX, + 32); + + static const vec4_t white = vec4(1, 1, 1, 1); + glyphas_block_add_characters( + block, hud->font.sans, &white, text, strlen(text)); + + const vec2_t scale = vec2( + 2/hud->shaders->resolution.x, 2/hud->shaders->resolution.y); + glyphas_block_scale(block, &scale); + + return block; +} + +static void loplayer_hud_initialize_effect_text_( + loplayer_hud_t* hud, loresource_set_t* res) { + assert(hud != NULL); + +# define init_effect_text_(name) \ + hud->effects.name.text = loplayer_hud_create_effect_text_block_( \ + hud, loresource_text_get(res->lang, "effect_"#name)) + + init_effect_text_(curse); + init_effect_text_(amnesia); + init_effect_text_(lost); + +# undef init_effect_ +} + +static void loplayer_hud_update_bars_(loplayer_hud_t* hud) { + assert(hud != NULL); + + const float d = hud->ticker->delta_f; + + locommon_easing_smooth_float( + &hud->prev_madness, hud->status->recipient.madness, d*2); + locommon_easing_smooth_float( + &hud->prev_faith, hud->status->recipient.faith, d*2); +} + +static void loplayer_hud_update_lasting_effect_( + loplayer_hud_t* hud, + loplayer_hud_effect_t* e, + const loeffect_generic_lasting_param_t* p) { + assert(hud != NULL); + assert(e != NULL); + assert(p != NULL); + + const uint64_t end = p->begin + p->duration; + const uint64_t t = hud->ticker->time; + if (p->duration == 0 || end <= t || p->begin > t) { + e->remain = 0; + return; + } + e->remain = 1 - (t - p->begin)*1.f/p->duration; +} + +static void loplayer_hud_update_effects_(loplayer_hud_t* hud) { + assert(hud != NULL); + + const float d = hud->ticker->delta_f; + + loplayer_hud_update_lasting_effect_( + hud, &hud->effects.curse, &hud->status->recipient.effects.curse); + loplayer_hud_update_lasting_effect_( + hud, &hud->effects.amnesia, &hud->status->recipient.effects.amnesia); + hud->effects.lost.remain = + hud->status->recipient.faith > 0? 0: (1-hud->status->recipient.madness); + + static const size_t len = + sizeof(hud->effects.array)/sizeof(hud->effects.array[0]); + for (size_t i = 0; i < len; ++i) { + loplayer_hud_effect_t* e = &hud->effects.array[i]; + locommon_easing_linear_float(&e->alpha, !!(e->remain > 0), d*2); + } +} + +static void loplayer_hud_draw_bars_(const loplayer_hud_t* hud) { + assert(hud != NULL); + + const typeof(hud->geometry)* geo = &hud->geometry; + + const loshader_hud_bar_drawer_instance_t madness = { + .pos = geo->madness_bar_pos, + .size = geo->madness_bar_size, + .bgcolor = vec4(0, 0, 0, 1*hud->alpha), + .fgcolor = vec4(1, 1, 1, .9f*hud->alpha), + .value = MATH_CLAMP(hud->status->recipient.madness, 0, 1), + .prev_value = MATH_CLAMP(hud->prev_madness, 0, 1), + }; + loshader_hud_bar_drawer_add_instance(hud->shaders->drawer.hud_bar, &madness); + + const loshader_hud_bar_drawer_instance_t faith = { + .pos = geo->faith_bar_pos, + .size = geo->faith_bar_size, + .bgcolor = vec4(0, 0, 0, 1*hud->alpha), + .fgcolor = vec4(.9f, .9f, .9f, .9f*hud->alpha), + .value = MATH_CLAMP(hud->status->recipient.faith, 0, 1), + .prev_value = MATH_CLAMP(hud->prev_faith, 0, 1), + }; + loshader_hud_bar_drawer_add_instance(hud->shaders->drawer.hud_bar, &faith); +} + +static void loplayer_hud_draw_effects_(const loplayer_hud_t* hud) { + assert(hud != NULL); + + const typeof(hud->geometry)* geo = &hud->geometry; + + const float lineheight = hud->geometry.fontsz_normal.y; + float h = 0; + + static const size_t len = + sizeof(hud->effects.array)/sizeof(hud->effects.array[0]); + for (size_t i = 0; i < len; ++i) { + const loplayer_hud_effect_t* e = &hud->effects.array[i]; + if (e->alpha == 0) continue; + + const float y = h*lineheight + geo->effect_bar_pos.y; + + static const vec2_t origin = vec2(1, -1); + glyphas_block_set_origin(e->text, &origin); + + const vec2_t trans = vec2(1-geo->padding.x, y); + glyphas_block_translate(e->text, &trans); + glyphas_block_set_alpha(e->text, e->alpha); + + loshader_hud_text_drawer_add_block( + &hud->shaders->drawer.hud_text, e->text); + + const loshader_hud_bar_drawer_instance_t instance = { + .pos = vec2(geo->effect_bar_pos.x, y), + .size = vec2(-geo->effect_bar_size.x, geo->effect_bar_size.y), + .bgcolor = vec4(1, 1, 1, .2f*hud->alpha), + .fgcolor = vec4(1, 1, 1, .8f*hud->alpha), + .value = MATH_CLAMP(e->remain, 0, 1), + .prev_value = MATH_CLAMP(e->remain, 0, 1), + }; + loshader_hud_bar_drawer_add_instance( + hud->shaders->drawer.hud_bar, &instance); + + h += e->alpha * e->alpha * (3-2*e->alpha); + } +} + +loplayer_hud_t* loplayer_hud_new( + loresource_set_t* res, + loshader_set_t* shaders, + const locommon_ticker_t* ticker, + const loplayer_event_t* event, + const loplayer_status_t* status, + const loplayer_entity_t* entity) { + assert(res != NULL); + assert(shaders != NULL); + assert(ticker != NULL); + assert(event != NULL); + assert(status != NULL); + assert(entity != NULL); + + loplayer_hud_t* hud = memory_new(sizeof(*hud)); + *hud = (typeof(*hud)) { + .shaders = shaders, + .ticker = ticker, + .event = event, + .status = status, + .entity = entity, + }; + loplayer_hud_calculate_geometry_(hud); + + hud->font = (typeof(hud->font)) { + .sans = glyphas_cache_new( + shaders->tex.hud_text, + &res->font.sans, + hud->geometry.fontsz_normal_px.x, + hud->geometry.fontsz_normal_px.y), + .serif = glyphas_cache_new( + shaders->tex.hud_text, + &res->font.serif, + hud->geometry.fontsz_small_px.x, + hud->geometry.fontsz_small_px.y), + }; + + hud->biome_text = glyphas_block_new( + GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL, + -hud->geometry.fontsz_normal_px.y, + INT32_MAX, + 32); + + loplayer_hud_initialize_effect_text_(hud, res); + + return hud; +} + +void loplayer_hud_delete(loplayer_hud_t* hud) { + if (hud == NULL) return; + + static const size_t len = + sizeof(hud->effects.array) / sizeof(hud->effects.array[0]); + for (size_t i = 0; i < len; ++i) { + glyphas_block_delete(hud->effects.array[i].text); + } + + glyphas_block_delete(hud->biome_text); + + glyphas_cache_delete(hud->font.sans); + glyphas_cache_delete(hud->font.serif); + + memory_delete(hud); +} + +void loplayer_hud_show(loplayer_hud_t* hud) { + assert(hud != NULL); + + hud->shown = true; +} + +void loplayer_hud_hide(loplayer_hud_t* hud) { + assert(hud != NULL); + + hud->shown = false; +} + +void loplayer_hud_set_biome_text(loplayer_hud_t* hud, const char* text) { + assert(hud != NULL); + + glyphas_block_clear(hud->biome_text); + + static const vec4_t white = vec4(1, 1, 1, 1); + glyphas_block_add_characters( + hud->biome_text, hud->font.serif, &white, text, strlen(text)); + + glyphas_block_set_origin(hud->biome_text, &vec2(0, -1)); + + const vec2_t scale = vec2( + 2/hud->shaders->resolution.x, 2/hud->shaders->resolution.y); + glyphas_block_scale(hud->biome_text, &scale); + + const typeof(hud->geometry)* geo = &hud->geometry; + glyphas_block_translate( + hud->biome_text, &vec2(-1+geo->padding.x, -1+geo->padding.y)); + + glyphas_block_make_glitched(hud->biome_text, hud->ticker->time); +} + +void loplayer_hud_update(loplayer_hud_t* hud) { + assert(hud != NULL); + + bool shown = hud->shown; + + const loplayer_event_param_t* e = loplayer_event_get_param(hud->event); + if (e != NULL) { + shown = shown && !e->hide_hud; + } + + const float dt = hud->ticker->delta_f; + locommon_easing_smooth_float(&hud->alpha, !!shown, dt*2); + + loplayer_hud_update_bars_(hud); + loplayer_hud_update_effects_(hud); +} + +void loplayer_hud_draw_ui(const loplayer_hud_t* hud) { + assert(hud != NULL); + + hud->shaders->drawer.hud_text.alpha = hud->alpha; + + loplayer_hud_draw_bars_(hud); + loplayer_hud_draw_effects_(hud); + + loshader_hud_text_drawer_add_block( + &hud->shaders->drawer.hud_text, hud->biome_text); +} diff --git a/core/loplayer/hud.h b/core/loplayer/hud.h new file mode 100644 index 0000000..1c788d3 --- /dev/null +++ b/core/loplayer/hud.h @@ -0,0 +1,53 @@ +#pragma once + +#include "core/locommon/ticker.h" +#include "core/loresource/set.h" +#include "core/loshader/set.h" + +#include "./entity.h" +#include "./event.h" +#include "./status.h" + +struct loplayer_hud_t; +typedef struct loplayer_hud_t loplayer_hud_t; + +loplayer_hud_t* +loplayer_hud_new( + loresource_set_t* resource, + loshader_set_t* shaders, + const locommon_ticker_t* ticker, + const loplayer_event_t* event, + const loplayer_status_t* status, + const loplayer_entity_t* entity +); + +void +loplayer_hud_delete( + loplayer_hud_t* hud +); + +void +loplayer_hud_show( + loplayer_hud_t* hud +); + +void +loplayer_hud_hide( + loplayer_hud_t* hud +); + +void +loplayer_hud_set_biome_text( + loplayer_hud_t* hud, + const char* text +); + +void +loplayer_hud_update( + loplayer_hud_t* hud +); + +void +loplayer_hud_draw_ui( + const loplayer_hud_t* hud +); diff --git a/core/loplayer/menu.c b/core/loplayer/menu.c new file mode 100644 index 0000000..3e58593 --- /dev/null +++ b/core/loplayer/menu.c @@ -0,0 +1,565 @@ +#include "./menu.h" + +#include +#include +#include +#include +#include + +#include "util/glyphas/block.h" +#include "util/glyphas/cache.h" +#include "util/math/algorithm.h" +#include "util/math/constant.h" +#include "util/memory/memory.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/locommon/easing.h" +#include "core/locommon/ticker.h" +#include "core/loeffect/stance.h" +#include "core/loresource/set.h" +#include "core/loresource/text.h" +#include "core/loshader/set.h" + +#include "./controller.h" +#include "./entity.h" +#include "./status.h" + +typedef struct { + glyphas_block_t* name; + glyphas_block_t* desc; + glyphas_block_t* note; +} loplayer_menu_stance_text_set_t; + +typedef enum { + LOPLAYER_MENU_STATE_HIDDEN, + LOPLAYER_MENU_STATE_STATUS, + LOPLAYER_MENU_STATE_POPUP, +} loplayer_menu_state_t; + +struct loplayer_menu_t { + /* injected deps */ + loresource_set_t* res; + loshader_set_t* shaders; + + const locommon_ticker_t* ticker; + const loplayer_status_t* status; + const loplayer_controller_t* controller; + + /* owned objects */ + struct { + glyphas_cache_t* large; + glyphas_cache_t* normal; + glyphas_cache_t* small; + } font; + + struct { + loplayer_menu_stance_text_set_t stance[LOEFFECT_STANCE_ID_LENGTH_+1]; + /* [0] is for unknown stance */ + + glyphas_block_t* popup_title; + glyphas_block_t* popup_body; + + glyphas_block_t* exit; + } text; + + /* immutable params */ + struct { + vec2_t fontsz_large; + vec2_t fontsz_normal; + vec2_t fontsz_small; + + vec2_t fontsz_large_px; + vec2_t fontsz_normal_px; + vec2_t fontsz_small_px; + + vec2_t range; + vec2_t icon; + vec2_t padding; + } geometry; + + /* mutable params */ + loplayer_menu_state_t state; + float alpha; + + bool stance_hovering; + size_t display_stance_text_index; + + float stance_highlight; + size_t highlighted_stance; + + bool request_exit; +}; + +static void loplayer_menu_calculate_geometry_(loplayer_menu_t* menu) { + assert(menu != NULL); + + const vec2_t* dpi = &menu->shaders->dpi; + const vec2_t* reso = &menu->shaders->resolution; + + typeof(menu->geometry)* geo = &menu->geometry; + + geo->fontsz_large_px = *dpi; + vec2_muleq(&geo->fontsz_large_px, .2f); + + geo->fontsz_normal_px = *dpi; + vec2_muleq(&geo->fontsz_normal_px, .15f); + + geo->fontsz_small_px = *dpi; + vec2_muleq(&geo->fontsz_small_px, .12f); + +# define px_to_disp_(v) vec2((v).x/reso->x*2, (v).y/reso->y*2) + + geo->fontsz_large = px_to_disp_(geo->fontsz_large_px); + geo->fontsz_normal = px_to_disp_(geo->fontsz_normal_px); + geo->fontsz_small = px_to_disp_(geo->fontsz_small_px); + + geo->range = *dpi; + vec2_muleq(&geo->range, 2); + geo->range = px_to_disp_(geo->range); + vec2_diveq(&geo->range, MATH_MAX(geo->range.x, 1)); + vec2_diveq(&geo->range, MATH_MAX(geo->range.y, 1)); + + geo->icon = *dpi; + vec2_muleq(&geo->icon, .4f); + geo->icon = px_to_disp_(geo->icon); + + geo->padding = *dpi; + vec2_muleq(&geo->padding, .2f); + geo->padding = px_to_disp_(geo->padding); + +# undef px_to_disp_ +} + +static void loplayer_menu_initialize_stance_text_set_( + const loplayer_menu_t* menu, + loplayer_menu_stance_text_set_t* set, + const char* name, + const char* desc, + const char* note) { + assert(menu != NULL); + assert(set != NULL); + + const typeof(menu->geometry)* geo = &menu->geometry; + + *set = (typeof(*set)) { + .name = glyphas_block_new( + GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL, + -geo->fontsz_large_px.y, + INT32_MAX, + 32), + .desc = glyphas_block_new( + GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL, + -geo->fontsz_normal_px.y*1.3f, + geo->fontsz_normal_px.x*18, + 512), + .note = glyphas_block_new( + GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL, + -geo->fontsz_small_px.y*1.1f, + geo->fontsz_normal_px.x*18, + 512), + }; + + static const vec4_t white = vec4(1, 1, 1, 1); + static const vec4_t yellow = vec4(1, .7f, 0, 1); + + glyphas_block_add_characters( + set->name, menu->font.large, &white, name, strlen(name)); + glyphas_block_add_characters( + set->desc, menu->font.normal, &white, desc, strlen(desc)); + glyphas_block_add_characters( + set->note, menu->font.small, &yellow, note, strlen(note)); + + glyphas_block_set_origin(set->name, &vec2(.5f, -1)); + glyphas_block_set_origin(set->desc, &vec2(.5f, -.5f)); + glyphas_block_set_origin(set->note, &vec2(.5f, 0)); + + const vec2_t scale = + vec2(2/menu->shaders->resolution.x, 2/menu->shaders->resolution.y); + glyphas_block_scale(set->name, &scale); + glyphas_block_scale(set->desc, &scale); + glyphas_block_scale(set->note, &scale); + + vec2_t offset, size; + glyphas_block_calculate_geometry(set->desc, &offset, &size); + + glyphas_block_translate(set->name, &vec2(0, size.y/2 * 1.1f)); + glyphas_block_translate(set->note, &vec2(0, -size.y/2 * 1.2f)); +} + +static void loplayer_menu_deinitialize_stance_text_set_( + loplayer_menu_stance_text_set_t* set) { + assert(set != NULL); + + glyphas_block_delete(set->name); + glyphas_block_delete(set->desc); + glyphas_block_delete(set->note); +} + +static void loplayer_menu_create_stance_text_(loplayer_menu_t* menu) { + assert(menu != NULL); + +# define text_(name) loresource_text_get(menu->res->lang, name) + + loplayer_menu_initialize_stance_text_set_( + menu, &menu->text.stance[0], "???", "???", ""); + +# define init_stance_set_(NAME, name) \ + loplayer_menu_initialize_stance_text_set_( \ + menu, \ + &menu->text.stance[LOEFFECT_STANCE_ID_##NAME+1], \ + text_("stance_"#name"_name"), \ + text_("stance_"#name"_desc"), \ + text_("stance_"#name"_note")) + + LOEFFECT_STANCE_EACH(init_stance_set_); + +# undef init_stance_set_ +# undef text_ +} + +static void loplayer_menu_create_exit_text_(loplayer_menu_t* menu) { + assert(menu != NULL); + + menu->text.exit = glyphas_block_new( + GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL, + -menu->geometry.fontsz_normal_px.y, + INT32_MAX, + 32); + + const char* text = loresource_text_get(menu->res->lang, "menu_exit"); + + static const vec4_t white = vec4(1, 1, 1, 1); + glyphas_block_add_characters( + menu->text.exit, menu->font.normal, &white, text, strlen(text)); + + glyphas_block_set_origin(menu->text.exit, &vec2(.5f, -1)); + + const vec2_t scale = + vec2(2/menu->shaders->resolution.x, 2/menu->shaders->resolution.y); + glyphas_block_scale(menu->text.exit, &scale); + + glyphas_block_translate(menu->text.exit, &vec2(0, -1+menu->geometry.padding.y)); +} + +static void loplayer_menu_build_popup_text_( + loplayer_menu_t* menu, const char* title, const char* text) { + assert(menu != NULL); + + glyphas_block_clear(menu->text.popup_title); + glyphas_block_clear(menu->text.popup_body); + + static const vec4_t white = vec4(1, 1, 1, 1); + glyphas_block_add_characters( + menu->text.popup_title, menu->font.large, &white, title, strlen(title)); + glyphas_block_add_characters( + menu->text.popup_body, menu->font.normal, &white, text, strlen(text)); + + static const vec2_t bottom = vec2(.5f, -1); + static const vec2_t center = vec2(.5f, -.5f); + glyphas_block_set_origin(menu->text.popup_title, &bottom); + glyphas_block_set_origin(menu->text.popup_body, ¢er); + + const vec2_t scale = + vec2(2/menu->shaders->resolution.x, 2/menu->shaders->resolution.y); + glyphas_block_scale(menu->text.popup_title, &scale); + glyphas_block_scale(menu->text.popup_body, &scale); + + vec2_t offset, size; + glyphas_block_calculate_geometry(menu->text.popup_body, &offset, &size); + + const vec2_t upper = vec2(0, size.y/2*1.1f); + glyphas_block_translate(menu->text.popup_title, &upper); +} + +static bool loplayer_menu_find_stance_icon_at_( + const loplayer_menu_t* menu, size_t* index, const vec2_t* pos) { + assert(menu != NULL); + assert(index != NULL); + assert(vec2_valid(pos)); + + const typeof(menu->geometry)* geo = &menu->geometry; + + for (size_t i = 0; i < 5; ++i) { + const float theta = MATH_PI*2/5 * i + MATH_PI/2; + const vec2_t p = + vec2(geo->range.x*cos(theta), geo->range.y*sin(theta)); + + vec2_t diff = *pos; + vec2_subeq(&diff, &p); + + diff.x /= geo->icon.x; + diff.y /= geo->icon.y; + + const float manhattan = MATH_ABS(diff.x) + MATH_ABS(diff.y); + if (manhattan < 1) { + *index = i; + return true; + } + } + return false; +} + +static void loplayer_menu_update_status_(loplayer_menu_t* menu) { + assert(menu != NULL); + + if (menu->alpha != 1) return; + + locommon_easing_linear_float( + &menu->stance_highlight, 0, menu->ticker->delta_f); + + size_t hovered; + menu->stance_hovering = loplayer_menu_find_stance_icon_at_( + menu, &hovered, &menu->controller->cursor); + if (menu->stance_hovering) { + const bool taken = + loeffect_stance_set_has(&menu->status->stances, hovered); + menu->display_stance_text_index = taken? hovered+1: 0; + } + + const typeof(menu->geometry)* geo = &menu->geometry; + if (menu->controller->cursor.y < -1+geo->fontsz_normal.y+geo->padding.y) { + if (menu->controller->prev.buttons & LOCOMMON_INPUT_BUTTON_ATTACK) { + menu->request_exit = true; + } + } +} + +static void loplayer_menu_update_popup_(loplayer_menu_t* menu) { + assert(menu != NULL); + +} + +static void loplayer_menu_draw_stance_icons_(const loplayer_menu_t* menu) { + assert(menu != NULL); + + const typeof(menu->geometry)* geo = &menu->geometry; + + for (size_t i = 0; i < 5; ++i) { + const bool taken = + loeffect_stance_set_has(&menu->status->stances, i); + + const float theta = MATH_PI*2/5 * i + MATH_PI/2; + + const loshader_menu_stance_drawer_instance_t instance = { + .id = taken? + loeffect_stance_get_id_for_menu_shader(i): + LOSHADER_MENU_STANCE_ID_EMPTY, + .pos = vec2(geo->range.x*cos(theta), geo->range.y*sin(theta)), + .size = geo->icon, + .alpha = menu->alpha, + .highlight = menu->stance_highlight * !!(menu->highlighted_stance == i), + }; + loshader_menu_stance_drawer_add_instance( + menu->shaders->drawer.menu_stance, &instance); + } +} + +static void loplayer_menu_draw_status_(const loplayer_menu_t* menu) { + assert(menu != NULL); + + loplayer_menu_draw_stance_icons_(menu); + + if (menu->stance_hovering) { + const loplayer_menu_stance_text_set_t* set = + &menu->text.stance[menu->display_stance_text_index]; + loshader_menu_text_drawer_add_block( + &menu->shaders->drawer.menu_text, set->name); + loshader_menu_text_drawer_add_block( + &menu->shaders->drawer.menu_text, set->desc); + loshader_menu_text_drawer_add_block( + &menu->shaders->drawer.menu_text, set->note); + } + loshader_menu_text_drawer_add_block( + &menu->shaders->drawer.menu_text, menu->text.exit); +} + +static void loplayer_menu_draw_popup_(const loplayer_menu_t* menu) { + assert(menu != NULL); + + loshader_menu_text_drawer_add_block( + &menu->shaders->drawer.menu_text, menu->text.popup_title); + loshader_menu_text_drawer_add_block( + &menu->shaders->drawer.menu_text, menu->text.popup_body); +} + +loplayer_menu_t* loplayer_menu_new( + loresource_set_t* res, + loshader_set_t* shaders, + const locommon_ticker_t* ticker, + const loplayer_status_t* status, + const loplayer_controller_t* controller) { + assert(res != NULL); + assert(shaders != NULL); + assert(ticker != NULL); + assert(status != NULL); + assert(controller != NULL); + + loplayer_menu_t* menu = memory_new(sizeof(*menu)); + *menu = (typeof(*menu)) { + .res = res, + .shaders = shaders, + .ticker = ticker, + .status = status, + .controller = controller, + }; + + loplayer_menu_calculate_geometry_(menu); + + menu->font = (typeof(menu->font)) { + .large = glyphas_cache_new( + shaders->tex.menu_text, + &res->font.sans, + menu->geometry.fontsz_large_px.x, + menu->geometry.fontsz_large_px.y), + .normal = glyphas_cache_new( + shaders->tex.menu_text, + &res->font.sans, + menu->geometry.fontsz_normal_px.x, + menu->geometry.fontsz_normal_px.y), + .small = glyphas_cache_new( + shaders->tex.menu_text, + &res->font.serif, + menu->geometry.fontsz_small_px.x, + menu->geometry.fontsz_small_px.y), + }; + + menu->text = (typeof(menu->text)) { + .popup_title = glyphas_block_new( + GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL, + -menu->geometry.fontsz_large_px.y, + INT32_MAX, + 32), + .popup_body = glyphas_block_new( + GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL, + -menu->geometry.fontsz_normal_px.y*1.3f, + menu->geometry.fontsz_normal_px.x*18, + 512), + }; + loplayer_menu_create_stance_text_(menu); + loplayer_menu_create_exit_text_(menu); + + return menu; +} + +void loplayer_menu_delete(loplayer_menu_t* menu) { + if (menu == NULL) return; + + static const size_t len = + sizeof(menu->text.stance)/sizeof(menu->text.stance[0]); + for (size_t i = 0; i < len; ++i) { + loplayer_menu_deinitialize_stance_text_set_(&menu->text.stance[i]); + } + + glyphas_block_delete(menu->text.popup_title); + glyphas_block_delete(menu->text.popup_body); + glyphas_block_delete(menu->text.exit); + + glyphas_cache_delete(menu->font.large); + glyphas_cache_delete(menu->font.normal); + glyphas_cache_delete(menu->font.small); + + memory_delete(menu); +} + +void loplayer_menu_show_status(loplayer_menu_t* menu) { + assert(menu != NULL); + + menu->state = LOPLAYER_MENU_STATE_STATUS; + menu->stance_hovering = false; + menu->stance_highlight = 0; +} + +void loplayer_menu_show_status_with_stance_highlighted( + loplayer_menu_t* menu, loeffect_stance_id_t id) { + assert(menu != NULL); + + loplayer_menu_show_status(menu); + menu->stance_highlight = 1; + menu->highlighted_stance = id; +} + +void loplayer_menu_popup( + loplayer_menu_t* menu, const char* title, const char* text) { + assert(menu != NULL); + + menu->state = LOPLAYER_MENU_STATE_POPUP; + loplayer_menu_build_popup_text_(menu, title, text); +} + +void loplayer_menu_hide(loplayer_menu_t* menu) { + assert(menu != NULL); + + menu->state = LOPLAYER_MENU_STATE_HIDDEN; +} + +void loplayer_menu_update(loplayer_menu_t* menu) { + assert(menu != NULL); + + if (menu->state == LOPLAYER_MENU_STATE_HIDDEN) { + locommon_easing_linear_float(&menu->alpha, 0, menu->ticker->delta_f*2); + return; + } + locommon_easing_linear_float(&menu->alpha, 1, menu->ticker->delta_f*2); + + switch (menu->state) { + case LOPLAYER_MENU_STATE_STATUS: + loplayer_menu_update_status_(menu); + return; + case LOPLAYER_MENU_STATE_POPUP: + loplayer_menu_update_popup_(menu); + return; + default: + assert(false); + } +} + +void loplayer_menu_draw_ui(const loplayer_menu_t* menu) { + assert(menu != NULL); + + loshader_menu_background_drawer_set_alpha( + menu->shaders->drawer.menu_background, menu->alpha); + menu->shaders->drawer.menu_text.alpha = menu->alpha; + + switch (menu->state) { + case LOPLAYER_MENU_STATE_HIDDEN: + return; + case LOPLAYER_MENU_STATE_STATUS: + loplayer_menu_draw_status_(menu); + return; + case LOPLAYER_MENU_STATE_POPUP: + loplayer_menu_draw_popup_(menu); + return; + default: + assert(false); + } +} + +bool loplayer_menu_is_shown(const loplayer_menu_t* menu) { + assert(menu != NULL); + + return menu->state != LOPLAYER_MENU_STATE_HIDDEN; +} + +bool loplayer_menu_is_exit_requested(const loplayer_menu_t* menu) { + assert(menu != NULL); + + return menu->request_exit; +} + +void loplayer_menu_pack(const loplayer_menu_t* menu, msgpack_packer* packer) { + assert(menu != NULL); + assert(packer != NULL); + + mpkutil_pack_bool(packer, menu->state != LOPLAYER_MENU_STATE_HIDDEN); +} + +bool loplayer_menu_unpack(loplayer_menu_t* menu, const msgpack_object* obj) { + assert(menu != NULL); + + bool shown = false; + if (!mpkutil_get_bool(obj, &shown)) return false; + + if (shown) loplayer_menu_show_status(menu); + return true; +} diff --git a/core/loplayer/menu.h b/core/loplayer/menu.h new file mode 100644 index 0000000..5312551 --- /dev/null +++ b/core/loplayer/menu.h @@ -0,0 +1,86 @@ +#pragma once + +#include + +#include + +#include "core/locommon/ticker.h" +#include "core/loeffect/stance.h" +#include "core/loresource/set.h" +#include "core/loshader/set.h" + +#include "./controller.h" +#include "./entity.h" +#include "./status.h" + +struct loplayer_menu_t; +typedef struct loplayer_menu_t loplayer_menu_t; + +loplayer_menu_t* /* OWNERSHIP */ +loplayer_menu_new( + loresource_set_t* res, + loshader_set_t* shaders, + const locommon_ticker_t* ticker, + const loplayer_status_t* status, + const loplayer_controller_t* controller +); + +void +loplayer_menu_delete( + loplayer_menu_t* menu /* OWNERSHIP */ +); + +void +loplayer_menu_show_status( + loplayer_menu_t* menu +); + +void +loplayer_menu_show_status_with_stance_highlighted( + loplayer_menu_t* menu, + loeffect_stance_id_t id +); + +void +loplayer_menu_popup( + loplayer_menu_t* menu, + const char* title, + const char* text +); + +void +loplayer_menu_hide( + loplayer_menu_t* menu +); + +void +loplayer_menu_update( + loplayer_menu_t* menu +); + +void +loplayer_menu_draw_ui( + const loplayer_menu_t* menu +); + +bool +loplayer_menu_is_shown( + const loplayer_menu_t* menu +); + +bool +loplayer_menu_is_exit_requested( + const loplayer_menu_t* menu +); + +void +loplayer_menu_pack( + const loplayer_menu_t* menu, + msgpack_packer* packer +); + +bool +loplayer_menu_unpack( + loplayer_menu_t* menu, + const msgpack_object* obj /* NULLABLE */ +); diff --git a/core/loplayer/player.c b/core/loplayer/player.c new file mode 100644 index 0000000..83d2199 --- /dev/null +++ b/core/loplayer/player.c @@ -0,0 +1,288 @@ +#include "./player.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "util/math/matrix.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/lobullet/pool.h" +#include "core/locommon/input.h" +#include "core/locommon/position.h" +#include "core/locommon/ticker.h" +#include "core/loentity/entity.h" +#include "core/loentity/store.h" +#include "core/loshader/hud_bar.h" +#include "core/loshader/posteffect.h" +#include "core/loshader/set.h" + +#include "./action.h" +#include "./camera.h" +#include "./combat.h" +#include "./controller.h" +#include "./entity.h" +#include "./event.h" +#include "./hud.h" +#include "./menu.h" +#include "./status.h" + +#define LOPLAYER_COMBAT_ATTACK_RESERVE 32 + +void loplayer_initialize( + loplayer_t* player, + loentity_id_t id, + loresource_set_t* res, + loshader_set_t* shaders, + const locommon_ticker_t* ticker, + lobullet_pool_t* bullets, + loentity_store_t* entities, + const mat4_t* proj) { + assert(player != NULL); + assert(res != NULL); + assert(shaders != NULL); + assert(ticker != NULL); + assert(bullets != NULL); + assert(entities != NULL); + assert(mat4_valid(proj)); + + *player = (typeof(*player)) { + .shaders = shaders, + }; + + player->event = loplayer_event_new( + res, + shaders); + + loplayer_status_initialize(&player->status, res, ticker); + + loplayer_entity_initialize( + &player->entity, + id, + res->sound, + shaders->drawer.character, + ticker, + entities, + player->event, + &player->status); + loentity_store_add(entities, &player->entity.super.super); + + player->combat = loplayer_combat_new( + res->sound, + shaders->drawer.combat_ring, + ticker, + entities, + &player->status, + &player->entity, + LOPLAYER_COMBAT_ATTACK_RESERVE); + + loplayer_controller_initialize(&player->controller); + + loplayer_camera_initialize( + &player->camera, + shaders, + ticker, + player->event, + &player->status, + &player->entity, + proj); + player->hud = loplayer_hud_new( + res, + shaders, + ticker, + player->event, + &player->status, + &player->entity); + player->menu = loplayer_menu_new( + res, + shaders, + ticker, + &player->status, + &player->controller); + + player->action = loplayer_action_new( + res, + ticker, + bullets, + entities, + player->event, + &player->status, + &player->entity, + player->combat, + &player->controller, + &player->camera, + player->hud, + player->menu); +} + +void loplayer_deinitialize(loplayer_t* player) { + assert(player != NULL); + + loplayer_action_delete(player->action); + + loplayer_menu_delete(player->menu); + loplayer_hud_delete(player->hud); + loplayer_camera_deinitialize(&player->camera); + + loplayer_controller_deinitialize(&player->controller); + + loplayer_combat_delete(player->combat); + + loplayer_entity_deinitialize(&player->entity); + loplayer_status_deinitialize(&player->status); + + loplayer_event_delete(player->event); +} + +void loplayer_popup(loplayer_t* player, const char* title, const char* text) { + assert(player != NULL); + + loplayer_action_start_menu_popup_state(player->action); + loplayer_menu_popup(player->menu, title, text); +} + +bool loplayer_attack( + loplayer_t* player, const loplayer_combat_attack_t* attack) { + assert(player != NULL); + assert(attack != NULL); + + return loplayer_combat_add_attack(player->combat, attack); +} + +void loplayer_touch_encephalon(loplayer_t* player) { + assert(player != NULL); + + loplayer_status_set_respawn_position( + &player->status, &player->entity.super.super.pos); + loplayer_status_reset(&player->status); +} + +void loplayer_gain_stance(loplayer_t* player, loeffect_stance_id_t id) { + assert(player != NULL); + + if (!loplayer_status_add_stance(&player->status, id)) return; + + loplayer_action_start_menu_popup_state(player->action); + loplayer_menu_show_status_with_stance_highlighted(player->menu, id); +} + +void loplayer_gain_faith(loplayer_t* player, float amount) { + assert(player != NULL); + + player->status.recipient.faith += amount; +} + +void loplayer_update( + loplayer_t* player, + const locommon_input_t* input, + const locommon_position_t* cursor) { + assert(player != NULL); + assert(input != NULL); + assert(locommon_position_valid(cursor)); + + loplayer_status_update(&player->status); + /* entity is updated through entity store. */ + + loplayer_combat_update(player->combat); + + loplayer_camera_update(&player->camera); + loplayer_hud_update(player->hud); + loplayer_menu_update(player->menu); + + loplayer_controller_update(&player->controller, input, cursor); + loplayer_action_execute(player->action); +} + +void loplayer_draw(const loplayer_t* player) { + assert(player != NULL); + + loplayer_camera_draw(&player->camera); + loplayer_event_draw(player->event); + + loplayer_combat_draw_ui(player->combat); + loplayer_hud_draw_ui(player->hud); + loplayer_menu_draw_ui(player->menu); +} + +void loplayer_pack(const loplayer_t* player, msgpack_packer* packer) { + assert(player != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, 6); + + mpkutil_pack_str(packer, "status"); + loplayer_status_pack(&player->status, packer); + + mpkutil_pack_str(packer, "entity"); + loplayer_entity_pack(&player->entity, packer); + + mpkutil_pack_str(packer, "combat"); + loplayer_combat_pack(player->combat, packer); + + mpkutil_pack_str(packer, "camera"); + loplayer_camera_pack(&player->camera, packer); + + mpkutil_pack_str(packer, "menu"); + loplayer_menu_pack(player->menu, packer); + + mpkutil_pack_str(packer, "action"); + loplayer_action_pack(player->action, packer); +} + +bool loplayer_unpack(loplayer_t* player, const msgpack_object* obj) { + assert(player != NULL); + + if (obj == NULL) return false; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + if (!loplayer_status_unpack(&player->status, item_("status")) || + !loplayer_entity_unpack(&player->entity, item_("entity")) || + !loplayer_combat_unpack( player->combat, item_("combat")) || + !loplayer_camera_unpack(&player->camera, item_("camera")) || + !loplayer_menu_unpack ( player->menu, item_("menu" )) || + !loplayer_action_unpack( player->action, item_("action"))) { + return false; + } +# undef item_ + return true; +} + +void loplayer_test_packing(const loplayer_t* player) { + assert(player != NULL); + + msgpack_sbuffer buf; + msgpack_sbuffer_init(&buf); + + msgpack_packer pk; + msgpack_packer_init(&pk, &buf, msgpack_sbuffer_write); + + loplayer_pack(player, &pk); + + msgpack_unpacked upk; + msgpack_unpacked_init(&upk); + + size_t off = 0; + const msgpack_unpack_return r = + msgpack_unpack_next(&upk, buf.data, buf.size, &off); + if (r != MSGPACK_UNPACK_SUCCESS) { + fprintf(stderr, "player: invalid msgpack format\n"); + abort(); + } + if (!loplayer_unpack((loplayer_t*) player, &upk.data)) { + fprintf(stderr, "player: failed to unpack\n"); + abort(); + } + printf("player: packing test passed\n"); + msgpack_unpacked_destroy(&upk); + msgpack_sbuffer_destroy(&buf); +} diff --git a/core/loplayer/player.h b/core/loplayer/player.h new file mode 100644 index 0000000..499b3f2 --- /dev/null +++ b/core/loplayer/player.h @@ -0,0 +1,122 @@ +#pragma once + +#include + +#include + +#include "util/math/matrix.h" + +#include "core/lobullet/pool.h" +#include "core/locommon/input.h" +#include "core/locommon/position.h" +#include "core/locommon/ticker.h" +#include "core/loeffect/stance.h" +#include "core/loentity/entity.h" +#include "core/loentity/store.h" +#include "core/loresource/set.h" +#include "core/loshader/set.h" + +#include "./action.h" +#include "./camera.h" +#include "./combat.h" +#include "./controller.h" +#include "./entity.h" +#include "./event.h" +#include "./hud.h" +#include "./menu.h" +#include "./status.h" + +typedef struct { + loshader_set_t* shaders; + + loplayer_event_t* event; + + loplayer_status_t status; + loplayer_entity_t entity; + + loplayer_combat_t* combat; + + loplayer_controller_t controller; + + loplayer_camera_t camera; + loplayer_hud_t* hud; + loplayer_menu_t* menu; + + loplayer_action_t* action; +} loplayer_t; + +void +loplayer_initialize( + loplayer_t* player, + loentity_id_t id, + loresource_set_t* res, + loshader_set_t* shaders, + const locommon_ticker_t* ticker, + lobullet_pool_t* bullets, + loentity_store_t* entities, + const mat4_t* proj +); + +void +loplayer_deinitialize( + loplayer_t* player +); + +void +loplayer_popup( + loplayer_t* player, + const char* title, + const char* text +); + +bool +loplayer_attack( + loplayer_t* player, + const loplayer_combat_attack_t* attack +); + +void +loplayer_touch_encephalon( + loplayer_t* player +); + +void +loplayer_gain_stance( + loplayer_t* player, + loeffect_stance_id_t id +); + +void +loplayer_gain_faith( + loplayer_t* player, + float amount +); + +void +loplayer_update( + loplayer_t* player, + const locommon_input_t* input, + const locommon_position_t* cursor +); + +void +loplayer_draw( + const loplayer_t* player +); + +void +loplayer_pack( + const loplayer_t* player, + msgpack_packer* packer +); + +bool +loplayer_unpack( + loplayer_t* player, + const msgpack_object* obj /* NULLABLE */ +); + +void +loplayer_test_packing( + const loplayer_t* player +); diff --git a/core/loplayer/status.c b/core/loplayer/status.c new file mode 100644 index 0000000..34e1376 --- /dev/null +++ b/core/loplayer/status.c @@ -0,0 +1,193 @@ +#include "./status.h" + +#include +#include +#include +#include + +#include + +#include "util/math/algorithm.h" +#include "util/math/vector.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/locommon/easing.h" +#include "core/locommon/msgpack.h" +#include "core/locommon/position.h" +#include "core/locommon/ticker.h" +#include "core/loeffect/recipient.h" +#include "core/loeffect/stance.h" +#include "core/loresource/set.h" +#include "core/loresource/sound.h" + +static const loeffect_recipient_status_t loplayer_status_base_ = { + .attack = .3f, + .defence = .2f, + .speed = .3f, + .jump = 1.f, +}; + +#define LOPLAYER_STATUS_PARAM_TO_PACK_EACH_(PROC) do { \ + PROC("stances", stances); \ + PROC("madness", recipient.madness); \ + PROC("faith", recipient.faith); \ + PROC("effects", recipient.effects); \ + PROC("respawn-pos", respawn_pos); \ + PROC("last-damage-time", last_damage_time); \ + PROC("bullet-immune-until", bullet_immune_until); \ +} while (0) +#define LOPLAYER_STATUS_PARAM_TO_PACK_COUNT 7 + +#define LOPLAYER_STATUS_MADNESS_DYING_THRESHOLD .3f + +void loplayer_status_initialize( + loplayer_status_t* status, + loresource_set_t* res, + const locommon_ticker_t* ticker) { + assert(status != NULL); + assert(ticker != NULL); + + *status = (typeof(*status)) { + .res = res, + .ticker = ticker, + + .respawn_pos = locommon_position(0, 0, vec2(.5f, .5f)), + }; + + loeffect_stance_set_initialize(&status->stances); + + loeffect_recipient_initialize(&status->recipient, ticker); +} + +void loplayer_status_deinitialize(loplayer_status_t* status) { + assert(status != NULL); + + loeffect_stance_set_deinitialize(&status->stances); + loeffect_recipient_deinitialize(&status->recipient); +} + +void loplayer_status_reset(loplayer_status_t* status) { + assert(status != NULL); + + loeffect_recipient_reset(&status->recipient); +} + +bool loplayer_status_add_stance( + loplayer_status_t* status, loeffect_stance_id_t id) { + assert(status != NULL); + + if (loeffect_stance_set_has(&status->stances, id)) return false; + loeffect_stance_set_add(&status->stances, id); + return true; +} + +void loplayer_status_remove_stance( + loplayer_status_t* status, loeffect_stance_id_t id) { + assert(status != NULL); + + loeffect_stance_set_remove(&status->stances, id); +} + +void loplayer_status_apply_effect( + loplayer_status_t* status, const loeffect_t* effect) { + assert(status != NULL); + assert(effect != NULL); + + if (effect->id == LOEFFECT_ID_IMMEDIATE_DAMAGE) { + status->last_damage_time = status->ticker->time; + loresource_sound_play(status->res->sound, "damage"); + } + + loeffect_recipient_apply_effect(&status->recipient, effect); +} + +void loplayer_status_update(loplayer_status_t* status) { + assert(status != NULL); + + static const uint64_t dying_sound_period = 4000; + + loeffect_recipient_status_t base = loplayer_status_base_; + if (loeffect_stance_set_has(&status->stances, LOEFFECT_STANCE_ID_PHILOSOPHER)) { + base.defence += .3f; + } + + loeffect_recipient_update(&status->recipient, &base); + + /* Makes decreasing madness faster if the player has a stance, UNFINISHER. */ + if (loeffect_stance_set_has( + &status->stances, LOEFFECT_STANCE_ID_UNFINISHER)) { + if (status->recipient.madness > 0) { + if (status->recipient.faith <= 0) { + status->recipient.madness -= status->ticker->delta_f/30; + status->recipient.last_damage = LOEFFECT_ID_LOST; + } else if (status->recipient.faith >= .5f) { + status->recipient.madness += status->ticker->delta_f/120; + } + } + } + + if (status->recipient.madness > 0 && + status->recipient.madness <= LOPLAYER_STATUS_MADNESS_DYING_THRESHOLD) { + uint64_t t = status->ticker->time; + uint64_t pt = t - status->ticker->delta; + + status->dying_effect = t%dying_sound_period*1.f / dying_sound_period; + + t /= dying_sound_period; + pt /= dying_sound_period; + + if (t != pt) loresource_sound_play(status->res->sound, "dying"); + } else { + locommon_easing_linear_float( + &status->dying_effect, 0, status->ticker->delta_f*5); + } +} + +void loplayer_status_set_respawn_position( + loplayer_status_t* status, const locommon_position_t* pos) { + assert(status != NULL); + assert(locommon_position_valid(pos)); + + status->respawn_pos = *pos; +} + +void loplayer_status_pack( + const loplayer_status_t* status, msgpack_packer* packer) { + assert(status != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, LOPLAYER_STATUS_PARAM_TO_PACK_COUNT); + +# define pack_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + LOCOMMON_MSGPACK_PACK_ANY(packer, &status->var); \ + } while (0) + + LOPLAYER_STATUS_PARAM_TO_PACK_EACH_(pack_); + +# undef pack_ +} + +bool loplayer_status_unpack( + loplayer_status_t* status, const msgpack_object* obj) { + assert(status != NULL); + + if (obj == NULL) return false; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + +# define unpack_(name, var) do { \ + if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(name), &status->var)) { \ + return false; \ + } \ + } while (0) + + LOPLAYER_STATUS_PARAM_TO_PACK_EACH_(unpack_); + return true; + +# undef unpack_ +# undef item_ +} diff --git a/core/loplayer/status.h b/core/loplayer/status.h new file mode 100644 index 0000000..8c5d261 --- /dev/null +++ b/core/loplayer/status.h @@ -0,0 +1,87 @@ +#pragma once + +#include +#include + +#include + +#include "util/math/vector.h" + +#include "core/locommon/position.h" +#include "core/locommon/ticker.h" +#include "core/loeffect/recipient.h" +#include "core/loeffect/stance.h" +#include "core/loresource/set.h" + +typedef struct { + loresource_set_t* res; + const locommon_ticker_t* ticker; + + loeffect_stance_set_t stances; + loeffect_recipient_t recipient; + + locommon_position_t respawn_pos; + + uint64_t last_damage_time; + uint64_t bullet_immune_until; + + float dying_effect; /* 0~1 */ +} loplayer_status_t; + +void +loplayer_status_initialize( + loplayer_status_t* status, + loresource_set_t* res, + const locommon_ticker_t* ticker +); + +void +loplayer_status_deinitialize( + loplayer_status_t* status +); + +void +loplayer_status_reset( + loplayer_status_t* status +); + +bool +loplayer_status_add_stance( + loplayer_status_t* status, + loeffect_stance_id_t id +); + +void +loplayer_status_remove_stance( + loplayer_status_t* status, + loeffect_stance_id_t id +); + +void +loplayer_status_apply_effect( + loplayer_status_t* status, + const loeffect_t* effect +); + +void +loplayer_status_update( + loplayer_status_t* status +); + +void +loplayer_status_set_respawn_position( + loplayer_status_t* status, + const locommon_position_t* pos +); + +void +loplayer_status_pack( + const loplayer_status_t* status, + msgpack_packer* packer +); + +bool +loplayer_status_unpack( + loplayer_status_t* status, + const msgpack_object* obj +); diff --git a/core/loresource/CMakeLists.txt b/core/loresource/CMakeLists.txt new file mode 100644 index 0000000..5bcd7b8 --- /dev/null +++ b/core/loresource/CMakeLists.txt @@ -0,0 +1,40 @@ +add_library(loresource + font.c + language.c + music.c + set.c + sound.c + text.c +) +target_any_sources(loresource + font/sans.woff + font/serif.woff + + music/biome_boss.mp3 + music/biome_cavias_camp.mp3 + music/biome_laboratory.mp3 + music/biome_metaphysical_gate.mp3 + music/boss_big_warder.mp3 + music/boss_greedy_scientist.mp3 + music/boss_theists_child.mp3 + music/title.mp3 + + sound/bomb.mp3 + sound/enemy_shoot.mp3 + sound/enemy_trigger.mp3 + sound/damage.mp3 + sound/dodge.mp3 + sound/dying.mp3 + sound/guard.mp3 + sound/player_shoot.mp3 + sound/player_trigger.mp3 + sound/reflection.mp3 + sound/touch_gate.mp3 +) +target_link_libraries(loresource + dictres + glyphas + jukebox + math + memory +) diff --git a/core/loresource/font.c b/core/loresource/font.c new file mode 100644 index 0000000..81fe60d --- /dev/null +++ b/core/loresource/font.c @@ -0,0 +1,41 @@ +#include "./font.h" + +#include +#include + +#include "util/glyphas/context.h" +#include "util/glyphas/face.h" + +/* resources */ +#include "anysrc/font/sans.woff.h" +#include "anysrc/font/serif.woff.h" + +void loresource_font_initialize(loresource_font_t* font) { + assert(font != NULL); + + *font = (typeof(*font)) {0}; + + glyphas_context_initialize(&font->glyphas); + +# define load_face_(name) \ + glyphas_face_initialize_from_buffer( \ + &font->name, \ + &font->glyphas, \ + loresource_font_##name##_woff_, \ + sizeof(loresource_font_##name##_woff_), \ + 0) + + load_face_(sans); + load_face_(serif); + +# undef load_face_ +} + +void loresource_font_deinitialize(loresource_font_t* font) { + assert(font != NULL); + + glyphas_face_deinitialize(&font->sans); + glyphas_face_deinitialize(&font->serif); + + glyphas_context_deinitialize(&font->glyphas); +} diff --git a/core/loresource/font.h b/core/loresource/font.h new file mode 100644 index 0000000..5df58c1 --- /dev/null +++ b/core/loresource/font.h @@ -0,0 +1,21 @@ +#pragma once + +#include "util/glyphas/context.h" +#include "util/glyphas/face.h" + +typedef struct { + glyphas_context_t glyphas; + + glyphas_face_t sans; + glyphas_face_t serif; +} loresource_font_t; + +void +loresource_font_initialize( + loresource_font_t* font +); + +void +loresource_font_deinitialize( + loresource_font_t* font +); diff --git a/core/loresource/font/LICENSE-SIL.txt b/core/loresource/font/LICENSE-SIL.txt new file mode 100644 index 0000000..c3045a2 --- /dev/null +++ b/core/loresource/font/LICENSE-SIL.txt @@ -0,0 +1,93 @@ +Copyright (c) 2011, Cyreal (www.cyreal.org), +with Reserved Font Name "Junge". +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. \ No newline at end of file diff --git a/core/loresource/font/README.md b/core/loresource/font/README.md new file mode 100644 index 0000000..5c55bef --- /dev/null +++ b/core/loresource/font/README.md @@ -0,0 +1,13 @@ +font files +==== + +All fonts are under **SIL OPEN FONT LICENSE**. +Please see LICENSE.txt in the same directory. + +## sans.woff + +[Noto Sans CJK Jp Regular (minimized version)](https://github.com/hirofumii/Noto-Sans-CJK-JP.min) + +## serif.woff + +[Noto Serif CJK Jp Regular (minimized version)](https://github.com/hirofumii/Noto-Serif-CJK-JP.min) diff --git a/core/loresource/font/sans.woff b/core/loresource/font/sans.woff new file mode 100755 index 0000000..da49050 Binary files /dev/null and b/core/loresource/font/sans.woff differ diff --git a/core/loresource/font/serif.woff b/core/loresource/font/serif.woff new file mode 100755 index 0000000..e9e23b4 Binary files /dev/null and b/core/loresource/font/serif.woff differ diff --git a/core/loresource/language.c b/core/loresource/language.c new file mode 100644 index 0000000..acfefde --- /dev/null +++ b/core/loresource/language.c @@ -0,0 +1,40 @@ +#include "./language.h" + +#include +#include +#include +#include + +#define LORESOURCE_LANGUAGE_EACH_(PROC) do { \ + PROC(JP, jp); \ +} while (0) + +const char* loresource_language_stringify(loresource_language_t lang) { +# define each_(NAME, name) \ + if (lang == LORESOURCE_LANGUAGE_##NAME) return #name; + + LORESOURCE_LANGUAGE_EACH_(each_); + + assert(false); + return NULL; + +# undef each_ +} + +bool loresource_language_unstringify( + loresource_language_t* lang, const char* str, size_t len) { + assert(lang != NULL); + assert(str != NULL || len == 0); + +# define each_(NAME, name) do { \ + if (strncmp(str, #name, len) == 0 && #name[len] == 0) { \ + *lang = LORESOURCE_LANGUAGE_##NAME; \ + return true; \ + } \ + } while (0) + + LORESOURCE_LANGUAGE_EACH_(each_); + return false; + +# undef each_ +} diff --git a/core/loresource/language.h b/core/loresource/language.h new file mode 100644 index 0000000..5940c6e --- /dev/null +++ b/core/loresource/language.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +typedef enum { + LORESOURCE_LANGUAGE_JP, +} loresource_language_t; + +const char* +loresource_language_stringify( + loresource_language_t lang +); + +bool +loresource_language_unstringify( + loresource_language_t* lang, + const char* str, + size_t len +); diff --git a/core/loresource/music.c b/core/loresource/music.c new file mode 100644 index 0000000..3da6f09 --- /dev/null +++ b/core/loresource/music.c @@ -0,0 +1,101 @@ +#include "./music.h" + +#include +#include +#include +#include +#include + +#include "util/jukebox/amp.h" +#include "util/jukebox/composite.h" +#include "util/jukebox/decoder.h" +#include "util/jukebox/format.h" +#include "util/jukebox/mixer.h" +#include "util/math/rational.h" + +/* resources */ +#include "anysrc/music/biome_boss.mp3.h" +#include "anysrc/music/biome_cavias_camp.mp3.h" +#include "anysrc/music/biome_laboratory.mp3.h" +#include "anysrc/music/biome_metaphysical_gate.mp3.h" +#include "anysrc/music/boss_big_warder.mp3.h" +#include "anysrc/music/boss_greedy_scientist.mp3.h" +#include "anysrc/music/boss_theists_child.mp3.h" +#include "anysrc/music/boss_theists_child.mp3.h" +#include "anysrc/music/title.mp3.h" + +#define LORESOURCE_MUSIC_EACH_(PROC) do { \ + PROC(biome_boss); \ + PROC(biome_cavias_camp); \ + PROC(biome_laboratory); \ + PROC(biome_metaphysical_gate); \ + PROC(boss_big_warder); \ + PROC(boss_greedy_scientist); \ + PROC(boss_theists_child); \ + PROC(title); \ +} while (0) + +static void loresource_music_player_initialize_( + loresource_music_player_t* p, + const jukebox_format_t* format, + const void* buf, + size_t len) { + assert(p != NULL); + assert(jukebox_format_valid(format)); + assert(buf != NULL || len == 0); + + *p = (typeof(*p)) { + .decoder = jukebox_decoder_new_from_memory_mp3(format, buf, len), + .compo = jukebox_composite_new(format, 2), + }; + jukebox_amp_initialize(&p->amp, format); + + jukebox_composite_add_effect(p->compo, (jukebox_effect_t*) p->decoder); + jukebox_composite_add_effect(p->compo, (jukebox_effect_t*) &p->amp); + jukebox_composite_play(p->compo); +} + +static void loresource_music_player_deinitialize_( + loresource_music_player_t* p) { + assert(p != NULL); + + jukebox_composite_delete(p->compo); + jukebox_amp_deinitialize(&p->amp); + jukebox_decoder_delete(p->decoder); +} + +void loresource_music_initialize( + loresource_music_t* music, + jukebox_mixer_t* mixer, + const jukebox_format_t* format) { + assert(music != NULL); + assert(mixer != NULL); + assert(jukebox_format_valid(format)); + + *music = (typeof(*music)) {0}; + +# define each_(n) do { \ + loresource_music_player_initialize_( \ + &music->n, \ + format, \ + loresource_music_##n##_mp3_, \ + sizeof(loresource_music_##n##_mp3_)); \ + jukebox_mixer_add_effect(mixer, (jukebox_effect_t*) music->n.compo); \ + } while (0) + + LORESOURCE_MUSIC_EACH_(each_); + +# undef each_ +} + +void loresource_music_deinitialize(loresource_music_t* music) { + assert(music != NULL); + +# define each_(n) do { \ + loresource_music_player_deinitialize_(&music->n); \ + } while (0) + + LORESOURCE_MUSIC_EACH_(each_); + +# undef each_ +} diff --git a/core/loresource/music.h b/core/loresource/music.h new file mode 100644 index 0000000..9952dae --- /dev/null +++ b/core/loresource/music.h @@ -0,0 +1,38 @@ +#pragma once + +#include "util/jukebox/amp.h" +#include "util/jukebox/composite.h" +#include "util/jukebox/decoder.h" +#include "util/jukebox/mixer.h" +#include "util/math/rational.h" + +typedef struct { + jukebox_decoder_t* decoder; + jukebox_amp_t amp; + jukebox_composite_t* compo; +} loresource_music_player_t; + +struct loresource_music_t; +typedef struct { + loresource_music_player_t + biome_boss, + biome_cavias_camp, + biome_metaphysical_gate, + biome_laboratory, + boss_big_warder, + boss_greedy_scientist, + boss_theists_child, + title; +} loresource_music_t; + +void +loresource_music_initialize( + loresource_music_t* music, + jukebox_mixer_t* mixer, + const jukebox_format_t* format +); + +void +loresource_music_deinitialize( + loresource_music_t* music +); diff --git a/core/loresource/music/README.md b/core/loresource/music/README.md new file mode 100644 index 0000000..5d858e5 --- /dev/null +++ b/core/loresource/music/README.md @@ -0,0 +1,30 @@ +music +==== + +## biome_cavias_camp.mp3 + +[UNDERGROUND SOUNDS - 99 sounds](https://99sounds.org/underground-sounds/) + +## biome_laboratory.mp3 + +[UNDERGROUND SOUNDS - 99 sounds](https://99sounds.org/underground-sounds/) + +## biome_metaphysical_gate.mp3 + +original + +## boss_big_warder.mp3 + +[loop](https://audius.co/catfoot/loop-86327) + +## boss_greedy_scientist.mp3 + +[stay-frozen - Kasane Teto](https://audius.co/catfoot/stay-frozen-kasane-teto-88761) (remixed for LEFTONE) + +## boss_theists_child.mp3 + +[revolution - Kasane Teto](https://audius.co/catfoot/revolution-kasane-teto-88758) + +## title.mp3 + +[remnant](https://audius.co/catfoot/remnant-84992) diff --git a/core/loresource/music/biome_boss.mp3 b/core/loresource/music/biome_boss.mp3 new file mode 100644 index 0000000..1db3b00 Binary files /dev/null and b/core/loresource/music/biome_boss.mp3 differ diff --git a/core/loresource/music/biome_cavias_camp.mp3 b/core/loresource/music/biome_cavias_camp.mp3 new file mode 100644 index 0000000..f4c06fb Binary files /dev/null and b/core/loresource/music/biome_cavias_camp.mp3 differ diff --git a/core/loresource/music/biome_laboratory.mp3 b/core/loresource/music/biome_laboratory.mp3 new file mode 100644 index 0000000..f14905b Binary files /dev/null and b/core/loresource/music/biome_laboratory.mp3 differ diff --git a/core/loresource/music/biome_metaphysical_gate.mp3 b/core/loresource/music/biome_metaphysical_gate.mp3 new file mode 100644 index 0000000..e5e1149 Binary files /dev/null and b/core/loresource/music/biome_metaphysical_gate.mp3 differ diff --git a/core/loresource/music/boss_big_warder.mp3 b/core/loresource/music/boss_big_warder.mp3 new file mode 100644 index 0000000..970ff4a Binary files /dev/null and b/core/loresource/music/boss_big_warder.mp3 differ diff --git a/core/loresource/music/boss_greedy_scientist.mp3 b/core/loresource/music/boss_greedy_scientist.mp3 new file mode 100644 index 0000000..728d063 Binary files /dev/null and b/core/loresource/music/boss_greedy_scientist.mp3 differ diff --git a/core/loresource/music/boss_theists_child.mp3 b/core/loresource/music/boss_theists_child.mp3 new file mode 100644 index 0000000..bfd4332 Binary files /dev/null and b/core/loresource/music/boss_theists_child.mp3 differ diff --git a/core/loresource/music/title.mp3 b/core/loresource/music/title.mp3 new file mode 100644 index 0000000..8949545 Binary files /dev/null and b/core/loresource/music/title.mp3 differ diff --git a/core/loresource/set.c b/core/loresource/set.c new file mode 100644 index 0000000..8f513de --- /dev/null +++ b/core/loresource/set.c @@ -0,0 +1,46 @@ +#include "./set.h" + +#include +#include + +#include "util/jukebox/format.h" +#include "util/jukebox/mixer.h" + +#include "./font.h" +#include "./language.h" +#include "./music.h" +#include "./sound.h" +#include "./text.h" + +void loresource_set_initialize( + loresource_set_t* res, + jukebox_mixer_t* mixer, + const jukebox_format_t* format, + loresource_language_t lang) { + assert(res != NULL); + assert(mixer != NULL); + assert(jukebox_format_valid(format)); + + *res = (typeof(*res)) { + .sound = loresource_sound_new(mixer, format), + }; + loresource_music_initialize(&res->music, mixer, format); + loresource_font_initialize(&res->font); + loresource_set_change_language(res, lang); +} + +void loresource_set_deinitialize(loresource_set_t* res) { + assert(res != NULL); + + loresource_font_deinitialize(&res->font); + loresource_sound_delete(res->sound); + loresource_music_deinitialize(&res->music); +} + +void loresource_set_change_language( + loresource_set_t* res, loresource_language_t lang) { + assert(res != NULL); + + res->lang = lang; + loresource_text_optimize(res->lang); +} diff --git a/core/loresource/set.h b/core/loresource/set.h new file mode 100644 index 0000000..0198f24 --- /dev/null +++ b/core/loresource/set.h @@ -0,0 +1,36 @@ +#pragma once + +#include "util/jukebox/format.h" +#include "util/jukebox/mixer.h" + +#include "./font.h" +#include "./language.h" +#include "./music.h" +#include "./sound.h" + +typedef struct { + loresource_sound_t* sound; + loresource_music_t music; + loresource_font_t font; + + loresource_language_t lang; +} loresource_set_t; + +void +loresource_set_initialize( + loresource_set_t* res, + jukebox_mixer_t* mixer, + const jukebox_format_t* format, + loresource_language_t lang +); + +void +loresource_set_deinitialize( + loresource_set_t* res +); + +void +loresource_set_change_language( + loresource_set_t* res, + loresource_language_t lang +); diff --git a/core/loresource/sound.c b/core/loresource/sound.c new file mode 100644 index 0000000..46c05b7 --- /dev/null +++ b/core/loresource/sound.c @@ -0,0 +1,263 @@ +#include "./sound.h" + +#include +#include +#include + +#include "util/jukebox/amp.h" +#include "util/jukebox/composite.h" +#include "util/jukebox/delay.h" +#include "util/jukebox/format.h" +#include "util/jukebox/mixer.h" +#include "util/jukebox/sound.h" +#include "util/math/algorithm.h" +#include "util/math/rational.h" +#include "util/memory/memory.h" + +/* resources */ +#include "anysrc/sound/bomb.mp3.h" +#include "anysrc/sound/enemy_shoot.mp3.h" +#include "anysrc/sound/enemy_trigger.mp3.h" +#include "anysrc/sound/damage.mp3.h" +#include "anysrc/sound/dodge.mp3.h" +#include "anysrc/sound/dying.mp3.h" +#include "anysrc/sound/guard.mp3.h" +#include "anysrc/sound/player_shoot.mp3.h" +#include "anysrc/sound/player_trigger.mp3.h" +#include "anysrc/sound/reflection.mp3.h" +#include "anysrc/sound/touch_gate.mp3.h" + +#define LORESOURCE_SOUND_DECL_(PROC) \ + PROC(bomb); \ + PROC(enemy_shoot); \ + PROC(enemy_trigger); \ + PROC(damage); \ + PROC(dodge); \ + PROC(dying); \ + PROC(guard); \ + PROC(player_shoot); \ + PROC(player_trigger); \ + PROC(reflection); \ + PROC(touch_gate); + +#define LORESOURCE_SOUND_EACH_(PROC) do { \ + LORESOURCE_SOUND_DECL_(PROC) \ +} while (0) + +struct loresource_sound_t { + struct { +# define each_(n) \ + jukebox_sound_buffer_t* n + + LORESOURCE_SOUND_DECL_(each_); + +# undef each_ + } buffers; + + struct { +# define each_(n) \ + jukebox_sound_t* n + + LORESOURCE_SOUND_DECL_(each_); + +# undef each_ + } sounds; + + struct { + jukebox_delay_t* delay; + jukebox_composite_t* compo; + } combat; + struct { + jukebox_delay_t* delay; + jukebox_composite_t* compo; + } env; + struct { + jukebox_composite_t* compo; + } direction; + + struct { + jukebox_amp_t amp; + jukebox_composite_t* compo; + } root; +}; + +#define LORESOURCE_SOUND_MAX_CONCURRENT_PLAY 32 + +static void loresource_sound_initialize_combat_( + loresource_sound_t* sound, + jukebox_mixer_t* mixer, + const jukebox_format_t* format) { + assert(sound != NULL); + assert(mixer != NULL); + assert(jukebox_format_valid(format)); + + typeof(sound->sounds)* s = &sound->sounds; + typeof(sound->combat)* c = &sound->combat; + + c->delay = jukebox_delay_new(format, &rational(5, 100), .1f, .4f); + + c->compo = jukebox_composite_new(format, 7); + jukebox_composite_add_effect(c->compo, (jukebox_effect_t*) s->damage); + jukebox_composite_add_effect(c->compo, (jukebox_effect_t*) s->dodge); + jukebox_composite_add_effect(c->compo, (jukebox_effect_t*) s->guard); + jukebox_composite_add_effect(c->compo, (jukebox_effect_t*) s->player_shoot); + jukebox_composite_add_effect(c->compo, (jukebox_effect_t*) s->player_trigger); + jukebox_composite_add_effect(c->compo, (jukebox_effect_t*) s->reflection); + jukebox_composite_add_effect(c->compo, (jukebox_effect_t*) c->delay); + + jukebox_composite_play(c->compo); +} +static void loresource_sound_deinitialize_combat_(loresource_sound_t* sound) { + assert(sound != NULL); + + typeof(sound->combat)* c = &sound->combat; + + jukebox_composite_delete(c->compo); + jukebox_delay_delete(c->delay); +} + +static void loresource_sound_initialize_env_( + loresource_sound_t* sound, + jukebox_mixer_t* mixer, + const jukebox_format_t* format) { + assert(sound != NULL); + assert(mixer != NULL); + assert(jukebox_format_valid(format)); + + typeof(sound->sounds)* s = &sound->sounds; + typeof(sound->env)* e = &sound->env; + + e->delay = jukebox_delay_new(format, &rational(1, 10), .3f, .4f); + + e->compo = jukebox_composite_new(format, 5); + jukebox_composite_add_effect(e->compo, (jukebox_effect_t*) s->bomb); + jukebox_composite_add_effect(e->compo, (jukebox_effect_t*) s->enemy_shoot); + jukebox_composite_add_effect(e->compo, (jukebox_effect_t*) s->enemy_trigger); + jukebox_composite_add_effect(e->compo, (jukebox_effect_t*) s->touch_gate); + jukebox_composite_add_effect(e->compo, (jukebox_effect_t*) e->delay); + + jukebox_composite_play(e->compo); +} +static void loresource_sound_deinitialize_env_(loresource_sound_t* sound) { + assert(sound != NULL); + + typeof(sound->env)* e = &sound->env; + + jukebox_composite_delete(e->compo); + jukebox_delay_delete(e->delay); +} + +static void loresource_sound_initialize_direction_( + loresource_sound_t* sound, + jukebox_mixer_t* mixer, + const jukebox_format_t* format) { + assert(sound != NULL); + assert(mixer != NULL); + assert(jukebox_format_valid(format)); + + typeof(sound->sounds)* s = &sound->sounds; + typeof(sound->direction)* d = &sound->direction; + + d->compo = jukebox_composite_new(format, 1); + jukebox_composite_add_effect(d->compo, (jukebox_effect_t*) s->dying); + + jukebox_composite_play(d->compo); +} +static void loresource_sound_deinitialize_direction_(loresource_sound_t* sound) { + assert(sound != NULL); + + typeof(sound->direction)* d = &sound->direction; + + jukebox_composite_delete(d->compo); +} + +loresource_sound_t* loresource_sound_new( + jukebox_mixer_t* mixer, const jukebox_format_t* format) { + assert(mixer != NULL); + assert(jukebox_format_valid(format)); + + loresource_sound_t* sound = memory_new(sizeof(*sound)); + *sound = (typeof(*sound)) {0}; + +# define each_(n) do { \ + sound->buffers.n = jukebox_sound_buffer_new_from_memory_mp3( \ + format, \ + loresource_sound_##n##_mp3_, \ + sizeof(loresource_sound_##n##_mp3_)); \ + sound->sounds.n = jukebox_sound_new( \ + sound->buffers.n, LORESOURCE_SOUND_MAX_CONCURRENT_PLAY); \ + } while (0) + + LORESOURCE_SOUND_EACH_(each_); + +# undef each_ + + sound->root.compo = jukebox_composite_new(format, 4); + + loresource_sound_initialize_combat_(sound, mixer, format); + jukebox_composite_add_effect( + sound->root.compo, (jukebox_effect_t*) sound->combat.compo); + + loresource_sound_initialize_env_(sound, mixer, format); + jukebox_composite_add_effect( + sound->root.compo, (jukebox_effect_t*) sound->env.compo); + + loresource_sound_initialize_direction_(sound, mixer, format); + jukebox_composite_add_effect( + sound->root.compo, (jukebox_effect_t*) sound->direction.compo); + + jukebox_amp_initialize(&sound->root.amp, format); + jukebox_composite_add_effect(sound->root.compo, &sound->root.amp.super); + + jukebox_amp_change_volume(&sound->root.amp, 1, &rational(1, 1)); + jukebox_composite_play(sound->root.compo); + + jukebox_mixer_add_effect(mixer, (jukebox_effect_t*) sound->root.compo); + return sound; +} + +void loresource_sound_delete(loresource_sound_t* sound) { + if (sound == NULL) return; + + jukebox_composite_delete(sound->root.compo); + jukebox_amp_deinitialize(&sound->root.amp); + + loresource_sound_deinitialize_combat_(sound); + loresource_sound_deinitialize_env_(sound); + loresource_sound_deinitialize_direction_(sound); + +# define each_(n) do { \ + jukebox_sound_delete(sound->sounds.n); \ + jukebox_sound_buffer_delete(sound->buffers.n); \ + } while (0) + + LORESOURCE_SOUND_EACH_(each_); + +# undef each_ + + memory_delete(sound); +} + +void loresource_sound_play(loresource_sound_t* sound, const char* name) { + assert(sound != NULL); + +# define each_(n) do { \ + if (strcmp(name, #n) == 0) { \ + jukebox_sound_play(sound->sounds.n); \ + return; \ + } \ + } while (0) + + LORESOURCE_SOUND_EACH_(each_); + +# undef each_ +} + +void loresource_sound_change_master_volume( + loresource_sound_t* sound, float v, const rational_t* duration) { + assert(sound != NULL); + assert(MATH_FLOAT_VALID(v)); + assert(rational_valid(duration)); + + jukebox_amp_change_volume(&sound->root.amp, v, duration); +} diff --git a/core/loresource/sound.h b/core/loresource/sound.h new file mode 100644 index 0000000..9054547 --- /dev/null +++ b/core/loresource/sound.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include "util/jukebox/format.h" +#include "util/jukebox/mixer.h" +#include "util/math/rational.h" + +struct loresource_sound_t; +typedef struct loresource_sound_t loresource_sound_t; + +loresource_sound_t* /* OWNERSHIP */ +loresource_sound_new( + jukebox_mixer_t* mixer, + const jukebox_format_t* format +); + +void +loresource_sound_delete( + loresource_sound_t* sound /* OWNERSHIP */ +); + +void +loresource_sound_play( + loresource_sound_t* sound, + const char* name +); + +void +loresource_sound_change_master_volume( + loresource_sound_t* sound, + float v, + const rational_t* duration +); diff --git a/core/loresource/sound/README.md b/core/loresource/sound/README.md new file mode 100644 index 0000000..9a37c75 --- /dev/null +++ b/core/loresource/sound/README.md @@ -0,0 +1,16 @@ +sound +==== + +Items whose source is not described are made by catfoot. + +- [bomb.mp3](https://99sounds.org/free-sound-effects/) (edited) +- [enemy_shoot.mp3](https://99sounds.org/mangling-audio/) +- [enemy_trigger.mp3](https://99sounds.org/mangling-audio/) +- [damage.mp3](https://99sounds.org/mangling-audio/) +- [dodge.mp3](https://otologic.jp/free/se/throw01.html) (edited) +- [dying.mp3](https://99sounds.org/free-sound-effects/) (edited) +- guard.mp3 +- [player_shoot.mp3](https://99sounds.org/mangling-audio/) +- [player_trigger.mp3](https://99sounds.org/mangling-audio/) +- reflection.mp3 +- [touch_gate.mp3](https://99sounds.org/mangling-audio/) diff --git a/core/loresource/sound/bomb.mp3 b/core/loresource/sound/bomb.mp3 new file mode 100644 index 0000000..ecea7a8 Binary files /dev/null and b/core/loresource/sound/bomb.mp3 differ diff --git a/core/loresource/sound/damage.mp3 b/core/loresource/sound/damage.mp3 new file mode 100644 index 0000000..a26b6d3 Binary files /dev/null and b/core/loresource/sound/damage.mp3 differ diff --git a/core/loresource/sound/dodge.mp3 b/core/loresource/sound/dodge.mp3 new file mode 100644 index 0000000..06d16c5 Binary files /dev/null and b/core/loresource/sound/dodge.mp3 differ diff --git a/core/loresource/sound/dying.mp3 b/core/loresource/sound/dying.mp3 new file mode 100644 index 0000000..febbaef Binary files /dev/null and b/core/loresource/sound/dying.mp3 differ diff --git a/core/loresource/sound/enemy_shoot.mp3 b/core/loresource/sound/enemy_shoot.mp3 new file mode 100644 index 0000000..55f0ebb Binary files /dev/null and b/core/loresource/sound/enemy_shoot.mp3 differ diff --git a/core/loresource/sound/enemy_trigger.mp3 b/core/loresource/sound/enemy_trigger.mp3 new file mode 100644 index 0000000..8791a89 Binary files /dev/null and b/core/loresource/sound/enemy_trigger.mp3 differ diff --git a/core/loresource/sound/guard.mp3 b/core/loresource/sound/guard.mp3 new file mode 100644 index 0000000..3694e90 Binary files /dev/null and b/core/loresource/sound/guard.mp3 differ diff --git a/core/loresource/sound/player_shoot.mp3 b/core/loresource/sound/player_shoot.mp3 new file mode 100644 index 0000000..3c14527 Binary files /dev/null and b/core/loresource/sound/player_shoot.mp3 differ diff --git a/core/loresource/sound/player_trigger.mp3 b/core/loresource/sound/player_trigger.mp3 new file mode 100644 index 0000000..78eb1fd Binary files /dev/null and b/core/loresource/sound/player_trigger.mp3 differ diff --git a/core/loresource/sound/reflection.mp3 b/core/loresource/sound/reflection.mp3 new file mode 100644 index 0000000..aedad0d Binary files /dev/null and b/core/loresource/sound/reflection.mp3 differ diff --git a/core/loresource/sound/touch_gate.mp3 b/core/loresource/sound/touch_gate.mp3 new file mode 100644 index 0000000..1800fb8 Binary files /dev/null and b/core/loresource/sound/touch_gate.mp3 differ diff --git a/core/loresource/text.c b/core/loresource/text.c new file mode 100644 index 0000000..be289a7 --- /dev/null +++ b/core/loresource/text.c @@ -0,0 +1,32 @@ +#include "./text.h" + +#include + +#include "util/dictres/dictres.h" + +#include "./language.h" + +/* text resource */ +#include "./text/jp.h" + +#define LORESOURCE_TEXT_SWITCH_BY_LANG(lang, each) do { \ + switch (lang) { \ + case LORESOURCE_LANGUAGE_JP: each(loresource_text_jp_); break; \ + default: assert(false); \ + } \ +} while (0) + +void loresource_text_optimize(loresource_language_t lang) { +# define each_(i) dictres_optimize(i) + LORESOURCE_TEXT_SWITCH_BY_LANG(lang, each_); +# undef each_ +} + +const char* loresource_text_get(loresource_language_t lang, const char* key) { +# define each_(i) return dictres_find(i, key) + LORESOURCE_TEXT_SWITCH_BY_LANG(lang, each_); + + assert(false); + return NULL; +# undef each_ +} diff --git a/core/loresource/text.h b/core/loresource/text.h new file mode 100644 index 0000000..1863d1c --- /dev/null +++ b/core/loresource/text.h @@ -0,0 +1,14 @@ +#pragma once + +#include "./language.h" + +void +loresource_text_optimize( + loresource_language_t lang +); + +const char* +loresource_text_get( + loresource_language_t lang, + const char* key +); diff --git a/core/loresource/text/jp.h b/core/loresource/text/jp.h new file mode 100644 index 0000000..044d673 --- /dev/null +++ b/core/loresource/text/jp.h @@ -0,0 +1,88 @@ +#pragma once + +#include + +#include "util/dictres/dictres.h" + +static dictres_item_t loresource_text_jp_[] = { + {"app_name", u8"LEFTONE"}, + {"author", u8"catfoot"}, + + {"biome_metaphysical_gate", u8"不可視の大門"}, + {"biome_cavias_camp", u8"収容棟"}, + {"biome_laboratory", u8"研究棟"}, + {"biome_boss_theists_child", u8"信者の落胤"}, + {"biome_boss_big_warder", u8"巨頭の看守"}, + {"biome_boss_greedy_scientist", u8"貪欲な科学者"}, + + {"boss_big_warder_line0", u8"知能すら持たぬ家畜に踏み殺された仲間たちへの哀れみは"}, + {"boss_big_warder_line1", u8"いまや羨望へとその形を変えた"}, + + {"boss_greedy_scientist_line0", u8"その選択はお前自身のものか?"}, + {"boss_greedy_scientist_line1", u8"胸を張ってそうだと,私達の前で言えるのか?"}, + + {"boss_theists_child_line0", u8"全ては計画通りだった"}, + {"boss_theists_child_line1", u8"しかし空論は未だ宙に浮いたまま"}, + {"boss_theists_child_line2", u8"それが\"創造主\"の意志だというのか"}, + + {"effect_curse", u8"呪縛"}, + {"effect_amnesia", u8"忘却"}, + {"effect_lost", u8"喪失"}, + + {"stance_missionary_name", u8"宣教師"}, + {"stance_missionary_desc", + u8" あなたは自らの使命を果たすために歩き続ける.\n\n" + u8" - 攻撃を受けたとき,信仰をすべて失ったとき,あなたは狂気を徐々に失う.\n" + u8" - 狂気をすべて失ったとき,最後に触れた不可視の大門に転送される."}, + {"stance_missionary_note", + u8"「ああ,そうだ,君が見つけてきてくれよ." + u8"終幕を望む科学者がきっとどこかにいるはずだ.」"}, + + {"stance_philosopher_name", u8"哲学者"}, + {"stance_philosopher_desc", + u8" 鋭く冷たい慧眼は狂気を涵養し,創造主の思惑さえも明るみに引きずり出す.\n\n" + u8" - 攻撃を受けた際の狂気の減少量が低下する."}, + {"stance_philosopher_note", + u8"「あらゆる学問が発展したが世界の成り立ちだけが解明されず,誰も興味を持たない." + u8"まるで洗脳されたように永遠を希求し,脳髄は脳髄自身を疑わず.」"}, + + {"stance_revolutioner_name", u8"革命者"}, + {"stance_revolutioner_desc", + u8" あくまでも優秀な血脈より生まれた意志はあなたに唯一の能動的攻撃手段を授ける.\n\n" + u8" - 攻撃を行うことで,僅かなダメージとノックバックを生む弾丸を発砲する."}, + {"stance_revolutioner_note", + u8"「地階の同胞は今までこの境遇に一切の疑問を持たなかった程に愚かだ." + u8"しかしそれ故に扇動も容易い.着火するのは少しの血だ.」"}, + + {"stance_unfinisher_name", u8"未完走者"}, + {"stance_unfinisher_desc", + u8" 幾度締め上げようと再生する細胞は永遠への敬遠と共に能力をもたらす.\n\n" + u8" - 信仰が50%以上残っている場合にあなたの狂気は徐々に回復する.\n" + u8" - 信仰をすべて失ったとき,狂気の減少速度が増加する."}, + {"stance_unfinisher_note", + u8"「忌々しい奴らに殺された仲間たちを,昔は気の毒だと思っていたよ." + u8"今は妬みしか感じない.」"}, + + {"menu_exit", "EXIT"}, + + {"title_authors", u8"catfoot"}, + {"title_buttons", u8"ESC / CLK"}, + + {"tutorial_title_dead_by_combat", u8"近接戦闘"}, + {"tutorial_text_dead_by_combat", + u8" 薄弱な意志には能動的攻撃の手段は限られる." + u8"凶悪な敵意を跳ね返すことのみがあなたにできるせめてもの抵抗だ.\n\n" + u8" - リング上のマーカーが赤色の部分に触れている間,防御をし続けることで相手を攻撃する.\n" + u8" - 防御に失敗した場合,ダメージを受ける.\n" + u8" - 背後から攻撃を受けた場合,防御が困難になる."}, + {"tutorial_title_dead_by_curse", u8"呪縛"}, + {"tutorial_text_dead_by_curse", + u8" 世界とは裏腹に意志はより強い意志に惹かれ残留する.\n\n" + u8" - あなたが呪縛に苦しめられている間,相手は狂気が尽きようと立ち続ける.\n" + u8" - 呪縛の終了時に相手の狂気が残っている場合,あなたの狂気は消滅する."}, + {"tutorial_title_dead_by_lost", u8"喪失"}, + {"tutorial_text_dead_by_lost", + u8" 悠久の時を信仰なくして立ち続けることはできない.\n\n" + u8" - 信仰が全て失われたとき狂気が徐々に減少する."}, +}; + diff --git a/core/loscene/CMakeLists.txt b/core/loscene/CMakeLists.txt new file mode 100644 index 0000000..94b424b --- /dev/null +++ b/core/loscene/CMakeLists.txt @@ -0,0 +1,23 @@ +add_library(loscene + context.c + game.c + scene.c + title.c +) +target_link_libraries(loscene + msgpackc + + glyphas + jukebox + math + memory + mpkutil + + locommon + loentity + loground + loplayer + loresource + loshader + loworld +) diff --git a/core/loscene/context.c b/core/loscene/context.c new file mode 100644 index 0000000..c6dfc14 --- /dev/null +++ b/core/loscene/context.c @@ -0,0 +1,127 @@ +#include "./context.h" + +#include +#include +#include +#include + +#include "util/glyphas/context.h" +#include "util/jukebox/mixer.h" +#include "util/math/algorithm.h" +#include "util/math/vector.h" +#include "util/memory/memory.h" + +#include "core/locommon/input.h" +#include "core/locommon/ticker.h" +#include "core/loresource/set.h" +#include "core/loshader/set.h" + +#include "./game.h" +#include "./param.h" +#include "./scene.h" +#include "./title.h" + +struct loscene_context_t { + glyphas_context_t glyphas; + jukebox_mixer_t* mixer; + + locommon_ticker_t ticker; + + loresource_set_t resources; + loshader_set_t shaders; + + loscene_t* scene; + + loscene_param_t param; +}; + +#define loscene_context_mixer_reserve_ 256 + +static const jukebox_format_t loscene_context_mixer_format_ = { + .sample_rate = 48000, + .channels = 2, +}; + +static loscene_t* loscene_context_create_start_scene_(loscene_context_t* ctx) { + assert(ctx != NULL); + + /* Unless the context is deleted, scenes can hold a pointer to ctx->param. */ + + if (ctx->param.skip_title) { + return loscene_game_new( + &ctx->param, &ctx->resources, &ctx->shaders, &ctx->ticker, true); + } + return loscene_title_new( + &ctx->param, &ctx->resources, &ctx->shaders, &ctx->ticker); +} + +loscene_context_t* loscene_context_new(const loscene_param_t* param) { + assert(param != NULL); + + loscene_context_t* ctx = memory_new(sizeof(*ctx)); + *ctx = (typeof(*ctx)) { + .param = *param, + }; + + glyphas_context_initialize(&ctx->glyphas); + + ctx->mixer = jukebox_mixer_new( + &loscene_context_mixer_format_, loscene_context_mixer_reserve_); + + locommon_ticker_initialize(&ctx->ticker, 0); + + loresource_set_initialize( + &ctx->resources, + ctx->mixer, + &loscene_context_mixer_format_, + LORESOURCE_LANGUAGE_JP); + + loshader_set_initialize( + &ctx->shaders, + param->width, + param->height, + ¶m->dpi, + param->max_msaa); + + ctx->scene = loscene_context_create_start_scene_(ctx); + return ctx; +} + +void loscene_context_delete(loscene_context_t* ctx) { + if (ctx == NULL) return; + + /* Firstly delete the mixer working in other thread. */ + jukebox_mixer_delete(ctx->mixer); + + loscene_delete(ctx->scene); + + loshader_set_deinitialize(&ctx->shaders); + loresource_set_deinitialize(&ctx->resources); + + locommon_ticker_deinitialize(&ctx->ticker); + + glyphas_context_deinitialize(&ctx->glyphas); + + memory_delete(ctx); +} + +bool loscene_context_update( + loscene_context_t* ctx, const locommon_input_t* input, uint64_t uptime) { + assert(ctx != NULL); + assert(input != NULL); + + locommon_ticker_tick(&ctx->ticker, uptime); + + loscene_t* s = loscene_update(ctx->scene, input); + if (s != ctx->scene) { + loscene_delete(ctx->scene); + ctx->scene = s; + } + return ctx->scene != NULL; +} + +void loscene_context_draw(loscene_context_t* ctx) { + assert(ctx != NULL); + + loscene_draw(ctx->scene); +} diff --git a/core/loscene/context.h b/core/loscene/context.h new file mode 100644 index 0000000..80da79f --- /dev/null +++ b/core/loscene/context.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +#include "util/math/vector.h" + +#include "core/locommon/input.h" + +#include "./param.h" + +struct loscene_context_t; +typedef struct loscene_context_t loscene_context_t; + +loscene_context_t* /* OWNERSHIP */ +loscene_context_new( + const loscene_param_t* param +); + +void +loscene_context_delete( + loscene_context_t* ctx /* OWNERSHIP */ +); + +bool +loscene_context_update( + loscene_context_t* ctx, + const locommon_input_t* input, + uint64_t uptime +); + +void +loscene_context_draw( + loscene_context_t* ctx +); diff --git a/core/loscene/game.c b/core/loscene/game.c new file mode 100644 index 0000000..d65c845 --- /dev/null +++ b/core/loscene/game.c @@ -0,0 +1,398 @@ +#include "./game.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "util/math/matrix.h" +#include "util/math/vector.h" +#include "util/memory/memory.h" +#include "util/mpkutil/file.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/lobullet/pool.h" +#include "core/locharacter/pool.h" +#include "core/locommon/counter.h" +#include "core/locommon/ticker.h" +#include "core/loentity/store.h" +#include "core/loground/pool.h" +#include "core/loplayer/camera.h" +#include "core/loplayer/player.h" +#include "core/loresource/set.h" +#include "core/loshader/set.h" +#include "core/loworld/environment.h" +#include "core/loworld/generator.h" +#include "core/loworld/poolset.h" +#include "core/loworld/store.h" +#include "core/loworld/template.h" +#include "core/loworld/view.h" + +#include "./param.h" +#include "./scene.h" +#include "./title.h" + +typedef struct { + loscene_t header; + + const loscene_param_t* param; + const locommon_ticker_t* app_ticker; + loresource_set_t* res; + loshader_set_t* shaders; + + uint64_t app_begin_time; + uint64_t begin_time; + + locommon_counter_t idgen; + locommon_ticker_t ticker; + + loentity_store_t* entities; + + loplayer_t player; + loworld_poolset_t pools; + + loworld_generator_t* generator; + loworld_store_t* world; + loworld_view_t* view; + loworld_environment_t environment; + + /* temporary parameters */ + bool updated; + mat4_t proj; +} loscene_game_t; + +#define LOSCENE_GAME_MAX_DELTA_TIME 500 + +#define LOSCENE_GAME_SCALE 4.0f + +#define LOSCENE_GAME_ENTITY_STORE_RESERVE 256 +#define LOSCENE_GAME_WORLD_STORE_CHUNK_RESERVE 64 +#define LOSCENE_GAME_BULLETS_PER_CHUNK 64 + +#define LOSCENE_GAME_DATA_BASEPATH "./data/" + +#define LOSCENE_GAME_DATA_FILE_PATH \ + (LOSCENE_GAME_DATA_BASEPATH"game.msgpack") +#define LOSCENE_GAME_WORLD_STORE_BASEPATH \ + (LOSCENE_GAME_DATA_BASEPATH"world/") + +static void loscene_game_build_projection_matrix_(loscene_game_t* s) { + assert(s != NULL); + + static const float chunk_inch = 16; + static const float max_scale = 1/.5f; + + float yscale = s->shaders->dpi.y*chunk_inch/s->shaders->resolution.y*2; + float xscale = s->shaders->dpi.x*chunk_inch/s->shaders->resolution.x*2; + + if (xscale > max_scale) { + yscale *= max_scale/xscale; + xscale = max_scale; + } + + s->proj = mat4_scale(xscale, yscale, 1); +} + +static void loscene_game_convert_viewport_pos_to_chunk_pos_( + loscene_game_t* s, locommon_position_t* pos, const vec2_t* vpos) { + assert(s != NULL); + assert(locommon_position_valid(pos)); + + mat4_t m, inv; + mat4_mul(&m, &s->proj, &s->player.camera.matrix); + mat4_inv(&inv, &m); + + vec4_t disp4; + const vec4_t vpos4 = vec4(vpos->x, vpos->y, 0, 1); + mat4_mul_vec4(&disp4, &inv, &vpos4); + + vec2_addeq(&pos->fract, &disp4.xy); + locommon_position_reduce(pos); +} + +static bool loscene_game_load_(loscene_game_t* scene) { + assert(scene != NULL); + /* ! Please note that world and view objects may be invalid now. ! */ + + bool ret = false; + + msgpack_unpacker unpacker; + if (!msgpack_unpacker_init(&unpacker, 1024)) return false; + + msgpack_unpacked unpacked; + msgpack_unpacked_init(&unpacked); + + FILE* fp = fopen(LOSCENE_GAME_DATA_FILE_PATH, "rb"); + if (fp == NULL) goto FINALIZE; + + const bool loaded = + mpkutil_file_unpack_with_unpacker(&unpacked, fp, &unpacker); + fclose(fp); + if (!loaded) goto FINALIZE; + + const msgpack_object_map* root = mpkutil_get_map(&unpacked.data); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + + if (!loplayer_unpack(&scene->player, item_("player")) || + !loworld_generator_unpack(scene->generator, item_("generator")) || + !locommon_ticker_unpack(&scene->ticker, item_("ticker")) || + !locommon_counter_unpack(&scene->idgen, item_("idgen"))) { + goto FINALIZE; + } + +# undef item_ + + ret = true; +FINALIZE: + msgpack_unpacked_destroy(&unpacked); + msgpack_unpacker_destroy(&unpacker); + + scene->begin_time = scene->ticker.time; + return ret; +} + +static bool loscene_game_save_(loscene_game_t* scene) { + assert(scene != NULL); + + bool ret = true; + + loworld_view_flush_store(scene->view); + if (loworld_store_is_error_happened(scene->world)) ret = false; + + FILE* fp = fopen(LOSCENE_GAME_DATA_FILE_PATH, "wb"); + if (fp == NULL) return false; + + msgpack_packer pk; + msgpack_packer_init(&pk, fp, msgpack_fbuffer_write); + + msgpack_pack_map(&pk, 4); + + mpkutil_pack_str(&pk, "player"); + loplayer_pack(&scene->player, &pk); + + mpkutil_pack_str(&pk, "generator"); + loworld_generator_pack(scene->generator, &pk); + + mpkutil_pack_str(&pk, "ticker"); + locommon_ticker_pack(&scene->ticker, &pk); + + mpkutil_pack_str(&pk, "idgen"); + locommon_counter_pack(&scene->idgen, &pk); + + fclose(fp); + return ret; +} + +static void loscene_game_delete_(loscene_t* scene) { + if (scene == NULL) return; + + loscene_game_t* s = (typeof(s)) scene; + if (!loscene_game_save_(s)) { + fprintf(stderr, "failed to save game data\n"); + } + + loworld_environment_deinitialize(&s->environment); + loworld_view_delete(s->view); + loworld_store_delete(s->world); + loworld_generator_delete(s->generator); + + loentity_store_clear(s->entities); + + locharacter_pool_delete(s->pools.character); + loplayer_deinitialize(&s->player); + + lobullet_pool_delete(s->pools.bullet); + loground_pool_delete(s->pools.ground); + + loentity_store_delete(s->entities); + + locommon_ticker_deinitialize(&s->ticker); + locommon_counter_deinitialize(&s->idgen); + + memory_delete(scene); +} + +static loscene_t* loscene_game_update_( + loscene_t* scene, const locommon_input_t* input) { + assert(scene != NULL); + assert(input != NULL); + + loscene_game_t* s = (typeof(s)) scene; + s->updated = false; + + const uint64_t t = s->app_ticker->time - s->app_begin_time + s->begin_time; + locommon_ticker_tick(&s->ticker, t); + if (s->ticker.delta > LOSCENE_GAME_MAX_DELTA_TIME) { + fprintf(stderr, "1 tick took too long (%"PRId64" ms)\n", s->ticker.delta); + return scene; + } + + locommon_position_t cursor = s->player.camera.pos; + loscene_game_convert_viewport_pos_to_chunk_pos_(s, &cursor, &input->cursor); + + loplayer_update(&s->player, input, &cursor); + if (loplayer_menu_is_exit_requested(s->player.menu)) { + return loscene_title_new(s->param, s->res, s->shaders, s->app_ticker); + } + + loworld_view_look(s->view, &s->player.camera.pos); + loworld_view_update(s->view); + + loworld_environment_update(&s->environment); + + s->updated = true; + return scene; +} + +static void loscene_game_draw_(loscene_t* scene) { + assert(scene != NULL); + + loscene_game_t* s = (typeof(s)) scene; + if (!s->updated) return; + + const loshader_uniblock_param_t p = { + .proj = s->proj, + .cam = s->player.camera.matrix, + .pos = s->player.camera.pos, + .time = s->ticker.time%60000/1000.0f, + }; + loshader_uniblock_update_param(s->shaders->uniblock, &p); + + loshader_set_clear_all(s->shaders); + + loworld_environment_draw(&s->environment); + loworld_view_draw(s->view); + loplayer_draw(&s->player); + + loshader_set_draw_all(s->shaders); +} + +static void loscene_game_execute_tests_( + const loscene_game_t* scene, const loscene_param_t* param) { + assert(scene != NULL); + assert(param != NULL); + + if (param->test.loworld_poolset_packing) { + loworld_poolset_test_packing(&scene->pools); + } + if (param->test.loplayer_packing) { + loplayer_test_packing(&scene->player); + } +} + +loscene_t* loscene_game_new( + const loscene_param_t* param, + loresource_set_t* res, + loshader_set_t* shaders, + const locommon_ticker_t* ticker, + bool load) { + assert(param != NULL); + assert(shaders != NULL); + assert(res != NULL); + assert(ticker != NULL); + + loshader_set_drop_cache(shaders); + + loscene_game_t* scene = memory_new(sizeof(*scene)); + *scene = (typeof(*scene)) { + .header = { + .vtable = { + .delete = loscene_game_delete_, + .update = loscene_game_update_, + .draw = loscene_game_draw_, + }, + }, + .param = param, + .app_ticker = ticker, + .res = res, + .shaders = shaders, + .app_begin_time = ticker->time, + }; + loscene_game_build_projection_matrix_(scene); + + locommon_counter_initialize(&scene->idgen, 0); + locommon_ticker_initialize(&scene->ticker, 0); + + scene->entities = loentity_store_new( + LOSCENE_GAME_ENTITY_STORE_RESERVE); + + scene->pools.ground = loground_pool_new( + scene->shaders->drawer.ground, + &scene->idgen, + LOSCENE_GAME_WORLD_STORE_CHUNK_RESERVE* + LOWORLD_TEMPLATE_MAX_CHARACTERS_PER_CHUNK); + + scene->pools.bullet = lobullet_pool_new( + res, + scene->shaders->drawer.bullet, + &scene->idgen, + &scene->ticker, + scene->entities, + LOSCENE_GAME_WORLD_STORE_CHUNK_RESERVE* + LOSCENE_GAME_BULLETS_PER_CHUNK); + + loplayer_initialize( + &scene->player, + locommon_counter_count(&scene->idgen), /* = Absolutely 0 */ + res, + scene->shaders, + &scene->ticker, + scene->pools.bullet, + scene->entities, + &scene->proj); + scene->player.camera.brightness = param->brightness/1000.f; + + scene->pools.character = locharacter_pool_new( + res, + scene->shaders->drawer.character, + &scene->idgen, + &scene->ticker, + scene->pools.bullet, + scene->entities, + &scene->player, + LOSCENE_GAME_WORLD_STORE_CHUNK_RESERVE* + LOWORLD_TEMPLATE_MAX_CHARACTERS_PER_CHUNK); + + scene->generator = loworld_generator_new( + &scene->pools, + scene->app_ticker->time*98641 /* = prime number */); + + if (load) { + if (!loscene_game_load_(scene)) { + fprintf(stderr, "failed to load game data\n"); + } + } + + scene->world = loworld_store_new( + &scene->pools, + scene->generator, + LOSCENE_GAME_WORLD_STORE_CHUNK_RESERVE, + LOSCENE_GAME_WORLD_STORE_BASEPATH, + strlen(LOSCENE_GAME_WORLD_STORE_BASEPATH)); + + scene->view = loworld_view_new( + scene->world, + scene->entities, + &scene->player.camera.pos); + + loworld_environment_initialize( + &scene->environment, + res, + scene->shaders, + &scene->ticker, + scene->view, + &scene->player, + ¶m->environment); + + loscene_game_execute_tests_(scene, param); + + return &scene->header; +} diff --git a/core/loscene/game.h b/core/loscene/game.h new file mode 100644 index 0000000..e6c907f --- /dev/null +++ b/core/loscene/game.h @@ -0,0 +1,17 @@ +#pragma once + +#include "core/locommon/ticker.h" +#include "core/loresource/set.h" +#include "core/loshader/set.h" + +#include "./param.h" +#include "./scene.h" + +loscene_t* /* OWNERSHIP */ +loscene_game_new( + const loscene_param_t* param, + loresource_set_t* res, + loshader_set_t* shaders, + const locommon_ticker_t* ticker, + bool load +); diff --git a/core/loscene/param.h b/core/loscene/param.h new file mode 100644 index 0000000..7510b2f --- /dev/null +++ b/core/loscene/param.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +#include "util/math/vector.h" + +#include "core/loworld/environment.h" + +typedef struct { + int32_t width; + int32_t height; + vec2_t dpi; + + int32_t max_msaa; + int32_t brightness; /* darkest |0 <- 1000 -> 2000| brightest */ + + loworld_environment_config_t environment; + + bool skip_title; + + struct { + bool loworld_poolset_packing; + bool loplayer_packing; + } test; +} loscene_param_t; diff --git a/core/loscene/scene.c b/core/loscene/scene.c new file mode 100644 index 0000000..9ca3f5d --- /dev/null +++ b/core/loscene/scene.c @@ -0,0 +1,28 @@ +#include "./scene.h" + +#include +#include + +#include "core/locommon/input.h" + +void loscene_delete(loscene_t* scene) { + if (scene == NULL) return; + + assert(scene->vtable.delete != NULL); + scene->vtable.delete(scene); +} + +loscene_t* loscene_update(loscene_t* scene, const locommon_input_t* input) { + assert(scene != NULL); + assert(input != NULL); + + assert(scene->vtable.update != NULL); + return scene->vtable.update(scene, input); +} + +void loscene_draw(loscene_t* scene) { + assert(scene != NULL); + + assert(scene->vtable.draw != NULL); + scene->vtable.draw(scene); +} diff --git a/core/loscene/scene.h b/core/loscene/scene.h new file mode 100644 index 0000000..3ec1b37 --- /dev/null +++ b/core/loscene/scene.h @@ -0,0 +1,43 @@ +#pragma once + +#include "core/locommon/input.h" + +struct loscene_t; +typedef struct loscene_t loscene_t; + +typedef struct { + void + (*delete)( + loscene_t* scene + ); + + loscene_t* + (*update)( + loscene_t* scene, + const locommon_input_t* input + ); + void + (*draw)( + loscene_t* scene + ); +} loscene_vtable_t; + +struct loscene_t { + loscene_vtable_t vtable; +}; + +void +loscene_delete( + loscene_t* scene +); + +loscene_t* +loscene_update( + loscene_t* scene, + const locommon_input_t* input +); + +void +loscene_draw( + loscene_t* scene +); diff --git a/core/loscene/title.c b/core/loscene/title.c new file mode 100644 index 0000000..10f387b --- /dev/null +++ b/core/loscene/title.c @@ -0,0 +1,333 @@ +#include "./title.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include "util/chaos/xorshift.h" +#include "util/glyphas/block.h" +#include "util/glyphas/cache.h" +#include "util/jukebox/amp.h" +#include "util/jukebox/decoder.h" +#include "util/math/matrix.h" +#include "util/math/rational.h" +#include "util/math/vector.h" +#include "util/memory/memory.h" + +#include "core/locommon/input.h" +#include "core/locommon/position.h" +#include "core/loresource/music.h" +#include "core/loresource/set.h" +#include "core/loresource/text.h" +#include "core/loshader/backwall.h" +#include "core/loshader/fog.h" +#include "core/loshader/menu_text.h" +#include "core/loshader/set.h" + +#include "./game.h" +#include "./param.h" +#include "./scene.h" + +typedef enum { + LOSCENE_TITLE_STATE_EXPECT_BUTTON_RELEASE, + LOSCENE_TITLE_STATE_EXPECT_BUTTON_PRESS, + + LOSCENE_TITLE_STATE_FADING_TO_GAME, + LOSCENE_TITLE_STATE_FADING_TO_EXIT, +} loscene_title_state_t; + +typedef struct { + loscene_t header; + + const loscene_param_t* param; + loresource_set_t* res; + loshader_set_t* shaders; + const locommon_ticker_t* ticker; + + struct { + vec2_t fontsz_large; + vec2_t fontsz_small; + + vec2_t fontpx_large; + vec2_t fontpx_small; + } geo; + + struct { + glyphas_cache_t* large; + glyphas_cache_t* small; + } font; + + struct { + glyphas_block_t* title; + glyphas_block_t* author; + glyphas_block_t* buttons; + } text; + + loresource_music_player_t* music; + + loscene_title_state_t state; + uint64_t since; + + float fade; + +} loscene_title_t; + +static void loscene_title_calculate_geometry_(loscene_title_t* s) { + assert(s != NULL); + + const vec2_t* dpi = &s->shaders->dpi; + const vec2_t* reso = &s->shaders->resolution; + + s->geo.fontpx_large = *dpi; + vec2_muleq(&s->geo.fontpx_large, .8f); + + s->geo.fontpx_small = *dpi; + vec2_muleq(&s->geo.fontpx_small, .14f); + +# define px_to_disp_(v) vec2((v).x/reso->x*2, (v).y/reso->y*2) + + s->geo.fontsz_large = px_to_disp_(s->geo.fontpx_large); + s->geo.fontsz_small = px_to_disp_(s->geo.fontpx_small); + +# undef px_to_disp_ +} + +static void loscene_title_create_text_block_(loscene_title_t* s) { + assert(s != NULL); + + const char* title = loresource_text_get(s->res->lang, "app_name"); + const char* author = loresource_text_get(s->res->lang, "title_authors"); + const char* buttons = loresource_text_get(s->res->lang, "title_buttons"); + + static const vec4_t color = vec4(1, 1, 1, 1); + + s->text = (typeof(s->text)) { + .title = glyphas_block_new( + GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL, + -s->geo.fontpx_large.y, + INT32_MAX, + 16), + .author = glyphas_block_new( + GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL, + -s->geo.fontpx_small.y, + INT32_MAX, + 256), + .buttons = glyphas_block_new( + GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL, + -s->geo.fontpx_small.y, + INT32_MAX, + 16), + }; + + glyphas_block_add_characters( + s->text.title, s->font.large, &color, title, strlen(title)); + glyphas_block_add_characters( + s->text.author, s->font.small, &color, author, strlen(author)); + glyphas_block_add_characters( + s->text.buttons, s->font.small, &color, buttons, strlen(buttons)); + + const vec2_t scale = vec2( + 2/s->shaders->resolution.x, 2/s->shaders->resolution.y); + glyphas_block_scale(s->text.title, &scale); + glyphas_block_scale(s->text.author, &scale); + glyphas_block_scale(s->text.buttons, &scale); + + glyphas_block_set_origin(s->text.title, &vec2(.5f, -.5f)); + glyphas_block_set_origin(s->text.author, &vec2(.5f, 0)); + glyphas_block_set_origin(s->text.buttons, &vec2(.5f, -1.f)); + + glyphas_block_translate( + s->text.author, &vec2(0, -s->geo.fontsz_large.y*.7f)); + glyphas_block_translate( + s->text.buttons, &vec2(0, -.9f)); +} + +static void loscene_title_update_uniblock_(loscene_title_t* s) { + assert(s != NULL); + + static const uint64_t camspeed = 5000; + + const uint64_t chunk = s->ticker->time/camspeed; + const float fract = s->ticker->time%camspeed*1.f/camspeed; + + const loshader_uniblock_param_t p = { + .proj = mat4_identity(), + .cam = mat4_identity(), + .pos = locommon_position(chunk, 0, vec2(fract, 0)), + .time = s->ticker->time%60000/1000.0f, + }; + loshader_uniblock_update_param(s->shaders->uniblock, &p); +} + +static void loscene_title_delete_(loscene_t* scene) { + if (scene == NULL) return; + + loscene_title_t* s = (typeof(s)) scene; + + glyphas_block_delete(s->text.title); + glyphas_block_delete(s->text.author); + glyphas_block_delete(s->text.buttons); + + glyphas_cache_delete(s->font.large); + glyphas_cache_delete(s->font.small); + + memory_delete(s); +} + +static loscene_t* loscene_title_update_( + loscene_t* scene, const locommon_input_t* input) { + assert(scene != NULL); + assert(input != NULL); + + static const uint64_t fadedur = 1500; + + loscene_title_t* s = (typeof(s)) scene; + + if (s->music == NULL) { + s->music = &s->res->music.title; + jukebox_decoder_play(s->music->decoder, &rational(0, 1), true); + jukebox_amp_change_volume(&s->music->amp, 1, &rational(1, 1)); + } + + switch (s->state) { + case LOSCENE_TITLE_STATE_EXPECT_BUTTON_RELEASE: + s->fade = 1 - (s->ticker->time - s->since)*1.f / fadedur; + if (s->fade < 0) s->fade = 0; + if (input->buttons == 0 && s->fade <= 0) { + s->state = LOSCENE_TITLE_STATE_EXPECT_BUTTON_PRESS; + } + break; + case LOSCENE_TITLE_STATE_EXPECT_BUTTON_PRESS: + if (input->buttons & LOCOMMON_INPUT_BUTTON_ATTACK) { + jukebox_amp_change_volume(&s->music->amp, 0, &rational(fadedur, 1000)); + jukebox_decoder_stop_after(s->music->decoder, &rational(fadedur, 1000)); + + s->state = LOSCENE_TITLE_STATE_FADING_TO_GAME; + s->since = s->ticker->time; + } else if (input->buttons & LOCOMMON_INPUT_BUTTON_MENU) { + jukebox_amp_change_volume(&s->music->amp, 0, &rational(fadedur, 1000)); + jukebox_decoder_stop_after(s->music->decoder, &rational(fadedur, 1000)); + + s->state = LOSCENE_TITLE_STATE_FADING_TO_EXIT; + s->since = s->ticker->time; + } + break; + case LOSCENE_TITLE_STATE_FADING_TO_GAME: + if (s->since + fadedur < s->ticker->time) { + return loscene_game_new( + s->param, s->res, s->shaders, s->ticker, true /* = load data */); + } else { + s->fade = (s->ticker->time - s->since)*1.f/fadedur; + } + break; + case LOSCENE_TITLE_STATE_FADING_TO_EXIT: + if (s->since + fadedur < s->ticker->time) { + return NULL; + } else { + s->fade = (s->ticker->time - s->since)*1.f/fadedur; + } + break; + } + return scene; +} + +static void loscene_title_draw_(loscene_t* scene) { + assert(scene != NULL); + + loscene_title_t* s = (typeof(s)) scene; + loscene_title_update_uniblock_(s); + + loshader_set_clear_all(s->shaders); + + loshader_backwall_drawer_set_param( + s->shaders->drawer.backwall, + &(loshader_backwall_drawer_param_t) { + .type = LOSHADER_BACKWALL_TYPE_JAIL, + .transition = 1, + }); + + loshader_fog_drawer_set_param( + s->shaders->drawer.fog, + &(loshader_fog_drawer_param_t) { + .type = LOSHADER_FOG_TYPE_WHITE_CLOUD, + .transition = 1, + }); + + loshader_pixsort_drawer_set_intensity(s->shaders->drawer.pixsort, 0); + + loshader_posteffect_drawer_set_param( + s->shaders->drawer.posteffect, + &(loshader_posteffect_drawer_param_t) { + .whole_blur = 1, + .radial_displacement = .05f, + .amnesia_displacement = .1f, + .radial_fade = .5f + s->fade*.3f, + .brightness = s->param->brightness/1000.f, + }); + + s->shaders->drawer.menu_text.alpha = 1; + loshader_menu_text_drawer_add_block( + &s->shaders->drawer.menu_text, s->text.title); + loshader_menu_text_drawer_add_block( + &s->shaders->drawer.menu_text, s->text.author); + loshader_menu_text_drawer_add_block( + &s->shaders->drawer.menu_text, s->text.buttons); + + loshader_cinescope_drawer_set_param( + s->shaders->drawer.cinescope, + &(loshader_cinescope_drawer_param_t) {0}); + + loshader_set_draw_all(s->shaders); +} + +loscene_t* loscene_title_new( + const loscene_param_t* param, + loresource_set_t* res, + loshader_set_t* shaders, + const locommon_ticker_t* ticker) { + assert(param != NULL); + assert(res != NULL); + assert(shaders != NULL); + assert(ticker != NULL); + + loshader_set_drop_cache(shaders); + + loscene_title_t* scene = memory_new(sizeof(*scene)); + *scene = (typeof(*scene)) { + .header = { + .vtable = { + .delete = loscene_title_delete_, + .update = loscene_title_update_, + .draw = loscene_title_draw_, + }, + }, + .param = param, + .res = res, + .shaders = shaders, + .ticker = ticker, + + .state = LOSCENE_TITLE_STATE_EXPECT_BUTTON_RELEASE, + .since = ticker->time, + }; + loscene_title_calculate_geometry_(scene); + + scene->font = (typeof(scene->font)) { + .large = glyphas_cache_new( + shaders->tex.menu_text, + &res->font.sans, + scene->geo.fontpx_large.x, + scene->geo.fontpx_large.y), + .small = glyphas_cache_new( + shaders->tex.menu_text, + &res->font.sans, + scene->geo.fontpx_small.x, + scene->geo.fontpx_small.y), + }; + loscene_title_create_text_block_(scene); + return &scene->header; +} diff --git a/core/loscene/title.h b/core/loscene/title.h new file mode 100644 index 0000000..9795a05 --- /dev/null +++ b/core/loscene/title.h @@ -0,0 +1,16 @@ +#pragma once + +#include "core/locommon/ticker.h" +#include "core/loresource/set.h" +#include "core/loshader/set.h" + +#include "./param.h" +#include "./scene.h" + +loscene_t* /* OWNERSHIP */ +loscene_title_new( + const loscene_param_t* param, + loresource_set_t* res, + loshader_set_t* shaders, + const locommon_ticker_t* ticker +); diff --git a/core/loshader/CMakeLists.txt b/core/loshader/CMakeLists.txt new file mode 100644 index 0000000..c045519 --- /dev/null +++ b/core/loshader/CMakeLists.txt @@ -0,0 +1,63 @@ +add_library(loshader + backwall.c + bullet.c + character.c + cinescope.c + combat_ring.c + event_line.c + fog.c + ground.c + hud_bar.c + hud_text.c + menu_background.c + menu_stance.c + menu_text.c + pixsort.c + posteffect.c + set.c + uniblock.c +) +target_any_sources(loshader + backwall.vshader + backwall.fshader + bullet.vshader + bullet.fshader + character.vshader + character.fshader + cinescope.vshader + cinescope.fshader + combat_ring.vshader + combat_ring.fshader + event_line.vshader + event_line.fshader + fog.vshader + fog.fshader + ground.vshader + ground.fshader + header.shader + hud_bar.vshader + hud_bar.fshader + hud_text.vshader + hud_text.fshader + menu_background.vshader + menu_background.fshader + menu_stance.vshader + menu_stance.fshader + menu_text.vshader + menu_text.fshader + pixsort.vshader + pixsort.fshader + posteffect.vshader + posteffect.fshader +) +target_link_libraries(loshader + GLEW::GLEW + OpenGL::GL + + gleasy + glyphas + math + memory + + locommon +) diff --git a/core/loshader/backwall.c b/core/loshader/backwall.c new file mode 100644 index 0000000..62e96f5 --- /dev/null +++ b/core/loshader/backwall.c @@ -0,0 +1,118 @@ +#include "./backwall.h" + +#include +#include +#include + +#include "util/gleasy/buffer.h" +#include "util/gleasy/program.h" +#include "util/math/vector.h" +#include "util/memory/memory.h" + +#include "./uniblock.h" + +/* resources */ +#include "anysrc/header.shader.h" +#include "anysrc/backwall.vshader.h" +#include "anysrc/backwall.fshader.h" + +struct loshader_backwall_drawer_t { + const loshader_backwall_program_t* prog; + const loshader_uniblock_t* uniblock; + + gleasy_buffer_uniform_t param; +}; + +#pragma pack(push, 1) +typedef struct { + float type; + float prev_type; + float transition; +} loshader_backwall_drawer_internal_param_t; +_Static_assert( + sizeof(float)*3 == + sizeof(loshader_backwall_drawer_internal_param_t)); +#pragma pack(pop) + +#define LOSHADER_BACKWALL_UNIBLOCK_INDEX 0 +#define LOSHADER_BACKWALL_PARAM_INDEX 1 + +#define LOSHADER_BACKWALL_PRIMITIVE_COUNT 6 + +void loshader_backwall_program_initialize(loshader_backwall_program_t* prog) { + assert(prog != NULL); + + *prog = gleasy_program_new( + loshader_header_shader_, sizeof(loshader_header_shader_), + loshader_backwall_vshader_, sizeof(loshader_backwall_vshader_), + loshader_backwall_fshader_, sizeof(loshader_backwall_fshader_)); + + const GLuint uniblock = glGetUniformBlockIndex(*prog, "uniblock"); + assert(glGetError() == GL_NO_ERROR); + glUniformBlockBinding(*prog, uniblock, LOSHADER_BACKWALL_UNIBLOCK_INDEX); + + const GLuint param = glGetUniformBlockIndex(*prog, "param"); + assert(glGetError() == GL_NO_ERROR); + glUniformBlockBinding(*prog, param, LOSHADER_BACKWALL_PARAM_INDEX); +} + +void loshader_backwall_program_deinitialize(loshader_backwall_program_t* prog) { + assert(prog != NULL); + + glDeleteProgram(*prog); +} + +loshader_backwall_drawer_t* loshader_backwall_drawer_new( + const loshader_backwall_program_t* prog, + const loshader_uniblock_t* uniblock) { + assert(prog != NULL); + assert(uniblock != NULL); + + loshader_backwall_drawer_t* drawer = memory_new(sizeof(*drawer)); + *drawer = (typeof(*drawer)) { + .prog = prog, + .uniblock = uniblock, + }; + + glGenBuffers(1, &drawer->param); + glBindBuffer(GL_UNIFORM_BUFFER, drawer->param); + glBufferData(GL_UNIFORM_BUFFER, + sizeof(loshader_backwall_drawer_internal_param_t), NULL, GL_DYNAMIC_DRAW); + + return drawer; +} + +void loshader_backwall_drawer_delete(loshader_backwall_drawer_t* drawer) { + if (drawer == NULL) return; + + glDeleteBuffers(1, &drawer->param); + + memory_delete(drawer); +} + +void loshader_backwall_drawer_set_param( + loshader_backwall_drawer_t* drawer, + const loshader_backwall_drawer_param_t* param) { + assert(drawer != NULL); + assert(param != NULL); + + const loshader_backwall_drawer_internal_param_t p = { + .type = param->type, + .prev_type = param->prev_type, + .transition = param->transition, + }; + glBindBuffer(GL_UNIFORM_BUFFER, drawer->param); + glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(p), &p); +} + +void loshader_backwall_drawer_draw(const loshader_backwall_drawer_t* drawer) { + assert(drawer != NULL); + + glUseProgram(*drawer->prog); + + loshader_uniblock_bind(drawer->uniblock, LOSHADER_BACKWALL_UNIBLOCK_INDEX); + glBindBufferBase(GL_UNIFORM_BUFFER, + LOSHADER_BACKWALL_PARAM_INDEX, drawer->param); + + glDrawArrays(GL_TRIANGLES, 0, LOSHADER_BACKWALL_PRIMITIVE_COUNT); +} diff --git a/core/loshader/backwall.fshader b/core/loshader/backwall.fshader new file mode 100644 index 0000000..52433f7 --- /dev/null +++ b/core/loshader/backwall.fshader @@ -0,0 +1,220 @@ +layout(std140) uniform param { + float type; + float prev_type; + float transition; +} p; + +in vec2 v_uv; + +out vec4 o_color; + +const float EPSILON = 1e-4; +const float INF = 1e+2; + +/* ---- UTILITY FUNCTIONS ---- */ +vec3 get_ray_direction(in vec2 p, in vec2 s, float fov) { + return normalize(vec3(p*s/2., s.y/tan(radians(fov)/2.))); +} +mat2 rot(in float theta) { + float c = cos(theta); + float s = sin(theta); + return mat2(c, -s, s, c); +} +#define get_normal(sdf, d, p) \ + normalize(d - vec3( \ + sdf(p-vec3(EPSILON, 0., 0.)), \ + sdf(p-vec3(0., EPSILON, 0.)), \ + sdf(p-vec3(0., 0., EPSILON)))) + +/* ---- SIGNED DISTANCE FUNCTIONS ---- */ +/* https://iquilezles.org/www/articles/distfunctions/distfunctions.htm */ +float sd_box( vec3 p, vec3 b ) { + vec3 q = abs(p) - b; + return length(max(q,0.)) + min(max(q.x,max(q.y,q.z)),0.); +} +float sd_round_box( vec3 p, vec3 b, float r ) { + vec3 q = abs(p) - b; + return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0) - r; +} + +/* ---- SCENE: infinite boxes ---- */ +float infinite_boxes_sd_scene(in vec3 p) { + p = abs(p); + p = mod(p, 2.) - 1.; + return sd_box(p, vec3(.5)); +} +vec3 infinite_boxes_raymarch(in vec2 uv, in vec3 eye) { + vec3 dir = get_ray_direction(uv, uni.resolution, 60.); + + float h = 2., d, i; + for (i = 0.; i < 100.; ++i) { + d = infinite_boxes_sd_scene(eye + dir*h); + if (d < EPSILON || h > 100.) break; + h += d; + } + float a = 45./(i+1.)/h; + return mix(vec3(.3, .3, .4), vec3(.6, .8, .7), clamp(a, 0., 1.)); +} +vec4 infinite_boxes(void) { + vec2 e2 = vec2(0., 1.) / uni.resolution; + + vec3 eye = vec3(0., fract(uni.time/60.)*2., 0.); + + eye.xy += fract(uni.pos.xy/2.)*2. + uni.pos.zw - vec2(.5, .5); + + vec3 col = infinite_boxes_raymarch(v_uv, eye); + vec3 scol = + infinite_boxes_raymarch(v_uv + e2, eye) + + infinite_boxes_raymarch(v_uv - e2, eye); + scol /= 2.; + return vec4(mix(col, scol, .5), 1.); +} + +/* ---- SCENE: HOLLOW MOUNTAINS ---- */ +float hollow_mountains_sd_scene(in vec3 p, in vec3 v) { + float s = 8., r; + p = abs(mod(p, 2.) - 1.)*2.; + for (float i = 0.; i < 5.; ++i) { + p = 1. - abs(p-v); + r = 1.4/dot(p, p); + s *= r; + p *= r; + } + return length(p)/s; +} +vec4 hollow_mountains(in vec3 color) { + vec3 dir = get_ray_direction(v_uv, uni.resolution, 60.); + + vec3 eye = vec3(0., 0., 0.); + eye.xy = fract(uni.pos.xy/2.)*2. + uni.pos.zw; + + float t = abs(fract(uni.time/20.)*2.-1.); + t = t*t*(3.-2.*t); + vec3 v = vec3(.9, 1., .94+t*.1); + + float h = 1.3, d, i; + for (i = 0.; i < 60.; ++i) { + d = hollow_mountains_sd_scene(eye + dir*h, v); + if (d < EPSILON || h > 100.) break; + h += d; + } + + float a = 200./(i+1.)/h; + return vec4(mix(vec3(.2, .2, .2), color, a), 1.); +} + +/* ---- SCENE: JAIL ---- */ +float jail_sd_scene(in vec3 p) { + p = mod(p, 2.) - 1.; + + float dist = INF; + for (float i = 0.; i < 7.; ++i) { + float t = PI/6.*i*2.; + + vec3 p2 = p; + p2.x += cos(t)*.1; + p2.xz *= rot(t); + p2.y += .4*(i-3.); + + dist = min(dist, sd_box(p2, vec3(.19))); + } + return dist; +} +vec3 jail_raymarch(in vec2 uv, in vec3 eye) { + vec3 dir = get_ray_direction(uv, uni.resolution.xy, 60.); + + float h = 1.6, d, i; + for (i = 0.; i < 20.; ++i) { + d = jail_sd_scene(eye + dir*h); + if (d < EPSILON || h > INF) break; + h += d; + } + vec3 p = eye + dir*h; + vec3 norm = get_normal(jail_sd_scene, d, p); + + float diffuse = clamp(dot(vec3(0., 0., 1.), norm), 0., 1.); + + float a = 3. + diffuse*.7; + + a /= pow(h*.3, 2.) * (i*.2+1.); + a = clamp(a, 0., 1.); + + return mix(vec3(.2, .3, .3), vec3(.4, .4, .5), a) + vec3(.7, .4, .4)*a; +} +vec4 jail(void) { + vec2 e1 = vec2(1., 0.)/uni.resolution.xy; + vec2 e2 = vec2(0., 1.)/uni.resolution.xy; + + vec3 eye = vec3(0., 0., -4.); + + eye.xy += fract(uni.pos.xy/2.)*2. + uni.pos.zw + vec2(.5, .5); + + vec3 col = jail_raymarch(v_uv, eye); + vec3 scol = + jail_raymarch(v_uv + e1 + e2, eye) + + jail_raymarch(v_uv + e1 - e2, eye) + + jail_raymarch(v_uv - e1 + e2, eye) + + jail_raymarch(v_uv - e1 - e2, eye); + scol /= 4.; + return vec4(mix(col, scol, .5), 1.); +} + +/* ---- SCENE: fabric ---- */ +float fabric_sd_rope(in vec3 p) { + p.yz *= rot(PI/8. + p.x*PI); + return sd_round_box(p, vec3(2.5, .2, .2), .03); +} +float fabric_sd_scene(in vec3 p) { + p = mod(p, 4.) - 2.; + + vec3 p2 = p/sqrt(2.); + p2.xy *= rot(PI/4.); + p2.y = mod(p2.y, 2.) - 1.; + float dist = fabric_sd_rope(p2); + + p2 = p/sqrt(2.); + p2.xy *= rot(-PI/4.); + p2.y = mod(p2.y, 2.) - 1.; + dist = min(dist, fabric_sd_rope(p2)); + + return dist; +} +vec4 fabric(void) { + vec3 dir = get_ray_direction(v_uv, uni.resolution.xy, 80.); + vec3 eye = vec3(0., 0., -4.); + + eye.xy += uni.pos.zw*4.; + + float h = 4.5, d, i; + for (i = 0.; i < 50.; ++i) { + d = fabric_sd_scene(eye + dir*h); + if (d < EPSILON || h > INF) break; + h += d; + } + + vec3 norm = get_normal(fabric_sd_scene, h, eye + dir*h); + float diffuse = clamp(dot(vec3(0., 0., -1.), norm), .5, 1.); + + float a = diffuse*.9 - .3; + a /= pow(h*.08, 2.) * (i*.3+.5); + a = clamp(a, 0., 1.); + + vec3 col = mix(vec3(.25, .3, .2), vec3(.4, .6, .7), a) + vec3(1., .0, .0)*a; + return vec4(col, 1.); +} + +vec4 scene(in float type) { + return + type == 1.? infinite_boxes(): + type == 2.? hollow_mountains(vec3(.1, .4, .1)): + type == 3.? hollow_mountains(vec3(.4, .1, .1)): + type == 4.? jail(): + type == 5.? fabric(): + vec4(1.); +} +void main(void) { + vec4 prev = vec4(0.), next = vec4(0.); + if (p.transition > 0.) next = clamp(scene(p.type), 0., 1.); + if (p.transition < 1.) prev = clamp(scene(p.prev_type), 0., 1.); + o_color = mix(prev, next, p.transition); +} diff --git a/core/loshader/backwall.h b/core/loshader/backwall.h new file mode 100644 index 0000000..8e30ebf --- /dev/null +++ b/core/loshader/backwall.h @@ -0,0 +1,60 @@ +#pragma once + +#include + +#include "util/gleasy/program.h" + +#include "./uniblock.h" + +typedef gleasy_program_t loshader_backwall_program_t; + +struct loshader_backwall_drawer_t; +typedef struct loshader_backwall_drawer_t loshader_backwall_drawer_t; + +typedef enum { + LOSHADER_BACKWALL_TYPE_WHITE = 0, + LOSHADER_BACKWALL_TYPE_INFINITE_BOXES = 1, + LOSHADER_BACKWALL_TYPE_HOLLOW_MOUNTAINS = 2, + LOSHADER_BACKWALL_TYPE_HOLLOW_MOUNTAINS_RED = 3, + LOSHADER_BACKWALL_TYPE_JAIL = 4, + LOSHADER_BACKWALL_TYPE_FABRIC = 5, +} loshader_backwall_type_t; + +typedef struct { + loshader_backwall_type_t type; + loshader_backwall_type_t prev_type; + + float transition; +} loshader_backwall_drawer_param_t; + +void +loshader_backwall_program_initialize( + loshader_backwall_program_t* prog +); + +void +loshader_backwall_program_deinitialize( + loshader_backwall_program_t* prog +); + +loshader_backwall_drawer_t* +loshader_backwall_drawer_new( + const loshader_backwall_program_t* prog, + const loshader_uniblock_t* uniblock +); + +void +loshader_backwall_drawer_delete( + loshader_backwall_drawer_t* drawer +); + +void +loshader_backwall_drawer_set_param( + loshader_backwall_drawer_t* drawer, + const loshader_backwall_drawer_param_t* param +); + +void +loshader_backwall_drawer_draw( + const loshader_backwall_drawer_t* drawer +); diff --git a/core/loshader/backwall.vshader b/core/loshader/backwall.vshader new file mode 100644 index 0000000..92a44f8 --- /dev/null +++ b/core/loshader/backwall.vshader @@ -0,0 +1,11 @@ +out vec2 v_uv; + +void main(void) { + const vec2[] verts = vec2[]( + vec2(-1., 1.), vec2(-1., -1.), vec2( 1., -1.), + vec2(-1., 1.), vec2( 1., -1.), vec2( 1., 1.) + ); + v_uv = gl_VertexID < verts.length()? verts[gl_VertexID]: vec2(0.); + + gl_Position = vec4(v_uv, 0., 1.); +} diff --git a/core/loshader/bullet.c b/core/loshader/bullet.c new file mode 100644 index 0000000..306af9d --- /dev/null +++ b/core/loshader/bullet.c @@ -0,0 +1,191 @@ +#include "./bullet.h" + +#include +#include +#include +#include +#include + +#include + +#include "util/gleasy/buffer.h" +#include "util/gleasy/program.h" +#include "util/math/vector.h" +#include "util/memory/memory.h" + +#include "./uniblock.h" + +/* resources */ +#include "anysrc/header.shader.h" +#include "anysrc/bullet.vshader.h" +#include "anysrc/bullet.fshader.h" + +#define LOSHADER_BULLET_VSHADER_IN_BULLET_ID 0 +#define LOSHADER_BULLET_VSHADER_IN_POS 1 +#define LOSHADER_BULLET_VSHADER_IN_SIZE 2 +#define LOSHADER_BULLET_VSHADER_IN_THETA 3 +#define LOSHADER_BULLET_VSHADER_IN_TIME 4 +#define LOSHADER_BULLET_VSHADER_IN_COLOR 5 + +struct loshader_bullet_drawer_t { + const loshader_bullet_program_t* prog; + const loshader_uniblock_t* uniblock; + + GLuint vao; + + gleasy_buffer_array_t instances; + size_t instances_reserved; + size_t instances_length; +}; + +#pragma pack(push, 1) +typedef struct { + uint16_t bullet_id; + + vec2_t pos; + vec2_t size; + float theta; + float time; + + vec4_t color; +} loshader_bullet_drawer_internal_instance_t; +#pragma pack(pop) + +#define LOSHADER_BULLET_UNIBLOCK_INDEX 0 + +#define LOSHADER_BULLET_PRIMITIVE_COUNT 6 + +static void loshader_bullet_program_setup_vao_( + gleasy_buffer_array_t instances) { + assert(instances != 0); + + glBindBuffer(GL_ARRAY_BUFFER, instances); + +# define enable_attrib_(NAME, name, dim, type) do { \ + glEnableVertexAttribArray(LOSHADER_BULLET_VSHADER_IN_##NAME); \ + glVertexAttribPointer( \ + LOSHADER_BULLET_VSHADER_IN_##NAME, dim, type, GL_FALSE, \ + sizeof(loshader_bullet_drawer_internal_instance_t), \ + NULL + offsetof(loshader_bullet_drawer_internal_instance_t, name)); \ + glVertexAttribDivisor(LOSHADER_BULLET_VSHADER_IN_##NAME, 1); \ + } while (0) + + enable_attrib_(BULLET_ID, bullet_id, 1, GL_UNSIGNED_SHORT); + + enable_attrib_(POS, pos, 2, GL_FLOAT); + enable_attrib_(SIZE, size, 2, GL_FLOAT); + enable_attrib_(THETA, theta, 1, GL_FLOAT); + enable_attrib_(TIME, time, 1, GL_FLOAT); + + enable_attrib_(COLOR, color, 4, GL_FLOAT); + +# undef enable_attrib_ +} + +void loshader_bullet_program_initialize(loshader_bullet_program_t* prog) { + assert(prog != NULL); + + *prog = gleasy_program_new( + loshader_header_shader_, sizeof(loshader_header_shader_), + loshader_bullet_vshader_, sizeof(loshader_bullet_vshader_), + loshader_bullet_fshader_, sizeof(loshader_bullet_fshader_)); + + const GLuint uniblock = glGetUniformBlockIndex(*prog, "uniblock"); + assert(glGetError() == GL_NO_ERROR); + + glUniformBlockBinding(*prog, uniblock, LOSHADER_BULLET_UNIBLOCK_INDEX); +} + +void loshader_bullet_program_deinitialize(loshader_bullet_program_t* prog) { + assert(prog != NULL); + + glDeleteProgram(*prog); +} + +loshader_bullet_drawer_t* loshader_bullet_drawer_new( + const loshader_bullet_program_t* prog, + const loshader_uniblock_t* uniblock) { + assert(prog != NULL); + assert(uniblock != NULL); + + loshader_bullet_drawer_t* drawer = memory_new(sizeof(*drawer)); + *drawer = (typeof(*drawer)) { + .prog = prog, + .uniblock = uniblock, + }; + + glCreateVertexArrays(1, &drawer->vao); + glBindVertexArray(drawer->vao); + + glGenBuffers(1, &drawer->instances); + loshader_bullet_program_setup_vao_(drawer->instances); + + return drawer; +} + +void loshader_bullet_drawer_delete(loshader_bullet_drawer_t* drawer) { + assert(drawer != NULL); + + glDeleteBuffers(1, &drawer->instances); + + glDeleteVertexArrays(1, &drawer->vao); + + memory_delete(drawer); +} + +void loshader_bullet_drawer_clear( + loshader_bullet_drawer_t* drawer, size_t reserve) { + assert(drawer != NULL); + assert(reserve > 0); + + drawer->instances_length = 0; + + if (drawer->instances_reserved < reserve) { + glBindBuffer(GL_ARRAY_BUFFER, drawer->instances); + glBufferData(GL_ARRAY_BUFFER, + reserve * sizeof(loshader_bullet_drawer_internal_instance_t), + NULL, GL_DYNAMIC_DRAW); + drawer->instances_reserved = reserve; + } +} + +void loshader_bullet_drawer_add_instance( + loshader_bullet_drawer_t* drawer, + const loshader_bullet_drawer_instance_t* instance) { + assert(drawer != NULL); + assert(instance != NULL); + + if (drawer->instances_length >= drawer->instances_reserved) { + fprintf(stderr, "bullet drawer instance overflow\n"); + abort(); + } + + const loshader_bullet_drawer_internal_instance_t insta = { + .bullet_id = instance->bullet_id, + .pos = instance->pos, + .size = instance->size, + .theta = instance->theta, + .time = instance->time, + .color = instance->color, + }; + + const size_t offset = drawer->instances_length * sizeof(insta); + glBindBuffer(GL_ARRAY_BUFFER, drawer->instances); + glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(insta), &insta); + + ++drawer->instances_length; +} + +void loshader_bullet_drawer_draw(const loshader_bullet_drawer_t* drawer) { + assert(drawer != NULL); + + if (drawer->instances_length == 0) return; + + glUseProgram(*drawer->prog); + glBindVertexArray(drawer->vao); + + loshader_uniblock_bind(drawer->uniblock, LOSHADER_BULLET_UNIBLOCK_INDEX); + + glDrawArraysInstanced(GL_TRIANGLES, + 0, LOSHADER_BULLET_PRIMITIVE_COUNT, drawer->instances_length); +} diff --git a/core/loshader/bullet.fshader b/core/loshader/bullet.fshader new file mode 100644 index 0000000..a7efc1a --- /dev/null +++ b/core/loshader/bullet.fshader @@ -0,0 +1,53 @@ +in vec2 v_aa; +in float v_bullet_id; +in vec2 v_uv; +in vec2 v_size; +in float v_time; +in vec4 v_color; + +out vec4 o_color; + +float dot(in vec2 p, in vec2 q) { + return p.x*q.y - p.y*q.x; +} + +vec4 light(void) { + vec4 color = v_color; + color.a *= 1.-pow(length(v_uv), 1.5); + return color; +} + +vec4 square(void) { + vec4 color = v_color; + + float t = 1.-v_time; + + vec2 uv = abs(v_uv); + color.a *= max(step(t-v_aa.x, uv.x), step(t-v_aa.y, uv.y)); + return color; +} + +vec4 triangle(void) { + const vec2 disp = vec2(-1./12., 0.); + + float t = 1. - v_time; + vec2 v1 = vec2( 1., 0.)*t + disp*v_time; + vec2 v2 = vec2(-1., -1.)*t + disp*v_time; + vec2 v3 = vec2(-1., 1.)*t + disp*v_time; + + float b1 = dot(v_uv-v2, v1-v2); + float b2 = dot(v_uv-v3, v2-v3); + float b3 = dot(v_uv-v1, v3-v1); + + vec4 color = v_color; + color.a *= b1*b2 > 0. && b2*b3 > 0.? 0.: 1.; + return color; +} + +void main(void) { + o_color = + v_bullet_id == 0.? light(): + v_bullet_id == 1.? square(): + v_bullet_id == 2.? triangle(): + vec4(0.); +} diff --git a/core/loshader/bullet.h b/core/loshader/bullet.h new file mode 100644 index 0000000..fddd566 --- /dev/null +++ b/core/loshader/bullet.h @@ -0,0 +1,68 @@ +#pragma once + +#include + +#include "util/gleasy/program.h" +#include "util/math/vector.h" + +#include "./uniblock.h" + +typedef gleasy_program_t loshader_bullet_program_t; + +struct loshader_bullet_drawer_t; +typedef struct loshader_bullet_drawer_t loshader_bullet_drawer_t; + +typedef enum { + LOSHADER_BULLET_ID_LIGHT = 0, + LOSHADER_BULLET_ID_SQUARE = 1, + LOSHADER_BULLET_ID_TRIANGLE = 2, +} loshader_bullet_id_t; + +typedef struct { + loshader_bullet_id_t bullet_id; + + vec2_t pos; + vec2_t size; + float theta; + float time; + + vec4_t color; +} loshader_bullet_drawer_instance_t; + +void +loshader_bullet_program_initialize( + loshader_bullet_program_t* prog +); + +void +loshader_bullet_program_deinitialize( + loshader_bullet_program_t* prog +); + +loshader_bullet_drawer_t* +loshader_bullet_drawer_new( + const loshader_bullet_program_t* prog, + const loshader_uniblock_t* uniblock +); + +void +loshader_bullet_drawer_delete( + loshader_bullet_drawer_t* drawer +); + +void +loshader_bullet_drawer_clear( + loshader_bullet_drawer_t* drawer, + size_t reserve +); + +void +loshader_bullet_drawer_add_instance( + loshader_bullet_drawer_t* drawer, + const loshader_bullet_drawer_instance_t* instance +); + +void +loshader_bullet_drawer_draw( + const loshader_bullet_drawer_t* drawer +); diff --git a/core/loshader/bullet.vshader b/core/loshader/bullet.vshader new file mode 100644 index 0000000..2b32a4d --- /dev/null +++ b/core/loshader/bullet.vshader @@ -0,0 +1,52 @@ +layout (location = 0) in float i_bullet_id; +layout (location = 1) in vec2 i_pos; +layout (location = 2) in vec2 i_size; +layout (location = 3) in float i_theta; +layout (location = 4) in float i_time; +layout (location = 5) in vec4 i_color; + +out vec2 v_aa; +out float v_bullet_id; +out vec2 v_uv; +out vec2 v_size; +out float v_time; +out vec4 v_color; + +mat2 rot(in float theta) { + float c = cos(theta); + float s = sin(theta); + return mat2(c, -s, s, c); +} + +vec2 square(void) { + const vec2[] verts = vec2[]( + vec2(-1., 1.), vec2(-1., -1.), vec2( 1., -1.), + vec2(-1., 1.), vec2( 1., -1.), vec2( 1., 1.) + ); + return gl_VertexID < verts.length()? verts[gl_VertexID]: vec2(0.); +} + +vec2 triangle(void) { + const vec2[] verts = vec2[]( + vec2(-1., 1.), vec2(-1., -1.), vec2( 1., 0.) + ); + return gl_VertexID < verts.length()? verts[gl_VertexID]: vec2(0.); +} + +void main(void) { + v_bullet_id = i_bullet_id; + v_time = i_time; + v_color = i_color; + + v_aa = uni.aa / (uni.proj * uni.cam * vec4(i_size, 0., 0.)).xy; + + v_uv = + i_bullet_id == 0.? square(): + i_bullet_id == 1.? square(): + i_bullet_id == 2.? triangle(): + vec2(0.); + v_size = i_size; + + gl_Position = uni.proj * uni.cam * + vec4(rot(-i_theta)*(v_uv*i_size) + i_pos, 0., 1.); +} diff --git a/core/loshader/character.c b/core/loshader/character.c new file mode 100644 index 0000000..f39e935 --- /dev/null +++ b/core/loshader/character.c @@ -0,0 +1,205 @@ +#include "./character.h" + +#include +#include +#include +#include +#include + +#include + +#include "util/gleasy/buffer.h" +#include "util/gleasy/program.h" +#include "util/math/vector.h" +#include "util/memory/memory.h" + +#include "./uniblock.h" + +/* resources */ +#include "anysrc/header.shader.h" +#include "anysrc/character.vshader.h" +#include "anysrc/character.fshader.h" + +#define LOSHADER_CHARACTER_VSHADER_IN_CHARACTER_ID 0 +#define LOSHADER_CHARACTER_VSHADER_IN_FROM_MOTION_ID 1 +#define LOSHADER_CHARACTER_VSHADER_IN_TO_MOTION_ID 2 +#define LOSHADER_CHARACTER_VSHADER_IN_MOTION_TIME 3 +#define LOSHADER_CHARACTER_VSHADER_IN_MARKER 4 +#define LOSHADER_CHARACTER_VSHADER_IN_MARKER_OFFSET 5 +#define LOSHADER_CHARACTER_VSHADER_IN_POS 6 +#define LOSHADER_CHARACTER_VSHADER_IN_SIZE 7 +#define LOSHADER_CHARACTER_VSHADER_IN_COLOR 8 + +struct loshader_character_drawer_t { + const loshader_character_program_t* prog; + + const loshader_uniblock_t* uniblock; + + GLuint vao; + + gleasy_buffer_array_t instances; + size_t instances_reserved; + size_t instances_length; +}; + +#pragma pack(push, 1) +typedef struct { + uint16_t character_id; + + uint16_t from_motion_id; + uint16_t to_motion_id; + float motion_time; + + float marker; + vec2_t marker_offset; + + vec2_t pos; + vec2_t size; + vec4_t color; +} loshader_character_drawer_internal_instance_t; +#pragma pack(pop) + +#define LOSHADER_CHARACTER_UNIBLOCK_INDEX 0 + +#define LOSHADER_CHARACTER_PRIMITIVE_COUNT 54 + +static void loshader_character_program_setup_vao_( + const loshader_character_program_t* prog, gleasy_buffer_array_t instances) { + assert(prog != NULL); + assert(instances != 0); + + glBindBuffer(GL_ARRAY_BUFFER, instances); + +# define enable_attrib_(NAME, name, dim, type) do { \ + glEnableVertexAttribArray(LOSHADER_CHARACTER_VSHADER_IN_##NAME); \ + glVertexAttribPointer( \ + LOSHADER_CHARACTER_VSHADER_IN_##NAME, dim, type, GL_FALSE, \ + sizeof(loshader_character_drawer_internal_instance_t), \ + NULL + offsetof(loshader_character_drawer_internal_instance_t, name)); \ + glVertexAttribDivisor(LOSHADER_CHARACTER_VSHADER_IN_##NAME, 1); \ + } while (0) + + enable_attrib_(CHARACTER_ID, character_id, 1, GL_UNSIGNED_SHORT); + enable_attrib_(FROM_MOTION_ID, from_motion_id, 1, GL_UNSIGNED_SHORT); + enable_attrib_(TO_MOTION_ID, to_motion_id, 1, GL_UNSIGNED_SHORT); + enable_attrib_(MOTION_TIME, motion_time, 1, GL_FLOAT); + enable_attrib_(MARKER, marker, 1, GL_FLOAT); + enable_attrib_(MARKER_OFFSET, marker_offset, 2, GL_FLOAT); + + enable_attrib_(POS, pos, 2, GL_FLOAT); + enable_attrib_(SIZE, size, 2, GL_FLOAT); + enable_attrib_(COLOR, color, 4, GL_FLOAT); + +# undef enable_attrib_ +} + +void loshader_character_program_initialize(loshader_character_program_t* prog) { + assert(prog != NULL); + + *prog = gleasy_program_new( + loshader_header_shader_, sizeof(loshader_header_shader_), + loshader_character_vshader_, sizeof(loshader_character_vshader_), + loshader_character_fshader_, sizeof(loshader_character_fshader_)); + + const GLuint uniblock = glGetUniformBlockIndex(*prog, "uniblock"); + assert(glGetError() == GL_NO_ERROR); + + glUniformBlockBinding(*prog, uniblock, LOSHADER_CHARACTER_UNIBLOCK_INDEX); +} + +void loshader_character_program_deinitialize( + loshader_character_program_t* prog) { + assert(prog != NULL); + + glDeleteProgram(*prog); +} + +loshader_character_drawer_t* loshader_character_drawer_new( + const loshader_character_program_t* prog, + const loshader_uniblock_t* uniblock) { + assert(prog != NULL); + assert(uniblock != NULL); + + loshader_character_drawer_t* drawer = memory_new(sizeof(*drawer)); + *drawer = (typeof(*drawer)) { + .prog = prog, + .uniblock = uniblock, + }; + + glGenBuffers(1, &drawer->instances); + glCreateVertexArrays(1, &drawer->vao); + glBindVertexArray(drawer->vao); + loshader_character_program_setup_vao_(drawer->prog, drawer->instances); + + return drawer; +} + +void loshader_character_drawer_delete(loshader_character_drawer_t* drawer) { + if (drawer == NULL) return; + + glDeleteBuffers(1, &drawer->instances); + + glDeleteVertexArrays(1, &drawer->vao); + + memory_delete(drawer); +} + +void loshader_character_drawer_clear( + loshader_character_drawer_t* drawer, size_t reserve) { + assert(drawer != NULL); + assert(reserve > 0); + + drawer->instances_length = 0; + + if (drawer->instances_reserved < reserve) { + glBindBuffer(GL_ARRAY_BUFFER, drawer->instances); + glBufferData(GL_ARRAY_BUFFER, + reserve * sizeof(loshader_character_drawer_internal_instance_t), + NULL, GL_DYNAMIC_DRAW); + drawer->instances_reserved = reserve; + } +} + +void loshader_character_drawer_add_instance( + loshader_character_drawer_t* drawer, + const loshader_character_drawer_instance_t* instance) { + assert(drawer != NULL); + assert(instance != NULL); + + if (drawer->instances_length >= drawer->instances_reserved) { + fprintf(stderr, "character drawer instance overflow\n"); + abort(); + } + + const loshader_character_drawer_internal_instance_t insta = { + .character_id = instance->character_id, + .from_motion_id = instance->from_motion_id, + .to_motion_id = instance->to_motion_id, + .motion_time = instance->motion_time, + .marker = instance->marker, + .marker_offset = instance->marker_offset, + .pos = instance->pos, + .size = instance->size, + .color = instance->color, + }; + + const size_t offset = drawer->instances_length * sizeof(insta); + glBindBuffer(GL_ARRAY_BUFFER, drawer->instances); + glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(insta), &insta); + + ++drawer->instances_length; +} + +void loshader_character_drawer_draw(const loshader_character_drawer_t* drawer) { + assert(drawer != NULL); + + if (drawer->instances_length == 0) return; + + glUseProgram(*drawer->prog); + glBindVertexArray(drawer->vao); + + loshader_uniblock_bind(drawer->uniblock, LOSHADER_CHARACTER_UNIBLOCK_INDEX); + + glDrawArraysInstanced(GL_TRIANGLES, + 0, LOSHADER_CHARACTER_PRIMITIVE_COUNT, drawer->instances_length); +} diff --git a/core/loshader/character.fshader b/core/loshader/character.fshader new file mode 100644 index 0000000..1b0011d --- /dev/null +++ b/core/loshader/character.fshader @@ -0,0 +1,7 @@ +in vec4 v_color; + +out vec4 o_color; + +void main(void) { + o_color = v_color; +} diff --git a/core/loshader/character.h b/core/loshader/character.h new file mode 100644 index 0000000..c891ae7 --- /dev/null +++ b/core/loshader/character.h @@ -0,0 +1,85 @@ +#pragma once + +#include +#include + +#include "util/gleasy/program.h" +#include "util/math/vector.h" + +#include "./uniblock.h" + +typedef gleasy_program_t loshader_character_program_t; + +struct loshader_character_drawer_t; +typedef struct loshader_character_drawer_t loshader_character_drawer_t; + +typedef enum { + LOSHADER_CHARACTER_ID_PLAYER = 0, + LOSHADER_CHARACTER_ID_ENCEPHALON = 1, + LOSHADER_CHARACTER_ID_CAVIA = 2, + LOSHADER_CHARACTER_ID_SCIENTIST = 3, + LOSHADER_CHARACTER_ID_WARDER = 4, +} loshader_character_id_t; + +typedef enum { + LOSHADER_CHARACTER_MOTION_ID_STAND1 = 0, + LOSHADER_CHARACTER_MOTION_ID_STAND2 = 1, + LOSHADER_CHARACTER_MOTION_ID_WALK = 2, + LOSHADER_CHARACTER_MOTION_ID_ATTACK1 = 3, + LOSHADER_CHARACTER_MOTION_ID_ATTACK2 = 4, + LOSHADER_CHARACTER_MOTION_ID_SIT = 5, + LOSHADER_CHARACTER_MOTION_ID_DOWN = 6, +} loshader_character_motion_id_t; + +typedef struct { + loshader_character_id_t character_id; + + uint32_t from_motion_id; + uint32_t to_motion_id; + float motion_time; + + float marker; + vec2_t marker_offset; + + vec2_t pos; + vec2_t size; + vec4_t color; +} loshader_character_drawer_instance_t; + +void +loshader_character_program_initialize( + loshader_character_program_t* prog +); + +void +loshader_character_program_deinitialize( + loshader_character_program_t* prog +); + +loshader_character_drawer_t* /* OWNERSHIP */ +loshader_character_drawer_new( + const loshader_character_program_t* prog, + const loshader_uniblock_t* uniblock +); + +void +loshader_character_drawer_delete( + loshader_character_drawer_t* drawer /* OWNERSHIP */ +); + +void +loshader_character_drawer_clear( + loshader_character_drawer_t* drawer, + size_t reserve +); + +void +loshader_character_drawer_add_instance( + loshader_character_drawer_t* drawer, + const loshader_character_drawer_instance_t* instance +); + +void +loshader_character_drawer_draw( + const loshader_character_drawer_t* drawer +); diff --git a/core/loshader/character.vshader b/core/loshader/character.vshader new file mode 100644 index 0000000..ed387d8 --- /dev/null +++ b/core/loshader/character.vshader @@ -0,0 +1,375 @@ +layout (location = 0) in float i_character_id; +layout (location = 1) in float i_from_motion_id; +layout (location = 2) in float i_to_motion_id; +layout (location = 3) in float i_motion_time; +layout (location = 4) in float i_marker; +layout (location = 5) in vec2 i_marker_offset; +layout (location = 6) in vec2 i_pos; +layout (location = 7) in vec2 i_size; +layout (location = 8) in vec4 i_color; + +out vec4 v_color; + +const int vertex_count_ = 54; + +const vec2[] marker_ = vec2[]( + vec2( 0., 1.), vec2(-1., -1.), vec2( 1., -1.) +); +const vec4 marker_color_ = vec4(1., 1., 1., .6); + +vec2 player_stand1(void) { + const vec2[] verts = vec2[]( + vec2( 0.125, 0.986), vec2(-0.125, 0.957), vec2( 0.250, 0.900), + vec2(-0.125, 0.957), vec2(-0.175, 0.857), vec2( 0.250, 0.900), + vec2(-0.175, 0.857), vec2(-0.275, 0.571), vec2( 0.075, 0.886), + vec2(-0.050, 0.843), vec2(-0.275, 0.571), vec2( 0.000, 0.371), + vec2( 0.150, 0.843), vec2( 0.150, 0.757), vec2( 0.225, 0.843), + vec2( 0.075, 0.871), vec2( 0.125, 0.757), vec2( 0.125, 0.886), + vec2( 0.075, 0.871), vec2( 0.025, 0.814), vec2( 0.125, 0.757), + vec2( 0.000, 0.786), vec2( 0.000, 0.729), vec2( 0.075, 0.771), + vec2( 0.025, 0.714), vec2( 0.025, 0.386), vec2( 0.200, 0.571), + vec2( 0.125, 0.457), vec2( 0.025, 0.371), vec2( 0.200, 0.157), + vec2(-0.025, 0.371), vec2(-0.200, 0.186), vec2( 0.225, 0.086), + vec2(-0.200, 0.157), vec2( 0.150, -0.371), vec2( 0.225, 0.071), + vec2( 0.150, -0.371), vec2(-0.075, -0.571), vec2( 0.100, -0.857), + vec2( 0.100, -0.857), vec2( 0.000, -1.000), vec2( 0.500, -1.000), + vec2(-0.200, 0.157), vec2( 0.150, -0.371), vec2( 0.225, 0.071), + vec2( 0.150, -0.371), vec2(-0.075, -0.571), vec2( 0.100, -0.857), + vec2( 0.100, -0.857), vec2( 0.000, -1.000), vec2( 0.500, -1.000) + ); + vec2 p = gl_VertexID < verts.length()? verts[gl_VertexID]: vec2(0.); + return p*vec2(4./7., 1.); +} +vec2 player_stand2(void) { + return player_stand1()*vec2(1., .95) + vec2(0., -.05); +} +vec2 player_walk(void) { + const vec2[] verts = vec2[]( + vec2( 0.125, 0.986), vec2(-0.125, 0.957), vec2( 0.250, 0.900), + vec2(-0.125, 0.957), vec2(-0.175, 0.857), vec2( 0.250, 0.900), + vec2(-0.175, 0.857), vec2(-0.275, 0.571), vec2( 0.075, 0.886), + vec2(-0.050, 0.843), vec2(-0.275, 0.571), vec2( 0.000, 0.371), + vec2( 0.150, 0.843), vec2( 0.150, 0.757), vec2( 0.225, 0.843), + vec2( 0.075, 0.871), vec2( 0.125, 0.757), vec2( 0.125, 0.886), + vec2( 0.075, 0.871), vec2( 0.025, 0.814), vec2( 0.125, 0.757), + vec2( 0.000, 0.786), vec2( 0.000, 0.729), vec2( 0.075, 0.771), + vec2( 0.025, 0.714), vec2( 0.025, 0.386), vec2( 0.200, 0.571), + vec2( 0.125, 0.457), vec2( 0.025, 0.371), vec2( 0.200, 0.157), + vec2(-0.025, 0.371), vec2(-0.200, 0.186), vec2( 0.225, 0.086), + vec2(-0.200, 0.157), vec2( 0.450, -0.371), vec2( 0.225, 0.071), + vec2( 0.450, -0.371), vec2( 0.325, -0.571), vec2( 0.350, -0.857), + vec2( 0.350, -0.857), vec2( 0.275, -1.000), vec2( 0.950, -1.000), + vec2(-0.200, 0.157), vec2(-0.200, -0.371), vec2( 0.225, 0.071), + vec2(-0.200, -0.371), vec2(-0.450, -0.471), vec2(-0.600, -0.857), + vec2(-0.625, -0.857), vec2(-0.625, -1.000), vec2(-0.250, -1.000) + ); + vec2 p = gl_VertexID < verts.length()? verts[gl_VertexID]: vec2(0.); + return p*vec2(4./7., .95) + vec2(0., -.05); +} +vec2 player(in float motion_id) { + return + (motion_id == 0.)? player_stand1(): + (motion_id == 1.)? player_stand2(): + (motion_id == 2.)? player_walk(): + vec2(0.); +} + +vec2 encephalon(in float motion_id) { + const vec2[] verts = vec2[]( + vec2(-0.833, -0.783), vec2(-1.000, -1.000), vec2( 1.000, -1.000), + vec2(-.8333, -0.783), vec2( 1.000, -1.000), vec2( 0.833, -0.783), + vec2( 0.117, -0.367), vec2( 0.000, -0.750), vec2( 0.300, -0.533), + vec2( 0.117, 0.050), vec2(-0.083, -0.117), vec2( 0.367, -0.517), + vec2(-0.050, 0.283), vec2(-0.133, -0.117), vec2( 0.117, 0.083), + vec2( 0.500, 0.500), vec2(-0.050, 0.333), vec2( 0.150, 0.133), + vec2( 0.250, 0.917), vec2(-0.233, 0.317), vec2( 0.500, 0.533), + vec2( 0.217, 0.917), vec2(-0.300, 0.817), vec2(-0.283, 0.333), + vec2(-0.333, 0.783), vec2(-0.500, 0.567), vec2(-0.333, 0.333) + ); + return gl_VertexID < verts.length()? verts[gl_VertexID]: vec2(0.); +} + +vec2 cavia_stand1(void) { + const vec2[] verts = vec2[]( + vec2( 0.06, 1.00), vec2(-0.16, 0.72), vec2( 0.36, 0.58), + vec2( 0.00, 0.66), vec2(-0.20, 0.46), vec2(-0.04, 0.20), + vec2( 0.02, 0.58), vec2( 0.00, 0.20), vec2( 0.14, 0.42), + vec2(-0.20, 0.40), vec2(-0.20, 0.12), vec2( 0.12, -0.04), + vec2( 0.06, 0.24), vec2( 0.00, 0.16), vec2( 0.06, 0.08), + vec2(-0.18, 0.08), vec2( 0.06, -0.40), vec2( 0.06, -0.04), + vec2( 0.06, -0.44), vec2(-0.06, -0.58), vec2( 0.08, -0.90), + vec2( 0.08, -0.90), vec2( 0.00, -1.00), vec2( 0.30, -1.00), + vec2(-0.18, 0.08), vec2( 0.06, -0.40), vec2( 0.06, -0.04), + vec2( 0.06, -0.44), vec2(-0.06, -0.58), vec2( 0.08, -0.90), + vec2( 0.08, -0.90), vec2( 0.00, -1.00), vec2( 0.30, -1.00) + ); + return gl_VertexID < verts.length()? verts[gl_VertexID]: vec2(0.); +} +vec2 cavia_walk(void) { + const vec2[] verts = vec2[]( + vec2( 0.06, 1.00), vec2(-0.16, 0.72), vec2( 0.36, 0.58), + vec2( 0.00, 0.66), vec2(-0.20, 0.46), vec2(-0.04, 0.20), + vec2( 0.02, 0.58), vec2( 0.00, 0.20), vec2( 0.14, 0.42), + vec2(-0.20, 0.40), vec2(-0.20, 0.12), vec2( 0.12, -0.04), + vec2( 0.06, 0.24), vec2( 0.00, 0.16), vec2( 0.06, 0.08), + vec2(-0.18, 0.08), vec2(-0.18, -0.40), vec2( 0.06, -0.04), + vec2(-0.20, -0.42), vec2(-0.36, -0.42), vec2(-0.58, -0.90), + vec2(-0.58, -0.90), vec2(-0.62, -1.00), vec2(-0.32, -1.00), + vec2(-0.18, 0.08), vec2( 0.28, -0.40), vec2( 0.06, -0.04), + vec2( 0.28, -0.40), vec2( 0.20, -0.56), vec2( 0.42, -0.90), + vec2( 0.42, -0.90), vec2( 0.40, -1.00), vec2( 0.70, -1.00) + ); + vec2 p = gl_VertexID < verts.length()? verts[gl_VertexID]: vec2(0.); + return p*vec2(1., .95) + vec2(0., -.05); +} +vec2 cavia_attack1(void) { + return cavia_stand1()*vec2(1., 1.05) + vec2(0., .05); +} +vec2 cavia_attack2(void) { + const vec2[] verts = vec2[]( + vec2( 0.18, 0.94), vec2(-0.08, 0.70), vec2( 0.34, 0.46), + vec2( 0.06, 0.64), vec2(-0.20, 0.46), vec2(-0.04, 0.20), + vec2( 0.08, 0.56), vec2( 0.00, 0.20), vec2( 0.14, 0.42), + vec2(-0.20, 0.40), vec2(-0.20, 0.12), vec2( 0.12, -0.04), + vec2( 0.06, 0.24), vec2( 0.00, 0.16), vec2( 0.06, 0.08), + vec2(-0.18, 0.08), vec2( 0.06, -0.40), vec2( 0.06, -0.04), + vec2( 0.06, -0.44), vec2(-0.06, -0.58), vec2( 0.08, -0.90), + vec2( 0.08, -0.90), vec2( 0.00, -1.00), vec2( 0.30, -1.00), + vec2(-0.18, 0.08), vec2( 0.06, -0.40), vec2( 0.06, -0.04), + vec2( 0.06, -0.44), vec2(-0.06, -0.58), vec2( 0.08, -0.90), + vec2( 0.08, -0.90), vec2( 0.00, -1.00), vec2( 0.30, -1.00) + ); + return gl_VertexID < verts.length()? verts[gl_VertexID]: vec2(0.); +} +vec2 cavia_sit(void) { + const vec2[] verts = vec2[]( + vec2( 0.30, 0.40), vec2( 0.00, 0.18), vec2( 0.58, -0.02), + vec2( 0.12, 0.10), vec2(-0.22, -0.08), vec2(-0.10, -0.28), + vec2( 0.10, -0.02), vec2(-0.08, -0.30), vec2( 0.12, -0.20), + vec2(-0.24, -0.10), vec2(-0.28, -0.38), vec2(-0.04, -0.48), + vec2( 0.04, -0.26), vec2(-0.10, -0.32), vec2(-0.04, -0.42), + vec2(-0.26, -0.42), vec2( 0.00, -1.00), vec2(-0.06, -0.50), + vec2( 0.00, -1.00), vec2(-0.08, -0.86), vec2(-0.50, -0.92), + vec2(-0.50, -0.92), vec2(-0.54, -0.80), vec2(-0.76, -1.00), + vec2(-0.26, -0.42), vec2( 0.00, -1.00), vec2(-0.06, -0.50), + vec2( 0.00, -1.00), vec2(-0.08, -0.86), vec2(-0.50, -0.92), + vec2(-0.50, -0.92), vec2(-0.54, -0.80), vec2(-0.76, -1.00) + ); + return gl_VertexID < verts.length()? verts[gl_VertexID]: vec2(0.); +} +vec2 cavia_down(void) { + const vec2[] verts = vec2[]( + vec2( 0.78, -0.50), vec2( 0.46, -0.44), vec2( 0.66, -1.00), + vec2( 0.50, -0.58), vec2(-0.16, -0.38), vec2(-0.10, -0.54), + vec2( 0.38, -0.60), vec2(-0.02, -0.56), vec2( 0.24, -0.70), + vec2(-0.20, -0.40), vec2(-0.28, -0.60), vec2(-0.14, -0.60), + vec2(-0.02, -0.60), vec2(-0.10, -0.58), vec2(-0.08, -0.60), + vec2(-0.38, -0.62), vec2( 0.00, -1.00), vec2(-0.16, -0.62), + vec2( 0.00, -1.00), vec2(-0.18, -0.86), vec2(-0.50, -0.92), + vec2(-0.50, -0.92), vec2(-0.54, -0.80), vec2(-0.76, -1.00), + vec2(-0.38, -0.62), vec2( 0.00, -1.00), vec2(-0.16, -0.62), + vec2( 0.00, -1.00), vec2(-0.18, -0.86), vec2(-0.50, -0.92), + vec2(-0.50, -0.92), vec2(-0.54, -0.80), vec2(-0.76, -1.00) + ); + return gl_VertexID < verts.length()? verts[gl_VertexID]: vec2(0.); +} +vec2 cavia(in float motion_id) { + return + (motion_id == 0.)? cavia_stand1(): + (motion_id == 2.)? cavia_walk(): + (motion_id == 3.)? cavia_attack1(): + (motion_id == 4.)? cavia_attack2(): + (motion_id == 5.)? cavia_sit(): + (motion_id == 6.)? cavia_down(): + vec2(0.); +} + +vec2 scientist_stand1(void) { + const vec2[] verts = vec2[]( + vec2(-0.050, 0.982), vec2(-0.150, 0.764), vec2( 0.400, 0.655), + vec2(-0.050, 0.727), vec2(-0.475, 0.436), vec2( 0.225, 0.143), + vec2(-0.475, 0.418), vec2(-0.400, -0.709), vec2( 0.225, 0.127), + vec2( 0.225, 0.111), vec2(-0.400, -0.725), vec2( 0.225, -0.709), + vec2( 0.025, 0.673), vec2( 0.200, 0.255), vec2( 0.175, 0.636), + vec2( 0.000, -0.418), vec2(-0.150, -0.509), vec2(-0.075, -0.909), + vec2(-0.075, -0.909), vec2(-0.150, -1.000), vec2( 0.200, -1.000), + vec2( 0.000, -0.418), vec2(-0.150, -0.509), vec2(-0.075, -0.909), + vec2(-0.075, -0.909), vec2(-0.150, -1.000), vec2( 0.200, -1.000) + ); + return gl_VertexID < verts.length()? + verts[gl_VertexID]*vec2(.8, 1.): vec2(0.); +} +vec2 scientist_stand2(void) { + return scientist_stand1() + (gl_VertexID < 5*3? vec2(0., -.05): vec2(0.)); +} +vec2 scientist_attack1(void) { + const vec2[] verts = vec2[]( + vec2(-0.475, 0.982), vec2(-0.400, 0.727), vec2( 0.225, 0.927), + vec2(-0.225, 0.764), vec2(-0.675, 0.455), vec2( 0.225, 0.127), + vec2(-0.700, 0.418), vec2(-0.400, -0.709), vec2( 0.225, 0.127), + vec2( 0.225, 0.127), vec2(-0.400, -0.709), vec2( 0.350, -0.709), + vec2(-0.125, 0.727), vec2( 0.150, 0.309), vec2( 0.050, 0.691), + vec2( 0.000, -0.418), vec2(-0.150, -0.509), vec2(-0.075, -0.909), + vec2(-0.075, -0.909), vec2(-0.150, -1.000), vec2( 0.200, -1.000), + vec2( 0.000, -0.418), vec2(-0.150, -0.509), vec2(-0.075, -0.909), + vec2(-0.075, -0.909), vec2(-0.150, -1.000), vec2( 0.200, -1.000) + ); + return gl_VertexID < verts.length()? + verts[gl_VertexID]*vec2(.8, 1.): vec2(0.); +} +vec2 scientist_sit(void) { + const vec2[] verts = vec2[]( + vec2(-0.050, 0.782), vec2(-0.150, 0.564), vec2( 0.400, 0.455), + vec2(-0.050, 0.527), vec2(-0.475, 0.236), vec2( 0.225, -0.143), + vec2(-0.475, 0.218), vec2(-0.400, -0.909), vec2( 0.225, -0.127), + vec2( 0.225, -0.111), vec2(-0.400, -0.925), vec2( 0.225, -0.909), + vec2( 0.025, 0.473), vec2( 0.200, 0.055), vec2( 0.175, 0.436), + vec2( 0.000, -0.418), vec2(-0.150, -0.509), vec2(-0.075, -0.909), + vec2(-0.075, -0.909), vec2(-0.150, -1.000), vec2( 0.200, -1.000), + vec2( 0.000, -0.418), vec2(-0.150, -0.509), vec2(-0.075, -0.909), + vec2(-0.075, -0.909), vec2(-0.150, -1.000), vec2( 0.200, -1.000) + ); + return gl_VertexID < verts.length()? + verts[gl_VertexID]*vec2(.8, 1.): vec2(0.); +} +vec2 scientist(in float motion_id) { + return + (motion_id == 0.)? scientist_stand1(): + (motion_id == 1.)? scientist_stand2(): + (motion_id == 3.)? scientist_attack1(): + (motion_id == 5.)? scientist_sit(): + vec2(0.); +} + +vec2 warder_stand1(void) { + const vec2[] verts = vec2[]( + vec2(-0.10, 0.96), vec2(-0.24, 0.76), vec2( 0.22, 0.94), + vec2( 0.08, 0.84), vec2(-0.20, 0.70), vec2( 0.04, 0.70), + vec2(-0.10, 0.68), vec2(-0.24, 0.48), vec2(-0.08, 0.10), + vec2(-0.08, 0.60), vec2(-0.04, 0.12), vec2( 0.06, 0.42), + vec2(-0.22, 0.32), vec2(-0.26, 0.16), vec2(-0.06, 0.04), + vec2( 0.02, 0.20), vec2(-0.04, 0.04), vec2( 0.04, 0.00), + vec2(-0.24, 0.12), vec2( 0.00, -0.40), vec2( 0.06, -0.04), + vec2( 0.00, -0.42), vec2(-0.14, -0.54), vec2( 0.00, -0.90), + vec2( 0.00, -0.90), vec2(-0.04, -1.00), vec2( 0.24, -1.00), + vec2(-0.24, 0.12), vec2( 0.00, -0.40), vec2( 0.06, -0.04), + vec2( 0.00, -0.42), vec2(-0.14, -0.54), vec2( 0.00, -0.90), + vec2( 0.00, -0.90), vec2(-0.04, -1.00), vec2( 0.24, -1.00), + vec2(-0.26, -0.04), vec2(-0.24, -0.08), vec2( 0.58, 0.20) + ); + return gl_VertexID < verts.length()? verts[gl_VertexID]: vec2(0.); +} +vec2 warder_walk(void) { + const vec2[] verts = vec2[]( + vec2(-0.10, 0.96), vec2(-0.24, 0.76), vec2( 0.22, 0.94), + vec2( 0.08, 0.84), vec2(-0.20, 0.70), vec2( 0.04, 0.70), + vec2(-0.10, 0.68), vec2(-0.24, 0.48), vec2(-0.08, 0.10), + vec2(-0.08, 0.60), vec2(-0.04, 0.12), vec2( 0.06, 0.42), + vec2(-0.22, 0.32), vec2(-0.26, 0.16), vec2(-0.06, 0.04), + vec2( 0.02, 0.20), vec2(-0.04, 0.04), vec2( 0.04, 0.00), + vec2(-0.24, 0.12), vec2(-0.34, -0.40), vec2( 0.06, -0.04), + vec2(-0.34, -0.42), vec2(-0.50, -0.46), vec2(-0.62, -0.90), + vec2(-0.62, -0.90), vec2(-0.66, -1.00), vec2(-0.40, -1.00), + vec2(-0.24, 0.12), vec2( 0.30, -0.40), vec2( 0.06, -0.04), + vec2( 0.30, -0.40), vec2( 0.22, -0.56), vec2( 0.44, -0.90), + vec2( 0.44, -0.90), vec2( 0.40, -1.00), vec2( 0.66, -1.00), + vec2(-0.08, -0.08), vec2(-0.06, -0.12), vec2( 0.74, 0.20) + ); + vec2 p = gl_VertexID < verts.length()? verts[gl_VertexID]: vec2(0.); + return p*vec2(1., .95) + vec2(0., -.05); +} +vec2 warder_attack1(void) { + const vec2[] verts = vec2[]( + vec2(-0.10, 0.96), vec2(-0.24, 0.76), vec2( 0.22, 0.94), + vec2( 0.08, 0.84), vec2(-0.20, 0.70), vec2( 0.04, 0.70), + vec2(-0.10, 0.68), vec2(-0.24, 0.48), vec2(-0.08, 0.10), + vec2(-0.08, 0.60), vec2(-0.04, 0.12), vec2( 0.06, 0.42), + vec2(-0.22, 0.32), vec2(-0.26, 0.16), vec2(-0.06, 0.04), + vec2( 0.02, 0.20), vec2(-0.04, 0.04), vec2( 0.04, 0.00), + vec2(-0.24, 0.12), vec2(-0.34, -0.40), vec2( 0.06, -0.04), + vec2(-0.34, -0.42), vec2(-0.50, -0.46), vec2(-0.62, -0.90), + vec2(-0.62, -0.90), vec2(-0.66, -1.00), vec2(-0.40, -1.00), + vec2(-0.24, 0.12), vec2( 0.30, -0.40), vec2( 0.06, -0.04), + vec2( 0.30, -0.40), vec2( 0.22, -0.56), vec2( 0.44, -0.90), + vec2( 0.44, -0.90), vec2( 0.40, -1.00), vec2( 0.66, -1.00), + vec2(-0.56, 0.32), vec2(-0.56, 0.28), vec2( 0.30, 0.30) + ); + vec2 p = gl_VertexID < verts.length()? verts[gl_VertexID]: vec2(0.); + return p*vec2(1., .95) + vec2(0., -.05); +} +vec2 warder_attack2(void) { + const vec2[] verts = vec2[]( + vec2(-0.10, 0.96), vec2(-0.24, 0.76), vec2( 0.22, 0.94), + vec2( 0.08, 0.84), vec2(-0.20, 0.70), vec2( 0.04, 0.70), + vec2(-0.10, 0.68), vec2(-0.24, 0.48), vec2(-0.08, 0.10), + vec2(-0.08, 0.60), vec2(-0.04, 0.12), vec2( 0.06, 0.42), + vec2(-0.22, 0.32), vec2(-0.26, 0.16), vec2(-0.06, 0.04), + vec2( 0.02, 0.20), vec2(-0.04, 0.04), vec2( 0.04, 0.00), + vec2(-0.24, 0.12), vec2(-0.34, -0.40), vec2( 0.06, -0.04), + vec2(-0.34, -0.42), vec2(-0.50, -0.46), vec2(-0.62, -0.90), + vec2(-0.62, -0.90), vec2(-0.66, -1.00), vec2(-0.40, -1.00), + vec2(-0.24, 0.12), vec2( 0.30, -0.40), vec2( 0.06, -0.04), + vec2( 0.30, -0.40), vec2( 0.22, -0.56), vec2( 0.44, -0.90), + vec2( 0.44, -0.90), vec2( 0.40, -1.00), vec2( 0.66, -1.00), + vec2( 0.12, 0.32), vec2( 0.12, 0.28), vec2( 0.96, 0.30) + ); + return gl_VertexID < verts.length()? verts[gl_VertexID]: vec2(0.); +} +vec2 warder_down(void) { + const vec2[] verts = vec2[]( + vec2(-0.80, -1.00), vec2(-0.64, -1.00), vec2(-0.86, -0.62), + vec2(-0.76, -0.72), vec2(-0.58, -1.00), vec2(-0.64, -0.72), + vec2(-0.58, -0.86), vec2(-0.44, -0.98), vec2( 0.00, -0.82), + vec2(-0.50, -0.82), vec2( 0.00, -0.80), vec2(-0.36, -0.70), + vec2(-0.18, -0.92), vec2( 0.04, -1.00), vec2( 0.04, -0.82), + vec2(-0.12, -0.72), vec2( 0.04, -0.80), vec2( 0.04, -0.52), + vec2( 0.06, -1.00), vec2( 0.30, -0.46), vec2( 0.06, -0.54), + vec2( 0.30, -0.46), vec2( 0.30, -0.74), vec2( 0.50, -0.90), + vec2( 0.50, -0.90), vec2( 0.46, -1.00), vec2( 0.70, -1.00), + vec2( 0.06, -1.00), vec2( 0.30, -0.46), vec2( 0.06, -0.54), + vec2( 0.30, -0.46), vec2( 0.30, -0.74), vec2( 0.50, -0.90), + vec2( 0.50, -0.90), vec2( 0.46, -1.00), vec2( 0.70, -1.00), + vec2(-0.06, -0.96), vec2(-0.06, -1.00), vec2( 0.80, -1.00) + ); + return gl_VertexID < verts.length()? verts[gl_VertexID]: vec2(0.); +} +vec2 warder(in float motion_id) { + return + (motion_id == 0.)? warder_stand1(): + (motion_id == 2.)? warder_walk(): + (motion_id == 3.)? warder_attack1(): + (motion_id == 4.)? warder_attack2(): + (motion_id == 6.)? warder_down(): + vec2(0.); +} + +vec2 get_vert(in float motion_id) { + return + (i_character_id == 0.)? player(motion_id): + (i_character_id == 1.)? encephalon(motion_id): + (i_character_id == 2.)? cavia(motion_id): + (i_character_id == 3.)? scientist(motion_id): + (i_character_id == 4.)? warder(motion_id): + vec2(0.); +} +void main(void) { + if (gl_VertexID < vertex_count_-3) { + v_color = i_color; + + vec2 p = mix( + get_vert(i_from_motion_id), + get_vert(i_to_motion_id), + i_motion_time); + gl_Position = uni.proj * uni.cam * vec4(p*i_size+i_pos, 0., 1.); + + } else { + v_color = marker_color_; + v_color.a *= i_marker; + + vec2 p = marker_[gl_VertexID - (vertex_count_-3)]; + p *= uni.dpi*.03 / uni.resolution * 2.; + + gl_Position = uni.proj * uni.cam * vec4(i_pos+i_marker_offset, 0., 1.); + gl_Position.xy += p; + } +} diff --git a/core/loshader/cinescope.c b/core/loshader/cinescope.c new file mode 100644 index 0000000..7950be2 --- /dev/null +++ b/core/loshader/cinescope.c @@ -0,0 +1,116 @@ +#include "./cinescope.h" + +#include +#include +#include + +#include "util/gleasy/buffer.h" +#include "util/gleasy/program.h" +#include "util/math/vector.h" +#include "util/memory/memory.h" + +#include "./uniblock.h" + +/* resources */ +#include "anysrc/header.shader.h" +#include "anysrc/cinescope.vshader.h" +#include "anysrc/cinescope.fshader.h" + +struct loshader_cinescope_drawer_t { + const loshader_cinescope_program_t* prog; + const loshader_uniblock_t* uniblock; + + gleasy_buffer_uniform_t param; +}; + +#pragma pack(push, 1) +typedef struct { + vec4_t color; + float size; +} loshader_cinescope_drawer_internal_param_t; +_Static_assert( + sizeof(float)*5 == + sizeof(loshader_cinescope_drawer_internal_param_t)); +#pragma pack(pop) + +#define LOSHADER_CINESCOPE_UNIBLOCK_INDEX 0 +#define LOSHADER_CINESCOPE_PARAM_INDEX 1 + +#define LOSHADER_CINESCOPE_PRIMITIVE_COUNT 12 + +void loshader_cinescope_program_initialize(loshader_cinescope_program_t* prog) { + assert(prog != NULL); + + *prog = gleasy_program_new( + loshader_header_shader_, sizeof(loshader_header_shader_), + loshader_cinescope_vshader_, sizeof(loshader_cinescope_vshader_), + loshader_cinescope_fshader_, sizeof(loshader_cinescope_fshader_)); + + const GLuint uniblock = glGetUniformBlockIndex(*prog, "uniblock"); + assert(glGetError() == GL_NO_ERROR); + glUniformBlockBinding(*prog, uniblock, LOSHADER_CINESCOPE_UNIBLOCK_INDEX); + + const GLuint param = glGetUniformBlockIndex(*prog, "param"); + assert(glGetError() == GL_NO_ERROR); + glUniformBlockBinding(*prog, param, LOSHADER_CINESCOPE_PARAM_INDEX); +} + +void loshader_cinescope_program_deinitialize(loshader_cinescope_program_t* prog) { + assert(prog != NULL); + + glDeleteProgram(*prog); +} + +loshader_cinescope_drawer_t* loshader_cinescope_drawer_new( + const loshader_cinescope_program_t* prog, + const loshader_uniblock_t* uniblock) { + assert(prog != NULL); + assert(uniblock != NULL); + + loshader_cinescope_drawer_t* drawer = memory_new(sizeof(*drawer)); + *drawer = (typeof(*drawer)) { + .prog = prog, + .uniblock = uniblock, + }; + + glGenBuffers(1, &drawer->param); + glBindBuffer(GL_UNIFORM_BUFFER, drawer->param); + glBufferData(GL_UNIFORM_BUFFER, + sizeof(loshader_cinescope_drawer_internal_param_t), NULL, GL_DYNAMIC_DRAW); + + return drawer; +} + +void loshader_cinescope_drawer_delete(loshader_cinescope_drawer_t* drawer) { + if (drawer == NULL) return; + + glDeleteBuffers(1, &drawer->param); + + memory_delete(drawer); +} + +void loshader_cinescope_drawer_set_param( + loshader_cinescope_drawer_t* drawer, + const loshader_cinescope_drawer_param_t* param) { + assert(drawer != NULL); + assert(param != NULL); + + const loshader_cinescope_drawer_internal_param_t p = { + .size = param->size, + .color = param->color, + }; + glBindBuffer(GL_UNIFORM_BUFFER, drawer->param); + glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(p), &p); +} + +void loshader_cinescope_drawer_draw(const loshader_cinescope_drawer_t* drawer) { + assert(drawer != NULL); + + glUseProgram(*drawer->prog); + + loshader_uniblock_bind(drawer->uniblock, LOSHADER_CINESCOPE_UNIBLOCK_INDEX); + glBindBufferBase(GL_UNIFORM_BUFFER, + LOSHADER_CINESCOPE_PARAM_INDEX, drawer->param); + + glDrawArrays(GL_TRIANGLES, 0, LOSHADER_CINESCOPE_PRIMITIVE_COUNT); +} diff --git a/core/loshader/cinescope.fshader b/core/loshader/cinescope.fshader new file mode 100644 index 0000000..7f96146 --- /dev/null +++ b/core/loshader/cinescope.fshader @@ -0,0 +1,10 @@ +layout(std140) uniform param { + vec4 color; + float size; +} p; + +out vec4 o_color; + +void main(void) { + o_color = p.color; +} diff --git a/core/loshader/cinescope.h b/core/loshader/cinescope.h new file mode 100644 index 0000000..8cbee4a --- /dev/null +++ b/core/loshader/cinescope.h @@ -0,0 +1,48 @@ +#pragma once + +#include "util/gleasy/program.h" +#include "util/math/vector.h" + +#include "./uniblock.h" + +typedef gleasy_program_t loshader_cinescope_program_t; + +struct loshader_cinescope_drawer_t; +typedef struct loshader_cinescope_drawer_t loshader_cinescope_drawer_t; + +typedef struct { + float size; + vec4_t color; +} loshader_cinescope_drawer_param_t; + +void +loshader_cinescope_program_initialize( + loshader_cinescope_program_t* prog +); + +void +loshader_cinescope_program_deinitialize( + loshader_cinescope_program_t* prog +); + +loshader_cinescope_drawer_t* +loshader_cinescope_drawer_new( + const loshader_cinescope_program_t* prog, + const loshader_uniblock_t* uniblock +); + +void +loshader_cinescope_drawer_delete( + loshader_cinescope_drawer_t* drawer +); + +void +loshader_cinescope_drawer_set_param( + loshader_cinescope_drawer_t* drawer, + const loshader_cinescope_drawer_param_t* param +); + +void +loshader_cinescope_drawer_draw( + const loshader_cinescope_drawer_t* drawer +); diff --git a/core/loshader/cinescope.vshader b/core/loshader/cinescope.vshader new file mode 100644 index 0000000..53d3706 --- /dev/null +++ b/core/loshader/cinescope.vshader @@ -0,0 +1,22 @@ +layout(std140) uniform param { + vec4 color; + float size; +} p; + +const vec2[6] rect_ = vec2[]( + vec2(-1., 1.), + vec2(-1., 0.), + vec2( 1., 0.), + vec2(-1., 1.), + vec2( 1., 0.), + vec2( 1., 1.) +); + +void main(void) { + int id = gl_VertexID < 6? gl_VertexID: gl_VertexID-6; + + vec2 p = rect_[id]*vec2(1., p.size) + vec2(0., 1.-p.size); + p.y *= (id == gl_VertexID? 1.: -1.); + + gl_Position = vec4(p, 0., 1.); +} diff --git a/core/loshader/combat_ring.c b/core/loshader/combat_ring.c new file mode 100644 index 0000000..996cfbf --- /dev/null +++ b/core/loshader/combat_ring.c @@ -0,0 +1,183 @@ +#include "./combat_ring.h" + +#include +#include +#include +#include + +#include + +#include "util/gleasy/buffer.h" +#include "util/gleasy/program.h" +#include "util/math/algorithm.h" +#include "util/math/constant.h" +#include "util/math/vector.h" +#include "util/memory/memory.h" + +#include "./uniblock.h" + +/* resources */ +#include "anysrc/header.shader.h" +#include "anysrc/combat_ring.vshader.h" +#include "anysrc/combat_ring.fshader.h" + +#define LOSHADER_COMBAT_RING_VSHADER_IN_RANGE 0 +#define LOSHADER_COMBAT_RING_VSHADER_IN_PERIOD 1 +#define LOSHADER_COMBAT_RING_VSHADER_IN_COLOR 2 + +struct loshader_combat_ring_drawer_t { + const loshader_combat_ring_program_t* prog; + const loshader_uniblock_t* uniblock; + + GLuint vao; + + gleasy_buffer_array_t instances; + size_t instances_reserved; + size_t instances_length; +}; + +#pragma pack(push, 1) +typedef struct { + float range; + vec2_t period; /* x~y */ + vec4_t color; +} loshader_combat_ring_drawer_internal_instance_t; +#pragma pack(pop) + +#define LOSHADER_COMBAT_RING_UNIBLOCK_INDEX 0 + +#define LOSHADER_COMBAT_RING_PRIMITIVE_COUNT 6 + +static void loshader_combat_ring_program_setup_vao_( + gleasy_buffer_array_t instances) { + assert(instances != 0); + + glBindBuffer(GL_ARRAY_BUFFER, instances); + + glEnableVertexAttribArray(LOSHADER_COMBAT_RING_VSHADER_IN_RANGE); + glVertexAttribPointer( + LOSHADER_COMBAT_RING_VSHADER_IN_RANGE, 1, GL_FLOAT, GL_FALSE, + sizeof(loshader_combat_ring_drawer_internal_instance_t), + NULL + offsetof(loshader_combat_ring_drawer_internal_instance_t, range)); + glVertexAttribDivisor(LOSHADER_COMBAT_RING_VSHADER_IN_RANGE, 1); + + glEnableVertexAttribArray(LOSHADER_COMBAT_RING_VSHADER_IN_PERIOD); + glVertexAttribPointer( + LOSHADER_COMBAT_RING_VSHADER_IN_PERIOD, 2, GL_FLOAT, GL_FALSE, + sizeof(loshader_combat_ring_drawer_internal_instance_t), + NULL + offsetof(loshader_combat_ring_drawer_internal_instance_t, period)); + glVertexAttribDivisor(LOSHADER_COMBAT_RING_VSHADER_IN_PERIOD, 1); + + glEnableVertexAttribArray(LOSHADER_COMBAT_RING_VSHADER_IN_COLOR); + glVertexAttribPointer( + LOSHADER_COMBAT_RING_VSHADER_IN_COLOR, 4, GL_FLOAT, GL_FALSE, + sizeof(loshader_combat_ring_drawer_internal_instance_t), + NULL + offsetof(loshader_combat_ring_drawer_internal_instance_t, color)); + glVertexAttribDivisor(LOSHADER_COMBAT_RING_VSHADER_IN_COLOR, 1); +} + +void loshader_combat_ring_program_initialize( + loshader_combat_ring_program_t* prog) { + assert(prog != NULL); + + *prog = gleasy_program_new( + loshader_header_shader_, sizeof(loshader_header_shader_), + loshader_combat_ring_vshader_, sizeof(loshader_combat_ring_vshader_), + loshader_combat_ring_fshader_, sizeof(loshader_combat_ring_fshader_)); + + const GLuint uniblock = glGetUniformBlockIndex(*prog, "uniblock"); + assert(glGetError() == GL_NO_ERROR); + + glUniformBlockBinding(*prog, uniblock, LOSHADER_COMBAT_RING_UNIBLOCK_INDEX); +} + +void loshader_combat_ring_program_deinitialize( + loshader_combat_ring_program_t* prog) { + assert(prog != NULL); + + glDeleteProgram(*prog); +} + +loshader_combat_ring_drawer_t* loshader_combat_ring_drawer_new( + const loshader_combat_ring_program_t* prog, + const loshader_uniblock_t* uniblock) { + assert(prog != NULL); + assert(uniblock != NULL); + + loshader_combat_ring_drawer_t* drawer = memory_new(sizeof(*drawer)); + *drawer = (typeof(*drawer)) { + .prog = prog, + .uniblock = uniblock, + }; + + glCreateVertexArrays(1, &drawer->vao); + glBindVertexArray(drawer->vao); + + glGenBuffers(1, &drawer->instances); + loshader_combat_ring_program_setup_vao_(drawer->instances); + + return drawer; +} + +void loshader_combat_ring_drawer_delete(loshader_combat_ring_drawer_t* drawer) { + if (drawer == NULL) return; + + glDeleteBuffers(1, &drawer->instances); + glDeleteVertexArrays(1, &drawer->vao); + memory_delete(drawer); +} + +void loshader_combat_ring_drawer_clear( + loshader_combat_ring_drawer_t* drawer, size_t reserve) { + assert(drawer != NULL); + + drawer->instances_length = 0; + + if (drawer->instances_reserved < reserve) { + glBindBuffer(GL_ARRAY_BUFFER, drawer->instances); + glBufferData(GL_ARRAY_BUFFER, + reserve * sizeof(loshader_combat_ring_drawer_internal_instance_t), + NULL, GL_DYNAMIC_DRAW); + drawer->instances_reserved = reserve; + } +} + +void loshader_combat_ring_drawer_add_instance( + loshader_combat_ring_drawer_t* drawer, + const loshader_combat_ring_drawer_instance_t* instance) { + assert(drawer != NULL); + assert(instance != NULL); + + if (drawer->instances_length >= drawer->instances_reserved) { + fprintf(stderr, "combat ring drawer instance overflow\n"); + abort(); + } + + const size_t offset = drawer->instances_length * + sizeof(loshader_combat_ring_drawer_internal_instance_t); + + const loshader_combat_ring_drawer_internal_instance_t i = { + .range = instance->range, + .period = vec2(instance->start, instance->end), + .color = instance->color, + }; + glBindBuffer(GL_ARRAY_BUFFER, drawer->instances); + glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(i), &i); + + ++drawer->instances_length; +} + +void loshader_combat_ring_drawer_draw( + const loshader_combat_ring_drawer_t* drawer) { + assert(drawer != NULL); + + if (drawer->instances_length == 0) return; + + glUseProgram(*drawer->prog); + glBindVertexArray(drawer->vao); + + loshader_uniblock_bind(drawer->uniblock, LOSHADER_COMBAT_RING_UNIBLOCK_INDEX); + + glDrawArraysInstanced(GL_TRIANGLES, + 0, LOSHADER_COMBAT_RING_PRIMITIVE_COUNT, drawer->instances_length); +} diff --git a/core/loshader/combat_ring.fshader b/core/loshader/combat_ring.fshader new file mode 100644 index 0000000..372023a --- /dev/null +++ b/core/loshader/combat_ring.fshader @@ -0,0 +1,91 @@ +in vec2 v_uv; +in float v_aa; +in float v_range; +in vec2 v_period; +in vec4 v_color; + +out vec4 o_color; + +vec2 uv_; +float len_; +float theta_; + +float atan2(in vec2 p) { + return p.x == 0.? PI/2.*sign(p.y): atan(p.y, p.x); +} +float thetadist(in vec2 p, in float t) { + float a = tan(t); + float b = 1.; + if (cos(t) == 0.) { + a = 1.; + b = 0.; + } + return abs(b*p.y-a*p.x)/sqrt(a*a+b*b); +} + +float grid(in float n, in float ri, in float ro) { + float u = PI/n; + float t = floor(theta_/u)*u; + + float d = min(thetadist(uv_, t), thetadist(uv_, t+u)); + return + step(ri, len_)*step(len_, ro)* + smoothstep(v_aa, .0, d); +} + +float belt(in float t1, in float t2, in float r) { + float t1a = smoothstep(v_aa, 0., thetadist(uv_, t1))*step(abs(theta_-t1), .1); + float t2a = smoothstep(v_aa, 0., thetadist(uv_, t2))*step(abs(theta_-t2), .1); + + float a = t1a + t2a + step(t1, theta_)*step(theta_, t2); + a *= + (t1a + t2a)*smoothstep(.4, .45, len_) + + smoothstep(r-v_aa, r, len_)* + (1.-max(len_-r, 0.)/.2) + + smoothstep(v_aa, 0., abs(len_-.7*r)); + return clamp(a, 0., 1.); +} + +float circle(in float r) { + return + smoothstep(v_aa, 0., abs(len_-r)); +} + +float clockhand(in float t) { + float r = smoothstep(.25, .3, len_)*smoothstep(1., .85, len_); + float a = + r * + smoothstep(v_aa, 0., thetadist(uv_, t)) * + step(abs(theta_-t), .1); + float b = + r * max(1.-abs(theta_-t)/(PI/12.), 0.)*.7; + + return a + b; +} + +void main(void) { + uv_ = v_uv.yx; + len_ = length(uv_); + theta_ = atan2(uv_); + theta_ = theta_ < 0.? theta_+2.*PI: theta_; + + if (v_range <= -1.) { + o_color = v_color; + float a = + grid( 2., .4, 1.) + + grid(60., .75, .8) + + grid(10., .65, .8) + + grid(8., .5, .55) + + grid( 4., .3, .4) + + circle(.8); + o_color.a *= clamp(a, 0., 1.); + + } else if (v_range <= 0.) { + o_color = v_color*clockhand(v_period.x*PI*2.); + o_color.a = pow(o_color.a, 2.); + + } else { + o_color = v_color; + o_color.a *= belt(v_period.x*PI*2., v_period.y*PI*2., v_range); + } +} diff --git a/core/loshader/combat_ring.h b/core/loshader/combat_ring.h new file mode 100644 index 0000000..8d5270b --- /dev/null +++ b/core/loshader/combat_ring.h @@ -0,0 +1,58 @@ +#pragma once + +#include + +#include "util/gleasy/program.h" +#include "util/math/vector.h" + +#include "./uniblock.h" + +typedef gleasy_program_t loshader_combat_ring_program_t; + +struct loshader_combat_ring_drawer_t; +typedef struct loshader_combat_ring_drawer_t loshader_combat_ring_drawer_t; + +typedef struct { + float range; + float start; + float end; + vec4_t color; +} loshader_combat_ring_drawer_instance_t; + +void +loshader_combat_ring_program_initialize( + loshader_combat_ring_program_t* prog +); + +void +loshader_combat_ring_program_deinitialize( + loshader_combat_ring_program_t* prog +); + +loshader_combat_ring_drawer_t* /* OWNERSHIP */ +loshader_combat_ring_drawer_new( + const loshader_combat_ring_program_t* prog, + const loshader_uniblock_t* uniblock +); + +void +loshader_combat_ring_drawer_delete( + loshader_combat_ring_drawer_t* drawer /* OWNERSHIP */ +); + +void +loshader_combat_ring_drawer_clear( + loshader_combat_ring_drawer_t* drawer, + size_t reserve +); + +void +loshader_combat_ring_drawer_add_instance( + loshader_combat_ring_drawer_t* drawer, + const loshader_combat_ring_drawer_instance_t* instance +); + +void +loshader_combat_ring_drawer_draw( + const loshader_combat_ring_drawer_t* drawer +); diff --git a/core/loshader/combat_ring.vshader b/core/loshader/combat_ring.vshader new file mode 100644 index 0000000..9eedf14 --- /dev/null +++ b/core/loshader/combat_ring.vshader @@ -0,0 +1,31 @@ +layout (location = 0) in float i_range; +layout (location = 1) in vec2 i_period; +layout (location = 2) in vec4 i_color; + +out vec2 v_uv; +out float v_aa; +out float v_range; +out vec2 v_period; +out vec4 v_color; + +void main() { + v_uv = + (gl_VertexID == 0)? vec2(-1., 1.): + (gl_VertexID == 1)? vec2(-1., -1.): + (gl_VertexID == 2)? vec2( 1., 1.): + (gl_VertexID == 3)? vec2(-1., -1.): + (gl_VertexID == 4)? vec2( 1., -1.): + (gl_VertexID == 5)? vec2( 1., 1.): + vec2(0, 0); + + vec2 scale = uni.dpi*5. / uni.resolution; + scale /= max(scale.x, 1.); + scale /= max(scale.y, 1.); + + + gl_Position = vec4(v_uv*scale, 0., 1.); + v_aa = length(uni.aa / scale)*2.; + v_range = i_range; + v_period = i_period; + v_color = i_color; +} diff --git a/core/loshader/event_line.c b/core/loshader/event_line.c new file mode 100644 index 0000000..f555bce --- /dev/null +++ b/core/loshader/event_line.c @@ -0,0 +1,91 @@ +#include "./event_line.h" + +#include +#include + +#include "util/gleasy/program.h" +#include "util/gleasy/texture.h" +#include "util/glyphas/block.h" +#include "util/glyphas/drawer.h" + +#include "./uniblock.h" + +/* resources */ +#include "anysrc/header.shader.h" +#include "anysrc/event_line.vshader.h" +#include "anysrc/event_line.fshader.h" + +#define LOSHADER_EVENT_LINE_UNIBLOCK_INDEX 0 + +void loshader_event_line_program_initialize( + loshader_event_line_program_t* prog) { + assert(prog != NULL); + + *prog = gleasy_program_new( + loshader_header_shader_, sizeof(loshader_header_shader_), + loshader_event_line_vshader_, sizeof(loshader_event_line_vshader_), + loshader_event_line_fshader_, sizeof(loshader_event_line_fshader_)); + + const GLuint uniblock = glGetUniformBlockIndex(*prog, "uniblock"); + assert(glGetError() == GL_NO_ERROR); + glUniformBlockBinding(*prog, uniblock, LOSHADER_EVENT_LINE_UNIBLOCK_INDEX); +} + +void loshader_event_line_program_deinitialize( + loshader_event_line_program_t* prog) { + assert(prog != NULL); + + glDeleteProgram(*prog); +} + +void loshader_event_line_drawer_initialize( + loshader_event_line_drawer_t* drawer, + const loshader_event_line_program_t* prog, + const loshader_uniblock_t* uniblock, + gleasy_texture_2d_t tex) { + assert(drawer != NULL); + assert(prog != NULL); + assert(uniblock != NULL); + assert(tex != 0); + + *drawer = (typeof(*drawer)) { + .prog = prog, + .uniblock = uniblock, + .tex = tex, + + .glyphas = glyphas_drawer_new(), + }; +} + +void loshader_event_line_drawer_deinitialize( + loshader_event_line_drawer_t* drawer) { + assert(drawer != NULL); + + glyphas_drawer_delete(drawer->glyphas); +} + +void loshader_event_line_drawer_clear( + loshader_event_line_drawer_t* drawer, size_t reserve) { + assert(drawer != NULL); + assert(reserve > 0); + + glyphas_drawer_clear(drawer->glyphas, drawer->tex, reserve); +} + +void loshader_event_line_drawer_add_block( + loshader_event_line_drawer_t* drawer, const glyphas_block_t* block) { + assert(drawer != NULL); + assert(block != NULL); + + glyphas_drawer_add_block(drawer->glyphas, block); +} + +void loshader_event_line_drawer_draw(const loshader_event_line_drawer_t* drawer) { + assert(drawer != NULL); + + glUseProgram(*drawer->prog); + + loshader_uniblock_bind(drawer->uniblock, LOSHADER_EVENT_LINE_UNIBLOCK_INDEX); + + glyphas_drawer_draw(drawer->glyphas); +} diff --git a/core/loshader/event_line.fshader b/core/loshader/event_line.fshader new file mode 100644 index 0000000..b062ab7 --- /dev/null +++ b/core/loshader/event_line.fshader @@ -0,0 +1,11 @@ +layout (location = 0) uniform sampler2D u_tex; + +in vec2 v_uv; +in vec4 v_color; + +out vec4 o_color; + +void main(void) { + float a = texture(u_tex, v_uv).r; + o_color = vec4(v_color.rgb, v_color.a*a); +} diff --git a/core/loshader/event_line.h b/core/loshader/event_line.h new file mode 100644 index 0000000..14bdc2c --- /dev/null +++ b/core/loshader/event_line.h @@ -0,0 +1,63 @@ +#pragma once + +#include + +#include "util/gleasy/program.h" +#include "util/gleasy/texture.h" +#include "util/glyphas/block.h" +#include "util/glyphas/drawer.h" + +#include "./uniblock.h" + +typedef gleasy_program_t loshader_event_line_program_t; + +typedef struct { + /* injected deps */ + const loshader_event_line_program_t* prog; + const loshader_uniblock_t* uniblock; + + gleasy_texture_2d_t tex; + + /* owned objects */ + glyphas_drawer_t* glyphas; +} loshader_event_line_drawer_t; + +void +loshader_event_line_program_initialize( + loshader_event_line_program_t* prog +); + +void +loshader_event_line_program_deinitialize( + loshader_event_line_program_t* prog +); + +void +loshader_event_line_drawer_initialize( + loshader_event_line_drawer_t* drawer, + const loshader_event_line_program_t* prog, + const loshader_uniblock_t* uniblock, + gleasy_texture_2d_t tex +); + +void +loshader_event_line_drawer_deinitialize( + loshader_event_line_drawer_t* drawer +); + +void +loshader_event_line_drawer_clear( + loshader_event_line_drawer_t* drawer, + size_t reserve +); + +void +loshader_event_line_drawer_add_block( + loshader_event_line_drawer_t* drawer, + const glyphas_block_t* block +); + +void +loshader_event_line_drawer_draw( + const loshader_event_line_drawer_t* drawer +); diff --git a/core/loshader/event_line.vshader b/core/loshader/event_line.vshader new file mode 100644 index 0000000..436de14 --- /dev/null +++ b/core/loshader/event_line.vshader @@ -0,0 +1,22 @@ +layout (location = 0) in vec2 i_pos; +layout (location = 1) in vec2 i_size; +layout (location = 2) in vec2 i_uv_pos; +layout (location = 3) in vec2 i_uv_size; +layout (location = 4) in vec4 i_color; + +out vec2 v_uv; +out vec4 v_color; + +void main(void) { + const vec2[] verts = vec2[]( + vec2( 0., 0.), vec2( 0., -1.), vec2( 1., -1.), + vec2( 0., 0.), vec2( 1., -1.), vec2( 1., 0.) + ); + + vec2 p = gl_VertexID < verts.length()? verts[gl_VertexID]: vec2(0.); + vec2 dp = p*i_size + i_pos; + + gl_Position = vec4(dp, 0, 1); + v_uv = p*i_uv_size + i_uv_pos; + v_color = i_color; +} diff --git a/core/loshader/fog.c b/core/loshader/fog.c new file mode 100644 index 0000000..d5d7026 --- /dev/null +++ b/core/loshader/fog.c @@ -0,0 +1,131 @@ +#include "./fog.h" + +#include +#include +#include + +#include "util/gleasy/buffer.h" +#include "util/gleasy/program.h" +#include "util/math/vector.h" +#include "util/memory/memory.h" + +#include "core/locommon/position.h" + +#include "./uniblock.h" + +/* resources */ +#include "anysrc/header.shader.h" +#include "anysrc/fog.vshader.h" +#include "anysrc/fog.fshader.h" + +struct loshader_fog_drawer_t { + const loshader_fog_program_t* prog; + const loshader_uniblock_t* uniblock; + + gleasy_buffer_uniform_t param; +}; + +#pragma pack(push, 1) +typedef struct { + float type; + float prev_type; + float transition; + + float bounds_fog; + vec4_t bounds_pos; + vec2_t bounds_size; +} loshader_fog_drawer_internal_param_t; +_Static_assert( + sizeof(float)*10 == + sizeof(loshader_fog_drawer_internal_param_t)); +#pragma pack(pop) + +#define LOSHADER_FOG_UNIBLOCK_INDEX 0 +#define LOSHADER_FOG_PARAM_INDEX 1 + +#define LOSHADER_FOG_PRIMITIVE_COUNT 6 + +void loshader_fog_program_initialize(loshader_fog_program_t* prog) { + assert(prog != NULL); + + *prog = gleasy_program_new( + loshader_header_shader_, sizeof(loshader_header_shader_), + loshader_fog_vshader_, sizeof(loshader_fog_vshader_), + loshader_fog_fshader_, sizeof(loshader_fog_fshader_)); + + const GLuint uniblock = glGetUniformBlockIndex(*prog, "uniblock"); + assert(glGetError() == GL_NO_ERROR); + glUniformBlockBinding(*prog, uniblock, LOSHADER_FOG_UNIBLOCK_INDEX); + + const GLuint param = glGetUniformBlockIndex(*prog, "param"); + assert(glGetError() == GL_NO_ERROR); + glUniformBlockBinding(*prog, param, LOSHADER_FOG_PARAM_INDEX); +} + +void loshader_fog_program_deinitialize(loshader_fog_program_t* prog) { + assert(prog != NULL); + + glDeleteProgram(*prog); +} + +loshader_fog_drawer_t* loshader_fog_drawer_new( + const loshader_fog_program_t* prog, + const loshader_uniblock_t* uniblock) { + assert(prog != NULL); + assert(uniblock != NULL); + + loshader_fog_drawer_t* drawer = memory_new(sizeof(*drawer)); + *drawer = (typeof(*drawer)) { + .prog = prog, + .uniblock = uniblock, + }; + + glGenBuffers(1, &drawer->param); + glBindBuffer(GL_UNIFORM_BUFFER, drawer->param); + glBufferData(GL_UNIFORM_BUFFER, + sizeof(loshader_fog_drawer_internal_param_t), NULL, GL_DYNAMIC_DRAW); + + return drawer; +} + +void loshader_fog_drawer_delete(loshader_fog_drawer_t* drawer) { + if (drawer == NULL) return; + + glDeleteBuffers(1, &drawer->param); + + memory_delete(drawer); +} + +void loshader_fog_drawer_set_param( + loshader_fog_drawer_t* drawer, + const loshader_fog_drawer_param_t* param) { + assert(drawer != NULL); + assert(param != NULL); + + const loshader_fog_drawer_internal_param_t p = { + .type = param->type, + .prev_type = param->prev_type, + .transition = param->transition, + .bounds_fog = param->bounds_fog, + .bounds_pos = vec4( + param->bounds_pos.chunk.x, + param->bounds_pos.chunk.y, + param->bounds_pos.fract.x, + param->bounds_pos.fract.y), + .bounds_size = param->bounds_size, + }; + glBindBuffer(GL_UNIFORM_BUFFER, drawer->param); + glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(p), &p); +} + +void loshader_fog_drawer_draw(const loshader_fog_drawer_t* drawer) { + assert(drawer != NULL); + + glUseProgram(*drawer->prog); + + loshader_uniblock_bind(drawer->uniblock, LOSHADER_FOG_UNIBLOCK_INDEX); + glBindBufferBase(GL_UNIFORM_BUFFER, + LOSHADER_FOG_PARAM_INDEX, drawer->param); + + glDrawArrays(GL_TRIANGLES, 0, LOSHADER_FOG_PRIMITIVE_COUNT); +} diff --git a/core/loshader/fog.fshader b/core/loshader/fog.fshader new file mode 100644 index 0000000..b39c66d --- /dev/null +++ b/core/loshader/fog.fshader @@ -0,0 +1,104 @@ +layout(std140) uniform param { + float type; + float prev_type; + float transition; + + float bounds_fog; + vec4 bounds_pos; /* xy: chunk, zw: fract */ + vec2 bounds_size; +} p; + +in vec2 v_pos; +in vec2 v_uv; + +out vec4 o_color; + +const float EPSILON = 1e-4; + +/* ---- UTILITY FUNCTIONS ---- */ +vec3 get_ray_direction(in vec2 p, in vec2 s, float fov) { + return normalize(vec3(p*s/2., s.y/tan(radians(fov)/2.))); +} +float rand(in vec2 p) { + /* https://qiita.com/shimacpyon/items/d15dee44a0b8b3883f76 */ + return fract(sin(dot(p ,vec2(12.9898,78.233))) * 43758.5453); +} +float noise(in vec3 p) { + /* https://www.shadertoy.com/view/4dS3Wd */ + const vec3 step = vec3(110, 241, 171); + + vec3 i = floor(p); + vec3 f = fract(p); + + float n = dot(i, step); + + vec3 u = f * f * (3.0 - 2.0 * f); + + const vec3 e1 = vec3(1., 0., 0.); + const vec3 e2 = vec3(0., 1., 0.); + const vec3 e3 = vec3(0., 0., 1.); + return mix(mix(mix(rand(dot(step, vec3(0.))+vec2(n)), rand(dot(step, e1 )+vec2(n)), u.x), + mix(rand(dot(step, e2)+vec2(n)), rand(dot(step, e1+e2 )+vec2(n)), u.x), u.y), + mix(mix(rand(dot(step, e3)+vec2(n)), rand(dot(step, e1+e3 )+vec2(n)), u.x), + mix(rand(dot(step, e2+e3)+vec2(n)), rand(dot(step, e1+e2+e3)+vec2(n)), u.x), u.y), u.z); +} +float fbm15(in vec3 p) { + float v = 0., a = .5, f = 0.; + + for (int i = 0; i < 15; ++i) { + v += a*noise(p); + p *= 2.; + a *= .5; + } + return v; +} + +/* ---- SCENE: white cloud ---- */ +vec4 white_cloud(void) { + vec3 dir = get_ray_direction(v_uv, uni.resolution, 60.); + vec3 eye = vec3(0.); + + eye.xy += uni.pos.xy + uni.pos.zw; + + float a = 0.; + for (float i = 1.; i <= 5.; ++i) { + a += fbm15(eye + dir*i/3.) / pow(i, 2.); + } + return vec4(.6, .6, .5, clamp(pow(a, 8.), 0., .8)); +} + +/* ---- SCENE: bounds fog ---- */ +vec4 bounds_fog(void) { + float aa = uni.aa * 100; + + vec2 pos; + pos.x = (p.bounds_pos.x - uni.pos.x) + (p.bounds_pos.z - uni.pos.z); + pos.y = (p.bounds_pos.y - uni.pos.y) + (p.bounds_pos.y - uni.pos.y); + pos = (uni.proj * uni.cam * vec4(pos, 0., 1.)).xy; + + vec2 size = (uni.proj * uni.cam * vec4(p.bounds_size, 0., 0.)).xy; + + vec2 area_pos = v_uv - pos; + + float r = + smoothstep(size.x-aa, size.x+aa, abs(area_pos.x)) + + smoothstep(size.y-aa, size.y+aa, abs(area_pos.y)); + + float a = fbm15(vec3(area_pos, abs(fract(uni.time/60.)*2.-1.))); + + return vec4(1.) * a * r * p.bounds_fog; +} + +vec4 scene(in float type) { + return + type == 1.? white_cloud(): + vec4(0.); +} +void main(void) { + vec4 prev = vec4(0.), next = vec4(0.); + if (p.transition > 0.) next = clamp(scene(p.type), 0., 1.); + if (p.transition < 1.) prev = clamp(scene(p.prev_type), 0., 1.); + o_color = mix(prev, next, p.transition); + + o_color += bounds_fog(); +} diff --git a/core/loshader/fog.h b/core/loshader/fog.h new file mode 100644 index 0000000..47db16c --- /dev/null +++ b/core/loshader/fog.h @@ -0,0 +1,59 @@ +#pragma once + +#include "util/gleasy/program.h" + +#include "core/locommon/position.h" + +#include "./uniblock.h" + +typedef gleasy_program_t loshader_fog_program_t; + +typedef struct loshader_fog_drawer_t loshader_fog_drawer_t; + +typedef enum { + LOSHADER_FOG_TYPE_NONE = 0, + LOSHADER_FOG_TYPE_WHITE_CLOUD = 1, +} loshader_fog_type_t; + +typedef struct { + loshader_fog_type_t type; + loshader_fog_type_t prev_type; + + float transition; + + float bounds_fog; + locommon_position_t bounds_pos; + vec2_t bounds_size; +} loshader_fog_drawer_param_t; + +void +loshader_fog_program_initialize( + loshader_fog_program_t* prog +); + +void +loshader_fog_program_deinitialize( + loshader_fog_program_t* prog +); + +loshader_fog_drawer_t* +loshader_fog_drawer_new( + const loshader_fog_program_t* prog, + const loshader_uniblock_t* uniblock +); + +void +loshader_fog_drawer_delete( + loshader_fog_drawer_t* drawer +); + +void +loshader_fog_drawer_set_param( + loshader_fog_drawer_t* drawer, + const loshader_fog_drawer_param_t* param +); + +void +loshader_fog_drawer_draw( + const loshader_fog_drawer_t* drawer +); diff --git a/core/loshader/fog.vshader b/core/loshader/fog.vshader new file mode 100644 index 0000000..7147bc0 --- /dev/null +++ b/core/loshader/fog.vshader @@ -0,0 +1,15 @@ +out vec2 v_uv; + +const vec2[6] square_ = vec2[]( + vec2(-1., 1.), + vec2(-1., -1.), + vec2( 1., -1.), + vec2(-1., 1.), + vec2( 1., -1.), + vec2( 1., 1.) +); + +void main(void) { + v_uv = square_[gl_VertexID]; + gl_Position = vec4(v_uv, 0., 1.); +} diff --git a/core/loshader/ground.c b/core/loshader/ground.c new file mode 100644 index 0000000..aca1a08 --- /dev/null +++ b/core/loshader/ground.c @@ -0,0 +1,174 @@ +#include "./ground.h" + +#include +#include +#include +#include +#include + +#include + +#include "util/math/algorithm.h" +#include "util/math/matrix.h" +#include "util/math/vector.h" +#include "util/memory/memory.h" +#include "util/gleasy/buffer.h" +#include "util/gleasy/program.h" + +#include "./uniblock.h" + +/* resources */ +#include "anysrc/header.shader.h" +#include "anysrc/ground.vshader.h" +#include "anysrc/ground.fshader.h" + +#define LOSHADER_GROUND_VSHADER_IN_GROUND_ID 0 +#define LOSHADER_GROUND_VSHADER_IN_POS 1 +#define LOSHADER_GROUND_VSHADER_IN_SIZE 2 + +struct loshader_ground_drawer_t { + const loshader_ground_program_t* prog; + + const loshader_uniblock_t* uniblock; + + GLuint vao; + + gleasy_buffer_array_t instances; + size_t instances_reserved; + size_t instances_length; +}; + +#pragma pack(push, 1) +typedef struct { + uint16_t ground_id; + + vec2_t pos; + vec2_t size; +} loshader_ground_drawer_internal_instance_t; +#pragma pack(pop) + +#define LOSHADER_GROUND_UNIBLOCK_INDEX 0 + +#define LOSHADER_GROUND_PRIMITIVE_COUNT 6 + +static void loshader_ground_program_setup_vao_( + gleasy_buffer_array_t instances) { + assert(instances != 0); + + glBindBuffer(GL_ARRAY_BUFFER, instances); + +# define enable_attrib_(NAME, name, dim, type) do { \ + glEnableVertexAttribArray(LOSHADER_GROUND_VSHADER_IN_##NAME); \ + glVertexAttribPointer( \ + LOSHADER_GROUND_VSHADER_IN_##NAME, \ + dim, type, GL_FALSE, sizeof(loshader_ground_drawer_internal_instance_t), \ + NULL + offsetof(loshader_ground_drawer_internal_instance_t, name)); \ + glVertexAttribDivisor(LOSHADER_GROUND_VSHADER_IN_##NAME, 1); \ + } while (0) + enable_attrib_(GROUND_ID, ground_id, 1, GL_UNSIGNED_SHORT); + enable_attrib_(POS, pos, 2, GL_FLOAT); + enable_attrib_(SIZE, size, 2, GL_FLOAT); +# undef enable_attrib_ +} + +void loshader_ground_program_initialize(loshader_ground_program_t* prog) { + *prog = gleasy_program_new( + loshader_header_shader_, sizeof(loshader_header_shader_), + loshader_ground_vshader_, sizeof(loshader_ground_vshader_), + loshader_ground_fshader_, sizeof(loshader_ground_fshader_)); + + const GLuint uniblock = glGetUniformBlockIndex(*prog, "uniblock"); + assert(glGetError() == GL_NO_ERROR); + + glUniformBlockBinding(*prog, uniblock, LOSHADER_GROUND_UNIBLOCK_INDEX); +} +void loshader_ground_program_deinitialize(loshader_ground_program_t* prog) { + assert(prog != NULL); + + glDeleteProgram(*prog); +} + +loshader_ground_drawer_t* loshader_ground_drawer_new( + const loshader_ground_program_t* prog, + const loshader_uniblock_t* uniblock) { + assert(prog != NULL); + assert(uniblock != NULL); + + loshader_ground_drawer_t* drawer = memory_new(sizeof(*drawer)); + *drawer = (typeof(*drawer)) { + .prog = prog, + .uniblock = uniblock, + }; + + glCreateVertexArrays(1, &drawer->vao); + glBindVertexArray(drawer->vao); + + glGenBuffers(1, &drawer->instances); + loshader_ground_program_setup_vao_(drawer->instances); + return drawer; +} + +void loshader_ground_drawer_delete(loshader_ground_drawer_t* drawer) { + if (drawer == NULL) return; + + glDeleteBuffers(1, &drawer->instances); + + glDeleteVertexArrays(1, &drawer->vao); + + memory_delete(drawer); +} + +void loshader_ground_drawer_clear( + loshader_ground_drawer_t* drawer, size_t reserve) { + assert(drawer != NULL); + + drawer->instances_length = 0; + + if (drawer->instances_reserved < reserve) { + glBindBuffer(GL_ARRAY_BUFFER, drawer->instances); + glBufferData(GL_ARRAY_BUFFER, + reserve * sizeof(loshader_ground_drawer_instance_t), + NULL, GL_DYNAMIC_DRAW); + drawer->instances_reserved = reserve; + } +} + +void loshader_ground_drawer_add_instance( + loshader_ground_drawer_t* drawer, + const loshader_ground_drawer_instance_t* instance) { + assert(drawer != NULL); + assert(instance != NULL); + + if (drawer->instances_length >= drawer->instances_reserved) { + fprintf(stderr, "ground drawer instance overflow\n"); + abort(); + } + + const loshader_ground_drawer_internal_instance_t inst = { + .ground_id = instance->ground_id, + .pos = instance->pos, + .size = instance->size, + }; + + glBindBuffer(GL_ARRAY_BUFFER, drawer->instances); + glBufferSubData(GL_ARRAY_BUFFER, + drawer->instances_length*sizeof(inst), sizeof(inst), &inst); + + ++drawer->instances_length; +} + +void loshader_ground_drawer_draw(const loshader_ground_drawer_t* drawer) { + assert(drawer != NULL); + + if (drawer->instances_length == 0) return; + + glUseProgram(*drawer->prog); + glBindVertexArray(drawer->vao); + + loshader_uniblock_bind( + drawer->uniblock, LOSHADER_GROUND_UNIBLOCK_INDEX); + + glDrawArraysInstanced(GL_TRIANGLES, + 0, LOSHADER_GROUND_PRIMITIVE_COUNT, drawer->instances_length); +} + diff --git a/core/loshader/ground.fshader b/core/loshader/ground.fshader new file mode 100644 index 0000000..a95c149 --- /dev/null +++ b/core/loshader/ground.fshader @@ -0,0 +1,9 @@ +in float v_ground_id; +in vec2 v_size; +in vec2 v_uv; + +out vec4 o_color; + +void main(void) { + o_color = vec4(0., 0., 0., .6); +} diff --git a/core/loshader/ground.h b/core/loshader/ground.h new file mode 100644 index 0000000..1864811 --- /dev/null +++ b/core/loshader/ground.h @@ -0,0 +1,62 @@ +#pragma once + +#include + +#include "util/gleasy/program.h" +#include "util/math/vector.h" + +#include "./uniblock.h" + +typedef gleasy_program_t loshader_ground_program_t; + +struct loshader_ground_drawer_t; +typedef struct loshader_ground_drawer_t loshader_ground_drawer_t; + +typedef enum { + LOSHADER_GROUND_ID_ISLAND = 0, +} loshader_ground_id_t; + +typedef struct { + loshader_ground_id_t ground_id; + + vec2_t pos; + vec2_t size; +} loshader_ground_drawer_instance_t; + +void +loshader_ground_program_initialize( + loshader_ground_program_t* prog +); + +void +loshader_ground_program_deinitialize( + loshader_ground_program_t* prog +); + +loshader_ground_drawer_t* +loshader_ground_drawer_new( + const loshader_ground_program_t* prog, + const loshader_uniblock_t* uniblock +); + +void +loshader_ground_drawer_delete( + loshader_ground_drawer_t* drawer +); + +void +loshader_ground_drawer_clear( + loshader_ground_drawer_t* drawer, + size_t reserve +); + +void +loshader_ground_drawer_add_instance( + loshader_ground_drawer_t* drawer, + const loshader_ground_drawer_instance_t* instance +); + +void +loshader_ground_drawer_draw( + const loshader_ground_drawer_t* drawer +); diff --git a/core/loshader/ground.vshader b/core/loshader/ground.vshader new file mode 100644 index 0000000..c773b63 --- /dev/null +++ b/core/loshader/ground.vshader @@ -0,0 +1,20 @@ +layout (location = 0) in float i_ground_id; +layout (location = 1) in vec2 i_pos; +layout (location = 2) in vec2 i_size; + +out float v_ground_id; +out vec2 v_size; +out vec2 v_uv; + +const vec2[] square_ = vec2[]( + vec2(-1., 1.), vec2(-1., -1.), vec2( 1., -1.), + vec2(-1., 1.), vec2( 1., -1.), vec2( 1., 1.) +); + +void main(void) { + v_ground_id = i_ground_id; + v_size = i_size; + v_uv = square_[gl_VertexID]; + gl_Position = uni.proj * uni.cam * vec4(v_uv*i_size+i_pos, 0., 1.); +} + diff --git a/core/loshader/header.shader b/core/loshader/header.shader new file mode 100644 index 0000000..4fa573d --- /dev/null +++ b/core/loshader/header.shader @@ -0,0 +1,22 @@ +#version 330 +#extension GL_ARB_explicit_uniform_location : enable + +#define PI radians(180.) + +precision mediump float; + +layout(std140) uniform uniblock { + vec2 resolution; + vec2 dpi; + float aa; + + mat4 proj; + mat4 cam; + vec4 pos; /* chunk x, chunk y, fract x, fract y*/ + + float time; /* %60sec */ +} uni; + +/* To make it explicit which errors happened in header or body, + adds 10000 to body's line number. */ +#line 100001 diff --git a/core/loshader/hud_bar.c b/core/loshader/hud_bar.c new file mode 100644 index 0000000..1c354ad --- /dev/null +++ b/core/loshader/hud_bar.c @@ -0,0 +1,187 @@ +#include "./hud_bar.h" + +#include +#include +#include +#include + +#include + +#include "util/gleasy/buffer.h" +#include "util/gleasy/program.h" +#include "util/math/vector.h" +#include "util/memory/memory.h" + +#include "./uniblock.h" + +/* resources */ +#include "anysrc/header.shader.h" +#include "anysrc/hud_bar.vshader.h" +#include "anysrc/hud_bar.fshader.h" + +#define LOSHADER_HUD_BAR_VSHADER_IN_POS 0 +#define LOSHADER_HUD_BAR_VSHADER_IN_SIZE 1 +#define LOSHADER_HUD_BAR_VSHADER_IN_BGCOLOR 2 +#define LOSHADER_HUD_BAR_VSHADER_IN_FGCOLOR 3 +#define LOSHADER_HUD_BAR_VSHADER_IN_VALUE 4 +#define LOSHADER_HUD_BAR_VSHADER_IN_PREV_VALUE 5 + +struct loshader_hud_bar_drawer_t { + const loshader_hud_bar_program_t* prog; + const loshader_uniblock_t* uniblock; + + GLuint vao; + + gleasy_buffer_array_t instances; + size_t instances_reserved; + size_t instances_length; +}; + +#pragma pack(push, 1) +typedef struct { + vec2_t pos; + vec2_t size; + + vec4_t bgcolor; + vec4_t fgcolor; + + float value; + float prev_value; +} loshader_hud_bar_drawer_internal_instance_t; +#pragma pack(pop) + +#define LOSHADER_HUD_BAR_UNIBLOCK_INDEX 0 + +#define LOSHADER_HUD_BAR_PRIMITIVE_COUNT 18 + +static void loshader_hud_bar_program_setup_vao_( + gleasy_buffer_array_t instances) { + assert(instances != 0); + + glBindBuffer(GL_ARRAY_BUFFER, instances); + +# define enable_attrib_(NAME, name, dim, type) do { \ + glEnableVertexAttribArray(LOSHADER_HUD_BAR_VSHADER_IN_##NAME); \ + glVertexAttribPointer( \ + LOSHADER_HUD_BAR_VSHADER_IN_##NAME, dim, type, GL_FALSE, \ + sizeof(loshader_hud_bar_drawer_internal_instance_t), \ + NULL + offsetof(loshader_hud_bar_drawer_internal_instance_t, name)); \ + glVertexAttribDivisor(LOSHADER_HUD_BAR_VSHADER_IN_##NAME, 1); \ + } while (0) + + enable_attrib_(POS, pos, 2, GL_FLOAT); + enable_attrib_(SIZE, size, 2, GL_FLOAT); + enable_attrib_(BGCOLOR, bgcolor, 4, GL_FLOAT); + enable_attrib_(FGCOLOR, fgcolor, 4, GL_FLOAT); + enable_attrib_(VALUE, value, 1, GL_FLOAT); + enable_attrib_(PREV_VALUE, prev_value, 1, GL_FLOAT); + +# undef enable_attrib_ +} + +void loshader_hud_bar_program_initialize(loshader_hud_bar_program_t* prog) { + assert(prog != NULL); + + *prog = gleasy_program_new( + loshader_header_shader_, sizeof(loshader_header_shader_), + loshader_hud_bar_vshader_, sizeof(loshader_hud_bar_vshader_), + loshader_hud_bar_fshader_, sizeof(loshader_hud_bar_fshader_)); + + const GLuint uniblock = glGetUniformBlockIndex(*prog, "uniblock"); + assert(glGetError() == GL_NO_ERROR); + glUniformBlockBinding(*prog, uniblock, LOSHADER_HUD_BAR_UNIBLOCK_INDEX); +} + +void loshader_hud_bar_program_deinitialize(loshader_hud_bar_program_t* prog) { + assert(prog != NULL); + + glDeleteProgram(*prog); +} + +loshader_hud_bar_drawer_t* loshader_hud_bar_drawer_new( + const loshader_hud_bar_program_t* prog, + const loshader_uniblock_t* uniblock) { + assert(prog != NULL); + assert(uniblock != NULL); + + loshader_hud_bar_drawer_t* drawer = memory_new(sizeof(*drawer)); + *drawer = (typeof(*drawer)) { + .prog = prog, + .uniblock = uniblock, + }; + + glCreateVertexArrays(1, &drawer->vao); + glBindVertexArray(drawer->vao); + + glGenBuffers(1, &drawer->instances); + loshader_hud_bar_program_setup_vao_(drawer->instances); + + return drawer; +} + +void loshader_hud_bar_drawer_delete(loshader_hud_bar_drawer_t* drawer) { + if (drawer == NULL) return; + + glDeleteBuffers(1, &drawer->instances); + + glDeleteVertexArrays(1, &drawer->vao); + + memory_delete(drawer); +} + +void loshader_hud_bar_drawer_clear( + loshader_hud_bar_drawer_t* drawer, size_t reserve) { + assert(drawer != NULL); + assert(reserve > 0); + + drawer->instances_length = 0; + + if (drawer->instances_reserved < reserve) { + glBindBuffer(GL_ARRAY_BUFFER, drawer->instances); + glBufferData(GL_ARRAY_BUFFER, + sizeof(loshader_hud_bar_drawer_internal_instance_t) * reserve, + NULL, GL_DYNAMIC_DRAW); + drawer->instances_reserved = reserve; + } +} + +void loshader_hud_bar_drawer_add_instance( + loshader_hud_bar_drawer_t* drawer, + const loshader_hud_bar_drawer_instance_t* instance) { + assert(drawer != NULL); + assert(instance != NULL); + + if (drawer->instances_length >= drawer->instances_reserved) { + fprintf(stderr, "hud bar drawer instance overflow\n"); + abort(); + } + + const loshader_hud_bar_drawer_internal_instance_t insta = { + .pos = instance->pos, + .size = instance->size, + .bgcolor = instance->bgcolor, + .fgcolor = instance->fgcolor, + .value = instance->value, + .prev_value = instance->prev_value, + }; + + const size_t offset = drawer->instances_length * sizeof(insta); + glBindBuffer(GL_ARRAY_BUFFER, drawer->instances); + glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(insta), &insta); + + ++drawer->instances_length; +} + +void loshader_hud_bar_drawer_draw(const loshader_hud_bar_drawer_t* drawer) { + assert(drawer != NULL); + + if (drawer->instances_length == 0) return; + + glUseProgram(*drawer->prog); + glBindVertexArray(drawer->vao); + + loshader_uniblock_bind(drawer->uniblock, LOSHADER_HUD_BAR_UNIBLOCK_INDEX); + + glDrawArraysInstanced(GL_TRIANGLES, + 0, LOSHADER_HUD_BAR_PRIMITIVE_COUNT, drawer->instances_length); +} diff --git a/core/loshader/hud_bar.fshader b/core/loshader/hud_bar.fshader new file mode 100644 index 0000000..f557802 --- /dev/null +++ b/core/loshader/hud_bar.fshader @@ -0,0 +1,21 @@ +in vec2 v_pos; +in vec2 v_size; +in vec2 v_aa; +in vec2 v_dp; +in vec4 v_color; + +out vec4 o_color; + +void main() { + vec2 dp = v_dp; + dp *= pow(length(dp), .1); + + vec2 diff = abs(dp - v_pos); + float aa = uni.aa * 4.; + float a = + (1.-smoothstep(v_size.x-aa, v_size.x, diff.x)) * + (1.-smoothstep(v_size.y-aa, v_size.y, diff.y)); + + o_color = v_color; + o_color.a *= a; +} diff --git a/core/loshader/hud_bar.h b/core/loshader/hud_bar.h new file mode 100644 index 0000000..7beb0ba --- /dev/null +++ b/core/loshader/hud_bar.h @@ -0,0 +1,60 @@ +#pragma once + +#include "util/gleasy/program.h" +#include "util/math/vector.h" + +#include "./uniblock.h" + +typedef gleasy_program_t loshader_hud_bar_program_t; + +struct loshader_hud_bar_drawer_t; +typedef struct loshader_hud_bar_drawer_t loshader_hud_bar_drawer_t; + +typedef struct { + vec2_t pos; + vec2_t size; + + vec4_t bgcolor; + vec4_t fgcolor; + + float value; + float prev_value; +} loshader_hud_bar_drawer_instance_t; + +void +loshader_hud_bar_program_initialize( + loshader_hud_bar_program_t* prog +); + +void +loshader_hud_bar_program_deinitialize( + loshader_hud_bar_program_t* prog +); + +loshader_hud_bar_drawer_t* /* OWNERSHIP */ +loshader_hud_bar_drawer_new( + const loshader_hud_bar_program_t* prog, + const loshader_uniblock_t* uniblock +); + +void +loshader_hud_bar_drawer_delete( + loshader_hud_bar_drawer_t* drawer /* OWNERSHIP */ +); + +void +loshader_hud_bar_drawer_clear( + loshader_hud_bar_drawer_t* drawer, + size_t reserve +); + +void +loshader_hud_bar_drawer_add_instance( + loshader_hud_bar_drawer_t* drawer, + const loshader_hud_bar_drawer_instance_t* instance +); + +void +loshader_hud_bar_drawer_draw( + const loshader_hud_bar_drawer_t* drawer +); diff --git a/core/loshader/hud_bar.vshader b/core/loshader/hud_bar.vshader new file mode 100644 index 0000000..44142bc --- /dev/null +++ b/core/loshader/hud_bar.vshader @@ -0,0 +1,56 @@ +layout (location = 0) in vec2 i_pos; +layout (location = 1) in vec2 i_size; +layout (location = 2) in vec4 i_bgcolor; +layout (location = 3) in vec4 i_fgcolor; +layout (location = 4) in float i_value; +layout (location = 5) in float i_prev_value; + +out vec2 v_pos; +out vec2 v_size; +out vec2 v_aa; +out vec2 v_dp; +out vec4 v_color; + +void main() { + const vec2[] verts = vec2[]( + vec2(-1., 1.), + vec2(-1., -1.), + vec2( 1., -1.), + vec2(-1., 1.), + vec2( 1., -1.), + vec2( 1., 1.) + ); + + float scale = (uni.cam * vec4(1., 0., 0., 0.)).x; + scale = pow(1.2, scale-1.); + + v_pos = i_pos; + v_size = i_size; + + int id = 0; + if (gl_VertexID < 6) { + id = gl_VertexID; + v_size += vec2(2.)/uni.resolution; + v_color = i_bgcolor; + } else if (gl_VertexID < 12) { + id = gl_VertexID - 6; + v_pos.x -= v_size.x*(1.-i_prev_value); + v_size.x *= i_prev_value; + v_color = mix(i_bgcolor, i_fgcolor, .5); + } else { + id = gl_VertexID - 12; + v_pos.x -= v_size.x*(1.-i_value); + v_size.x *= i_value; + v_color = i_fgcolor; + } + v_pos *= scale; + v_size *= scale; + + v_size = abs(v_size); + v_aa = uni.aa / v_size; + + v_dp = verts[id]*1.5*v_size + v_pos; + v_dp /= pow(length(v_dp), .1); + + gl_Position = vec4(v_dp, 0., 1.); +} diff --git a/core/loshader/hud_text.c b/core/loshader/hud_text.c new file mode 100644 index 0000000..4d68fc7 --- /dev/null +++ b/core/loshader/hud_text.c @@ -0,0 +1,95 @@ +#include "./hud_text.h" + +#include +#include + +#include "util/gleasy/program.h" +#include "util/gleasy/texture.h" +#include "util/glyphas/block.h" +#include "util/glyphas/drawer.h" + +#include "./uniblock.h" + +/* resources */ +#include "anysrc/header.shader.h" +#include "anysrc/hud_text.vshader.h" +#include "anysrc/hud_text.fshader.h" + +#define LOSHADER_HUD_TEXT_UNIFORM_ALPHA 1 + +#define LOSHADER_HUD_TEXT_UNIBLOCK_INDEX 0 + +void loshader_hud_text_program_initialize(loshader_hud_text_program_t* prog) { + assert(prog != NULL); + + *prog = gleasy_program_new( + loshader_header_shader_, sizeof(loshader_header_shader_), + loshader_hud_text_vshader_, sizeof(loshader_hud_text_vshader_), + loshader_hud_text_fshader_, sizeof(loshader_hud_text_fshader_)); + + const GLuint uniblock = glGetUniformBlockIndex(*prog, "uniblock"); + assert(glGetError() == GL_NO_ERROR); + glUniformBlockBinding(*prog, uniblock, LOSHADER_HUD_TEXT_UNIBLOCK_INDEX); +} + +void loshader_hud_text_program_deinitialize(loshader_hud_text_program_t* prog) { + assert(prog != NULL); + + glDeleteProgram(*prog); +} + +void loshader_hud_text_drawer_initialize( + loshader_hud_text_drawer_t* drawer, + const loshader_hud_text_program_t* prog, + const loshader_uniblock_t* uniblock, + gleasy_texture_2d_t tex) { + assert(drawer != NULL); + assert(prog != NULL); + assert(uniblock != NULL); + assert(tex != 0); + + *drawer = (typeof(*drawer)) { + .prog = prog, + .uniblock = uniblock, + .tex = tex, + + .glyphas = glyphas_drawer_new(), + + .alpha = 1, + }; +} + +void loshader_hud_text_drawer_deinitialize( + loshader_hud_text_drawer_t* drawer) { + assert(drawer != NULL); + + glyphas_drawer_delete(drawer->glyphas); +} + +void loshader_hud_text_drawer_clear( + loshader_hud_text_drawer_t* drawer, size_t reserve) { + assert(drawer != NULL); + assert(reserve > 0); + + glyphas_drawer_clear(drawer->glyphas, drawer->tex, reserve); +} + +void loshader_hud_text_drawer_add_block( + loshader_hud_text_drawer_t* drawer, const glyphas_block_t* block) { + assert(drawer != NULL); + assert(block != NULL); + + glyphas_drawer_add_block(drawer->glyphas, block); +} + +void loshader_hud_text_drawer_draw(const loshader_hud_text_drawer_t* drawer) { + assert(drawer != NULL); + + glUseProgram(*drawer->prog); + + loshader_uniblock_bind(drawer->uniblock, LOSHADER_HUD_TEXT_UNIBLOCK_INDEX); + + glUniform1f(LOSHADER_HUD_TEXT_UNIFORM_ALPHA, drawer->alpha); + + glyphas_drawer_draw(drawer->glyphas); +} diff --git a/core/loshader/hud_text.fshader b/core/loshader/hud_text.fshader new file mode 100644 index 0000000..8ca1604 --- /dev/null +++ b/core/loshader/hud_text.fshader @@ -0,0 +1,28 @@ +layout (location = 0) uniform sampler2D u_tex; +layout (location = 1) uniform float u_alpha; + +in vec2 v_pos; +in vec2 v_size; +in vec2 v_uv_pos; +in vec2 v_uv_size; +in vec2 v_dp; +in vec4 v_color; + +out vec4 o_color; + +void main(void) { + vec2 dp = v_dp; + dp *= pow(length(dp), .1); + + vec2 uvp = (dp - v_pos) / v_size; + vec2 uv = uvp*v_uv_size + v_uv_pos; + uvp = abs(uvp); + + float a = + texture(u_tex, uv).r * + step(abs(uvp.x-.5), .5) * + step(abs(uvp.y-.5), .5); + + o_color = v_color; + o_color.a *= a*u_alpha; +} diff --git a/core/loshader/hud_text.h b/core/loshader/hud_text.h new file mode 100644 index 0000000..d381f2c --- /dev/null +++ b/core/loshader/hud_text.h @@ -0,0 +1,66 @@ +#pragma once + +#include + +#include "util/gleasy/program.h" +#include "util/gleasy/texture.h" +#include "util/glyphas/block.h" +#include "util/glyphas/drawer.h" + +#include "./uniblock.h" + +typedef gleasy_program_t loshader_hud_text_program_t; + +typedef struct { + /* injected deps */ + const loshader_hud_text_program_t* prog; + const loshader_uniblock_t* uniblock; + + gleasy_texture_2d_t tex; + + /* owned objects */ + glyphas_drawer_t* glyphas; + + /* public params */ + float alpha; +} loshader_hud_text_drawer_t; + +void +loshader_hud_text_program_initialize( + loshader_hud_text_program_t* prog +); + +void +loshader_hud_text_program_deinitialize( + loshader_hud_text_program_t* prog +); + +void +loshader_hud_text_drawer_initialize( + loshader_hud_text_drawer_t* drawer, + const loshader_hud_text_program_t* prog, + const loshader_uniblock_t* uniblock, + gleasy_texture_2d_t tex +); + +void +loshader_hud_text_drawer_deinitialize( + loshader_hud_text_drawer_t* drawer +); + +void +loshader_hud_text_drawer_clear( + loshader_hud_text_drawer_t* drawer, + size_t reserve +); + +void +loshader_hud_text_drawer_add_block( + loshader_hud_text_drawer_t* drawer, + const glyphas_block_t* block +); + +void +loshader_hud_text_drawer_draw( + const loshader_hud_text_drawer_t* drawer +); diff --git a/core/loshader/hud_text.vshader b/core/loshader/hud_text.vshader new file mode 100644 index 0000000..1117814 --- /dev/null +++ b/core/loshader/hud_text.vshader @@ -0,0 +1,33 @@ +layout (location = 0) in vec2 i_pos; +layout (location = 1) in vec2 i_size; +layout (location = 2) in vec2 i_uv_pos; +layout (location = 3) in vec2 i_uv_size; +layout (location = 4) in vec4 i_color; + +out vec2 v_pos; +out vec2 v_size; +out vec2 v_uv_pos; +out vec2 v_uv_size; +out vec2 v_dp; +out vec4 v_color; + +void main(void) { + const vec2[] verts = vec2[]( + vec2( 0., 0.), vec2( 0., -1.), vec2( 1., -1.), + vec2( 0., 0.), vec2( 1., -1.), vec2( 1., 0.) + ); + + float scale = (uni.cam * vec4(1., 0., 0., 0.)).x; + scale = pow(1.2, scale-1.); + + v_pos = i_pos * scale; + v_size = i_size * scale; + v_uv_pos = i_uv_pos; + v_uv_size = i_uv_size; + v_color = i_color; + + v_dp = verts[gl_VertexID]*v_size*1.5 + v_pos; + v_dp /= pow(length(v_dp), .1); + + gl_Position = vec4(v_dp, 0., 1.); +} diff --git a/core/loshader/menu_background.c b/core/loshader/menu_background.c new file mode 100644 index 0000000..9dbb994 --- /dev/null +++ b/core/loshader/menu_background.c @@ -0,0 +1,97 @@ +#include "./menu_background.h" + +#include + +#include + +#include "util/gleasy/program.h" +#include "util/math/algorithm.h" +#include "util/memory/memory.h" + +#include "./uniblock.h" + +/* resources */ +#include "anysrc/header.shader.h" +#include "anysrc/menu_background.vshader.h" +#include "anysrc/menu_background.fshader.h" + +#define LOSHADER_MENU_BACKGROUND_UNIFORM_ALPHA 0 + +struct loshader_menu_background_drawer_t { + const loshader_menu_background_program_t* prog; + const loshader_uniblock_t* uniblock; + + float alpha; +}; + +#define LOSHADER_MENU_BACKGROUND_UNIBLOCK_INDEX 0 + +void loshader_menu_background_program_initialize( + loshader_menu_background_program_t* prog) { + assert(prog != NULL); + + *prog = gleasy_program_new( + loshader_header_shader_, + sizeof(loshader_header_shader_), + loshader_menu_background_vshader_, + sizeof(loshader_menu_background_vshader_), + loshader_menu_background_fshader_, + sizeof(loshader_menu_background_fshader_)); + + const GLuint uniblock = glGetUniformBlockIndex(*prog, "uniblock"); + assert(glGetError() == GL_NO_ERROR); + glUniformBlockBinding( + *prog, uniblock, LOSHADER_MENU_BACKGROUND_UNIBLOCK_INDEX); +} + +void loshader_menu_background_program_deinitialize( + loshader_menu_background_program_t* prog) { + assert(prog != NULL); + + glDeleteProgram(*prog); +} + +loshader_menu_background_drawer_t* loshader_menu_background_drawer_new( + const loshader_menu_background_program_t* prog, + const loshader_uniblock_t* uniblock) { + assert(prog != NULL); + assert(uniblock != NULL); + + loshader_menu_background_drawer_t* drawer = memory_new(sizeof(*drawer)); + *drawer = (typeof(*drawer)) { + .prog = prog, + .uniblock = uniblock, + }; + return drawer; +} + +void loshader_menu_background_drawer_delete( + loshader_menu_background_drawer_t* drawer) { + if (drawer == NULL) return; + + memory_delete(drawer); +} + +void loshader_menu_background_drawer_set_alpha( + loshader_menu_background_drawer_t* drawer, float alpha) { + assert(drawer != NULL); + assert(MATH_FLOAT_VALID(alpha)); + + drawer->alpha = alpha; +} + +void loshader_menu_background_drawer_draw( + const loshader_menu_background_drawer_t* drawer) { + assert(drawer != NULL); + + if (drawer->alpha == 0) return; + + glUseProgram(*drawer->prog); + + loshader_uniblock_bind( + drawer->uniblock, LOSHADER_MENU_BACKGROUND_UNIBLOCK_INDEX); + + glUniform1f(LOSHADER_MENU_BACKGROUND_UNIFORM_ALPHA, drawer->alpha); + + glDrawArrays(GL_TRIANGLES, 0, 6); +} diff --git a/core/loshader/menu_background.fshader b/core/loshader/menu_background.fshader new file mode 100644 index 0000000..c488af3 --- /dev/null +++ b/core/loshader/menu_background.fshader @@ -0,0 +1,35 @@ +layout (location = 0) uniform float u_alpha; + +in vec2 v_uv; + +out vec4 o_color; + +float atan2(in vec2 p){ + return p.x == 0. ? sign(p.y)*PI/2. : atan(p.y, p.x); +} + +float map(in vec2 p, in float t) { + if (p.x >= 2.) return 0.; + + vec2 v = vec2(.3, .4 + t*.3); + for (float i = 0.; i < 10.; ++i) { + p = abs(p) - v; + v /= dot(p, p); + } + return abs(p.x); +} + +void main(void) { + vec2 uv = v_uv * uni.resolution.xy / (4.*uni.dpi); + + vec2 polar = vec2(length(uv), atan2(uv)); + polar.y = abs(mod(polar.y, PI/3.) - PI/6.); + + vec2 p = vec2(cos(polar.y), sin(polar.y))*polar.x; + + o_color = vec4(0, 0, 0, .7*u_alpha); + + float t = 1.-pow(1.-u_alpha, 8.); + float fractal = step(100.-t*100.+3., map(p*1.8, t)) * polar.x; + o_color = mix(o_color, vec4(.4, .2, .2, .1), fractal); +} diff --git a/core/loshader/menu_background.h b/core/loshader/menu_background.h new file mode 100644 index 0000000..b4ec9ce --- /dev/null +++ b/core/loshader/menu_background.h @@ -0,0 +1,44 @@ +#pragma once + +#include "util/gleasy/program.h" + +#include "./uniblock.h" + +typedef gleasy_program_t loshader_menu_background_program_t; + +struct loshader_menu_background_drawer_t; +typedef + struct loshader_menu_background_drawer_t + loshader_menu_background_drawer_t; + +void +loshader_menu_background_program_initialize( + loshader_menu_background_program_t* prog +); + +void +loshader_menu_background_program_deinitialize( + loshader_menu_background_program_t* prog +); + +loshader_menu_background_drawer_t* +loshader_menu_background_drawer_new( + const loshader_menu_background_program_t* prog, + const loshader_uniblock_t* uniblock +); + +void +loshader_menu_background_drawer_delete( + loshader_menu_background_drawer_t* drawer +); + +void +loshader_menu_background_drawer_set_alpha( + loshader_menu_background_drawer_t* drawer, + float alpha +); + +void +loshader_menu_background_drawer_draw( + const loshader_menu_background_drawer_t* drawer +); diff --git a/core/loshader/menu_background.vshader b/core/loshader/menu_background.vshader new file mode 100644 index 0000000..ed3aa73 --- /dev/null +++ b/core/loshader/menu_background.vshader @@ -0,0 +1,11 @@ +out vec2 v_uv; + +void main(void) { + const vec2[] verts = vec2[]( + vec2(-1., 1.), vec2(-1., -1.), vec2( 1., -1.), + vec2(-1., 1.), vec2( 1., -1.), vec2( 1., 1.) + ); + + v_uv = gl_VertexID < verts.length()? verts[gl_VertexID]: vec2(0.); + gl_Position = vec4(v_uv, 0., 1.); +} diff --git a/core/loshader/menu_stance.c b/core/loshader/menu_stance.c new file mode 100644 index 0000000..c31486f --- /dev/null +++ b/core/loshader/menu_stance.c @@ -0,0 +1,185 @@ +#include "./menu_stance.h" + +#include +#include +#include +#include + +#include + +#include "util/gleasy/buffer.h" +#include "util/gleasy/program.h" +#include "util/math/vector.h" +#include "util/memory/memory.h" + +#include "./uniblock.h" + +/* resources */ +#include "anysrc/header.shader.h" +#include "anysrc/menu_stance.vshader.h" +#include "anysrc/menu_stance.fshader.h" + +#define LOSHADER_MENU_STANCE_VSHADER_IN_ID 0 +#define LOSHADER_MENU_STANCE_VSHADER_IN_POS 1 +#define LOSHADER_MENU_STANCE_VSHADER_IN_SIZE 2 +#define LOSHADER_MENU_STANCE_VSHADER_IN_ALPHA 3 +#define LOSHADER_MENU_STANCE_VSHADER_IN_HIGHLIGHT 4 + +struct loshader_menu_stance_drawer_t { + const loshader_menu_stance_program_t* prog; + const loshader_uniblock_t* uniblock; + + GLuint vao; + + gleasy_buffer_array_t instances; + size_t instances_length; + size_t instances_reserved; +}; + +#pragma pack(push, 1) +typedef struct { + uint8_t id; + vec2_t pos; + vec2_t size; + float alpha; + float highlight; +} loshader_menu_stance_drawer_internal_instance_t; +#pragma pack(pop) + +#define LOSHADER_MENU_STANCE_UNIBLOCK_INDEX 0 +#define LOSHADER_MENU_STANCE_PRIMITIVE_COUNT 60 + +static void loshader_menu_stance_program_setup_vao_( + gleasy_buffer_array_t instances) { + assert(instances != 0); + + glBindBuffer(GL_ARRAY_BUFFER, instances); + +# define enable_attrib_(NAME, name, dim, type) do { \ + glEnableVertexAttribArray(LOSHADER_MENU_STANCE_VSHADER_IN_##NAME); \ + glVertexAttribPointer( \ + LOSHADER_MENU_STANCE_VSHADER_IN_##NAME, dim, type, GL_FALSE, \ + sizeof(loshader_menu_stance_drawer_internal_instance_t), \ + NULL + offsetof(loshader_menu_stance_drawer_internal_instance_t, name)); \ + glVertexAttribDivisor(LOSHADER_MENU_STANCE_VSHADER_IN_##NAME, 1); \ + } while (0) + + enable_attrib_(ID, id, 1, GL_UNSIGNED_BYTE); + enable_attrib_(POS, pos, 2, GL_FLOAT); + enable_attrib_(SIZE, size, 2, GL_FLOAT); + enable_attrib_(ALPHA, alpha, 1, GL_FLOAT); + enable_attrib_(HIGHLIGHT, highlight, 1, GL_FLOAT); + +# undef enable_attrib_ +} + +void loshader_menu_stance_program_initialize( + loshader_menu_stance_program_t* prog) { + assert(prog != NULL); + + *prog = gleasy_program_new( + loshader_header_shader_, sizeof(loshader_header_shader_), + loshader_menu_stance_vshader_, sizeof(loshader_menu_stance_vshader_), + loshader_menu_stance_fshader_, sizeof(loshader_menu_stance_fshader_)); + + const GLuint uniblock = glGetUniformBlockIndex(*prog, "uniblock"); + assert(glGetError() == GL_NO_ERROR); + glUniformBlockBinding(*prog, uniblock, LOSHADER_MENU_STANCE_UNIBLOCK_INDEX); +} + +void loshader_menu_stance_program_deinitialize( + loshader_menu_stance_program_t* prog) { + assert(prog != NULL); + + glDeleteProgram(*prog); +} + +loshader_menu_stance_drawer_t* loshader_menu_stance_drawer_new( + const loshader_menu_stance_program_t* prog, + const loshader_uniblock_t* uniblock) { + assert(prog != NULL); + assert(uniblock != NULL); + + loshader_menu_stance_drawer_t* drawer = memory_new(sizeof(*drawer)); + *drawer = (typeof(*drawer)) { + .prog = prog, + .uniblock = uniblock, + }; + + glCreateVertexArrays(1, &drawer->vao); + glBindVertexArray(drawer->vao); + + glGenBuffers(1, &drawer->instances); + loshader_menu_stance_program_setup_vao_(drawer->instances); + + return drawer; +} + +void loshader_menu_stance_drawer_delete( + loshader_menu_stance_drawer_t* drawer) { + assert(drawer != NULL); + + glDeleteBuffers(1, &drawer->instances); + + glDeleteVertexArrays(1, &drawer->vao); + + memory_delete(drawer); +} + +void loshader_menu_stance_drawer_clear( + loshader_menu_stance_drawer_t* drawer, size_t reserve) { + assert(drawer != NULL); + assert(reserve > 0); + + drawer->instances_length = 0; + + if (drawer->instances_reserved < reserve) { + glBindBuffer(GL_ARRAY_BUFFER, drawer->instances); + glBufferData(GL_ARRAY_BUFFER, + reserve * sizeof(loshader_menu_stance_drawer_internal_instance_t), + NULL, GL_DYNAMIC_DRAW); + drawer->instances_reserved = reserve; + } +} + +void loshader_menu_stance_drawer_add_instance( + loshader_menu_stance_drawer_t* drawer, + const loshader_menu_stance_drawer_instance_t* instance) { + assert(drawer != NULL); + assert(instance != NULL); + + if (drawer->instances_length >= drawer->instances_reserved) { + fprintf(stderr, "menu stance drawer instance overflow\n"); + abort(); + } + + const loshader_menu_stance_drawer_internal_instance_t insta = { + .id = instance->id, + .pos = instance->pos, + .size = instance->size, + .alpha = instance->alpha, + .highlight = instance->highlight, + }; + + const size_t offset = drawer->instances_length * sizeof(insta); + glBindBuffer(GL_ARRAY_BUFFER, drawer->instances); + glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(insta), &insta); + + ++drawer->instances_length; +} + +void loshader_menu_stance_drawer_draw( + const loshader_menu_stance_drawer_t* drawer) { + assert(drawer != NULL); + + if (drawer->instances_length == 0) return; + + glUseProgram(*drawer->prog); + glBindVertexArray(drawer->vao); + + loshader_uniblock_bind( + drawer->uniblock, LOSHADER_MENU_STANCE_UNIBLOCK_INDEX); + + glDrawArraysInstanced(GL_TRIANGLES, + 0, LOSHADER_MENU_STANCE_PRIMITIVE_COUNT, drawer->instances_length); +} diff --git a/core/loshader/menu_stance.fshader b/core/loshader/menu_stance.fshader new file mode 100644 index 0000000..43b553f --- /dev/null +++ b/core/loshader/menu_stance.fshader @@ -0,0 +1,9 @@ +in float v_id; +in vec2 v_uv; +in float v_alpha; + +out vec4 o_color; + +void main(void) { + o_color = vec4(1., 1., 1., v_alpha*.8); +} diff --git a/core/loshader/menu_stance.h b/core/loshader/menu_stance.h new file mode 100644 index 0000000..93d41f2 --- /dev/null +++ b/core/loshader/menu_stance.h @@ -0,0 +1,68 @@ +#pragma once + +#include + +#include "util/gleasy/program.h" +#include "util/math/vector.h" + +#include "./uniblock.h" + +typedef gleasy_program_t loshader_menu_stance_program_t; + +struct loshader_menu_stance_drawer_t; +typedef struct loshader_menu_stance_drawer_t loshader_menu_stance_drawer_t; + +typedef enum { + LOSHADER_MENU_STANCE_ID_EMPTY = 0, + LOSHADER_MENU_STANCE_ID_MISSIONARY = 1, + LOSHADER_MENU_STANCE_ID_REVOLUTIONER = 2, + LOSHADER_MENU_STANCE_ID_UNFINISHER = 3, + LOSHADER_MENU_STANCE_ID_PHILOSOPHER = 4, +} loshader_menu_stance_id_t; + +typedef struct { + loshader_menu_stance_id_t id; + + vec2_t pos; + vec2_t size; + float alpha; + float highlight; +} loshader_menu_stance_drawer_instance_t; + +void +loshader_menu_stance_program_initialize( + loshader_menu_stance_program_t* prog +); + +void +loshader_menu_stance_program_deinitialize( + loshader_menu_stance_program_t* prog +); + +loshader_menu_stance_drawer_t* +loshader_menu_stance_drawer_new( + const loshader_menu_stance_program_t* prog, + const loshader_uniblock_t* uniblock +); + +void +loshader_menu_stance_drawer_delete( + loshader_menu_stance_drawer_t* drawer +); + +void +loshader_menu_stance_drawer_clear( + loshader_menu_stance_drawer_t* drawer, + size_t reserve +); + +void +loshader_menu_stance_drawer_add_instance( + loshader_menu_stance_drawer_t* drawer, + const loshader_menu_stance_drawer_instance_t* instance +); + +void +loshader_menu_stance_drawer_draw( + const loshader_menu_stance_drawer_t* drawer +); diff --git a/core/loshader/menu_stance.vshader b/core/loshader/menu_stance.vshader new file mode 100644 index 0000000..10bc297 --- /dev/null +++ b/core/loshader/menu_stance.vshader @@ -0,0 +1,93 @@ +layout (location = 0) in float i_id; +layout (location = 1) in vec2 i_pos; +layout (location = 2) in vec2 i_size; +layout (location = 3) in float i_alpha; +layout (location = 4) in float i_highlight; + +out float v_id; +out vec2 v_uv; +out float v_alpha; + +const vec2[] frame_ = vec2[]( + vec2( 0.0, 1.0), vec2(-1.0, 0.0), vec2( 0.0, 0.9), + vec2( 0.0, 0.9), vec2(-1.0, 0.0), vec2(-0.9, 0.0), + vec2(-1.0, 0.0), vec2( 0.0, -1.0), vec2(-0.9, 0.0), + vec2(-0.9, 0.0), vec2( 0.0, -1.0), vec2( 0.0, -0.9), + vec2( 0.9, 0.0), vec2( 0.0, -0.9), vec2( 0.0, -1.0), + vec2( 1.0, 0.0), vec2( 0.9, 0.0), vec2( 0.0, -1.0), + vec2( 0.0, 1.0), vec2( 0.9, 0.0), vec2( 1.0, 0.0), + vec2( 0.0, 1.0), vec2( 0.0, 0.9), vec2( 0.9, 0.0) +); + +vec2 empty(in int id) { + const vec2[] verts = vec2[]( + vec2( 0., 1.), vec2(-1., 0.), vec2( 0., -1.), + vec2( 0., 1.), vec2( 1., 0.), vec2( 0., -1.) + ); + return id < verts.length()? verts[id]: vec2(0.); +} + +vec2 missionary(in int id) { + const vec2[] verts = vec2[]( + vec2(-0.12, 0.52), vec2(-0.52, 0.24), vec2( 0.04, 0.40), + vec2(-0.52, 0.24), vec2( 0.00, 0.00), vec2( 0.00, 0.12), + vec2(-0.60, 0.08), vec2(-0.68, 0.00), vec2(-0.04, -0.36), + vec2(-0.72, 0.00), vec2(-0.08, -0.52), vec2( 0.00, -0.40), + vec2(-0.76, -0.04), vec2(-0.60, -0.36), vec2(-0.12, -0.56), + vec2(-0.60, -0.40), vec2(-0.04, -0.96), vec2(-0.12, -0.60) + ); + return + id < verts.length() ? verts[id]: + id < verts.length()*2? verts[id-verts.length()]*vec2(-1., 1.): + vec2(0.); +} +vec2 revolutioner(in int id) { + const vec2[] verts = vec2[]( + vec2(-0.52, 0.24), vec2(-0.60, 0.20), vec2( 0.00, -0.80), + vec2( 0.00, 0.36), vec2(-0.48, 0.08), vec2(-0.24, -0.16), + vec2( 0.08, 0.64), vec2(-0.16, 0.40), vec2( 0.44, 0.36), + vec2( 0.04, 0.24), vec2(-0.16, -0.12), vec2( 0.56, 0.28) + ); + return id < verts.length()? verts[id]: vec2(0.); +} +vec2 unfinisher(in int id) { + const vec2[] verts = vec2[]( + vec2( 0.00, 1.00), vec2(-0.08, 0.20), vec2( 0.08, 0.20), + vec2(-0.08, 0.20), vec2(-0.40, 0.00), vec2(-0.40, -0.40), + vec2(-0.40, -0.40), vec2( 0.00, -0.60), vec2( 0.40, -0.40), + vec2( 0.08, 0.20), vec2( 0.40, 0.00), vec2( 0.40, -0.40) + ); + return id < verts.length()? verts[id]: vec2(0.); +} +vec2 philosopher(in int id) { + const vec2[] verts = vec2[]( + vec2( 0.117, -0.367), vec2( 0.000, -0.750), vec2( 0.300, -0.533), + vec2( 0.117, 0.050), vec2(-0.083, -0.117), vec2( 0.367, -0.517), + vec2(-0.050, 0.283), vec2(-0.133, -0.117), vec2( 0.117, 0.083), + vec2( 0.500, 0.500), vec2(-0.050, 0.333), vec2( 0.150, 0.133), + vec2( 0.250, 0.917), vec2(-0.233, 0.317), vec2( 0.500, 0.533), + vec2( 0.217, 0.917), vec2(-0.300, 0.817), vec2(-0.283, 0.333), + vec2(-0.333, 0.783), vec2(-0.500, 0.567), vec2(-0.333, 0.333) + ); + return id < verts.length()? verts[id]*vec2(1.1, .9)+vec2(0, -.1): vec2(0.); +} + +vec2 get_vertex(in int id) { + return + i_id == 1.? missionary(id): + i_id == 2.? revolutioner(id): + i_id == 3.? unfinisher(id): + i_id == 4.? philosopher(id): + empty(id); +} + +void main(void) { + v_uv = gl_VertexID < frame_.length()? + frame_[gl_VertexID]: get_vertex(gl_VertexID - frame_.length())*.8; + + v_id = i_id; + v_alpha = i_alpha; + + float s = i_highlight*.2 + 1.; + gl_Position = vec4(v_uv*i_size*s + i_pos, 0., 1.); +} diff --git a/core/loshader/menu_text.c b/core/loshader/menu_text.c new file mode 100644 index 0000000..ee6e339 --- /dev/null +++ b/core/loshader/menu_text.c @@ -0,0 +1,97 @@ +#include "./menu_text.h" + +#include +#include + +#include "util/gleasy/program.h" +#include "util/gleasy/texture.h" +#include "util/glyphas/block.h" +#include "util/glyphas/drawer.h" + +#include "./uniblock.h" + +/* resources */ +#include "anysrc/header.shader.h" +#include "anysrc/menu_text.vshader.h" +#include "anysrc/menu_text.fshader.h" + +#define LOSHADER_MENU_TEXT_UNIFORM_ALPHA 1 + +#define LOSHADER_MENU_TEXT_UNIBLOCK_INDEX 0 + +void loshader_menu_text_program_initialize( + loshader_menu_text_program_t* prog) { + assert(prog != NULL); + + *prog = gleasy_program_new( + loshader_header_shader_, sizeof(loshader_header_shader_), + loshader_menu_text_vshader_, sizeof(loshader_menu_text_vshader_), + loshader_menu_text_fshader_, sizeof(loshader_menu_text_fshader_)); + + const GLuint uniblock = glGetUniformBlockIndex(*prog, "uniblock"); + assert(glGetError() == GL_NO_ERROR); + glUniformBlockBinding(*prog, uniblock, LOSHADER_MENU_TEXT_UNIBLOCK_INDEX); +} + +void loshader_menu_text_program_deinitialize( + loshader_menu_text_program_t* prog) { + assert(prog != NULL); + + glDeleteProgram(*prog); +} + +void loshader_menu_text_drawer_initialize( + loshader_menu_text_drawer_t* drawer, + const loshader_menu_text_program_t* prog, + const loshader_uniblock_t* uniblock, + gleasy_texture_2d_t tex) { + assert(drawer != NULL); + assert(prog != NULL); + assert(uniblock != NULL); + assert(tex != 0); + + *drawer = (typeof(*drawer)) { + .prog = prog, + .uniblock = uniblock, + .tex = tex, + + .glyphas = glyphas_drawer_new(), + + .alpha = 1, + }; +} + +void loshader_menu_text_drawer_deinitialize( + loshader_menu_text_drawer_t* drawer) { + assert(drawer != NULL); + + glyphas_drawer_delete(drawer->glyphas); +} + +void loshader_menu_text_drawer_clear( + loshader_menu_text_drawer_t* drawer, size_t reserve) { + assert(drawer != NULL); + assert(reserve > 0); + + glyphas_drawer_clear(drawer->glyphas, drawer->tex, reserve); +} + +void loshader_menu_text_drawer_add_block( + loshader_menu_text_drawer_t* drawer, const glyphas_block_t* block) { + assert(drawer != NULL); + assert(block != NULL); + + glyphas_drawer_add_block(drawer->glyphas, block); +} + +void loshader_menu_text_drawer_draw(const loshader_menu_text_drawer_t* drawer) { + assert(drawer != NULL); + + glUseProgram(*drawer->prog); + + loshader_uniblock_bind(drawer->uniblock, LOSHADER_MENU_TEXT_UNIBLOCK_INDEX); + + glUniform1f(LOSHADER_MENU_TEXT_UNIFORM_ALPHA, drawer->alpha); + + glyphas_drawer_draw(drawer->glyphas); +} diff --git a/core/loshader/menu_text.fshader b/core/loshader/menu_text.fshader new file mode 100644 index 0000000..8106b30 --- /dev/null +++ b/core/loshader/menu_text.fshader @@ -0,0 +1,12 @@ +layout (location = 0) uniform sampler2D u_tex; +layout (location = 1) uniform float u_alpha; + +in vec2 v_uv; +in vec4 v_color; + +out vec4 o_color; + +void main(void) { + float a = texture(u_tex, v_uv).r; + o_color = vec4(v_color.rgb, v_color.a*a*u_alpha); +} diff --git a/core/loshader/menu_text.h b/core/loshader/menu_text.h new file mode 100644 index 0000000..64f5261 --- /dev/null +++ b/core/loshader/menu_text.h @@ -0,0 +1,66 @@ +#pragma once + +#include + +#include "util/gleasy/program.h" +#include "util/gleasy/texture.h" +#include "util/glyphas/block.h" +#include "util/glyphas/drawer.h" + +#include "./uniblock.h" + +typedef gleasy_program_t loshader_menu_text_program_t; + +typedef struct { + /* injected deps */ + const loshader_menu_text_program_t* prog; + const loshader_uniblock_t* uniblock; + + gleasy_texture_2d_t tex; + + /* owned objects */ + glyphas_drawer_t* glyphas; + + /* public params */ + float alpha; +} loshader_menu_text_drawer_t; + +void +loshader_menu_text_program_initialize( + loshader_menu_text_program_t* prog +); + +void +loshader_menu_text_program_deinitialize( + loshader_menu_text_program_t* prog +); + +void +loshader_menu_text_drawer_initialize( + loshader_menu_text_drawer_t* drawer, + const loshader_menu_text_program_t* prog, + const loshader_uniblock_t* uniblock, + gleasy_texture_2d_t tex +); + +void +loshader_menu_text_drawer_deinitialize( + loshader_menu_text_drawer_t* drawer +); + +void +loshader_menu_text_drawer_clear( + loshader_menu_text_drawer_t* drawer, + size_t reserve +); + +void +loshader_menu_text_drawer_add_block( + loshader_menu_text_drawer_t* drawer, + const glyphas_block_t* block +); + +void +loshader_menu_text_drawer_draw( + const loshader_menu_text_drawer_t* drawer +); diff --git a/core/loshader/menu_text.vshader b/core/loshader/menu_text.vshader new file mode 100644 index 0000000..ad100cb --- /dev/null +++ b/core/loshader/menu_text.vshader @@ -0,0 +1,24 @@ +layout (location = 0) in vec2 i_pos; +layout (location = 1) in vec2 i_size; +layout (location = 2) in vec2 i_uv_pos; +layout (location = 3) in vec2 i_uv_size; +layout (location = 4) in vec4 i_color; + +out vec2 v_uv; +out vec4 v_color; + +void main(void) { + const vec2[] verts = vec2[]( + vec2( 0., 0.), vec2( 0., -1.), vec2( 1., -1.), + vec2( 0., 0.), vec2( 1., -1.), vec2( 1., 0.) + + ); + + vec2 p = gl_VertexID < verts.length()? verts[gl_VertexID]: vec2(0.); + vec2 dp = p*i_size + i_pos; + + gl_Position = vec4(dp, 0, 1); + v_uv = p*i_uv_size + i_uv_pos; + v_color = i_color; +} + diff --git a/core/loshader/pixsort.c b/core/loshader/pixsort.c new file mode 100644 index 0000000..c4d9426 --- /dev/null +++ b/core/loshader/pixsort.c @@ -0,0 +1,109 @@ +#include "./pixsort.h" + +#include +#include +#include +#include + +#include + +#include "util/gleasy/framebuffer.h" +#include "util/gleasy/program.h" +#include "util/math/algorithm.h" +#include "util/memory/memory.h" + +#include "./uniblock.h" + +/* resources */ +#include "anysrc/header.shader.h" +#include "anysrc/pixsort.vshader.h" +#include "anysrc/pixsort.fshader.h" + +#define LOSHADER_PIXSORT_UNIFORM_SRC 0 +#define LOSHADER_PIXSORT_UNIFORM_INTENSITY 1 + +#define LOSHADER_PIXSORT_UNIBLOCK_INDEX 0 + +struct loshader_pixsort_drawer_t { + const loshader_pixsort_program_t* prog; + const loshader_uniblock_t* uniblock; + + const gleasy_framebuffer_t* fb; + + float intensity; +}; + +void loshader_pixsort_program_initialize( + loshader_pixsort_program_t* prog) { + assert(prog != NULL); + + *prog = gleasy_program_new( + loshader_header_shader_, sizeof(loshader_header_shader_), + loshader_pixsort_vshader_, sizeof(loshader_pixsort_vshader_), + loshader_pixsort_fshader_, sizeof(loshader_pixsort_fshader_)); + + const GLuint uniblock = glGetUniformBlockIndex(*prog, "uniblock"); + assert(glGetError() == GL_NO_ERROR); + glUniformBlockBinding(*prog, uniblock, LOSHADER_PIXSORT_UNIBLOCK_INDEX); +} + +void loshader_pixsort_program_deinitialize( + loshader_pixsort_program_t* prog) { + assert(prog != NULL); + + glDeleteProgram(*prog); +} + +loshader_pixsort_drawer_t* loshader_pixsort_drawer_new( + const loshader_pixsort_program_t* prog, + const loshader_uniblock_t* uniblock, + const gleasy_framebuffer_t* fb) { + assert(prog != NULL); + assert(uniblock != NULL); + assert(fb != NULL); + + loshader_pixsort_drawer_t* drawer = memory_new(sizeof(*drawer)); + *drawer = (typeof(*drawer)) { + .prog = prog, + .uniblock = uniblock, + .fb = fb, + }; + return drawer; +} + +void loshader_pixsort_drawer_delete(loshader_pixsort_drawer_t* drawer) { + if (drawer == NULL) return; + + memory_delete(drawer); +} + +void loshader_pixsort_drawer_set_intensity( + loshader_pixsort_drawer_t* drawer, + float intensity) { + assert(drawer != NULL); + assert(MATH_FLOAT_VALID(intensity)); + + drawer->intensity = intensity; +} + +void loshader_pixsort_drawer_draw( + const loshader_pixsort_drawer_t* drawer) { + assert(drawer != NULL); + + glUseProgram(*drawer->prog); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, drawer->fb->colorbuf); + glUniform1i(LOSHADER_PIXSORT_UNIFORM_SRC, 0); + + glUniform1f(LOSHADER_PIXSORT_UNIFORM_INTENSITY, drawer->intensity); + + glDrawArrays(GL_TRIANGLES, 0, 6); +} + +bool loshader_pixsort_drawer_is_skippable( + const loshader_pixsort_drawer_t* drawer) { + assert(drawer != NULL); + + return drawer->intensity == 0; +} diff --git a/core/loshader/pixsort.fshader b/core/loshader/pixsort.fshader new file mode 100644 index 0000000..5585826 --- /dev/null +++ b/core/loshader/pixsort.fshader @@ -0,0 +1,71 @@ +layout (location = 0) uniform sampler2D u_src; +layout (location = 1) uniform float u_intensity; + +in vec2 v_uv; + +out vec4 o_color; + +const int N = 2; +const int S = 20; + +float luminance(in vec4 col) { + return col.r*.3+col.g*.6+col.b*.1; +} + +void main(void) { + vec2 uv = (v_uv + 1.)/2.; + vec2 uvi = uv * uni.resolution.y; + float unit = 1.0/uni.resolution.y; + + o_color = texture(u_src, uv); + + int darkest_y = 0; + float darkest_lum = 1.; + int brightest_y = 0; + float brightest_lum = 0.; + + int stride = int(uni.resolution.y/float(S)); + for (int i = 0; i < int(uni.resolution.y); i+=stride) { + vec4 col = texture(u_src, vec2(uv.x, unit*float(i))); + + float lum = luminance(col); + if (brightest_lum < lum) { + brightest_y = i; + brightest_lum = lum; + } + if (darkest_lum > lum) { + darkest_y = i; + darkest_lum = lum; + } + } + int st = min(darkest_y, brightest_y); + int ed = max(darkest_y, brightest_y); + st = ed-int(float(ed-st)*u_intensity); + + if (int(uvi.y) <= st || int(uvi.y) >= ed) { + return; + } + + float lum_unit = (brightest_lum-darkest_lum) / float(N); + stride /= 5; + + int count = st; + for (int n = 0; n < N; ++n) { + float n2 = float(darkest_y < brightest_y? n: N-n-1); + float low = lum_unit * n2 + darkest_lum; + float high = low + lum_unit; + + for (int i = st; i < ed; i+=stride) { + vec4 col = texture(u_src, vec2(uv.x, unit*float(i))); + + float lum = luminance(col); + if (low <= lum && lum <= high) { + count += stride; + if (count >= int(uvi.y)) { + o_color = col; + return; + } + } + } + } +} diff --git a/core/loshader/pixsort.h b/core/loshader/pixsort.h new file mode 100644 index 0000000..6fa8739 --- /dev/null +++ b/core/loshader/pixsort.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +#include "util/gleasy/framebuffer.h" +#include "util/gleasy/program.h" + +#include "./uniblock.h" + +typedef gleasy_program_t loshader_pixsort_program_t; + +struct loshader_pixsort_drawer_t; +typedef struct loshader_pixsort_drawer_t loshader_pixsort_drawer_t; + +void +loshader_pixsort_program_initialize( + loshader_pixsort_program_t* prog +); + +void +loshader_pixsort_program_deinitialize( + loshader_pixsort_program_t* prog +); + +loshader_pixsort_drawer_t* /* OWNERSHIP */ +loshader_pixsort_drawer_new( + const loshader_pixsort_program_t* prog, + const loshader_uniblock_t* uniblock, + const gleasy_framebuffer_t* fb +); + +void +loshader_pixsort_drawer_delete( + loshader_pixsort_drawer_t* drawer /* OWNERSHIP */ +); + +void +loshader_pixsort_drawer_set_intensity( + loshader_pixsort_drawer_t* drawer, + float intensity +); + +void +loshader_pixsort_drawer_draw( + const loshader_pixsort_drawer_t* drawer +); + +bool +loshader_pixsort_drawer_is_skippable( + const loshader_pixsort_drawer_t* drawer +); diff --git a/core/loshader/pixsort.vshader b/core/loshader/pixsort.vshader new file mode 100644 index 0000000..1d16d25 --- /dev/null +++ b/core/loshader/pixsort.vshader @@ -0,0 +1,12 @@ +out vec2 v_uv; + +void main(void) { + const vec2[] verts = vec2[]( + vec2(-1., 1.), vec2(-1., -1.), vec2( 1., -1.), + vec2(-1., 1.), vec2( 1., -1.), vec2( 1., 1.) + ); + + v_uv = gl_VertexID < verts.length()? verts[gl_VertexID]: vec2(0.); + + gl_Position = vec4(v_uv, 0., 1.); +} diff --git a/core/loshader/posteffect.c b/core/loshader/posteffect.c new file mode 100644 index 0000000..fbff4cd --- /dev/null +++ b/core/loshader/posteffect.c @@ -0,0 +1,144 @@ +#include "./posteffect.h" + +#include +#include +#include +#include + +#include + +#include "util/gleasy/buffer.h" +#include "util/gleasy/framebuffer.h" +#include "util/gleasy/program.h" +#include "util/math/vector.h" +#include "util/memory/memory.h" + +#include "./uniblock.h" + +/* resources */ +#include "anysrc/header.shader.h" +#include "anysrc/posteffect.vshader.h" +#include "anysrc/posteffect.fshader.h" + +#define LOSHADER_POSTEFFECT_UNIFORM_SRC 0 + +struct loshader_posteffect_drawer_t { + const loshader_posteffect_program_t* prog; + const loshader_uniblock_t* uniblock; + + const gleasy_framebuffer_t* fb; + + gleasy_buffer_uniform_t param; +}; + +#pragma pack(push, 1) +typedef struct { + float whole_blur; + float raster; + + float radial_displacement; + float amnesia_displacement; + float radial_fade; + + float brightness; +} loshader_posteffect_drawer_param_internal_t; +_Static_assert( + sizeof(float)*6 == + sizeof(loshader_posteffect_drawer_param_internal_t)); +#pragma pack(pop) + +#define LOSHADER_POSTEFFECT_UNIBLOCK_INDEX 0 +#define LOSHADER_POSTEFFECT_PARAM_INDEX 1 + +void loshader_posteffect_program_initialize( + loshader_posteffect_program_t* prog) { + assert(prog != NULL); + + *prog = gleasy_program_new( + loshader_header_shader_, sizeof(loshader_header_shader_), + loshader_posteffect_vshader_, sizeof(loshader_posteffect_vshader_), + loshader_posteffect_fshader_, sizeof(loshader_posteffect_fshader_)); + + const GLuint uniblock = glGetUniformBlockIndex(*prog, "uniblock"); + assert(glGetError() == GL_NO_ERROR); + glUniformBlockBinding(*prog, uniblock, LOSHADER_POSTEFFECT_UNIBLOCK_INDEX); + + const GLuint param = glGetUniformBlockIndex(*prog, "param"); + assert(glGetError() == GL_NO_ERROR); + glUniformBlockBinding(*prog, param, LOSHADER_POSTEFFECT_PARAM_INDEX); +} + +void loshader_posteffect_program_deinitialize( + loshader_posteffect_program_t* prog) { + assert(prog != NULL); + + glDeleteProgram(*prog); +} + +loshader_posteffect_drawer_t* loshader_posteffect_drawer_new( + const loshader_posteffect_program_t* prog, + const loshader_uniblock_t* uniblock, + const gleasy_framebuffer_t* fb) { + assert(prog != NULL); + assert(uniblock != NULL); + assert(fb != NULL); + + loshader_posteffect_drawer_t* drawer = memory_new(sizeof(*drawer)); + *drawer = (typeof(*drawer)) { + .prog = prog, + .uniblock = uniblock, + .fb = fb, + }; + + glGenBuffers(1, &drawer->param); + glBindBuffer(GL_UNIFORM_BUFFER, drawer->param); + glBufferData(GL_UNIFORM_BUFFER, + sizeof(loshader_posteffect_drawer_param_internal_t), + NULL, + GL_DYNAMIC_DRAW); + + return drawer; +} + +void loshader_posteffect_drawer_delete(loshader_posteffect_drawer_t* drawer) { + if (drawer == NULL) return; + + glDeleteBuffers(1, &drawer->param); + + memory_delete(drawer); +} + +void loshader_posteffect_drawer_set_param( + loshader_posteffect_drawer_t* drawer, + const loshader_posteffect_drawer_param_t* param) { + assert(drawer != NULL); + assert(param != NULL); + + const loshader_posteffect_drawer_param_internal_t p = { + .whole_blur = param->whole_blur, + .raster = param->raster, + .radial_displacement = param->radial_displacement, + .amnesia_displacement = param->amnesia_displacement, + .radial_fade = param->radial_fade, + .brightness = param->brightness, + }; + glBindBuffer(GL_UNIFORM_BUFFER, drawer->param); + glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(p), &p); +} + +void loshader_posteffect_drawer_draw( + const loshader_posteffect_drawer_t* drawer) { + assert(drawer != NULL); + + glUseProgram(*drawer->prog); + + loshader_uniblock_bind(drawer->uniblock, LOSHADER_POSTEFFECT_UNIBLOCK_INDEX); + glBindBufferBase(GL_UNIFORM_BUFFER, + LOSHADER_POSTEFFECT_PARAM_INDEX, drawer->param); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, drawer->fb->colorbuf); + glUniform1i(LOSHADER_POSTEFFECT_UNIFORM_SRC, 0); + + glDrawArrays(GL_TRIANGLES, 0, 6); +} diff --git a/core/loshader/posteffect.fshader b/core/loshader/posteffect.fshader new file mode 100644 index 0000000..d5f7d64 --- /dev/null +++ b/core/loshader/posteffect.fshader @@ -0,0 +1,139 @@ +layout (location = 0) uniform sampler2D u_src; + +layout(std140) uniform param { + float whole_blur; + float raster; + + float radial_displacement; + float amnesia_displacement; + + float radial_fade; + + float brightness; +} p; + +in vec2 v_uv; + +out vec4 o_color; + +float atan2(in vec2 p){ + return p.x == 0. ? sign(p.y)*PI/2. : atan(p.y, p.x); +} +vec2 to_polar(in vec2 p) { + return vec2(length(p), atan2(p)); +} +vec2 to_rectangular(in vec2 p) { + return vec2(cos(p.y)*p.x, sin(p.y)*p.x); +} +float rand(in vec2 p) { + /* https://thebookofshaders.com/13/?lan=jp */ + return fract(sin(dot(p.xy, vec2(12.9898, 78.233)))*43758.5453123); +} +float noise (in vec2 _st) { + /* https://thebookofshaders.com/13/?lan=jp*/ + vec2 i = floor(_st); + vec2 f = fract(_st); + + float a = rand(i); + float b = rand(i + vec2(1.0, 0.0)); + float c = rand(i + vec2(0.0, 1.0)); + float d = rand(i + vec2(1.0, 1.0)); + + vec2 u = f * f * (3.0 - 2.0 * f); + + return + mix(a, b, u.x) + + (c - a)* u.y * (1.0 - u.x) + + (d - b) * u.x * u.y; +} +float fbm (in vec2 _st) { + /* https://thebookofshaders.com/13/?lan=jp*/ + const float octaves = 5; + + float v = 0.0; + float a = 0.5; + vec2 shift = vec2(100.0); + mat2 rot = mat2(cos(0.5), sin(0.5), -sin(0.5), cos(0.50)); + + for (int i = 0; i < octaves; ++i) { + v += a * noise(_st); + _st = rot * _st * 2.0 + shift; + a *= 0.5; + } + return v; +} + +vec2 radial_displacement(in vec2 polar) { + float intensity = p.radial_displacement + 1.; + float r = polar.x; + + const float sqrt2 = sqrt(2.); + r = (1. - pow(abs(1. - r/sqrt2), intensity))*sqrt2 / intensity; + + return vec2(r, polar.y); +} +vec2 amnesia_displacement(in vec2 uv) { + vec2 auv = abs(uv); + float intensity = p.amnesia_displacement*(1.-pow(max(auv.x, auv.y), 2.)); + return uv + fbm(uv)*intensity - intensity/2.; +} + +float radial_fade(in float len) { + float intensity = p.radial_fade; + return clamp(1. - intensity * max(len - (1.-intensity), 0.), 0., 1.); +} + +vec4 chromatic_aberration(in vec2 uv) { + float a = length(v_uv)/sqrt(2.); + vec2 e = 1./uni.resolution*a; + + return vec4( + texture(u_src, uv+e).r, + texture(u_src, uv).g, + texture(u_src, uv-e).b, + 1.); +} + +vec4 blur(in vec2 uv) { + vec2 e1 = vec2(1./uni.resolution.x, 0.); + vec2 e2 = vec2(0., 1./uni.resolution.y); + + vec4 color = + texture(u_src, uv+e1+.0) + + texture(u_src, uv+e1+e2) + + texture(u_src, uv+.0+e2) + + texture(u_src, uv-e1+e2) + + texture(u_src, uv-e1+.0) + + texture(u_src, uv-e1-e2) + + texture(u_src, uv+.0-e2) + + texture(u_src, uv+e1-e2); + return color/8.; +} + +void main(void) { + vec2 uv = v_uv; + + /* transformation */ + vec2 polar = to_polar(uv); + polar = radial_displacement(polar); + + uv = to_rectangular(polar); + uv = amnesia_displacement(uv); + + uv.x += sin(v_uv.y*PI*2./uni.aa*5.)*.05*p.raster; + + /* pixel manipulation */ + uv = (uv+1.)/2.; + vec4 color = chromatic_aberration(uv); + + if (p.whole_blur > 0.) color = mix(color, blur(uv), p.whole_blur); + + /* blending */ + float a = radial_fade(polar.x); + o_color = mix(vec4(0., 0., 0., 1), color, a); + + /* color manip */ + o_color = pow(o_color, vec4(1.4)); + o_color = min(o_color, vec4(max(o_color.r, max(o_color.g, o_color.b)))*.95); + o_color *= p.brightness; +} diff --git a/core/loshader/posteffect.h b/core/loshader/posteffect.h new file mode 100644 index 0000000..91bf564 --- /dev/null +++ b/core/loshader/posteffect.h @@ -0,0 +1,57 @@ +#pragma once + +#include + +#include "util/gleasy/framebuffer.h" +#include "util/gleasy/program.h" + +#include "./uniblock.h" + +typedef gleasy_program_t loshader_posteffect_program_t; + +struct loshader_posteffect_drawer_t; +typedef struct loshader_posteffect_drawer_t loshader_posteffect_drawer_t; + +typedef struct { + float whole_blur; + float raster; + + float radial_displacement; + float amnesia_displacement; + float radial_fade; + + float brightness; +} loshader_posteffect_drawer_param_t; + +void +loshader_posteffect_program_initialize( + loshader_posteffect_program_t* prog +); + +void +loshader_posteffect_program_deinitialize( + loshader_posteffect_program_t* prog +); + +loshader_posteffect_drawer_t* /* OWNERSHIP */ +loshader_posteffect_drawer_new( + const loshader_posteffect_program_t* prog, + const loshader_uniblock_t* uniblock, + const gleasy_framebuffer_t* fb +); + +void +loshader_posteffect_drawer_delete( + loshader_posteffect_drawer_t* drawer /* OWNERSHIP */ +); + +void +loshader_posteffect_drawer_set_param( + loshader_posteffect_drawer_t* drawer, + const loshader_posteffect_drawer_param_t* param +); + +void +loshader_posteffect_drawer_draw( + const loshader_posteffect_drawer_t* drawer +); diff --git a/core/loshader/posteffect.vshader b/core/loshader/posteffect.vshader new file mode 100644 index 0000000..92a44f8 --- /dev/null +++ b/core/loshader/posteffect.vshader @@ -0,0 +1,11 @@ +out vec2 v_uv; + +void main(void) { + const vec2[] verts = vec2[]( + vec2(-1., 1.), vec2(-1., -1.), vec2( 1., -1.), + vec2(-1., 1.), vec2( 1., -1.), vec2( 1., 1.) + ); + v_uv = gl_VertexID < verts.length()? verts[gl_VertexID]: vec2(0.); + + gl_Position = vec4(v_uv, 0., 1.); +} diff --git a/core/loshader/set.c b/core/loshader/set.c new file mode 100644 index 0000000..505a180 --- /dev/null +++ b/core/loshader/set.c @@ -0,0 +1,250 @@ +#include "./set.h" + +#include +#include +#include +#include +#include + +#include + +#include "util/gleasy/atlas.h" +#include "util/gleasy/framebuffer.h" +#include "util/math/algorithm.h" +#include "util/math/vector.h" + +#include "./backwall.h" +#include "./bullet.h" +#include "./character.h" +#include "./cinescope.h" +#include "./combat_ring.h" +#include "./event_line.h" +#include "./fog.h" +#include "./ground.h" +#include "./hud_bar.h" +#include "./hud_text.h" +#include "./menu_background.h" +#include "./menu_stance.h" +#include "./menu_text.h" +#include "./posteffect.h" +#include "./uniblock.h" + +void loshader_set_initialize( + loshader_set_t* set, + int32_t width, + int32_t height, + const vec2_t* dpi, + int32_t max_msaa) { + assert(set != NULL); + assert(width > 0); + assert(height > 0); + assert(vec2_valid(dpi)); + assert(max_msaa > 0); + + *set = (typeof(*set)) { + .resolution = vec2(width, height), + .dpi = *dpi, + }; + + set->uniblock = loshader_uniblock_new(); + loshader_uniblock_update_display_param(set->uniblock, &set->resolution, dpi); + + int max_samples; + glGetIntegerv(GL_MAX_COLOR_TEXTURE_SAMPLES, &max_samples); + gleasy_framebuffer_initialize( + &set->framebuffer, width, height, MATH_MIN(max_samples, max_msaa)); + + /* TODO(catfoot): atlas size should depend on DPI. */ + set->tex = (typeof(set->tex)) { + .hud_text = gleasy_atlas_new(GL_RED, 1024, 1024, true), + .menu_text = gleasy_atlas_new(GL_RED, 1024, 1024, true), + .event_line = gleasy_atlas_new(GL_RED, 1024, 1024, true), + }; + + loshader_backwall_program_initialize(&set->program.backwall); + loshader_ground_program_initialize(&set->program.ground); + loshader_character_program_initialize(&set->program.character); + loshader_bullet_program_initialize(&set->program.bullet); + loshader_fog_program_initialize(&set->program.fog); + loshader_pixsort_program_initialize(&set->program.pixsort); + loshader_posteffect_program_initialize(&set->program.posteffect); + loshader_hud_bar_program_initialize(&set->program.hud_bar); + loshader_hud_text_program_initialize(&set->program.hud_text); + loshader_menu_background_program_initialize(&set->program.menu_background); + loshader_menu_text_program_initialize(&set->program.menu_text); + loshader_menu_stance_program_initialize(&set->program.menu_stance); + loshader_combat_ring_program_initialize(&set->program.combat_ring); + loshader_cinescope_program_initialize(&set->program.cinescope); + loshader_event_line_program_initialize(&set->program.event_line); + + set->drawer = (typeof(set->drawer)) { + .backwall = loshader_backwall_drawer_new( + &set->program.backwall, set->uniblock), + + .ground = loshader_ground_drawer_new( + &set->program.ground, set->uniblock), + + .character = loshader_character_drawer_new( + &set->program.character, set->uniblock), + + .bullet = loshader_bullet_drawer_new( + &set->program.bullet, set->uniblock), + + .fog = loshader_fog_drawer_new( + &set->program.fog, set->uniblock), + + .pixsort = loshader_pixsort_drawer_new( + &set->program.pixsort, set->uniblock, &set->framebuffer), + + .posteffect = loshader_posteffect_drawer_new( + &set->program.posteffect, set->uniblock, &set->framebuffer), + + .hud_bar = loshader_hud_bar_drawer_new( + &set->program.hud_bar, set->uniblock), + + .menu_background = loshader_menu_background_drawer_new( + &set->program.menu_background, set->uniblock), + + .menu_stance = loshader_menu_stance_drawer_new( + &set->program.menu_stance, set->uniblock), + + .combat_ring = loshader_combat_ring_drawer_new( + &set->program.combat_ring, set->uniblock), + + .cinescope = loshader_cinescope_drawer_new( + &set->program.cinescope, set->uniblock), + }; + + loshader_hud_text_drawer_initialize( + &set->drawer.hud_text, + &set->program.hud_text, + set->uniblock, + gleasy_atlas_get_texture(set->tex.hud_text)); + + loshader_menu_text_drawer_initialize( + &set->drawer.menu_text, + &set->program.menu_text, + set->uniblock, + gleasy_atlas_get_texture(set->tex.menu_text)); + + loshader_event_line_drawer_initialize( + &set->drawer.event_line, + &set->program.event_line, + set->uniblock, + gleasy_atlas_get_texture(set->tex.event_line)); +} + +void loshader_set_deinitialize(loshader_set_t* set) { + assert(set != NULL); + + loshader_backwall_drawer_delete(set->drawer.backwall); + loshader_ground_drawer_delete(set->drawer.ground); + loshader_character_drawer_delete(set->drawer.character); + loshader_bullet_drawer_delete(set->drawer.bullet); + loshader_fog_drawer_delete(set->drawer.fog); + loshader_pixsort_drawer_delete(set->drawer.pixsort); + loshader_posteffect_drawer_delete(set->drawer.posteffect); + loshader_hud_bar_drawer_delete(set->drawer.hud_bar); + loshader_hud_text_drawer_deinitialize(&set->drawer.hud_text); + loshader_menu_background_drawer_delete(set->drawer.menu_background); + loshader_menu_text_drawer_deinitialize(&set->drawer.menu_text); + loshader_menu_stance_drawer_delete(set->drawer.menu_stance); + loshader_combat_ring_drawer_delete(set->drawer.combat_ring); + loshader_cinescope_drawer_delete(set->drawer.cinescope); + loshader_event_line_drawer_deinitialize(&set->drawer.event_line); + + loshader_backwall_program_deinitialize(&set->program.backwall); + loshader_ground_program_deinitialize(&set->program.ground); + loshader_character_program_deinitialize(&set->program.character); + loshader_bullet_program_deinitialize(&set->program.bullet); + loshader_fog_program_deinitialize(&set->program.fog); + loshader_pixsort_program_deinitialize(&set->program.pixsort); + loshader_posteffect_program_deinitialize(&set->program.posteffect); + loshader_hud_bar_program_deinitialize(&set->program.hud_bar); + loshader_hud_text_program_deinitialize(&set->program.hud_text); + loshader_menu_background_program_deinitialize(&set->program.menu_background); + loshader_menu_text_program_deinitialize(&set->program.menu_text); + loshader_menu_stance_program_deinitialize(&set->program.menu_stance); + loshader_combat_ring_program_deinitialize(&set->program.combat_ring); + loshader_cinescope_program_deinitialize(&set->program.cinescope); + loshader_event_line_program_deinitialize(&set->program.event_line); + + gleasy_atlas_delete(set->tex.hud_text); + gleasy_atlas_delete(set->tex.menu_text); + gleasy_atlas_delete(set->tex.event_line); + + loshader_uniblock_delete(set->uniblock); + gleasy_framebuffer_deinitialize(&set->framebuffer); +} + +void loshader_set_clear_all(loshader_set_t* set) { + assert(set != NULL); + + loshader_ground_drawer_clear(set->drawer.ground, 256); + loshader_character_drawer_clear(set->drawer.character, 256); + loshader_bullet_drawer_clear(set->drawer.bullet, 256); + + loshader_hud_bar_drawer_clear(set->drawer.hud_bar, 16); + loshader_hud_text_drawer_clear(&set->drawer.hud_text, 256); + loshader_menu_background_drawer_set_alpha(set->drawer.menu_background, 0); + loshader_menu_text_drawer_clear(&set->drawer.menu_text, 512); + loshader_menu_stance_drawer_clear(set->drawer.menu_stance, 16); + loshader_combat_ring_drawer_clear(set->drawer.combat_ring, 8); + loshader_event_line_drawer_clear(&set->drawer.event_line, 256); +} + +void loshader_set_draw_all(const loshader_set_t* set) { + assert(set != NULL); + + /* ---- path 1: game ---- */ + gleasy_framebuffer_bind(&set->framebuffer); + + glEnable(GL_BLEND); + glEnable(GL_MULTISAMPLE); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + loshader_backwall_drawer_draw(set->drawer.backwall); + loshader_ground_drawer_draw(set->drawer.ground); + loshader_character_drawer_draw(set->drawer.character); + loshader_bullet_drawer_draw(set->drawer.bullet); + loshader_fog_drawer_draw(set->drawer.fog); + + gleasy_framebuffer_flush(&set->framebuffer); + + /* ---- path 2: pixsort ---- */ + if (!loshader_pixsort_drawer_is_skippable(set->drawer.pixsort)) { + gleasy_framebuffer_bind(&set->framebuffer); + loshader_pixsort_drawer_draw(set->drawer.pixsort); + gleasy_framebuffer_flush(&set->framebuffer); + } + + /* ---- path 2: HUD ---- */ + gleasy_framebuffer_bind(&set->framebuffer); + + glDisable(GL_BLEND); + glDisable(GL_MULTISAMPLE); + + loshader_posteffect_drawer_draw(set->drawer.posteffect); + + glEnable(GL_BLEND); + glEnable(GL_MULTISAMPLE); + + loshader_hud_bar_drawer_draw(set->drawer.hud_bar); + loshader_hud_text_drawer_draw(&set->drawer.hud_text); + loshader_menu_background_drawer_draw(set->drawer.menu_background); + loshader_menu_text_drawer_draw(&set->drawer.menu_text); + loshader_menu_stance_drawer_draw(set->drawer.menu_stance); + loshader_combat_ring_drawer_draw(set->drawer.combat_ring); + loshader_cinescope_drawer_draw(set->drawer.cinescope); + loshader_event_line_drawer_draw(&set->drawer.event_line); + + gleasy_framebuffer_flush_to_other(&set->framebuffer, 0); +} + +void loshader_set_drop_cache(loshader_set_t* set) { + assert(set != NULL); + + gleasy_atlas_clear(set->tex.hud_text); + gleasy_atlas_clear(set->tex.menu_text); + gleasy_atlas_clear(set->tex.event_line); +} diff --git a/core/loshader/set.h b/core/loshader/set.h new file mode 100644 index 0000000..bf81d26 --- /dev/null +++ b/core/loshader/set.h @@ -0,0 +1,103 @@ +#pragma once + +#include + +#include "util/gleasy/atlas.h" +#include "util/gleasy/framebuffer.h" +#include "util/math/vector.h" + +#include "./backwall.h" +#include "./bullet.h" +#include "./character.h" +#include "./cinescope.h" +#include "./combat_ring.h" +#include "./event_line.h" +#include "./fog.h" +#include "./ground.h" +#include "./hud_bar.h" +#include "./hud_text.h" +#include "./menu_background.h" +#include "./menu_stance.h" +#include "./menu_text.h" +#include "./pixsort.h" +#include "./posteffect.h" +#include "./uniblock.h" + +typedef struct { + gleasy_framebuffer_t framebuffer; + loshader_uniblock_t* uniblock; + + struct { + gleasy_atlas_t* hud_text; + gleasy_atlas_t* menu_text; + gleasy_atlas_t* event_line; + } tex; + + struct { + loshader_backwall_program_t backwall; + loshader_ground_program_t ground; + loshader_character_program_t character; + loshader_bullet_program_t bullet; + loshader_fog_program_t fog; + loshader_pixsort_program_t pixsort; + loshader_posteffect_program_t posteffect; + loshader_hud_bar_program_t hud_bar; + loshader_hud_text_program_t hud_text; + loshader_menu_background_program_t menu_background; + loshader_menu_text_program_t menu_text; + loshader_menu_stance_program_t menu_stance; + loshader_combat_ring_program_t combat_ring; + loshader_cinescope_program_t cinescope; + loshader_event_line_program_t event_line; + } program; + + struct { + loshader_backwall_drawer_t* backwall; + loshader_ground_drawer_t* ground; + loshader_character_drawer_t* character; + loshader_bullet_drawer_t* bullet; + loshader_fog_drawer_t* fog; + loshader_pixsort_drawer_t* pixsort; + loshader_posteffect_drawer_t* posteffect; + loshader_hud_bar_drawer_t* hud_bar; + loshader_hud_text_drawer_t hud_text; + loshader_menu_background_drawer_t* menu_background; + loshader_menu_text_drawer_t menu_text; + loshader_menu_stance_drawer_t* menu_stance; + loshader_combat_ring_drawer_t* combat_ring; + loshader_cinescope_drawer_t* cinescope; + loshader_event_line_drawer_t event_line; + } drawer; + + vec2_t resolution; + vec2_t dpi; +} loshader_set_t; + +void +loshader_set_initialize( + loshader_set_t* set, + int32_t width, + int32_t height, + const vec2_t* dpi, + int32_t max_msaa +); + +void +loshader_set_deinitialize( + loshader_set_t* set +); + +void +loshader_set_clear_all( + loshader_set_t* set +); + +void +loshader_set_draw_all( + const loshader_set_t* set +); + +void +loshader_set_drop_cache( + loshader_set_t* set +); diff --git a/core/loshader/uniblock.c b/core/loshader/uniblock.c new file mode 100644 index 0000000..2bc146f --- /dev/null +++ b/core/loshader/uniblock.c @@ -0,0 +1,117 @@ +#include "./uniblock.h" + +#include +#include +#include +#include + +#include + +#include "util/gleasy/buffer.h" +#include "util/math/algorithm.h" +#include "util/math/vector.h" +#include "util/math/matrix.h" +#include "util/memory/memory.h" + +#include "core/locommon/position.h" + +struct loshader_uniblock_t { + gleasy_buffer_uniform_t buf; +}; + +#pragma pack(push, 1) +typedef struct { + vec2_t resolution; + vec2_t dpi; + float aa; + + int8_t padding[12]; + + mat4_t proj; + mat4_t camera; + vec4_t pos; + float time; +} loshader_uniblock_internal_t; +_Static_assert( + sizeof(float)*5 + 12 + sizeof(float)*37 == + sizeof(loshader_uniblock_internal_t)); +#pragma pack(pop) + +bool loshader_uniblock_param_valid(const loshader_uniblock_param_t* param) { + return + param != NULL && + mat4_valid(¶m->proj) && + mat4_valid(¶m->cam) && + locommon_position_valid(¶m->pos) && + MATH_FLOAT_VALID(param->time); +} + +loshader_uniblock_t* loshader_uniblock_new(void) { + loshader_uniblock_t* uni = memory_new(sizeof(*uni)); + *uni = (typeof(*uni)) {0}; + + glGenBuffers(1, &uni->buf); + glBindBuffer(GL_UNIFORM_BUFFER, uni->buf); + glBufferData(GL_UNIFORM_BUFFER, + sizeof(loshader_uniblock_internal_t), NULL, GL_DYNAMIC_DRAW); + + return uni; +} + +void loshader_uniblock_delete(loshader_uniblock_t* uni) { + if (uni == NULL) return; + + glDeleteBuffers(1, &uni->buf); + memory_delete(uni); +} + +void loshader_uniblock_update_display_param( + loshader_uniblock_t* uni, const vec2_t* resolution, const vec2_t* dpi) { + assert(uni != NULL); + assert(vec2_valid(resolution)); + assert(vec2_valid(dpi)); + + const loshader_uniblock_internal_t internal = { + .resolution = *resolution, + .dpi = *dpi, + .aa = MATH_MAX(dpi->x, dpi->y) / 100000, + }; + + static const size_t size = + offsetof(loshader_uniblock_internal_t, aa) + sizeof(internal.aa); + + glBindBuffer(GL_UNIFORM_BUFFER, uni->buf); + glBufferSubData(GL_UNIFORM_BUFFER, 0, size, &internal); +} + +void loshader_uniblock_update_param( + loshader_uniblock_t* uni, + const loshader_uniblock_param_t* param) { + assert(uni != NULL); + assert(loshader_uniblock_param_valid(param)); + + const vec4_t pos = vec4( + param->pos.chunk.x, + param->pos.chunk.y, + param->pos.fract.x, + param->pos.fract.y); + + const loshader_uniblock_internal_t internal = { + .proj = param->proj, + .camera = param->cam, + .pos = pos, + .time = param->time, + }; + + static const size_t offset = offsetof(loshader_uniblock_internal_t, proj); + + glBindBuffer(GL_UNIFORM_BUFFER, uni->buf); + glBufferSubData(GL_UNIFORM_BUFFER, + offset, sizeof(internal)-offset, &internal.proj); +} + +void loshader_uniblock_bind(const loshader_uniblock_t* uni, GLuint index) { + assert(uni != NULL); + + glBindBufferBase(GL_UNIFORM_BUFFER, index, uni->buf); +} diff --git a/core/loshader/uniblock.h b/core/loshader/uniblock.h new file mode 100644 index 0000000..f05bb3d --- /dev/null +++ b/core/loshader/uniblock.h @@ -0,0 +1,56 @@ +#pragma once + +#include + +#include + +#include "util/math/vector.h" +#include "util/math/matrix.h" + +#include "core/locommon/position.h" + +struct loshader_uniblock_t; +typedef struct loshader_uniblock_t loshader_uniblock_t; + +typedef struct { + mat4_t proj; + mat4_t cam; + + locommon_position_t pos; + + float time; +} loshader_uniblock_param_t; + +bool +loshader_uniblock_param_valid( + const loshader_uniblock_param_t* param +); + +loshader_uniblock_t* /* OWNERSHIP */ +loshader_uniblock_new( + void +); + +void +loshader_uniblock_delete( + loshader_uniblock_t* uni /* OWNERSHIP */ +); + +void +loshader_uniblock_update_display_param( + loshader_uniblock_t* uni, + const vec2_t* resolution, + const vec2_t* dpi +); + +void +loshader_uniblock_update_param( + loshader_uniblock_t* uni, + const loshader_uniblock_param_t* param +); + +void +loshader_uniblock_bind( + const loshader_uniblock_t* uni, + GLuint index +); diff --git a/core/loworld/CMakeLists.txt b/core/loworld/CMakeLists.txt new file mode 100644 index 0000000..65f2a8a --- /dev/null +++ b/core/loworld/CMakeLists.txt @@ -0,0 +1,27 @@ +add_library(loworld + chunk.c + environment.c + generator.c + poolset.c + store.c + template.c + view.c +) +target_link_libraries(loworld + msgpackc + + chaos + container + jukebox + math + memory + mpkutil + + lobullet + locharacter + locommon + loentity + loground + loresource + loshader +) diff --git a/core/loworld/chunk.c b/core/loworld/chunk.c new file mode 100644 index 0000000..83b9331 --- /dev/null +++ b/core/loworld/chunk.c @@ -0,0 +1,159 @@ +#include "./chunk.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "util/container/array.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/loentity/entity.h" + +#include "./poolset.h" + +static void loworld_chunk_pack_entities_( + const loworld_chunk_t* chunk, msgpack_packer* packer) { + assert(chunk != NULL); + assert(packer != NULL); + + const size_t len = container_array_get_length(chunk->entities); + msgpack_pack_array(packer, len); + for (size_t i = 0; i < len; ++i) { + loentity_pack(chunk->entities[i], packer); + } +} + +const char* loworld_chunk_biome_stringify(loworld_chunk_biome_t biome) { +# define each_(NAME, name) \ + if (biome == LOWORLD_CHUNK_BIOME_##NAME) return #name; + + LOWORLD_CHUNK_BIOME_EACH_(each_); + + assert(false); + return NULL; + +# undef each_ +} + +bool loworld_chunk_biome_unstringify( + loworld_chunk_biome_t* biome, const char* str, size_t len) { + assert(biome != NULL); + assert(str != NULL || len == 0); + +# define each_(NAME, name) do { \ + if (strncmp(str, #name, len) == 0 && #name[len] == 0) { \ + *biome = LOWORLD_CHUNK_BIOME_##NAME; \ + return true; \ + } \ + } while (0) + + LOWORLD_CHUNK_BIOME_EACH_(each_); + return false; + +# undef each_ +} + +void loworld_chunk_initialize(loworld_chunk_t* chunk) { + assert(chunk != NULL); + + *chunk = (typeof(*chunk)) {0}; +} + +void loworld_chunk_deinitialize(loworld_chunk_t* chunk) { + if (chunk == NULL) return; + + loworld_chunk_clear(chunk); + container_array_delete(chunk->entities); +} + +void loworld_chunk_add_entity(loworld_chunk_t* chunk, loentity_t* entity) { + assert(chunk != NULL); + assert(entity != NULL); + + const size_t index = container_array_get_length(chunk->entities); + container_array_insert(chunk->entities, index); + chunk->entities[index] = entity; +} + +void loworld_chunk_clear(loworld_chunk_t* chunk) { + assert(chunk != NULL); + + const size_t len = container_array_get_length(chunk->entities); + for (size_t i = 0; i < len; ++i) { + loentity_delete(chunk->entities[i]); + } + container_array_resize(chunk->entities, 0); +} + +void loworld_chunk_pack(const loworld_chunk_t* chunk, msgpack_packer* packer) { + assert(chunk != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, 3); + + mpkutil_pack_str(packer, "pos"); + msgpack_pack_array(packer, 2); + msgpack_pack_int32(packer, chunk->pos.x); + msgpack_pack_int32(packer, chunk->pos.y); + + mpkutil_pack_str(packer, "biome"); + mpkutil_pack_str(packer, loworld_chunk_biome_stringify(chunk->biome)); + + mpkutil_pack_str(packer, "entities"); + loworld_chunk_pack_entities_(chunk, packer); +} + +bool loworld_chunk_unpack( + loworld_chunk_t* chunk, + const msgpack_object* obj, + const loworld_poolset_t* pools) { + assert(chunk != NULL); + assert(obj != NULL); + assert(pools != NULL); + + const msgpack_object_map* root = mpkutil_get_map(obj); + if (root == NULL) return false; + +#define item_(name) mpkutil_get_map_item_by_str(root, name) + + const msgpack_object_array* pos = mpkutil_get_array(item_("pos")); + if (pos == NULL || pos->size != 2 || + !mpkutil_get_int32(&pos->ptr[0], &chunk->pos.x) || + !mpkutil_get_int32(&pos->ptr[1], &chunk->pos.y)) { + return false; + } + + const char* biome; + size_t biome_len; + if (!mpkutil_get_str(item_("biome"), &biome, &biome_len) || + !loworld_chunk_biome_unstringify(&chunk->biome, biome, biome_len)) { + return false; + } + + const msgpack_object_array* entities = mpkutil_get_array(item_("entities")); + if (entities != NULL) { + container_array_reserve(chunk->entities, entities->size); + for (size_t i = 0; i < entities->size; ++i) { + loentity_t* e = loworld_poolset_unpack_entity(pools, &entities->ptr[i]); + if (e != NULL) loworld_chunk_add_entity(chunk, e); + } + } + +#undef item_ + return true; +} + +void loworld_chunk_build_filename( + const loworld_chunk_t* chunk, char* filename, size_t length) { + assert(chunk != NULL); + assert(filename != NULL || length == 0); + + snprintf(filename, length, + "%"PRId32"_%"PRId32".msgpack", chunk->pos.x, chunk->pos.y); +} diff --git a/core/loworld/chunk.h b/core/loworld/chunk.h new file mode 100644 index 0000000..f46fb8a --- /dev/null +++ b/core/loworld/chunk.h @@ -0,0 +1,98 @@ +#pragma once + +#include +#include +#include + +#include + +#include "util/container/array.h" + +#include "core/loentity/entity.h" + +#include "./poolset.h" + +#define LOWORLD_CHUNK_FILENAME_MAX 64 + +/* dont forget to change EACH macro */ +typedef enum { + LOWORLD_CHUNK_BIOME_METAPHYSICAL_GATE, + LOWORLD_CHUNK_BIOME_CAVIAS_CAMP, + LOWORLD_CHUNK_BIOME_LABORATORY, + LOWORLD_CHUNK_BIOME_BOSS_THEISTS_CHILD, + LOWORLD_CHUNK_BIOME_BOSS_BIG_WARDER, + LOWORLD_CHUNK_BIOME_BOSS_GREEDY_SCIENTIST, +} loworld_chunk_biome_t; + +#define LOWORLD_CHUNK_BIOME_EACH_(PROC) do { \ + PROC(METAPHYSICAL_GATE, metaphysical-gate); \ + PROC(CAVIAS_CAMP, cavias-camp); \ + PROC(LABORATORY, laboratory); \ + PROC(BOSS_THEISTS_CHILD, boss-theists-child); \ + PROC(BOSS_BIG_WARDER, boss-big-warder); \ + PROC(BOSS_GREEDY_SCIENTIST, boss-greedy-scientist); \ +} while (0) + +typedef struct { + struct { + int32_t x; + int32_t y; + } pos; + + loworld_chunk_biome_t biome; + + CONTAINER_ARRAY loentity_t** entities; +} loworld_chunk_t; + +const char* +loworld_chunk_biome_stringify( + loworld_chunk_biome_t biome +); + +bool +loworld_chunk_biome_unstringify( + loworld_chunk_biome_t* biome, + const char* str, + size_t len +); + +void +loworld_chunk_initialize( + loworld_chunk_t* chunk +); + +void +loworld_chunk_deinitialize( + loworld_chunk_t* chunk +); + +void +loworld_chunk_add_entity( + loworld_chunk_t* chunk, + loentity_t* entity /* OWNERSHIP */ +); + +void +loworld_chunk_clear( + loworld_chunk_t* chunk +); + +void +loworld_chunk_pack( + const loworld_chunk_t* chunk, + msgpack_packer* packer +); + +bool +loworld_chunk_unpack( + loworld_chunk_t* chunk, /* should be cleared */ + const msgpack_object* obj, + const loworld_poolset_t* pools +); + +void +loworld_chunk_build_filename( + const loworld_chunk_t* chunk, + char* filename, + size_t length +); diff --git a/core/loworld/environment.c b/core/loworld/environment.c new file mode 100644 index 0000000..2068483 --- /dev/null +++ b/core/loworld/environment.c @@ -0,0 +1,254 @@ +#include "./environment.h" + +#include +#include +#include + +#include "util/jukebox/amp.h" +#include "util/jukebox/decoder.h" +#include "util/math/rational.h" + +#include "core/locommon/easing.h" +#include "core/locommon/ticker.h" +#include "core/loplayer/event.h" +#include "core/loplayer/player.h" +#include "core/loresource/music.h" +#include "core/loresource/set.h" +#include "core/loresource/text.h" +#include "core/loshader/set.h" +#include "core/loshader/backwall.h" +#include "core/loshader/fog.h" + +#include "./view.h" + +static const char* loworld_environment_get_chunk_name_( + loresource_language_t lang, loworld_chunk_biome_t b) { + switch (b) { + case LOWORLD_CHUNK_BIOME_METAPHYSICAL_GATE: + return loresource_text_get(lang, "biome_metaphysical_gate"); + case LOWORLD_CHUNK_BIOME_CAVIAS_CAMP: + return loresource_text_get(lang, "biome_cavias_camp"); + case LOWORLD_CHUNK_BIOME_LABORATORY: + return loresource_text_get(lang, "biome_laboratory"); + case LOWORLD_CHUNK_BIOME_BOSS_THEISTS_CHILD: + return loresource_text_get(lang, "biome_boss_theists_child"); + case LOWORLD_CHUNK_BIOME_BOSS_BIG_WARDER: + return loresource_text_get(lang, "biome_boss_big_warder"); + case LOWORLD_CHUNK_BIOME_BOSS_GREEDY_SCIENTIST: + return loresource_text_get(lang, "biome_boss_greedy_scientist"); + } + assert(false); + return "unknown biome"; +} + +static loshader_backwall_type_t loworld_environment_get_backwall_type_( + loworld_chunk_biome_t b) { + switch (b) { + case LOWORLD_CHUNK_BIOME_METAPHYSICAL_GATE: + return LOSHADER_BACKWALL_TYPE_INFINITE_BOXES; + case LOWORLD_CHUNK_BIOME_CAVIAS_CAMP: + return LOSHADER_BACKWALL_TYPE_HOLLOW_MOUNTAINS; + case LOWORLD_CHUNK_BIOME_LABORATORY: + return LOSHADER_BACKWALL_TYPE_FABRIC; + case LOWORLD_CHUNK_BIOME_BOSS_THEISTS_CHILD: + return LOSHADER_BACKWALL_TYPE_HOLLOW_MOUNTAINS_RED; + case LOWORLD_CHUNK_BIOME_BOSS_BIG_WARDER: + return LOSHADER_BACKWALL_TYPE_JAIL; + case LOWORLD_CHUNK_BIOME_BOSS_GREEDY_SCIENTIST: + return LOSHADER_BACKWALL_TYPE_INFINITE_BOXES; + } + assert(false); + return LOSHADER_BACKWALL_TYPE_WHITE; +} + +static loshader_fog_type_t loworld_environment_get_fog_type_( + loworld_chunk_biome_t b) { + switch (b) { + case LOWORLD_CHUNK_BIOME_METAPHYSICAL_GATE: + case LOWORLD_CHUNK_BIOME_CAVIAS_CAMP: + case LOWORLD_CHUNK_BIOME_LABORATORY: + case LOWORLD_CHUNK_BIOME_BOSS_THEISTS_CHILD: + case LOWORLD_CHUNK_BIOME_BOSS_BIG_WARDER: + case LOWORLD_CHUNK_BIOME_BOSS_GREEDY_SCIENTIST: + return LOSHADER_FOG_TYPE_WHITE_CLOUD; + } + assert(false); + return LOSHADER_FOG_TYPE_NONE; +} + +static loresource_music_player_t* loworld_environment_get_music_( + loworld_chunk_biome_t b, loresource_music_t* m) { + switch (b) { + case LOWORLD_CHUNK_BIOME_METAPHYSICAL_GATE: + return &m->biome_metaphysical_gate; + case LOWORLD_CHUNK_BIOME_CAVIAS_CAMP: + return &m->biome_cavias_camp; + case LOWORLD_CHUNK_BIOME_LABORATORY: + return &m->biome_laboratory; + case LOWORLD_CHUNK_BIOME_BOSS_THEISTS_CHILD: + case LOWORLD_CHUNK_BIOME_BOSS_BIG_WARDER: + case LOWORLD_CHUNK_BIOME_BOSS_GREEDY_SCIENTIST: + return &m->biome_boss; + } + assert(false); + return NULL; +} + +static void loworld_environment_stop_music_(loworld_environment_t* env) { + assert(env != NULL); + if (env->music != NULL && env->music_control) { + jukebox_amp_change_volume(&env->music->amp, 0, &rational(1, 1)); + jukebox_decoder_stop_after(env->music->decoder, &rational(1, 1)); + } + env->music = NULL; +} + +static void loworld_environment_update_hud_(loworld_environment_t* env) { + assert(env != NULL); + + if (env->transition > 0) return; + + loplayer_hud_set_biome_text( + env->player->hud, + loworld_environment_get_chunk_name_(env->res->lang, env->biome)); +} + +static void loworld_environment_update_backwall_(loworld_environment_t* env) { + assert(env != NULL); + + loshader_backwall_type_t prev = env->backwall.prev_type; + + if (env->transition == 0) prev = env->backwall.type; + + env->backwall = (loshader_backwall_drawer_param_t) { + .prev_type = prev, + .type = LOSHADER_BACKWALL_TYPE_WHITE, + .transition = env->transition, + }; + if (!env->config.disable_heavy_backwall) { + env->backwall.type = loworld_environment_get_backwall_type_(env->biome); + } +} + +static void loworld_environment_update_fog_(loworld_environment_t* env) { + assert(env != NULL); + + if (env->transition == 0) env->fog.prev_type = env->fog.type; + + env->fog.type = LOSHADER_FOG_TYPE_NONE; + if (!env->config.disable_heavy_fog) { + env->fog.type = loworld_environment_get_fog_type_(env->biome); + } + env->fog.transition = env->transition; + + /* ---- bounds fog ---- */ + const loplayer_event_param_t* e = + loplayer_event_get_param(env->player->event); + if (e != NULL && vec2_pow_length(&e->area_size) > 0) { + env->fog.bounds_pos = e->area_pos; + env->fog.bounds_size = e->area_size; + + locommon_easing_smooth_float( + &env->fog.bounds_fog, 1, env->ticker->delta_f); + } else { + locommon_easing_smooth_float( + &env->fog.bounds_fog, 0, env->ticker->delta_f); + } +} + +static void loworld_environment_update_music_(loworld_environment_t* env) { + assert(env != NULL); + + bool control = true; + loresource_music_player_t* music = + loworld_environment_get_music_(env->biome, &env->res->music); + + const loplayer_event_param_t* e = + loplayer_event_get_param(env->player->event); + if (e != NULL) { + music = e->music; + control = false; + + if (!env->sound_attenuation) { + loresource_sound_change_master_volume( + env->res->sound, .2f, &rational(1, 1)); + env->sound_attenuation = true; + } + + } else { + if (env->sound_attenuation) { + loresource_sound_change_master_volume( + env->res->sound, 1, &rational(1, 1)); + env->sound_attenuation = false; + } + } + + if (music != env->music) { + loworld_environment_stop_music_(env); + env->music = music; + env->music_control = control; + if (env->music != NULL && env->music_control) { + jukebox_amp_change_volume(&env->music->amp, .6f, &rational(1, 1)); + jukebox_decoder_resume(env->music->decoder, true); + } + } +} + +void loworld_environment_initialize( + loworld_environment_t* env, + loresource_set_t* res, + loshader_set_t* shaders, + const locommon_ticker_t* ticker, + const loworld_view_t* view, + loplayer_t* player, + const loworld_environment_config_t* config) { + assert(env != NULL); + assert(res != NULL); + assert(shaders != NULL); + assert(ticker != NULL); + assert(view != NULL); + assert(player != NULL); + assert(config != NULL); + + *env = (typeof(*env)) { + .res = res, + .shaders = shaders, + .ticker = ticker, + .view = view, + .player = player, + + .config = *config, + }; +} + +void loworld_environment_deinitialize(loworld_environment_t* env) { + assert(env != NULL); + + loworld_environment_stop_music_(env); +} + +void loworld_environment_update(loworld_environment_t* env) { + assert(env != NULL); + + const loworld_chunk_t* chunk = loworld_view_get_looking_chunk(env->view); + + if (env->transition == 1 && env->biome != chunk->biome) { + env->biome = chunk->biome; + env->transition = 0; + } + loworld_environment_update_hud_(env); + loworld_environment_update_backwall_(env); + loworld_environment_update_fog_(env); + loworld_environment_update_music_(env); + + locommon_easing_linear_float(&env->transition, 1, env->ticker->delta_f); +} + +void loworld_environment_draw(const loworld_environment_t* env) { + assert(env != NULL); + + loshader_backwall_drawer_set_param( + env->shaders->drawer.backwall, &env->backwall); + loshader_fog_drawer_set_param( + env->shaders->drawer.fog, &env->fog); +} diff --git a/core/loworld/environment.h b/core/loworld/environment.h new file mode 100644 index 0000000..809aec9 --- /dev/null +++ b/core/loworld/environment.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include + +#include "core/locommon/ticker.h" +#include "core/loplayer/player.h" +#include "core/loresource/music.h" +#include "core/loresource/set.h" +#include "core/loshader/set.h" + +#include "./view.h" + +typedef struct { + bool disable_heavy_backwall; + bool disable_heavy_fog; +} loworld_environment_config_t; + +typedef struct { + /* injected deps */ + loresource_set_t* res; + loshader_set_t* shaders; + const locommon_ticker_t* ticker; + const loworld_view_t* view; + loplayer_t* player; + + /* immutable params */ + loworld_environment_config_t config; + + /* read-only mutable params */ + float transition; + loworld_chunk_biome_t biome; + + loresource_music_player_t* music; + bool music_control; + bool sound_attenuation; + + loshader_backwall_drawer_param_t backwall; + loshader_fog_drawer_param_t fog; +} loworld_environment_t; + +void +loworld_environment_initialize( + loworld_environment_t* env, + loresource_set_t* res, + loshader_set_t* shaders, + const locommon_ticker_t* ticker, + const loworld_view_t* view, + loplayer_t* player, + const loworld_environment_config_t* config +); + +void +loworld_environment_deinitialize( + loworld_environment_t* env +); + +void +loworld_environment_update( + loworld_environment_t* env +); + +void +loworld_environment_draw( + const loworld_environment_t* env +); diff --git a/core/loworld/generator.c b/core/loworld/generator.c new file mode 100644 index 0000000..2d397ee --- /dev/null +++ b/core/loworld/generator.c @@ -0,0 +1,245 @@ +#include "./generator.h" + +#include +#include + +#include + +#include "util/chaos/xorshift.h" +#include "util/math/algorithm.h" +#include "util/memory/memory.h" +#include "util/mpkutil/get.h" + +#include "./chunk.h" +#include "./poolset.h" +#include "./template.h" + +struct loworld_generator_t { + /* injected deps */ + const loworld_poolset_t* pools; + + /* parameters */ + uint64_t seed; +}; + +#define LOWORLD_GENERATOR_BLOCK_SIZE 8 + +static uint64_t loworld_generator_rand_( + const loworld_generator_t* gen, int32_t x, int32_t y) { + assert(gen != NULL); + + const uint64_t ux = (uint64_t) x - INT32_MIN; + const uint64_t uy = (uint64_t) y - INT32_MIN; + + /* multiply prime numbers */ + return ux*400206850629133 + uy*890206850629189 + gen->seed; +} + +static loworld_chunk_biome_t loworld_generator_decide_chunk_biome_( + const loworld_generator_t* gen, int32_t x, int32_t y) { + assert(gen != NULL); + + static const loworld_chunk_biome_t base_biomes[] = { + LOWORLD_CHUNK_BIOME_CAVIAS_CAMP, + LOWORLD_CHUNK_BIOME_LABORATORY, + }; + static const size_t base_biomes_length = + sizeof(base_biomes)/sizeof(base_biomes[0]); + + /* random seed which depends the block */ + uint64_t bs = loworld_generator_rand_(gen, x/LOWORLD_GENERATOR_BLOCK_SIZE, y); + const loworld_chunk_biome_t base = + base_biomes[(bs = chaos_xorshift(bs))%base_biomes_length]; + + uint64_t fx = x + (y == 0? 0: loworld_generator_rand_(gen, 0, y)); + fx = MATH_ABS(fx)%LOWORLD_GENERATOR_BLOCK_SIZE; + + if (fx == 0) return LOWORLD_CHUNK_BIOME_METAPHYSICAL_GATE; + if (fx == LOWORLD_GENERATOR_BLOCK_SIZE/2) { + switch (base) { + case LOWORLD_CHUNK_BIOME_CAVIAS_CAMP: + return LOWORLD_CHUNK_BIOME_BOSS_THEISTS_CHILD; + case LOWORLD_CHUNK_BIOME_LABORATORY: + return x%2? + LOWORLD_CHUNK_BIOME_BOSS_BIG_WARDER: + LOWORLD_CHUNK_BIOME_BOSS_GREEDY_SCIENTIST; + default: ; + } + } + return base; +} + +static void loworld_generator_generate_for_metaphysical_gate_( + const loworld_generator_t* gen, loworld_chunk_t* chunk) { + assert(gen != NULL); + assert(chunk != NULL); + + const loworld_template_building_param_t param = { + .target = chunk, + .pools = gen->pools, + .seed = loworld_generator_rand_(gen, chunk->pos.x, chunk->pos.y), + }; + loworld_template_metaphysical_gate_build_chunk(¶m); +} + +static void loworld_generator_generate_for_cavias_camp_( + const loworld_generator_t* gen, loworld_chunk_t* chunk) { + assert(gen != NULL); + assert(chunk != NULL); + + static void (*funcs[])(const loworld_template_building_param_t* p) = { + loworld_template_open_space_build_chunk, + loworld_template_broken_open_space_build_chunk, + loworld_template_passage_build_chunk, + loworld_template_broken_passage_build_chunk, + }; + static const size_t funcs_len = sizeof(funcs)/sizeof(funcs[0]); + + uint64_t s = loworld_generator_rand_(gen, chunk->pos.x, chunk->pos.y); + + const uint64_t r = (s = chaos_xorshift(s))%funcs_len; + + const loworld_template_building_param_t param = { + .target = chunk, + .pools = gen->pools, + .seed = chaos_xorshift(s), + }; + return funcs[r](¶m); +} + +static void loworld_generator_generate_for_laboratory_( + const loworld_generator_t* gen, loworld_chunk_t* chunk) { + assert(gen != NULL); + assert(chunk != NULL); + + static void (*funcs[])(const loworld_template_building_param_t* p) = { + loworld_template_passage_build_chunk, + loworld_template_broken_passage_build_chunk, + loworld_template_stairs_build_chunk, + }; + static const size_t funcs_len = sizeof(funcs)/sizeof(funcs[0]); + + uint64_t s = loworld_generator_rand_(gen, chunk->pos.x, chunk->pos.y); + + const uint64_t r = (s = chaos_xorshift(s))%funcs_len; + + const loworld_template_building_param_t param = { + .target = chunk, + .pools = gen->pools, + .seed = chaos_xorshift(s), + }; + return funcs[r](¶m); +} + +static void loworld_generator_generate_for_boss_theists_child_( + const loworld_generator_t* gen, loworld_chunk_t* chunk) { + assert(gen != NULL); + assert(chunk != NULL); + + const loworld_template_building_param_t param = { + .target = chunk, + .pools = gen->pools, + .seed = loworld_generator_rand_(gen, chunk->pos.x, chunk->pos.y), + }; + loworld_template_boss_theists_child_build_chunk(¶m); +} + +static void loworld_generator_generate_for_boss_big_warder_( + const loworld_generator_t* gen, loworld_chunk_t* chunk) { + assert(gen != NULL); + assert(chunk != NULL); + + const loworld_template_building_param_t param = { + .target = chunk, + .pools = gen->pools, + .seed = loworld_generator_rand_(gen, chunk->pos.x, chunk->pos.y), + }; + loworld_template_boss_big_warder_build_chunk(¶m); +} + +static void loworld_generator_generate_for_boss_greedy_scientist_( + const loworld_generator_t* gen, loworld_chunk_t* chunk) { + assert(gen != NULL); + assert(chunk != NULL); + + const loworld_template_building_param_t param = { + .target = chunk, + .pools = gen->pools, + .seed = loworld_generator_rand_(gen, chunk->pos.x, chunk->pos.y), + }; + loworld_template_boss_greedy_scientist_build_chunk(¶m); +} + +loworld_generator_t* loworld_generator_new( + const loworld_poolset_t* pools, uint64_t seed) { + assert(pools != NULL); + + loworld_generator_t* gen = memory_new(sizeof(*gen)); + *gen = (typeof(*gen)) { + .pools = pools, + }; + loworld_generator_randomize(gen, seed); + return gen; +} + +void loworld_generator_delete(loworld_generator_t* gen) { + assert(gen != NULL); + + memory_delete(gen); +} + +void loworld_generator_randomize(loworld_generator_t* gen, uint64_t seed) { + assert(gen != NULL); + + gen->seed = seed; +} + +void loworld_generator_generate( + const loworld_generator_t* gen, loworld_chunk_t* chunk) { + assert(gen != NULL); + assert(chunk != NULL); + + chunk->biome = + loworld_generator_decide_chunk_biome_(gen, chunk->pos.x, chunk->pos.y); + + switch (chunk->biome) { + case LOWORLD_CHUNK_BIOME_METAPHYSICAL_GATE: + loworld_generator_generate_for_metaphysical_gate_(gen, chunk); + break; + case LOWORLD_CHUNK_BIOME_CAVIAS_CAMP: + loworld_generator_generate_for_cavias_camp_(gen, chunk); + break; + case LOWORLD_CHUNK_BIOME_LABORATORY: + loworld_generator_generate_for_laboratory_(gen, chunk); + break; + case LOWORLD_CHUNK_BIOME_BOSS_THEISTS_CHILD: + loworld_generator_generate_for_boss_theists_child_(gen, chunk); + break; + case LOWORLD_CHUNK_BIOME_BOSS_BIG_WARDER: + loworld_generator_generate_for_boss_big_warder_(gen, chunk); + break; + case LOWORLD_CHUNK_BIOME_BOSS_GREEDY_SCIENTIST: + loworld_generator_generate_for_boss_greedy_scientist_(gen, chunk); + break; + } +} + +void loworld_generator_pack( + const loworld_generator_t* gen, msgpack_packer* packer) { + assert(gen != NULL); + assert(packer != NULL); + + msgpack_pack_uint64(packer, gen->seed); +} + +bool loworld_generator_unpack( + loworld_generator_t* gen, const msgpack_object* obj) { + assert(gen != NULL); + assert(obj != NULL); + + uint64_t seed; + if (!mpkutil_get_uint64(obj, &seed)) return false; + + loworld_generator_randomize(gen, seed); + return true; +} diff --git a/core/loworld/generator.h b/core/loworld/generator.h new file mode 100644 index 0000000..c51a813 --- /dev/null +++ b/core/loworld/generator.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +#include + +#include "./chunk.h" +#include "./poolset.h" + +struct loworld_generator_t; +typedef struct loworld_generator_t loworld_generator_t; + +loworld_generator_t* +loworld_generator_new( + const loworld_poolset_t* pools, + uint64_t seed +); + +void +loworld_generator_delete( + loworld_generator_t* gen +); + +void +loworld_generator_randomize( + loworld_generator_t* gen, + uint64_t seed +); + +void +loworld_generator_generate( + const loworld_generator_t* gen, + loworld_chunk_t* chunk +); + +void +loworld_generator_pack( + const loworld_generator_t* gen, + msgpack_packer* packer +); + +bool +loworld_generator_unpack( + loworld_generator_t* gen, + const msgpack_object* obj +); diff --git a/core/loworld/poolset.c b/core/loworld/poolset.c new file mode 100644 index 0000000..eb26abb --- /dev/null +++ b/core/loworld/poolset.c @@ -0,0 +1,38 @@ +#include "./poolset.h" + +#include +#include + +#include +#include + +#include "core/lobullet/pool.h" +#include "core/locharacter/pool.h" +#include "core/loground/pool.h" + +#include "./test.h" + +loentity_t* loworld_poolset_unpack_entity( + const loworld_poolset_t* pools, const msgpack_object* obj) { + assert(pools != NULL); + assert(obj != NULL); + + loentity_t* e; + + e = (typeof(e)) loground_pool_unpack_item(pools->ground, obj); + if (e != NULL) return e; + + e = (typeof(e)) lobullet_pool_unpack_item(pools->bullet, obj); + if (e != NULL) return e; + + e = (typeof(e)) locharacter_pool_unpack_item(pools->character, obj); + if (e != NULL) return e; + + return NULL; +} + +void loworld_poolset_test_packing(const loworld_poolset_t* pools) { + assert(pools != NULL); + + loworld_test_packing(pools); +} diff --git a/core/loworld/poolset.h b/core/loworld/poolset.h new file mode 100644 index 0000000..515ec01 --- /dev/null +++ b/core/loworld/poolset.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include + +#include "core/lobullet/pool.h" +#include "core/locharacter/pool.h" +#include "core/loground/pool.h" + +typedef struct { + loground_pool_t* ground; + lobullet_pool_t* bullet; + locharacter_pool_t* character; +} loworld_poolset_t; + +/* Initialize and Deinitialize each member manually + * because of a dependency issue. */ + +loentity_t* /* NULLABLE/OWNERSHIP */ +loworld_poolset_unpack_entity( + const loworld_poolset_t* pools, + const msgpack_object* obj +); + +void +loworld_poolset_test_packing( + const loworld_poolset_t* pools +); diff --git a/core/loworld/store.c b/core/loworld/store.c new file mode 100644 index 0000000..4c13d33 --- /dev/null +++ b/core/loworld/store.c @@ -0,0 +1,265 @@ +#include "./store.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "util/memory/memory.h" +#include "util/mpkutil/file.h" + +#include "./chunk.h" +#include "./generator.h" +#include "./poolset.h" + +#define LOWORLD_STORE_PATH_MAX_LENGTH 256 +#define LOWORLD_STORE_UNPACKER_BUFFER_SIZE (1024*1024) + +typedef struct { + loworld_chunk_t data; + + bool loaded; + bool used; + + uint64_t last_tick; +} loworld_store_chunk_t; + +struct loworld_store_t { + char path[LOWORLD_STORE_PATH_MAX_LENGTH+LOWORLD_CHUNK_FILENAME_MAX]; + size_t basepath_length; + + const loworld_poolset_t* pools; + const loworld_generator_t* generator; + + msgpack_unpacker unpacker; + msgpack_unpacked unpacked; + + uint64_t tick; + bool error; + + size_t chunks_length; + loworld_store_chunk_t chunks[1]; +}; + +static bool loworld_store_find_chunk_index_( + const loworld_store_t* store, size_t* index, int32_t x, int32_t y) { + assert(store != NULL); + assert(index != NULL); + + for (size_t i = 0; i < store->chunks_length; ++i) { + if (!store->chunks[i].loaded) continue; + + const loworld_chunk_t* chunk = &store->chunks[i].data; + if (chunk->pos.x == x && chunk->pos.y == y) { + *index = i; + return true; + } + } + return false; +} + +static bool loworld_store_find_unused_chunk_index_( + const loworld_store_t* store, size_t* index) { + assert(store != NULL); + assert(index != NULL); + + for (size_t i = 0; i < store->chunks_length; ++i) { + if (!store->chunks[i].loaded) { + *index = i; + return true; + } + } + + bool found = false; + uint64_t oldest = UINT64_MAX; + for (size_t i = 0; i < store->chunks_length; ++i) { + const loworld_store_chunk_t* chunk = &store->chunks[i]; + if (!chunk->used && chunk->last_tick <= oldest) { + *index = i; + oldest = chunk->last_tick; + found = true; + } + } + return found; +} + +/* Builds filename on the store->path as zero-terminated string. */ +static void loworld_store_build_chunk_filename_( + loworld_store_t* store, const loworld_chunk_t* chunk) { + assert(store != NULL); + assert(chunk != NULL); + + loworld_chunk_build_filename(chunk, + &store->path[store->basepath_length], LOWORLD_CHUNK_FILENAME_MAX); +} + +static bool loworld_store_load_chunk_from_file_( + loworld_store_t* store, loworld_chunk_t* chunk) { + assert(store != NULL); + assert(chunk != NULL); + + loworld_store_build_chunk_filename_(store, chunk); + + FILE* fp = fopen(store->path, "rb"); + if (fp == NULL) return false; + + bool success = false; + + msgpack_unpacker_reset(&store->unpacker); + if (mpkutil_file_unpack_with_unpacker( + &store->unpacked, fp, &store->unpacker)) { + loworld_chunk_clear(chunk); + success = loworld_chunk_unpack(chunk, &store->unpacked.data, store->pools); + } + fclose(fp); + + if (!success) { + fprintf(stderr, + "failed to load chunk (%"PRId32", %"PRId32")\n", + chunk->pos.x, chunk->pos.y); + } + return success; +} + +static bool loworld_store_save_chunk_to_file_( + loworld_store_t* store, const loworld_chunk_t* chunk) { + assert(store != NULL); + assert(chunk != NULL); + + loworld_store_build_chunk_filename_(store, chunk); + + FILE* fp = fopen(store->path, "wb"); + if (fp == NULL) return false; + + msgpack_packer packer; + msgpack_packer_init(&packer, fp, msgpack_fbuffer_write); + + loworld_chunk_pack(chunk, &packer); + + const bool success = (ferror(fp) == 0); + fclose(fp); + return success; +} + +loworld_store_t* loworld_store_new( + const loworld_poolset_t* pools, + const loworld_generator_t* generator, + size_t chunks_length, + const char* basepath, + size_t basepath_length) { + assert(pools != NULL); + assert(generator != NULL); + assert(chunks_length > 0); + + if (basepath_length >= LOWORLD_STORE_PATH_MAX_LENGTH) { + fprintf(stderr, "too long path name\n"); + abort(); + } + + loworld_store_t* store = memory_new( + sizeof(*store) + (chunks_length-1)*sizeof(store->chunks[0])); + *store = (typeof(*store)) { + .basepath_length = basepath_length, + .pools = pools, + .generator = generator, + .chunks_length = chunks_length, + }; + strncpy(store->path, basepath, LOWORLD_STORE_PATH_MAX_LENGTH); + + if (!msgpack_unpacker_init( + &store->unpacker, LOWORLD_STORE_UNPACKER_BUFFER_SIZE)) { + fprintf(stderr, "failed to initialize unpacker\n"); + abort(); + } + msgpack_unpacked_init(&store->unpacked); + + for (size_t i = 0; i < store->chunks_length; ++i) { + loworld_store_chunk_t* chunk = &store->chunks[i]; + *chunk = (typeof(*chunk)) {0}; + loworld_chunk_initialize(&chunk->data); + } + return store; +} + +void loworld_store_delete(loworld_store_t* store) { + if (store == NULL) return; + + msgpack_unpacker_destroy(&store->unpacker); + msgpack_unpacked_destroy(&store->unpacked); + + for (size_t i = 0; i < store->chunks_length; ++i) { + loworld_chunk_deinitialize(&store->chunks[i].data); + } + memory_delete(store); +} + +loworld_chunk_t* loworld_store_load_chunk( + loworld_store_t* store, int32_t chunk_x, int32_t chunk_y) { + assert(store != NULL); + + size_t index; + if (!loworld_store_find_chunk_index_(store, &index, chunk_x, chunk_y)) { + if (!loworld_store_find_unused_chunk_index_(store, &index)) { + fprintf(stderr, "world store chunk overflow\n"); + abort(); + } + } + + loworld_store_chunk_t* chunk = &store->chunks[index]; + + if (chunk->loaded && + (chunk->data.pos.x != chunk_x || chunk->data.pos.y != chunk_y)) { + assert(!chunk->used); + store->error = !loworld_store_save_chunk_to_file_(store, &chunk->data); + chunk->loaded = false; + } + if (!chunk->loaded) { + chunk->data.pos.x = chunk_x; + chunk->data.pos.y = chunk_y; + + if (!loworld_store_load_chunk_from_file_(store, &chunk->data)) { + loworld_chunk_clear(&chunk->data); + loworld_generator_generate(store->generator, &chunk->data); + } + chunk->loaded = true; + } + + chunk->used = true; + chunk->last_tick = store->tick++; + return &chunk->data; +} + +void loworld_store_unload_chunk( + loworld_store_t* store, loworld_chunk_t* chunk) { + assert(store != NULL); + assert(chunk != NULL); + + loworld_store_chunk_t* c = (typeof(c)) chunk; + assert(store->chunks <= c && c < store->chunks+store->chunks_length); + + c->used = false; +} + +void loworld_store_flush(loworld_store_t* store) { + assert(store != NULL); + + for (size_t i = 0; i < store->chunks_length; ++i) { + loworld_store_chunk_t* chunk = &store->chunks[i]; + if (!chunk->loaded) continue; + + store->error = !loworld_store_save_chunk_to_file_(store, &chunk->data); + } +} + +bool loworld_store_is_error_happened(const loworld_store_t* store) { + assert(store != NULL); + + return store->error; +} diff --git a/core/loworld/store.h b/core/loworld/store.h new file mode 100644 index 0000000..0b5cbd0 --- /dev/null +++ b/core/loworld/store.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include + +#include "./chunk.h" +#include "./generator.h" +#include "./poolset.h" + +struct loworld_store_t; +typedef struct loworld_store_t loworld_store_t; + +/* TODO(catfoot): make it possible to specify a path to chunk dir */ +loworld_store_t* /* OWNERSHIP */ +loworld_store_new( + const loworld_poolset_t* pools, + const loworld_generator_t* gen, + size_t chunks_length, + const char* basepath, /* must be terminated with slash */ + size_t basepath_length +); + +void +loworld_store_delete( + loworld_store_t* store /* OWNERSHIP */ +); + +loworld_chunk_t* /* NULLABLE */ +loworld_store_load_chunk( + loworld_store_t* store, + int32_t chunk_x, + int32_t chunk_y +); + +void +loworld_store_unload_chunk( + loworld_store_t* store, + loworld_chunk_t* chunk +); + +/* If there is an instance of loworld_view_t, this function may flush broken + chunks. So use loworld_view_flush_store function insteadly. */ +void +loworld_store_flush( + loworld_store_t* store +); + +bool +loworld_store_is_error_happened( + const loworld_store_t* store +); diff --git a/core/loworld/template.c b/core/loworld/template.c new file mode 100644 index 0000000..f85a4fd --- /dev/null +++ b/core/loworld/template.c @@ -0,0 +1,287 @@ +#include "./template.h" + +#include +#include +#include + +#include "util/chaos/xorshift.h" +#include "util/math/algorithm.h" +#include "util/math/vector.h" + +#include "core/locharacter/base.h" +#include "core/locharacter/big_warder.h" +#include "core/locharacter/cavia.h" +#include "core/locharacter/encephalon.h" +#include "core/locharacter/scientist.h" +#include "core/locharacter/greedy_scientist.h" +#include "core/locharacter/theists_child.h" +#include "core/locharacter/warder.h" +#include "core/locommon/position.h" +#include "core/loentity/entity.h" +#include "core/loground/base.h" +#include "core/loground/island.h" + +#include "./chunk.h" +#include "./poolset.h" + +static loentity_id_t loworld_template_add_ground_island_( + const loworld_template_building_param_t* param, + const vec2_t* pos, + const vec2_t* sz) { + assert(loworld_template_building_param_valid(param)); + assert(vec2_valid(pos)); + assert(vec2_valid(sz)); + assert(sz->x >= 0 && sz->y >= 0); + + const locommon_position_t p = locommon_position( + param->target->pos.x, param->target->pos.y, *pos); + + loground_base_t* island = loground_pool_create(param->pools->ground); + loground_island_build(island, &p, sz); + loworld_chunk_add_entity(param->target, &island->super.super); + return island->super.super.id; +} + +static loentity_id_t loworld_template_add_character_random_enemy_( + const loworld_template_building_param_t* param, + uint64_t seed, + loentity_id_t ground, + float pos) { + assert(param != NULL); + + locharacter_base_t* base = locharacter_pool_create(param->pools->character); + + bool built = false; + const uint64_t type = (seed = chaos_xorshift(seed))%3; + switch (type) { + case 1: + locharacter_warder_build(base, &(locharacter_warder_param_t) { + .ground = ground, + .pos = pos, + }); + built = true; + break; + case 2: + locharacter_scientist_build(base, &(locharacter_scientist_param_t) { + .ground = ground, + .pos = pos, + .direction = (seed = chaos_xorshift(seed))%2? 1: -1, + }); + built = true; + break; + } + if (!built) { + locharacter_cavia_build(base, &(locharacter_cavia_param_t) { + .ground = ground, + .pos = pos, + .direction = (seed = chaos_xorshift(seed))%2? 1: -1, + }); + } + loworld_chunk_add_entity(param->target, &base->super.super); + return base->super.super.id; +} + +bool loworld_template_building_param_valid( + const loworld_template_building_param_t* param) { + return + param != NULL && + param->target != NULL && + param->pools != NULL; +} + +void loworld_template_metaphysical_gate_build_chunk( + const loworld_template_building_param_t* param) { + assert(loworld_template_building_param_valid(param)); + + loworld_template_add_ground_island_( + param, &vec2(.5f, .2f), &vec2(.5f, .1f)); + + const loentity_id_t ground = loworld_template_add_ground_island_( + param, &vec2(.5f, .45f), &vec2(.2f, .02f)); + + locharacter_base_t* encephalon = + locharacter_pool_create(param->pools->character); + locharacter_encephalon_build(encephalon, ground); + loworld_chunk_add_entity(param->target, &encephalon->super.super); +} + +void loworld_template_open_space_build_chunk( + const loworld_template_building_param_t* param) { + assert(loworld_template_building_param_valid(param)); + + uint64_t s = param->seed; + + const size_t enemy_count = (s = chaos_xorshift(s))%3+1; + + const loentity_id_t ground = loworld_template_add_ground_island_( + param, &vec2(.5f, .2f), &vec2(.47f, .01f)); + + for (size_t i = 0; i < enemy_count; ++i) { + loworld_template_add_character_random_enemy_( + param, (s = chaos_xorshift(s)), ground, (2.0f/enemy_count*i - 1)*.75f); + } +} + +void loworld_template_broken_open_space_build_chunk( + const loworld_template_building_param_t* param) { + assert(loworld_template_building_param_valid(param)); + + uint64_t s = param->seed; + + const loentity_id_t floor1 = loworld_template_add_ground_island_( + param, &vec2(.2f, .2f), &vec2(.18f, .01f)); + if ((s = chaos_xorshift(s))%2) { + loworld_template_add_character_random_enemy_( + param, (s = chaos_xorshift(s)), floor1, .5f); + } + + const loentity_id_t floor2 = loworld_template_add_ground_island_( + param, &vec2(.8f, .2f), &vec2(.18f, .01f)); + if ((s = chaos_xorshift(s))%2) { + loworld_template_add_character_random_enemy_( + param, (s = chaos_xorshift(s)), floor2, .5f); + } + + loworld_template_add_ground_island_( + param, &vec2(.5f, .05f), &vec2(.15f, .01f)); +} + +void loworld_template_passage_build_chunk( + const loworld_template_building_param_t* param) { + assert(loworld_template_building_param_valid(param)); + + uint64_t s = param->seed; + + const loentity_id_t floor = loworld_template_add_ground_island_( + param, &vec2(.5f, .25f), &vec2(.5f, .01f)); + if ((s = chaos_xorshift(s))%2) { + loworld_template_add_character_random_enemy_( + param, (s = chaos_xorshift(s)), floor, .5f); + } + + const loentity_id_t ceiling = loworld_template_add_ground_island_( + param, &vec2(.55f, .4f), &vec2(.3f, .007f)); + if ((s = chaos_xorshift(s))%2) { + loworld_template_add_character_random_enemy_( + param, (s = chaos_xorshift(s)), ceiling, .2f); + } +} + +void loworld_template_broken_passage_build_chunk( + const loworld_template_building_param_t* param) { + assert(loworld_template_building_param_valid(param)); + + uint64_t s = param->seed; + + const loentity_id_t floor1 = loworld_template_add_ground_island_( + param, &vec2(.15f, .25f), &vec2(.15f, .01f)); + if ((s = chaos_xorshift(s))%2) { + loworld_template_add_character_random_enemy_( + param, (s = chaos_xorshift(s)), floor1, .5f); + } + + const loentity_id_t floor2 = loworld_template_add_ground_island_( + param, &vec2(.45f, .25f), &vec2(.1f, .01f)); + if ((s = chaos_xorshift(s))%2) { + loworld_template_add_character_random_enemy_( + param, (s = chaos_xorshift(s)), floor2, .5f); + } + + const loentity_id_t floor3 = loworld_template_add_ground_island_( + param, &vec2(.85f, .25f), &vec2(.15f, .01f)); + if ((s = chaos_xorshift(s))%2) { + loworld_template_add_character_random_enemy_( + param, (s = chaos_xorshift(s)), floor3, .5f); + } + + const uint64_t layout = (s = chaos_xorshift(s))%3; + if (layout == 0 || layout == 1) { + const loentity_id_t ceiling = loworld_template_add_ground_island_( + param, &vec2(.2f, .4f), &vec2(.15f, .007f)); + if ((s = chaos_xorshift(s))%2) { + loworld_template_add_character_random_enemy_( + param, (s = chaos_xorshift(s)), ceiling, .5f); + } + } + if (layout == 0 || layout == 2) { + const loentity_id_t ceiling = loworld_template_add_ground_island_( + param, &vec2(.7f, .38f), &vec2(.12f, .007f)); + if ((s = chaos_xorshift(s))%2) { + loworld_template_add_character_random_enemy_( + param, (s = chaos_xorshift(s)), ceiling, .5f); + } + } +} + +void loworld_template_stairs_build_chunk( + const loworld_template_building_param_t* param) { + assert(loworld_template_building_param_valid(param)); + + uint64_t s = param->seed; + + const loentity_id_t floor1 = loworld_template_add_ground_island_( + param, &vec2(.5f, .3f), &vec2(.5f, .015f)); + if ((s = chaos_xorshift(s))%2) { + loworld_template_add_character_random_enemy_( + param, (s = chaos_xorshift(s)), floor1, .5f); + } + + bool layout = (s = chaos_xorshift(s))%2; + const loentity_id_t floor2 = loworld_template_add_ground_island_( + param, &vec2(layout? .3f: .6f, .5f), &vec2(.2f, .015f)); + if ((s = chaos_xorshift(s))%2) { + loworld_template_add_character_random_enemy_( + param, (s = chaos_xorshift(s)), floor2, .5f); + } + + layout = !layout; + const loentity_id_t floor3 = loworld_template_add_ground_island_( + param, &vec2(layout? .2f: .8f, .7f), &vec2(.18f, .007f)); + if ((s = chaos_xorshift(s))%2) { + loworld_template_add_character_random_enemy_( + param, (s = chaos_xorshift(s)), floor3, .5f); + } + + const loentity_id_t floor4 = loworld_template_add_ground_island_( + param, &vec2(.5f, .9f), &vec2(.32f, .007f)); + if ((s = chaos_xorshift(s))%2) { + loworld_template_add_character_random_enemy_( + param, (s = chaos_xorshift(s)), floor4, .5f); + } +} + +void loworld_template_boss_theists_child_build_chunk( + const loworld_template_building_param_t* param) { + assert(loworld_template_building_param_valid(param)); + + const loentity_id_t ground = loworld_template_add_ground_island_( + param, &vec2(.5f, .1f), &vec2(.5f, .05f)); + + locharacter_base_t* boss = locharacter_pool_create(param->pools->character); + locharacter_theists_child_build(boss, ground); + loworld_chunk_add_entity(param->target, &boss->super.super); +} + +void loworld_template_boss_big_warder_build_chunk( + const loworld_template_building_param_t* param) { + assert(loworld_template_building_param_valid(param)); + + const loentity_id_t ground = loworld_template_add_ground_island_( + param, &vec2(.5f, .1f), &vec2(.5f, .05f)); + + locharacter_base_t* boss = locharacter_pool_create(param->pools->character); + locharacter_big_warder_build(boss, ground); + loworld_chunk_add_entity(param->target, &boss->super.super); +} + +void loworld_template_boss_greedy_scientist_build_chunk( + const loworld_template_building_param_t* param) { + assert(loworld_template_building_param_valid(param)); + + const loentity_id_t ground = loworld_template_add_ground_island_( + param, &vec2(.5f, .1f), &vec2(.5f, .05f)); + + locharacter_base_t* boss = locharacter_pool_create(param->pools->character); + locharacter_greedy_scientist_build(boss, ground); + loworld_chunk_add_entity(param->target, &boss->super.super); +} diff --git a/core/loworld/template.h b/core/loworld/template.h new file mode 100644 index 0000000..0c8ee28 --- /dev/null +++ b/core/loworld/template.h @@ -0,0 +1,138 @@ +#pragma once + +#include +#include +#include + +#include "./chunk.h" +#include "./poolset.h" + +#define LOWORLD_TEMPLATE_MAX_CHARACTERS_PER_CHUNK 5 +#define LOWORLD_TEMPLATE_MAX_GROUNDS_PER_CHUNK 5 + +typedef struct { + loworld_chunk_t* target; + + const loworld_poolset_t* pools; + uint64_t seed; +} loworld_template_building_param_t; + +bool +loworld_template_building_param_valid( + const loworld_template_building_param_t* param +); + +/* [metaphysical gate] + * 2 grounds (=) and the one encephalon statue (E) + * + * E + * ====== + * + * ================== + */ +void +loworld_template_metaphysical_gate_build_chunk( + const loworld_template_building_param_t* param +); + +/* [open space] + * 1 ground (=) and 0~3 enemies (E) + * + * + * E E E + * ================== + */ +void +loworld_template_open_space_build_chunk( + const loworld_template_building_param_t* param +); + +/* [broken open space] + * 1 ground (=) and 0~2 enemies (E) + * + * + * E E + * ======== ======== + * + * ====== + */ +void +loworld_template_broken_open_space_build_chunk( + const loworld_template_building_param_t* param +); + +/* [passage] + * 1 ground (=), ceiling (-), and 0~2 enemies (E) + * + * E + * ---------- + * E + * ================= + */ +void +loworld_template_passage_build_chunk( + const loworld_template_building_param_t* param +); + +/* [broken passage] + * 3 grounds (=), 1 or 2 ceilings (-), and 0~5 enemies (E) + * + * E E + * ---- ---- + * E E E + * ==== ==== ===== + */ +void +loworld_template_broken_passage_build_chunk( + const loworld_template_building_param_t* param +); + +/* [stairs] + * 4 grounds (=), and 0~4 enemies (E) + * + * E + * ========= + * E + * ====== + * E + * ========= + * E + * ================== + */ +void +loworld_template_stairs_build_chunk( + const loworld_template_building_param_t* param +); + +/* [BOSS: thiest's child] + * 1 ground (=), and The Theist's Child (E) + * + * E + * ================= + */ +void +loworld_template_boss_theists_child_build_chunk( + const loworld_template_building_param_t* param +); + +/* [BOSS: big warder] + * 1 ground (=), and The Big Warder (E) + * + * E + * ================= + */ +void +loworld_template_boss_big_warder_build_chunk( + const loworld_template_building_param_t* param +); + +/* [BOSS: greedy scientist] + * 1 ground (=), and The Greedy Scientist (E) + * + * E + * ================= + */ +void +loworld_template_boss_greedy_scientist_build_chunk( + const loworld_template_building_param_t* param +); diff --git a/core/loworld/test.h b/core/loworld/test.h new file mode 100644 index 0000000..b88be43 --- /dev/null +++ b/core/loworld/test.h @@ -0,0 +1,146 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include "core/lobullet/base.h" +#include "core/lobullet/bomb.h" +#include "core/lobullet/linear.h" +#include "core/locharacter/base.h" +#include "core/locharacter/big_warder.h" +#include "core/locharacter/cavia.h" +#include "core/locharacter/encephalon.h" +#include "core/locharacter/greedy_scientist.h" +#include "core/locharacter/theists_child.h" +#include "core/locharacter/scientist.h" +#include "core/locharacter/warder.h" +#include "core/locharacter/pool.h" +#include "core/loentity/entity.h" +#include "core/loground/base.h" +#include "core/loground/island.h" +#include "core/loground/pool.h" + +#include "./poolset.h" + +# define pack_and_unpack_(type, name) do { \ + msgpack_sbuffer_clear(buf); \ + loentity_pack(&base->super.super, &packer); \ + loentity_delete(&base->super.super); \ + \ + size_t offset = 0; \ + const msgpack_unpack_return r = \ + msgpack_unpack_next(upk, buf->data, buf->size, &offset); \ + if (r != MSGPACK_UNPACK_SUCCESS) { \ + fprintf(stderr, #type"_"#name": invalid msgpack format\n"); \ + abort(); \ + } \ + if (!type##_base_unpack(base, &upk->data)) { \ + fprintf(stderr, #type"_"#name": failed to unpack\n"); \ + abort(); \ + } \ + loentity_delete(&base->super.super); \ + printf(#type"_"#name": pack test passed\n"); \ + } while (0) + +static inline void loworld_test_packing_grounds( + loground_base_t* base, msgpack_sbuffer* buf, msgpack_unpacked* upk) { + assert(base != NULL); + assert(buf != NULL); + assert(upk != NULL); + + msgpack_packer packer; + msgpack_packer_init(&packer, buf, msgpack_sbuffer_write); + + loground_island_build( + base, &locommon_position(0, 0, vec2(0, 0)), &vec2(1, 1)); + pack_and_unpack_(loground, island); +} + +static inline void loworld_test_packing_bullets( + lobullet_base_t* base, msgpack_sbuffer* buf, msgpack_unpacked* upk) { + assert(base != NULL); + assert(buf != NULL); + assert(upk != NULL); + + msgpack_packer packer; + msgpack_packer_init(&packer, buf, msgpack_sbuffer_write); + + lobullet_bomb_square_build( + base, &((lobullet_bomb_param_t) { .step = 1, })); + pack_and_unpack_(lobullet, bomb); + + lobullet_bomb_triangle_build( + base, &((lobullet_bomb_param_t) { .step = 1, })); + pack_and_unpack_(lobullet, bomb); + + lobullet_linear_light_build( + base, &((lobullet_linear_param_t) { .duration = 1, })); + pack_and_unpack_(lobullet, linear); + + lobullet_linear_triangle_build( + base, &((lobullet_linear_param_t) { .duration = 1, })); + pack_and_unpack_(lobullet, linear); +} + +static inline void loworld_test_packing_characters( + locharacter_base_t* base, msgpack_sbuffer* buf, msgpack_unpacked* upk) { + assert(base != NULL); + assert(buf != NULL); + assert(upk != NULL); + + msgpack_packer packer; + msgpack_packer_init(&packer, buf, msgpack_sbuffer_write); + + locharacter_big_warder_build(base, 0); + pack_and_unpack_(locharacter, big_warder); + + locharacter_cavia_build( + base, &((locharacter_cavia_param_t) { .direction = 1, })); + pack_and_unpack_(locharacter, cavia); + + locharacter_encephalon_build(base, 0); + pack_and_unpack_(locharacter, encephalon); + + locharacter_theists_child_build(base, 0); + pack_and_unpack_(locharacter, theists_child); + + locharacter_greedy_scientist_build(base, 0); + pack_and_unpack_(locharacter, greedy_scientist); + + locharacter_scientist_build(base, &((locharacter_scientist_param_t) {0})); + pack_and_unpack_(locharacter, scientist); + + locharacter_warder_build(base, &((locharacter_warder_param_t) {0})); + pack_and_unpack_(locharacter, warder); +} + +# undef pack_and_unpack_ + +static inline void loworld_test_packing(const loworld_poolset_t* pools) { + assert(pools != NULL); + + msgpack_sbuffer buf; + msgpack_sbuffer_init(&buf); + + msgpack_unpacked upk; + msgpack_unpacked_init(&upk); + + loground_base_t* ground = loground_pool_create(pools->ground); + loworld_test_packing_grounds(ground, &buf, &upk); + loentity_delete(&ground->super.super); + + lobullet_base_t* bullet = lobullet_pool_create(pools->bullet); + loworld_test_packing_bullets(bullet, &buf, &upk); + loentity_delete(&bullet->super.super); + + locharacter_base_t* chara = locharacter_pool_create(pools->character); + loworld_test_packing_characters(chara, &buf, &upk); + loentity_delete(&chara->super.super); + + msgpack_unpacked_destroy(&upk); + msgpack_sbuffer_destroy(&buf); +} diff --git a/core/loworld/view.c b/core/loworld/view.c new file mode 100644 index 0000000..fde93ff --- /dev/null +++ b/core/loworld/view.c @@ -0,0 +1,171 @@ +#include "./view.h" + +#include +#include + +#include "util/math/vector.h" +#include "util/memory/memory.h" + +#include "core/locommon/position.h" +#include "core/loentity/store.h" + +#include "./chunk.h" +#include "./store.h" + +#define LOWORLD_VIEW_CHUNK_LOAD_RANGE 2 + +struct loworld_view_t { + loworld_store_t* world; + loentity_store_t* entities; + + locommon_position_t looking; + + loworld_chunk_t* chunks + [LOWORLD_VIEW_CHUNK_LOAD_RANGE*2+1][LOWORLD_VIEW_CHUNK_LOAD_RANGE*2+1]; +}; + +static void loworld_view_stage_chunk_( + loworld_view_t* view, loworld_chunk_t* chunk) { + assert(view != NULL); + assert(chunk != NULL); + + const size_t len = container_array_get_length(chunk->entities); + for (size_t i = 0; i < len; ++i) { + /* moving ownership from the chunk to the store */ + loentity_store_add(view->entities, chunk->entities[i]); + } + container_array_resize(chunk->entities, 0); +} + +static void loworld_view_unstage_chunk_( + loworld_view_t* view, loworld_chunk_t* chunk) { + assert(view != NULL); + assert(chunk != NULL); + + loentity_store_iterator_t itr = {0}; + while (loentity_store_iterate_next(view->entities, &itr)) { + if (itr.entity->dont_save || + itr.entity->pos.chunk.x != chunk->pos.x || + itr.entity->pos.chunk.y != chunk->pos.y) { + continue; + } + + const size_t index = container_array_get_length(chunk->entities); + container_array_insert(chunk->entities, index); + + /* moving ownership from the store to the chunk */ + chunk->entities[index] = loentity_store_remove(view->entities, &itr); + } +} + +static void loworld_view_load_all_chunks_(loworld_view_t* view) { + assert(view != NULL); + + /* Unload all chunks before calling this function. */ + + static const int32_t r = LOWORLD_VIEW_CHUNK_LOAD_RANGE; + + for (int32_t x = -r; x <= r; ++x) { + for (int32_t y = -r; y <= r; ++y) { + loworld_chunk_t* chunk = loworld_store_load_chunk( + view->world, view->looking.chunk.x + x, view->looking.chunk.y + y); + view->chunks[y+r][x+r] = chunk; + loworld_view_stage_chunk_(view, chunk); + } + } +} + +static void loworld_view_unload_all_chunks_(loworld_view_t* view) { + assert(view != NULL); + + static const int32_t r = LOWORLD_VIEW_CHUNK_LOAD_RANGE; + + for (int32_t x = -r; x <= r; ++x) { + for (int32_t y = -r; y <= r; ++y) { + loworld_chunk_t* chunk = view->chunks[y+r][x+r]; + if (chunk == NULL) continue; + + loworld_view_unstage_chunk_(view, chunk); + loworld_store_unload_chunk(view->world, chunk); + } + } +} + +loworld_view_t* loworld_view_new( + loworld_store_t* world, + loentity_store_t* entities, + const locommon_position_t* looking) { + assert(world != NULL); + assert(entities != NULL); + assert(locommon_position_valid(looking)); + + loworld_view_t* view = memory_new(sizeof(*view)); + *view = (typeof(*view)) { + .world = world, + .entities = entities, + .looking = *looking, + }; + + loworld_view_load_all_chunks_(view); + return view; +} + +void loworld_view_delete(loworld_view_t* view) { + if (view == NULL) return; + + loworld_view_unload_all_chunks_(view); + + memory_delete(view); +} + +void loworld_view_update(loworld_view_t* view) { + assert(view != NULL); + + loentity_store_iterator_t itr = {0}; + while (loentity_store_iterate_next(view->entities, &itr)) { + if (!loentity_update(itr.entity)) { + loentity_delete(loentity_store_remove(view->entities, &itr)); + } + } +} + +void loworld_view_draw(loworld_view_t* view) { + assert(view != NULL); + + loentity_store_iterator_t itr = {0}; + while (loentity_store_iterate_next(view->entities, &itr)) { + loentity_draw(itr.entity, &view->looking); + } +} + +void loworld_view_look(loworld_view_t* view, const locommon_position_t* pos) { + assert(view != NULL); + assert(locommon_position_valid(pos)); + + const bool chunk_moved = + view->looking.chunk.x != pos->chunk.x || + view->looking.chunk.y != pos->chunk.y; + + view->looking = *pos; + + if (chunk_moved) { + loworld_view_unload_all_chunks_(view); + loworld_view_load_all_chunks_(view); + } +} + +void loworld_view_flush_store(loworld_view_t* view) { + assert(view != NULL); + + loworld_view_unload_all_chunks_(view); + loworld_store_flush(view->world); + loworld_view_load_all_chunks_(view); +} + +const loworld_chunk_t* loworld_view_get_looking_chunk( + const loworld_view_t* view) { + assert(view != NULL); + + static const int32_t r = LOWORLD_VIEW_CHUNK_LOAD_RANGE; + return view->chunks[r][r]; +} diff --git a/core/loworld/view.h b/core/loworld/view.h new file mode 100644 index 0000000..b4606ed --- /dev/null +++ b/core/loworld/view.h @@ -0,0 +1,49 @@ +#pragma once + +#include "core/locommon/position.h" +#include "core/loentity/store.h" + +#include "./chunk.h" +#include "./store.h" + +struct loworld_view_t; +typedef struct loworld_view_t loworld_view_t; + +loworld_view_t* /* OWNERSHIP */ +loworld_view_new( + loworld_store_t* world, + loentity_store_t* entities, + const locommon_position_t* looking +); + +void +loworld_view_delete( + loworld_view_t* view /* OWNERSHIP */ +); + +void +loworld_view_update( + loworld_view_t* view +); + +void +loworld_view_draw( + loworld_view_t* view +); + +void +loworld_view_look( + loworld_view_t* view, + const locommon_position_t* pos +); + +/* Flushes all chunks safely including currently staged ones. */ +void +loworld_view_flush_store( + loworld_view_t* view +); + +const loworld_chunk_t* /* ALIVES UNTIL NEXT OPERATION */ +loworld_view_get_looking_chunk( + const loworld_view_t* view +); diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt new file mode 100644 index 0000000..8ffbfdd --- /dev/null +++ b/thirdparty/CMakeLists.txt @@ -0,0 +1,2 @@ +set(BUILD_TESTING OFF) +add_subdirectory(miniaudio) diff --git a/thirdparty/miniaudio/CMakeLists.txt b/thirdparty/miniaudio/CMakeLists.txt new file mode 100644 index 0000000..a3fa401 --- /dev/null +++ b/thirdparty/miniaudio/CMakeLists.txt @@ -0,0 +1,8 @@ +add_library(miniaudio + miniaudio.c +) +target_link_libraries(miniaudio + $<$:dl> + $<$:m> + $<$:pthread> +) diff --git a/thirdparty/miniaudio/miniaudio.c b/thirdparty/miniaudio/miniaudio.c new file mode 100644 index 0000000..a765e3a --- /dev/null +++ b/thirdparty/miniaudio/miniaudio.c @@ -0,0 +1,2 @@ +#define MINIAUDIO_IMPLEMENTATION +#include "./miniaudio.h" diff --git a/thirdparty/miniaudio/miniaudio.h b/thirdparty/miniaudio/miniaudio.h new file mode 100644 index 0000000..e3ac273 --- /dev/null +++ b/thirdparty/miniaudio/miniaudio.h @@ -0,0 +1,10 @@ +#pragma once + +#define MA_NO_ENCODING +#define MA_NO_FLAC + +#ifndef NDEBUG +# define MA_DEBUG_OUTPUT +#endif /* NDEBUG */ + +#include "./repo/miniaudio.h" diff --git a/thirdparty/miniaudio/repo b/thirdparty/miniaudio/repo new file mode 160000 index 0000000..b80f7f9 --- /dev/null +++ b/thirdparty/miniaudio/repo @@ -0,0 +1 @@ +Subproject commit b80f7f949152f93a0af499b4d6d07b8e60d0e673 diff --git a/tool/CMakeLists.txt b/tool/CMakeLists.txt new file mode 100644 index 0000000..e69de29 diff --git a/tool/bin2c.sh b/tool/bin2c.sh new file mode 100755 index 0000000..83a1d28 --- /dev/null +++ b/tool/bin2c.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +set -e + +name=`echo "$1" | sed -e 's/[^A-z0-9_]/_/g'` + +hpath="$2.h" +cpath="$2.c" + +data=`cat $in | xxd -i` +size=`echo $data | wc -w` + +echo "const char $name[$size] = {$data};" > $cpath +echo "extern const char $name[$size];" > $hpath diff --git a/tool/leak-check.sh b/tool/leak-check.sh new file mode 100755 index 0000000..c73c62e --- /dev/null +++ b/tool/leak-check.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +# This script works only with memory-trace file generated in Linux. + +file="memory-trace" +if [ ! -f $file ]; then + echo no memory trace file found + exit 1 +fi + +declare -A addrs + +while read line; do + IFS=" " read -r type addr1 trash1 addr2 trash2 <<< $line + + case "$type" in + "new") + addrs[$addr1]="allocated" + ;; + "resize") + addrs[$addr1]="freed" + addrs[$addr2]="allocated" + ;; + "delete") + addrs[$addr1]="freed" + ;; + *) ;; + esac +done < $file + +for addr in "${!addrs[@]}"; do + if [ "${addrs[$addr]}" == "allocated" ]; then + echo $addr + fi +done diff --git a/util/CMakeLists.txt b/util/CMakeLists.txt new file mode 100644 index 0000000..7e72637 --- /dev/null +++ b/util/CMakeLists.txt @@ -0,0 +1,12 @@ +add_subdirectory(chaos) +add_subdirectory(coly2d) +add_subdirectory(container) +add_subdirectory(conv) +add_subdirectory(dictres) +add_subdirectory(gleasy) +add_subdirectory(glyphas) +add_subdirectory(jukebox) +add_subdirectory(math) +add_subdirectory(memory) +add_subdirectory(mpkutil) +add_subdirectory(parsarg) diff --git a/util/chaos/CMakeLists.txt b/util/chaos/CMakeLists.txt new file mode 100644 index 0000000..8e15598 --- /dev/null +++ b/util/chaos/CMakeLists.txt @@ -0,0 +1,7 @@ +add_library(chaos + abchash.c + xorshift.c +) +target_link_libraries(chaos + math +) diff --git a/util/chaos/abchash.c b/util/chaos/abchash.c new file mode 100644 index 0000000..bb49bf5 --- /dev/null +++ b/util/chaos/abchash.c @@ -0,0 +1,32 @@ +#include "./abchash.h" + +#include +#include +#include +#include + +#define CHAOS_ABCHASH_GET_CODE(c) ( \ + ('0' <= c && c <= '9')? c-'0': \ + ('a' <= c && c <= 'z')? c-'a'+10: \ + (c == '_') ? 36: UINT8_MAX) + +#define CHAOS_ABCHASH_MAX_CODE 36 + +uint64_t chaos_abchash(const char* str, size_t len) { + assert(chaos_abchash_validate(str, len)); + + uint64_t ans = 0; + for (size_t i = 0; i < len; ++i) { + ans = ans*(CHAOS_ABCHASH_MAX_CODE+1) + CHAOS_ABCHASH_GET_CODE(str[i]); + } + return ans; +} + +bool chaos_abchash_validate(const char* str, size_t len) { + for (size_t i = 0; i < len; ++i) { + if (CHAOS_ABCHASH_GET_CODE(str[i]) > CHAOS_ABCHASH_MAX_CODE) { + return false; + } + } + return len > 0; +} diff --git a/util/chaos/abchash.h b/util/chaos/abchash.h new file mode 100644 index 0000000..8c8e6f3 --- /dev/null +++ b/util/chaos/abchash.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include + +/* DON'T EXPECT THIS ALGORITHM TO BE ONE-WAY */ + +uint64_t +chaos_abchash( + const char* str, + size_t len +); + +bool +chaos_abchash_validate( + const char* str, + size_t len +); diff --git a/util/chaos/xorshift.c b/util/chaos/xorshift.c new file mode 100644 index 0000000..6e11b84 --- /dev/null +++ b/util/chaos/xorshift.c @@ -0,0 +1,19 @@ +#include "./xorshift.h" + +#include +#include + +uint64_t chaos_xorshift(uint64_t seed) { + seed = seed ^ (seed << 13); + seed = seed ^ (seed >> 7); + return seed ^ (seed << 17); +} + +float chaos_xorshift_fract(uint64_t seed, uint64_t* next_seed) { + static const uint64_t period = 10000; + + seed = chaos_xorshift(seed); + if (next_seed != NULL) *next_seed = seed; + + return seed%period*1.f/period; +} diff --git a/util/chaos/xorshift.h b/util/chaos/xorshift.h new file mode 100644 index 0000000..7dc7344 --- /dev/null +++ b/util/chaos/xorshift.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +uint64_t +chaos_xorshift( + uint64_t seed +); + +float +chaos_xorshift_fract( + uint64_t seed, + uint64_t* next_seed /* NULLABLE */ +); diff --git a/util/coly2d/CMakeLists.txt b/util/coly2d/CMakeLists.txt new file mode 100644 index 0000000..4e55508 --- /dev/null +++ b/util/coly2d/CMakeLists.txt @@ -0,0 +1,7 @@ +add_library(coly2d + hittest.c + shape.c +) +target_link_libraries(coly2d + math +) diff --git a/util/coly2d/README.md b/util/coly2d/README.md new file mode 100644 index 0000000..f4b3665 --- /dev/null +++ b/util/coly2d/README.md @@ -0,0 +1,4 @@ +coly +==== + +simplest 2D collision calculation library for C diff --git a/util/coly2d/hittest.c b/util/coly2d/hittest.c new file mode 100644 index 0000000..36db351 --- /dev/null +++ b/util/coly2d/hittest.c @@ -0,0 +1,171 @@ +#include "./hittest.h" + +#include +#include + +#include "util/math/algorithm.h" +#include "util/math/vector.h" + +bool coly2d_hittest_point_and_rect( + const vec2_t* pos1, const vec2_t* pos2, const vec2_t* size2) { + assert(vec2_valid(pos1)); + assert(vec2_valid(pos2)); + assert(vec2_valid(size2)); + + vec2_t rpos; + vec2_sub(&rpos, pos2, pos1); + return + MATH_ABS(rpos.x) < size2->x && + MATH_ABS(rpos.y) < size2->y; +} + +bool coly2d_hittest_point_and_triangle( + const vec2_t* pos1, + const vec2_t* pos2_a, + const vec2_t* pos2_b, + const vec2_t* pos2_c) { + assert(vec2_valid(pos1)); + assert(vec2_valid(pos2_a)); + assert(vec2_valid(pos2_b)); + assert(vec2_valid(pos2_c)); + + vec2_t v1, v2; + + vec2_sub(&v1, pos1, pos2_a); + vec2_sub(&v2, pos2_b, pos2_a); + float a = vec2_cross(&v1, &v2); + + vec2_sub(&v1, pos1, pos2_b); + vec2_sub(&v2, pos2_c, pos2_b); + if (a * vec2_cross(&v1, &v2) <= 0) return false; + + vec2_sub(&v1, pos1, pos2_c); + vec2_sub(&v2, pos2_a, pos2_c); + return a*vec2_cross(&v1, &v2) > 0; +} + +bool coly2d_hittest_lineseg_and_lineseg( + const vec2_t* pos1_st, + const vec2_t* pos1_ed, + const vec2_t* pos2_st, + const vec2_t* pos2_ed) { + assert(vec2_valid(pos1_st)); + assert(vec2_valid(pos1_ed)); + assert(vec2_valid(pos2_st)); + assert(vec2_valid(pos2_ed)); + + vec2_t x; + vec2_sub(&x, pos1_ed, pos1_st); + vec2_t a; + vec2_sub(&a, pos2_st, pos1_st); + vec2_t b; + vec2_sub(&b, pos2_ed, pos1_st); + + vec2_t y; + vec2_sub(&y, pos2_ed, pos2_st); + vec2_t c; + vec2_sub(&c, pos1_st, pos2_st); + vec2_t d; + vec2_sub(&d, pos1_ed, pos2_st); + + return + vec2_cross(&x, &a) * vec2_cross(&x, &b) < 0 && + vec2_cross(&y, &c) * vec2_cross(&y, &d) < 0; +} + +bool coly2d_hittest_lineseg_and_rect( + const vec2_t* pos1_st, + const vec2_t* pos1_ed, + const vec2_t* pos2, + const vec2_t* size2) { + assert(vec2_valid(pos1_st)); + assert(vec2_valid(pos1_ed)); + assert(vec2_valid(pos2)); + assert(vec2_valid(size2)); + + if (coly2d_hittest_point_and_rect(pos1_st, pos2, size2) || + coly2d_hittest_point_and_rect(pos1_ed, pos2, size2)) { + return true; + } + + const float left = pos2->x - size2->x; + const float right = pos2->x + size2->x; + const float top = pos2->y + size2->y; + const float bottom = pos2->y - size2->y; + + const vec2_t v1 = vec2(left, top); + const vec2_t v2 = vec2(left, bottom); + const vec2_t v3 = vec2(right, bottom); + const vec2_t v4 = vec2(right, top); + + return + coly2d_hittest_lineseg_and_lineseg(pos1_st, pos1_ed, &v1, &v2) || + coly2d_hittest_lineseg_and_lineseg(pos1_st, pos1_ed, &v2, &v3) || + coly2d_hittest_lineseg_and_lineseg(pos1_st, pos1_ed, &v3, &v4) || + coly2d_hittest_lineseg_and_lineseg(pos1_st, pos1_ed, &v4, &v1); +} + +bool coly2d_hittest_lineseg_and_ellipse( + const vec2_t* pos1_st, + const vec2_t* pos1_ed, + const vec2_t* pos2, + const vec2_t* size2) { + assert(vec2_valid(pos1_st)); + assert(vec2_valid(pos1_ed)); + assert(vec2_valid(pos2)); + assert(vec2_valid(size2)); + + vec2_t p2 = *pos2; + p2.x /= size2->x; + p2.y /= size2->y; + + vec2_t p1st = *pos1_st; + p1st.x /= size2->x; + p1st.y /= size2->y; + + vec2_t c1; + vec2_sub(&c1, &p2, &p1st); + if (vec2_pow_length(&c1) < 1) return true; + + vec2_t p1ed = *pos1_ed; + p1ed.x /= size2->x; + p1ed.y /= size2->y; + + vec2_t c2; + vec2_sub(&c2, &p2, &p1ed); + if (vec2_pow_length(&c2) < 1) return true; + + vec2_t ed; + vec2_sub(&ed, &p1ed, &p1st); + const float cross = vec2_cross(&ed, &c1); + if (MATH_ABS(cross) >= vec2_length(&ed)) return false; + + vec2_t st; + vec2_sub(&st, &p1st, &p1ed); + return vec2_dot(&c1, &ed)*vec2_dot(&c2, &st) >= 0; +} + +bool coly2d_hittest_lineseg_and_triangle( + const vec2_t* pos1_st, + const vec2_t* pos1_ed, + const vec2_t* pos2_a, + const vec2_t* pos2_b, + const vec2_t* pos2_c) { + assert(vec2_valid(pos1_st)); + assert(vec2_valid(pos1_ed)); + assert(vec2_valid(pos2_a)); + assert(vec2_valid(pos2_b)); + assert(vec2_valid(pos2_c)); + + /* TODO(catfoot): Tomas Moller's algorithm may make this function faster. */ + + if (coly2d_hittest_lineseg_and_lineseg(pos1_st, pos1_ed, pos2_a, pos2_b) || + coly2d_hittest_lineseg_and_lineseg(pos1_st, pos1_ed, pos2_b, pos2_c) || + coly2d_hittest_lineseg_and_lineseg(pos1_st, pos1_ed, pos2_c, pos2_a)) { + return true; + } + + return + coly2d_hittest_point_and_triangle(pos1_st, pos2_a, pos2_b, pos2_c) || + coly2d_hittest_point_and_triangle(pos1_ed, pos2_a, pos2_b, pos2_c); +} diff --git a/util/coly2d/hittest.h b/util/coly2d/hittest.h new file mode 100644 index 0000000..33f7660 --- /dev/null +++ b/util/coly2d/hittest.h @@ -0,0 +1,53 @@ +#pragma once + +#include + +#include "util/math/vector.h" + +bool +coly2d_hittest_point_and_rect( + const vec2_t* pos1, + const vec2_t* pos2, + const vec2_t* size2 +); + +bool +coly2d_hittest_point_and_triangle( + const vec2_t* pos1, + const vec2_t* pos2_a, + const vec2_t* pos2_b, + const vec2_t* pos2_c +); + +bool +coly2d_hittest_lineseg_and_lineseg( + const vec2_t* pos1_st, + const vec2_t* pos1_ed, + const vec2_t* pos2_st, + const vec2_t* pos2_ed +); + +bool +coly2d_hittest_lineseg_and_rect( + const vec2_t* pos1_st, + const vec2_t* pos1_ed, + const vec2_t* pos2, + const vec2_t* size2 +); + +bool +coly2d_hittest_lineseg_and_ellipse( + const vec2_t* pos1_st, + const vec2_t* pos1_ed, + const vec2_t* pos2, + const vec2_t* size2 +); + +bool +coly2d_hittest_lineseg_and_triangle( + const vec2_t* pos1_st, + const vec2_t* pos1_ed, + const vec2_t* pos2_a, + const vec2_t* pos2_b, + const vec2_t* pos2_c +); diff --git a/util/coly2d/shape.c b/util/coly2d/shape.c new file mode 100644 index 0000000..c909d42 --- /dev/null +++ b/util/coly2d/shape.c @@ -0,0 +1,54 @@ +#include "./shape.h" + +#include +#include +#include +#include + +#include "util/math/algorithm.h" +#include "util/math/vector.h" + +#include "./hittest.h" + +bool coly2d_shape_valid(const coly2d_shape_t* shape) { + return + shape != NULL && + vec2_valid(&shape->size) && + MATH_FLOAT_VALID(shape->angle); +} + +bool coly2d_shape_hittest_lineseg( + const coly2d_shape_t* shape, const vec2_t* st, const vec2_t* ed) { + assert(coly2d_shape_valid(shape)); + assert(vec2_valid(st)); + assert(vec2_valid(ed)); + + static const vec2_t origin = vec2(0, 0); + + const float s = -sin(shape->angle); + const float c = cos(shape->angle); + + const vec2_t st_ = vec2(st->x*c-st->y*s, st->x*s+st->y*c); + const vec2_t ed_ = vec2(ed->x*c-ed->y*s, ed->x*s+ed->y*c); + + switch (shape->type) { + case COLY2D_SHAPE_TYPE_LINE: + return coly2d_hittest_lineseg_and_lineseg( + &st_, &ed_, &origin, &shape->size); + case COLY2D_SHAPE_TYPE_RECT: + return coly2d_hittest_lineseg_and_rect( + &st_, &ed_, &origin, &shape->size); + case COLY2D_SHAPE_TYPE_TRIANGLE: + return coly2d_hittest_lineseg_and_triangle( + &st_, &ed_, + &vec2( shape->size.x, 0), + &vec2(-shape->size.x, shape->size.y), + &vec2(-shape->size.x, -shape->size.y)); + case COLY2D_SHAPE_TYPE_ELLIPSE: + return coly2d_hittest_lineseg_and_ellipse( + &st_, &ed_, &origin, &shape->size); + } + + assert(false); + return false; +} diff --git a/util/coly2d/shape.h b/util/coly2d/shape.h new file mode 100644 index 0000000..daa1486 --- /dev/null +++ b/util/coly2d/shape.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +#include "util/math/vector.h" + +typedef enum { + COLY2D_SHAPE_TYPE_LINE, + COLY2D_SHAPE_TYPE_RECT, + COLY2D_SHAPE_TYPE_TRIANGLE, + COLY2D_SHAPE_TYPE_ELLIPSE, +} coly2d_shape_type_t; + +typedef struct { + coly2d_shape_type_t type; + vec2_t size; + float angle; +} coly2d_shape_t; + +#define coly2d_shape_line(ed, a) ((coly2d_shape_t) { \ + .type = COLY2D_SHAPE_TYPE_LINE, \ + .size = ed, \ + .angle = a, \ + }) +#define coly2d_shape_rect(sz, a) ((coly2d_shape_t) { \ + .type = COLY2D_SHAPE_TYPE_RECT, \ + .size = sz, \ + .angle = a, \ + }) +#define coly2d_shape_triangle(sz, a) ((coly2d_shape_t) { \ + .type = COLY2D_SHAPE_TYPE_TRIANGLE, \ + .size = sz, \ + .angle = a, \ + }) +#define coly2d_shape_ellipse(sz, a) ((coly2d_shape_t) { \ + .type = COLY2D_SHAPE_TYPE_ELLIPSE, \ + .size = sz, \ + .angle = a, \ + }) + +bool +coly2d_shape_valid( + const coly2d_shape_t* shape +); + +bool +coly2d_shape_hittest_lineseg( + const coly2d_shape_t* shape, + const vec2_t* st, + const vec2_t* ed +); diff --git a/util/container/CMakeLists.txt b/util/container/CMakeLists.txt new file mode 100644 index 0000000..7bfa905 --- /dev/null +++ b/util/container/CMakeLists.txt @@ -0,0 +1,11 @@ +add_library(container + array.c +) +target_link_libraries(container memory) + +if (BUILD_TESTING) + add_executable(container-test test.c) + target_link_libraries(container-test container) + + add_test(container-test container-test) +endif() diff --git a/util/container/array.c b/util/container/array.c new file mode 100644 index 0000000..5eb607d --- /dev/null +++ b/util/container/array.c @@ -0,0 +1,90 @@ +#include "./array.h" + +#include +#include +#include +#include + +#include "util/memory/memory.h" + +typedef struct { + size_t length; + size_t reserved; +} container_array_header_t; + +void container_array_delete(void* array) { + if (array == NULL) return; + memory_delete(array - sizeof(container_array_header_t)); +} + +void container_array_reserve_(void** array, size_t elmlen, size_t len) { + assert(array != NULL); + + if (len == 0) return; + + container_array_header_t* ptr; + if (*array == NULL) { + ptr = memory_new(sizeof(*ptr) + len*elmlen); + *ptr = (typeof(*ptr)) { + .length = 0, + .reserved = len, + }; + } else { + ptr = *array - sizeof(*ptr); + if (ptr->reserved >= len) return; + + ptr = memory_resize(ptr, sizeof(*ptr) + len*elmlen); + ptr->reserved = len; + } + *array = ptr + 1; +} + +void container_array_resize_(void** array, size_t elmlen, size_t len) { + assert(array != NULL); + + container_array_reserve_(array, elmlen, len); + if (*array == NULL) return; + + container_array_header_t* ptr = *array - sizeof(*ptr); + ptr->length = len; +} + +void container_array_insert_(void** array, size_t elmlen, size_t index) { + assert(array != NULL); + + const size_t len = container_array_get_length(*array) + 1; + assert(index < len); + container_array_resize_(array, elmlen, len); + + uint8_t* src = *array + (index)*elmlen; + memmove(src + elmlen, src, (len-index-1)*elmlen); +} + +void container_array_remove_(void** array, size_t elmlen, size_t index) { + const size_t oldlen = container_array_get_length(*array); + assert(index < oldlen); + + const size_t len = oldlen-1; + + uint8_t* dst = *array + index*elmlen; + memmove(dst, dst + elmlen, (len-index)*elmlen); + container_array_resize_(array, elmlen, len); +} + +void* container_array_duplicate_(const void* array, size_t elmlen) { + if (array == NULL) return NULL; + const container_array_header_t* ptr = array - sizeof(*ptr); + + const size_t sz = sizeof(*ptr) + ptr->length*elmlen; + + container_array_header_t* ptrdup = memory_new(sz); + memcpy(ptrdup, ptr, sz); + ptrdup->reserved = ptr->length; + return (void*) (ptrdup + 1); +} + +size_t container_array_get_length_(const void* array) { + if (array == NULL) return 0; + const container_array_header_t* ptr = array - sizeof(*ptr); + return ptr->length; +} diff --git a/util/container/array.h b/util/container/array.h new file mode 100644 index 0000000..b6939da --- /dev/null +++ b/util/container/array.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +#define CONTAINER_ARRAY + +void container_array_delete(void* array); + +#define container_array_reserve(array, len) \ + container_array_reserve_((void**) &array, sizeof(*array), len) +void container_array_reserve_(void** array, size_t elmlen, size_t len); + +#define container_array_resize(array, len) \ + container_array_resize_((void**) &array, sizeof(*array), len) +void container_array_resize_(void** array, size_t elmlen, size_t len); + +#define container_array_insert(array, index) \ + container_array_insert_((void**) &array, sizeof(*array), index) +void container_array_insert_(void** array, size_t elmlen, size_t index); + +#define container_array_remove(array, index) \ + container_array_remove_((void**) &array, sizeof(*array), index) +void container_array_remove_(void** array, size_t elmlen, size_t index); + +#define container_array_duplicate(array) \ + (typeof(array)) container_array_duplicate_( \ + (const void*) array, sizeof(*array)) +void* container_array_duplicate_(const void* array, size_t elmlen); + +#define container_array_get_length(array) container_array_get_length_(array) +size_t container_array_get_length_(const void* array); diff --git a/util/container/test.c b/util/container/test.c new file mode 100644 index 0000000..b0141f4 --- /dev/null +++ b/util/container/test.c @@ -0,0 +1,48 @@ +#undef NDEBUG + +#include +#include + +#include "./array.h" +static void test_array() { + int32_t* numbers = NULL; + assert(container_array_get_length(numbers) == 0); + + container_array_reserve(numbers, 10); + for (int32_t i = 50; i < 100; ++i) { + container_array_insert(numbers, i-50); + numbers[i-50] = i; + } + container_array_reserve(numbers, 10); + for (int32_t i = 0; i < 50; ++i) { + container_array_insert(numbers, i); + numbers[i] = i; + } + for (int32_t i = 0; i < 100; ++i) { + assert(numbers[i] == i); + } + + for (int32_t i = 99; i >= 0; i-=2) { + container_array_remove(numbers, i); + } + for (int32_t i = 0; i < 100; i+=2) { + assert(numbers[i/2] == i); + } + assert(container_array_get_length(numbers) == 50); + + container_array_resize(numbers, 25); + assert(container_array_get_length(numbers) == 25); + + int32_t* dup = container_array_duplicate(numbers); + for (int32_t i = 0; i < 25; ++i) { + assert(numbers[i] == dup[i]); + } + container_array_delete(dup); + + container_array_delete(numbers); +} + +int main(void) { + test_array(); + return 0; +} diff --git a/util/conv/CMakeLists.txt b/util/conv/CMakeLists.txt new file mode 100644 index 0000000..62e257c --- /dev/null +++ b/util/conv/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(conv + charcode.c +) diff --git a/util/conv/charcode.c b/util/conv/charcode.c new file mode 100644 index 0000000..ee4d31f --- /dev/null +++ b/util/conv/charcode.c @@ -0,0 +1,36 @@ +#include "./charcode.h" + +#include +#include +#include + +size_t conv_charcode_utf8_to_utf32(uint32_t* c, const char* s, size_t len) { + assert(c != NULL); + assert(s != NULL); + + if ((*s & 0x80) == 0x00) { + if (len < 1) return 0; + *c = s[0]; + return 1; + } + if ((*s & 0xE0) == 0xC0) { + if (len < 2) return 0; + *c = (s[0] & 0x1F) << 6 | (s[1] & 0x3F); + return 2; + } + if ((*s & 0xF0) == 0xE0) { + if (len < 3) return 0; + *c = (s[0] & 0x0F) << 12 | (s[1] & 0x3F) << 6 | (s[2] & 0x3F); + return 3; + } + if ((*s & 0xF8) == 0xF0) { + if (len < 4) return 0; + *c = + (s[0] & 0x07) << 18 | + (s[1] & 0x3F) << 12 | + (s[2] & 0x3F) << 6 | + (s[3] & 0x3F); + return 4; + } + return 0; +} diff --git a/util/conv/charcode.h b/util/conv/charcode.h new file mode 100644 index 0000000..95b0a86 --- /dev/null +++ b/util/conv/charcode.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +size_t +conv_charcode_utf8_to_utf32( + uint32_t* c, + const char* s, + size_t len +); diff --git a/util/dictres/CMakeLists.txt b/util/dictres/CMakeLists.txt new file mode 100644 index 0000000..b704332 --- /dev/null +++ b/util/dictres/CMakeLists.txt @@ -0,0 +1,11 @@ +add_library(dictres + dictres.c +) +target_link_libraries(dictres + chaos +) + +if (BUILD_TESTING) + add_executable(dictres-test test.c) + target_link_libraries(dictres-test dictres) +endif() diff --git a/util/dictres/dictres.c b/util/dictres/dictres.c new file mode 100644 index 0000000..8c759f5 --- /dev/null +++ b/util/dictres/dictres.c @@ -0,0 +1,87 @@ +#include "./dictres.h" + +#include +#include +#include +#include +#include +#include + +#include "util/chaos/abchash.h" + +static void dictres_calculate_hash_(dictres_item_t* items, size_t length) { + assert(items != NULL || length == 0); + + for (size_t i = 0; i < length; ++i) { + dictres_item_t* item = &items[i]; + + const size_t len = strlen(item->key); + if (!chaos_abchash_validate(item->key, len)) { + fprintf(stderr, "dictress: invalid key, '%s'\n", item->key); + abort(); + } + item->hash = chaos_abchash(item->key, len); + } +} + +static void dictres_sort_(dictres_item_t* items, size_t length) { + assert(items != NULL || length == 0); + + for (size_t i = 0; i < length; ++i) { + for (size_t j = 1; j < length-i; ++j) { + if (items[j-1].hash > items[j].hash) { + /* Speed of swapping can be improved but simple is best. :) */ + const dictres_item_t temp = items[j-1]; + items[j-1] = items[j]; + items[j] = temp; + } + } + } +} + +static void dictres_validate_(dictres_item_t* items, size_t length) { + assert(items != NULL || length == 0); + + for (size_t i = 1; i < length; ++i) { + if (items[i-1].hash == items[i].hash) { + fprintf(stderr, + "dictres: detected hash collision between '%s' and '%s'\n", + items[i-1].key, items[i].key); + } + } +} + +void dictres_optimize_(dictres_item_t* items, size_t length) { + assert(items != NULL || length == 0); + + dictres_calculate_hash_(items, length); + dictres_sort_(items, length); + dictres_validate_(items, length); +} + +const char* dictres_find_( + const dictres_item_t* items, size_t length, const char* key) { + assert(items != NULL || length == 0); + + const size_t keylen = strlen(key); + assert(chaos_abchash_validate(key, keylen)); + + const uint64_t hash = chaos_abchash(key, keylen); + + size_t l = 0, r = length-1; + while (l <= r && r < length) { + const size_t i = (l+r)/2; + + if (items[i].hash == hash) { + return items[i].value; + } + if (items[i].hash < hash) { + l = i+1; + } else { + if (i == 0) break; + r = i-1; + } + } + fprintf(stderr, "dictres: cannot find key, '%s'\n", key); + abort(); +} diff --git a/util/dictres/dictres.h b/util/dictres/dictres.h new file mode 100644 index 0000000..c0b8db4 --- /dev/null +++ b/util/dictres/dictres.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +typedef struct { + const char* key; + const char* value; + uint64_t hash; +} dictres_item_t; + +void +dictres_optimize_( + dictres_item_t* items, + size_t length +); +#define dictres_optimize(items) \ + dictres_optimize_(items, sizeof(items)/sizeof(items[0])) + +const char* +dictres_find_( + const dictres_item_t* items, + size_t length, + const char* key +); +#define dictres_find(items, key) \ + dictres_find_(items, sizeof(items)/sizeof(items[0]), key) diff --git a/util/dictres/test.c b/util/dictres/test.c new file mode 100644 index 0000000..11495c1 --- /dev/null +++ b/util/dictres/test.c @@ -0,0 +1,27 @@ +#undef NDEBUG + +#include +#include + +#include "./dictres.h" + +static dictres_item_t dictres_[] = { + {"app_name", + "dictres-test"}, + {"description", + "dictres provides a static dictionary type which finds value from key fast."}, + {"bye", + "see you!"}, +}; + +int main(void) { + dictres_optimize(dictres_); + + printf("%s\n", dictres_find(dictres_, "app_name")); + printf("====\n"); + + printf("%s\n", dictres_find(dictres_, "description")); + printf("%s\n", dictres_find(dictres_, "bye")); + + return EXIT_SUCCESS; +} diff --git a/util/gleasy/CMakeLists.txt b/util/gleasy/CMakeLists.txt new file mode 100644 index 0000000..9dbf764 --- /dev/null +++ b/util/gleasy/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(gleasy + atlas.c + framebuffer.c + program.c + shader.c +) +target_link_libraries(gleasy + GLEW::GLEW + OpenGL::GL + + container + math + memory +) diff --git a/util/gleasy/atlas.c b/util/gleasy/atlas.c new file mode 100644 index 0000000..501ab1f --- /dev/null +++ b/util/gleasy/atlas.c @@ -0,0 +1,194 @@ +#include "./atlas.h" + +#include +#include +#include + +#include + +#include "util/container/array.h" +#include "util/math/algorithm.h" +#include "util/memory/memory.h" + +#include "./misc.h" +#include "./texture.h" + +struct gleasy_atlas_t { + int32_t width; + int32_t height; + + int32_t consumed_y; + int32_t consumed_x; + + int32_t line_height; + + gleasy_texture_2d_t tex; + + CONTAINER_ARRAY uint8_t* resize_buffer; +}; + +static void gleasy_atlas_resize_bitmap_( + gleasy_atlas_t* atlas, + gleasy_atlas_bitmap_t* out, + const gleasy_atlas_bitmap_t* in) { + assert(atlas != NULL); + assert(out != NULL); + assert(in != NULL); + + *out = (typeof(*out)) { + .width = math_int32_next_power2(MATH_MAX(in->width, 4)), + .height = math_int32_next_power2(MATH_MAX(in->height, 4)), + .format = in->format, + .type = in->type, + .buffer = in->buffer, + }; + if (out->width == in->width && out->height == in->height) return; + + const size_t type = GLEASY_GET_BYTE_SIZE_OF_TYPE(out->type); + const size_t fmt = GLEASY_GET_CHANNELS_OF_TEXTURE_FORMAT(out->format); + + const size_t pixel = type*fmt; + assert(pixel > 0); + + container_array_resize(atlas->resize_buffer, out->width*out->height*pixel); + out->buffer = atlas->resize_buffer; + + const int32_t ymax = out->height * pixel; + const int32_t xmax = out->width * pixel; + + const uint8_t* src = in->buffer; + uint8_t* dst = atlas->resize_buffer; + for (int32_t y = 0; y < ymax; ++y) { + for (int32_t x = 0; x < xmax; ++x) { + if (x < in->width && y < in->height) { + *dst = *(src++); + } else { + *dst = 0; + } + ++dst; + } + } +} + +static bool gleasy_atlas_allocate_area_( + gleasy_atlas_t* atlas, + int32_t* x, + int32_t* y, + int32_t width, + int32_t height, + int32_t actual_width, + int32_t actual_height) { + assert(atlas != NULL); + assert(x != NULL); + assert(y != NULL); + assert(width > 0); + assert(height > 0); + assert(actual_width > 0); + assert(actual_height > 0); + + if (atlas->consumed_x + actual_width > atlas->width) { + atlas->consumed_x = 0; + atlas->consumed_y += atlas->line_height+1; + atlas->line_height = 0; + } + if (atlas->consumed_y + actual_height > atlas->height) { + return false; + } + + *x = atlas->consumed_x; + *y = atlas->consumed_y; + + atlas->consumed_x += width+1; + atlas->line_height = MATH_MAX(atlas->line_height, height); + return true; +} + +gleasy_atlas_t* gleasy_atlas_new( + GLenum format, int32_t width, int32_t height, bool aa) { + assert(width > 0); + assert(height > 0); + + gleasy_atlas_t* atlas = memory_new(sizeof(*atlas)); + *atlas = (typeof(*atlas)) { + .width = math_int32_next_power2(width), + .height = math_int32_next_power2(height), + }; + + glGenTextures(1, &atlas->tex); + glBindTexture(GL_TEXTURE_2D, atlas->tex); + + const GLenum filter = aa? GL_LINEAR: GL_NEAREST; + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); + + glTexImage2D(GL_TEXTURE_2D, 0, format, + atlas->width, atlas->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + + assert(glGetError() == GL_NO_ERROR); + + container_array_reserve(atlas->resize_buffer, width*height*4); + return atlas; +} + +void gleasy_atlas_delete(gleasy_atlas_t* atlas) { + if (atlas == NULL) return; + + glDeleteTextures(1, &atlas->tex); + + container_array_delete(atlas->resize_buffer); + + memory_delete(atlas); +} + +void gleasy_atlas_clear(gleasy_atlas_t* atlas) { + assert(atlas != NULL); + + atlas->consumed_y = 0; + atlas->consumed_x = 0; + + atlas->line_height = 0; +} + +bool gleasy_atlas_add( + gleasy_atlas_t* atlas, + gleasy_atlas_geometry_t* geo, + const gleasy_atlas_bitmap_t* bitmap) { + assert(atlas != NULL); + assert(geo != NULL); + assert(bitmap != NULL); + + gleasy_atlas_bitmap_t resized; + gleasy_atlas_resize_bitmap_(atlas, &resized, bitmap); + + int32_t x, y; + if (!gleasy_atlas_allocate_area_(atlas, + &x, &y, bitmap->width, bitmap->height, resized.width, resized.height)) { + return false; + } + + *geo = (typeof(*geo)) { + .left = x*1.0f / atlas->width, + .right = (x+bitmap->width)*1.0f / atlas->width, + .top = y*1.0f / atlas->height, + .bottom = (y+bitmap->height)*1.0f / atlas->height, + }; + + glBindTexture(GL_TEXTURE_2D, atlas->tex); + glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, + resized.width, + resized.height, + resized.format, + resized.type, + resized.buffer); + assert(glGetError() == GL_NO_ERROR); + + return true; +} + +gleasy_texture_2d_t gleasy_atlas_get_texture(const gleasy_atlas_t* atlas) { + assert(atlas != NULL); + + return atlas->tex; +} diff --git a/util/gleasy/atlas.h b/util/gleasy/atlas.h new file mode 100644 index 0000000..5f63ca3 --- /dev/null +++ b/util/gleasy/atlas.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include + +#include + +#include "./texture.h" + +struct gleasy_atlas_t; +typedef struct gleasy_atlas_t gleasy_atlas_t; + +typedef struct { + float left; + float right; + float top; + float bottom; +} gleasy_atlas_geometry_t; + +typedef struct { + int32_t width; + int32_t height; + GLenum format; + GLenum type; + const uint8_t* buffer; +} gleasy_atlas_bitmap_t; + +gleasy_atlas_t* +gleasy_atlas_new( + GLenum format, + int32_t width, + int32_t height, + bool aa +); + +void +gleasy_atlas_delete( + gleasy_atlas_t* atlas +); + +void +gleasy_atlas_clear( + gleasy_atlas_t* atlas +); + +bool +gleasy_atlas_add( + gleasy_atlas_t* atlas, + gleasy_atlas_geometry_t* geo, + const gleasy_atlas_bitmap_t* bitmap +); + +gleasy_texture_2d_t +gleasy_atlas_get_texture( + const gleasy_atlas_t* atlas +); diff --git a/util/gleasy/buffer.h b/util/gleasy/buffer.h new file mode 100644 index 0000000..1a80718 --- /dev/null +++ b/util/gleasy/buffer.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +typedef GLuint gleasy_buffer_array_t; +typedef GLuint gleasy_buffer_element_array_t; +typedef GLuint gleasy_buffer_uniform_t; diff --git a/util/gleasy/framebuffer.c b/util/gleasy/framebuffer.c new file mode 100644 index 0000000..670fef5 --- /dev/null +++ b/util/gleasy/framebuffer.c @@ -0,0 +1,96 @@ +#include "./framebuffer.h" + +#include +#include +#include +#include +#include + +#include + +#include "./texture.h" + +void gleasy_framebuffer_initialize( + gleasy_framebuffer_t* fb, int32_t width, int32_t height, int32_t samples) { + assert(fb != NULL); + assert(width > 0); + assert(height > 0); + assert(0 < samples && samples < GL_MAX_SAMPLES); + + *fb = (typeof(*fb)) { + .width = width, + .height = height, + }; + + glGenRenderbuffers(1, &fb->colorbuf_msaa); + glBindRenderbuffer(GL_RENDERBUFFER, fb->colorbuf_msaa); + glRenderbufferStorageMultisample( + GL_RENDERBUFFER, samples, GL_RGBA, width, height); + assert(glGetError() == GL_NO_ERROR); + + glGenTextures(1, &fb->colorbuf); + glBindTexture(GL_TEXTURE_2D, fb->colorbuf); + glTexImage2D(GL_TEXTURE_2D, + 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + assert(glGetError() == GL_NO_ERROR); + + GLuint fbo[2]; + glGenFramebuffers(2, fbo); + fb->id_msaa = fbo[0]; + fb->id = fbo[1]; + + glBindFramebuffer(GL_FRAMEBUFFER, fb->id_msaa); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, fb->colorbuf_msaa); + assert(glGetError() == GL_NO_ERROR); + + glBindFramebuffer(GL_FRAMEBUFFER, fb->id); + glFramebufferTexture2D(GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb->colorbuf, 0); + assert(glGetError() == GL_NO_ERROR); +} + +void gleasy_framebuffer_deinitialize(gleasy_framebuffer_t* fb) { + assert(fb != NULL); + + GLuint fbo[] = {fb->id_msaa, fb->id}; + glDeleteFramebuffers(2, fbo); + + glDeleteTextures(1, &fb->colorbuf); + + glDeleteRenderbuffers(1, &fb->colorbuf_msaa); +} + +void gleasy_framebuffer_bind(const gleasy_framebuffer_t* fb) { + assert(fb != NULL); + + glBindFramebuffer(GL_FRAMEBUFFER, fb->id_msaa); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + fprintf(stderr, "gleasy: framebuffer is in invalid state\n"); + abort(); + } +} + +void gleasy_framebuffer_flush(const gleasy_framebuffer_t* fb) { + assert(fb != NULL); + + gleasy_framebuffer_flush_to_other(fb, fb->id); +} + +void gleasy_framebuffer_flush_to_other(const gleasy_framebuffer_t* fb, GLuint id) { + assert(fb != NULL); + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, id); + glBindFramebuffer(GL_READ_FRAMEBUFFER, fb->id_msaa); + glBlitFramebuffer( + 0, 0, fb->width, fb->height, + 0, 0, fb->width, fb->height, + GL_COLOR_BUFFER_BIT, + GL_NEAREST); + assert(glGetError() == GL_NO_ERROR); +} diff --git a/util/gleasy/framebuffer.h b/util/gleasy/framebuffer.h new file mode 100644 index 0000000..0ccb58e --- /dev/null +++ b/util/gleasy/framebuffer.h @@ -0,0 +1,47 @@ +#pragma once + +#include + +#include + +#include "./texture.h" + +typedef struct { + int32_t width; + int32_t height; + + GLuint id_msaa; + GLuint colorbuf_msaa; + + GLuint id; + gleasy_texture_2d_t colorbuf; +} gleasy_framebuffer_t; + +void +gleasy_framebuffer_initialize( + gleasy_framebuffer_t* fb, + int32_t width, + int32_t height, + int32_t samples +); + +void +gleasy_framebuffer_deinitialize( + gleasy_framebuffer_t* fb +); + +void +gleasy_framebuffer_bind( + const gleasy_framebuffer_t* fb +); + +void +gleasy_framebuffer_flush( + const gleasy_framebuffer_t* fb +); + +void +gleasy_framebuffer_flush_to_other( + const gleasy_framebuffer_t* fb, + GLuint id +); diff --git a/util/gleasy/misc.h b/util/gleasy/misc.h new file mode 100644 index 0000000..aab2d94 --- /dev/null +++ b/util/gleasy/misc.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +#define GLEASY_GET_BYTE_SIZE_OF_TYPE(t) ( \ + (t) == GL_UNSIGNED_BYTE? 1: \ + (t) == GL_BYTE? 1: \ + (t) == GL_SHORT? 2: \ + (t) == GL_UNSIGNED_SHORT? 2: \ + (t) == GL_INT? 4: \ + (t) == GL_UNSIGNED_INT? 4: \ + (t) == GL_FLOAT? 4: 0) + +#define GLEASY_GET_CHANNELS_OF_TEXTURE_FORMAT(f) ( \ + (f) == GL_RGBA? 4: \ + (f) == GL_RGB? 3: \ + (f) == GL_RG? 2: \ + (f) == GL_RED? 1: 0) diff --git a/util/gleasy/program.c b/util/gleasy/program.c new file mode 100644 index 0000000..f0669e6 --- /dev/null +++ b/util/gleasy/program.c @@ -0,0 +1,66 @@ +#include "./program.h" + +#include +#include +#include +#include + +#include + +#include "util/math/algorithm.h" + +#include "./shader.h" + +gleasy_program_t gleasy_program_new( + const char* header, size_t header_len, + const char* vsrc, size_t vsrc_len, + const char* fsrc, size_t fsrc_len) { + assert(header != NULL || header_len == 0); + assert(vsrc != NULL || vsrc_len == 0); + assert(fsrc != NULL || fsrc_len == 0); + + gleasy_program_t program = glCreateProgram(); + if (program == 0) { + fprintf(stderr, "failed to create program"); + abort(); + } + + const gleasy_shader_t vshader = + gleasy_shader_new(GL_VERTEX_SHADER, header, header_len, vsrc, vsrc_len); + const gleasy_shader_t fshader = + gleasy_shader_new(GL_FRAGMENT_SHADER, header, header_len, fsrc, fsrc_len); + + glAttachShader(program, vshader); + glDeleteShader(vshader); + + glAttachShader(program, fshader); + glDeleteShader(fshader); + + glLinkProgram(program); + + GLint ok; + glGetProgramiv(program, GL_LINK_STATUS, &ok); + if (ok == GL_FALSE) { + char log[1024]; + const int len = + gleasy_program_get_log(program, log, sizeof(log)/sizeof(log[0])); + fprintf(stderr, "failed to link program\n%.*s\n", len, log); + abort(); + } + return program; +} + +size_t gleasy_program_get_log( + gleasy_program_t program, char* dst, size_t maxlen) { + assert(program != 0); + + GLint len; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &len); + if (dst == NULL) return len; + + len = MATH_MIN(len, (GLint) maxlen); + if (len == 0) return 0; + + glGetProgramInfoLog(program, len, &len, (GLchar*) dst); + return len; +} diff --git a/util/gleasy/program.h b/util/gleasy/program.h new file mode 100644 index 0000000..46b2370 --- /dev/null +++ b/util/gleasy/program.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include + +typedef GLuint gleasy_program_t; + +gleasy_program_t /* OWNERSHIP */ +gleasy_program_new( + const char* header, + size_t header_len, + const char* vsrc, + size_t vsrc_len, + const char* fsrc, + size_t fsrc_len +); + +size_t +gleasy_program_get_log( + gleasy_program_t program, + char* dst, /* when NULL, returns actual size */ + size_t maxlen +); diff --git a/util/gleasy/shader.c b/util/gleasy/shader.c new file mode 100644 index 0000000..4f35f80 --- /dev/null +++ b/util/gleasy/shader.c @@ -0,0 +1,57 @@ +#include "./shader.h" + +#include +#include +#include +#include +#include + +#include + +#include "util/math/algorithm.h" + +gleasy_shader_t gleasy_shader_new( + GLenum type, + const char* header, size_t header_len, + const char* src, size_t src_len) { + assert(header != NULL || header_len == 0); + assert(src != NULL || src_len == 0); + + const GLuint shader = glCreateShader(type); + if (shader == 0) { + fprintf(stderr, "failed to create shader\n"); + abort(); + } + + const GLchar* srcs[] = { header, src, }; + const GLint lens[] = { header_len, src_len, }; + const size_t offset = (header_len == 0? 1: 0); + glShaderSource(shader, 2-offset, srcs+offset, lens+offset); + glCompileShader(shader); + + GLint ok; + glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); + if (ok == GL_FALSE) { + char log[1024]; + const int loglen = + gleasy_shader_get_log(shader, log, sizeof(log)/sizeof(log[0])); + fprintf(stderr, "failed to compile shader\n%.*s\n", loglen, log); + abort(); + } + return shader; +} + +size_t gleasy_shader_get_log( + gleasy_shader_t shader, char* dst, size_t maxlen) { + assert(shader != 0); + + GLint len; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len); + if (dst == NULL) return len; + + len = MATH_MIN(len, (GLint) maxlen); + if (len == 0) return 0; + + glGetShaderInfoLog(shader, len, &len, (GLchar*) dst); + return len; +} diff --git a/util/gleasy/shader.h b/util/gleasy/shader.h new file mode 100644 index 0000000..3db6319 --- /dev/null +++ b/util/gleasy/shader.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +#include + +typedef GLuint gleasy_shader_t; + +gleasy_shader_t /* OWNERSHIP */ +gleasy_shader_new( + GLenum type, + const char* header, + size_t header_len, + const char* src, + size_t src_len +); + +size_t +gleasy_shader_get_log( + gleasy_shader_t shader, + char* dst, /* when NULL, returns actual size */ + size_t maxlen +); diff --git a/util/gleasy/texture.h b/util/gleasy/texture.h new file mode 100644 index 0000000..9a4a294 --- /dev/null +++ b/util/gleasy/texture.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +typedef GLuint gleasy_texture_2d_t; diff --git a/util/glyphas/CMakeLists.txt b/util/glyphas/CMakeLists.txt new file mode 100644 index 0000000..2cc583c --- /dev/null +++ b/util/glyphas/CMakeLists.txt @@ -0,0 +1,33 @@ +add_library(glyphas + aligner.c + block.c + cache.c + context.c + drawer.c + face.c + glyph.c +) +target_any_sources(glyphas + drawer.vshader + drawer.fshader +) +target_link_libraries(glyphas + Freetype::Freetype + GLEW::GLEW + OpenGL::GL + + chaos + container + conv + gleasy + math + memory +) + +if (BUILD_TESTING) + add_executable(glyphas-test test.c) + target_link_libraries(glyphas-test + SDL2::SDL2 + + glyphas) +endif() diff --git a/util/glyphas/README.md b/util/glyphas/README.md new file mode 100644 index 0000000..68c36fe --- /dev/null +++ b/util/glyphas/README.md @@ -0,0 +1,4 @@ +glyphas +==== + +GLYPH texture atlAS library wrapping FreeType diff --git a/util/glyphas/aligner.c b/util/glyphas/aligner.c new file mode 100644 index 0000000..99a2438 --- /dev/null +++ b/util/glyphas/aligner.c @@ -0,0 +1,78 @@ +#include "./aligner.h" + +#include +#include +#include +#include + +#include "util/gleasy/atlas.h" +#include "util/math/algorithm.h" + +#include "./cache.h" + +void glyphas_aligner_initialize( + glyphas_aligner_t* aligner, + glyphas_aligner_direction_t dir, + int32_t lineheight, + int32_t maxpos) { + assert(aligner != NULL); + assert(maxpos > 0); + + *aligner = (typeof(*aligner)) { + .dir = dir, + .lineheight = lineheight, + .maxpos = maxpos, + .line = lineheight, + }; +} + +void glyphas_aligner_deinitialize(glyphas_aligner_t* aligner) { + assert(aligner != NULL); + +} + +void glyphas_aligner_reset(glyphas_aligner_t* aligner) { + assert(aligner != NULL); + + aligner->line = aligner->lineheight; + aligner->pos = 0; +} + +void glyphas_aligner_push_character( + glyphas_aligner_t* aligner, + int32_t* x, + int32_t* y, + const glyphas_cache_glyph_t* g) { + assert(aligner != NULL); + assert(x != NULL); + assert(y != NULL); + assert(g != NULL); + + switch (aligner->dir) { + case GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL: + if (aligner->pos + g->hmetrics.advance > aligner->maxpos) { + glyphas_aligner_break_line(aligner); + } + *x = aligner->pos + g->hmetrics.bear_x; + *y = g->hmetrics.bear_y + aligner->line; + aligner->pos += g->hmetrics.advance; + break; + case GLYPHAS_ALIGNER_DIRECTION_VERTICAL: + if (aligner->pos - g->vmetrics.advance < -aligner->maxpos) { + glyphas_aligner_break_line(aligner); + } + *x = aligner->pos - g->vmetrics.bear_x + aligner->line; + *y = -g->vmetrics.bear_y; + aligner->pos -= g->vmetrics.advance; + break; + default: + assert(false); + } +} + +void glyphas_aligner_break_line(glyphas_aligner_t* aligner) { + assert(aligner != NULL); + + aligner->pos = 0; + aligner->line += aligner->lineheight; +} diff --git a/util/glyphas/aligner.h b/util/glyphas/aligner.h new file mode 100644 index 0000000..b424264 --- /dev/null +++ b/util/glyphas/aligner.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include + +#include "./cache.h" + +typedef enum { + GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL, + GLYPHAS_ALIGNER_DIRECTION_VERTICAL, +} glyphas_aligner_direction_t; + +typedef struct { + glyphas_aligner_direction_t dir; + int32_t lineheight; + int32_t maxpos; + + int32_t pos; + int32_t line; +} glyphas_aligner_t; + +void +glyphas_aligner_initialize( + glyphas_aligner_t* aligner, + glyphas_aligner_direction_t dir, + int32_t lineheight, + int32_t maxpos +); + +void +glyphas_aligner_deinitialize( + glyphas_aligner_t* aligner +); + +void +glyphas_aligner_reset( + glyphas_aligner_t* aligner +); + +void +glyphas_aligner_push_character( + glyphas_aligner_t* aligner, + int32_t* x, + int32_t* y, + const glyphas_cache_glyph_t* g +); + +void +glyphas_aligner_break_line( + glyphas_aligner_t* aligner +); diff --git a/util/glyphas/block.c b/util/glyphas/block.c new file mode 100644 index 0000000..d62bc92 --- /dev/null +++ b/util/glyphas/block.c @@ -0,0 +1,216 @@ +#include "./block.h" + +#include +#include +#include +#include +#include + +#include "util/chaos/xorshift.h" +#include "util/conv/charcode.h" +#include "util/math/algorithm.h" +#include "util/math/vector.h" +#include "util/gleasy/atlas.h" +#include "util/memory/memory.h" + +#include "./cache.h" + +struct glyphas_block_t { + glyphas_aligner_t aligner; + + size_t reserve; + size_t length; + glyphas_block_item_t items[1]; +}; + +glyphas_block_t* glyphas_block_new( + glyphas_aligner_direction_t dir, + int32_t lineheight, + int32_t maxpos, + size_t reserve) { + assert(maxpos > 0); + assert(reserve > 0); + + glyphas_block_t* block = + memory_new(sizeof(*block) + (reserve-1)*sizeof(block->items[0])); + *block = (typeof(*block)) { + .reserve = reserve, + }; + + glyphas_aligner_initialize(&block->aligner, dir, lineheight, maxpos); + + return block; +} + +void glyphas_block_delete(glyphas_block_t* block) { + if (block == NULL) return; + + glyphas_aligner_deinitialize(&block->aligner); + + memory_delete(block); +} + +void glyphas_block_clear(glyphas_block_t* block) { + assert(block != NULL); + + glyphas_aligner_reset(&block->aligner); + block->length = 0; +} + +void glyphas_block_add_character( + glyphas_block_t* block, + const glyphas_cache_glyph_t* glyph, + const vec4_t* color) { + assert(block != NULL); + assert(glyph != NULL); + assert(vec4_valid(color)); + + if (block->length >= block->reserve) { + fprintf(stderr, "glyphas block overflow\n"); + abort(); + } + + int32_t x, y; + glyphas_aligner_push_character(&block->aligner, &x, &y, glyph); + if (glyph->geometry.left >= glyph->geometry.right) return; + + block->items[block->length++] = (glyphas_block_item_t) { + .pos = vec2(x, y), + .size = vec2(glyph->width, glyph->height), + .color = *color, + .uv = glyph->geometry, + }; +} + +void glyphas_block_add_characters( + glyphas_block_t* block, + glyphas_cache_t* cache, + const vec4_t* color, + const char* str, + size_t len) { + assert(block != NULL); + assert(cache != NULL); + assert(vec4_valid(color)); + + const char* end = str + len; + while (str < end) { + uint32_t c; + const size_t consumed = conv_charcode_utf8_to_utf32(&c, str, end-str); + str += MATH_MAX(consumed, 1); + if (consumed == 0) continue; + + if (c == '\n') { + glyphas_block_break_line(block); + continue; + } + + const glyphas_cache_glyph_t* g = glyphas_cache_add_glyph(cache, c); + if (g == NULL) continue; + + glyphas_block_add_character(block, g, color); + } +} + +void glyphas_block_break_line(glyphas_block_t* block) { + assert(block != NULL); + + glyphas_aligner_break_line(&block->aligner); +} + +void glyphas_block_scale(glyphas_block_t* block, const vec2_t* s) { + assert(block != NULL); + assert(vec2_valid(s)); + + for (size_t i = 0; i < block->length; ++i) { + vec2_t* pos = &block->items[i].pos; + vec2_t* sz = &block->items[i].size; + + pos->x *= s->x; + pos->y *= s->y; + sz->x *= s->x; + sz->y *= s->y; + } +} + +void glyphas_block_translate(glyphas_block_t* block, const vec2_t* v) { + assert(block != NULL); + assert(vec2_valid(v)); + + for (size_t i = 0; i < block->length; ++i) { + vec2_addeq(&block->items[i].pos, v); + } +} + +void glyphas_block_set_origin(glyphas_block_t* block, const vec2_t* r) { + assert(block !=NULL); + assert(vec2_valid(r)); + + vec2_t offset, size; + glyphas_block_calculate_geometry(block, &offset, &size); + + size.x *= r->x; + size.y *= r->y; + + vec2_addeq(&offset, &size); + vec2_muleq(&offset, -1); + glyphas_block_translate(block, &offset); +} + +void glyphas_block_set_alpha(glyphas_block_t* block, float a) { + assert(block != NULL); + + for (size_t i = 0; i < block->length; ++i) { + block->items[i].color.w = a; + } +} + +void glyphas_block_make_glitched(glyphas_block_t* block, uint64_t seed) { + assert(block != NULL); + +# define randf() (seed = chaos_xorshift(seed), seed%1000/1000.f) + + for (size_t i = 0; i < block->length; ++i) { + const vec2_t* sz = &block->items[i].size; + + const vec2_t shift = vec2((randf()-.5f)*sz->x*.5f, (randf()-.5f)*sz->y*.5f); + const float scale = randf()*.4f + .8f; + + vec2_addeq(&block->items[i].pos, &shift); + vec2_muleq(&block->items[i].size, scale); + } + +# undef randf +} + +void glyphas_block_calculate_geometry( + const glyphas_block_t* block, vec2_t* offset, vec2_t* size) { + assert(block != NULL); + assert(offset != NULL); + assert(size != NULL); + + vec2_t lb = vec2(0, 0), rt = vec2(0, 0); + for (size_t i = 0; i < block->length; ++i) { + const glyphas_block_item_t* item = &block->items[i]; + + const float l = item->pos.x; + const float r = l + item->size.x; + const float t = item->pos.y; + const float b = t - item->size.y; + + lb.x = MATH_MIN(lb.x, l); + lb.y = MATH_MIN(lb.y, b); + rt.x = MATH_MAX(rt.x, r); + rt.y = MATH_MAX(rt.y, t); + } + *offset = vec2(lb.x, rt.y); + vec2_sub(size, &rt, &lb); +} + +const glyphas_block_item_t* glyphas_block_get_items( + const glyphas_block_t* block, size_t* len) { + assert(block != NULL); + assert(len != NULL); + + *len = block->length; + return block->items; +} diff --git a/util/glyphas/block.h b/util/glyphas/block.h new file mode 100644 index 0000000..3e5b23f --- /dev/null +++ b/util/glyphas/block.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#include + +#include "util/math/vector.h" +#include "util/gleasy/atlas.h" + +#include "./aligner.h" +#include "./cache.h" + +struct glyphas_block_t; +typedef struct glyphas_block_t glyphas_block_t; + +typedef struct { + vec2_t pos; + vec2_t size; + vec4_t color; + gleasy_atlas_geometry_t uv; +} glyphas_block_item_t; + +glyphas_block_t* +glyphas_block_new( + glyphas_aligner_direction_t dir, + int32_t lineheight, + int32_t maxpos, + size_t reserve +); + +void +glyphas_block_delete( + glyphas_block_t* block +); + +void +glyphas_block_clear( + glyphas_block_t* block +); + +void +glyphas_block_add_character( + glyphas_block_t* block, + const glyphas_cache_glyph_t* glyph, + const vec4_t* color +); + +void +glyphas_block_add_characters( + glyphas_block_t* block, + glyphas_cache_t* cache, + const vec4_t* color, + const char* str, + size_t len +); + +void +glyphas_block_break_line( + glyphas_block_t* block +); + +void +glyphas_block_scale( + glyphas_block_t* block, + const vec2_t* translation +); + +void +glyphas_block_translate( + glyphas_block_t* block, + const vec2_t* translation +); + +void +glyphas_block_set_origin( + glyphas_block_t* block, + const vec2_t* r +); + +void +glyphas_block_set_alpha( + glyphas_block_t* block, + float a +); + +void +glyphas_block_make_glitched( + glyphas_block_t* block, + uint64_t seed +); + +void +glyphas_block_calculate_geometry( + const glyphas_block_t* block, + vec2_t* offset, + vec2_t* size +); + +const glyphas_block_item_t* +glyphas_block_get_items( + const glyphas_block_t* block, + size_t* len +); diff --git a/util/glyphas/cache.c b/util/glyphas/cache.c new file mode 100644 index 0000000..3116316 --- /dev/null +++ b/util/glyphas/cache.c @@ -0,0 +1,111 @@ +#include "./cache.h" + +#include +#include +#include + +#include + +#include "util/container/array.h" +#include "util/gleasy/atlas.h" +#include "util/memory/memory.h" + +#include "./face.h" + +struct glyphas_cache_t { + gleasy_atlas_t* atlas; + glyphas_face_t* face; + + int32_t char_width; + int32_t char_height; + + /* TODO(catfoot): linear search isn't efficient. :( */ + CONTAINER_ARRAY glyphas_cache_glyph_t* items; +}; + +glyphas_cache_t* glyphas_cache_new( + gleasy_atlas_t* atlas, + glyphas_face_t* face, + int32_t char_width, + int32_t char_height) { + assert(atlas != NULL); + assert(face != NULL); + assert(char_width > 0); + assert(char_height > 0); + + glyphas_cache_t* cache = memory_new(sizeof(*cache)); + *cache = (typeof(*cache)) { + .atlas = atlas, + .face = face, + .char_width = char_width, + .char_height = char_height, + }; + return cache; +} + +void glyphas_cache_delete(glyphas_cache_t* cache) { + if (cache == NULL) return; + + container_array_delete(cache->items); + + memory_delete(cache); +} + +const glyphas_cache_glyph_t* glyphas_cache_add_glyph( + glyphas_cache_t* cache, uint32_t unicode) { + assert(cache != NULL); + + const glyphas_cache_glyph_t* found = + glyphas_cache_lookup_glyph(cache, unicode); + if (found != NULL) return found; + + if (!glyphas_face_set_pixel_size( + cache->face, cache->char_width, cache->char_height)) { + return NULL; + } + if (!glyphas_face_render_glyph(cache->face, unicode)) { + return NULL; + } + + gleasy_atlas_geometry_t geo = {0}; + + const glyphas_glyph_t* rendered = &cache->face->glyph; + if (rendered->bitmap.width > 0) { + const gleasy_atlas_bitmap_t bmp = { + .width = rendered->bitmap.width, + .height = rendered->bitmap.height, + .buffer = rendered->bitmap.buffer, + .format = GL_RED, + .type = GL_UNSIGNED_BYTE, + }; + if (!gleasy_atlas_add(cache->atlas, &geo, &bmp)) { + return NULL; + } + } + + const size_t index = container_array_get_length(cache->items); + container_array_insert(cache->items, index); + + glyphas_cache_glyph_t* g = &cache->items[index]; + *g = (typeof(*g)) { + .unicode = unicode, + .width = rendered->bitmap.width, + .height = rendered->bitmap.height, + .geometry = geo, + .hmetrics = rendered->hmetrics, + .vmetrics = rendered->vmetrics, + }; + return g; +} + +const glyphas_cache_glyph_t* glyphas_cache_lookup_glyph( + const glyphas_cache_t* cache, uint32_t unicode) { + assert(cache != NULL); + + const size_t len = container_array_get_length(cache->items); + for (size_t i = 0; i < len; ++i) { + const glyphas_cache_glyph_t* g = &cache->items[i]; + if (g->unicode == unicode) return g; + } + return NULL; +} diff --git a/util/glyphas/cache.h b/util/glyphas/cache.h new file mode 100644 index 0000000..080f51b --- /dev/null +++ b/util/glyphas/cache.h @@ -0,0 +1,47 @@ +#pragma once + +#include + +#include "util/gleasy/atlas.h" + +#include "./face.h" +#include "./glyph.h" + +typedef struct { + uint32_t unicode; + + int32_t width; + int32_t height; + + gleasy_atlas_geometry_t geometry; + glyphas_glyph_metrics_t hmetrics; + glyphas_glyph_metrics_t vmetrics; +} glyphas_cache_glyph_t; + +struct glyphas_cache_t; +typedef struct glyphas_cache_t glyphas_cache_t; + +glyphas_cache_t* +glyphas_cache_new( + gleasy_atlas_t* atlas, + glyphas_face_t* face, + int32_t char_width, + int32_t char_height +); + +void +glyphas_cache_delete( + glyphas_cache_t* cache +); + +const glyphas_cache_glyph_t* /* NULLABLE */ +glyphas_cache_add_glyph( + glyphas_cache_t* cache, + uint32_t unicode +); + +const glyphas_cache_glyph_t* /* NULLABLE */ +glyphas_cache_lookup_glyph( + const glyphas_cache_t* cache, + uint32_t unicode +); diff --git a/util/glyphas/context.c b/util/glyphas/context.c new file mode 100644 index 0000000..6d9a8a2 --- /dev/null +++ b/util/glyphas/context.c @@ -0,0 +1,21 @@ +#include "./context.h" + +#include +#include + +#include +#include FT_FREETYPE_H + +void glyphas_context_initialize(glyphas_context_t* ctx) { + assert(ctx != NULL); + + *ctx = (typeof(*ctx)) {0}; + + FT_Init_FreeType(&ctx->ft); +} + +void glyphas_context_deinitialize(glyphas_context_t* ctx) { + assert(ctx != NULL); + + FT_Done_FreeType(ctx->ft); +} diff --git a/util/glyphas/context.h b/util/glyphas/context.h new file mode 100644 index 0000000..193995d --- /dev/null +++ b/util/glyphas/context.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include FT_FREETYPE_H + +typedef struct { + FT_Library ft; +} glyphas_context_t; + +void +glyphas_context_initialize( + glyphas_context_t* ctx +); + +void +glyphas_context_deinitialize( + glyphas_context_t* ctx +); diff --git a/util/glyphas/drawer.c b/util/glyphas/drawer.c new file mode 100644 index 0000000..29a9b85 --- /dev/null +++ b/util/glyphas/drawer.c @@ -0,0 +1,164 @@ +#include "./drawer.h" + +#include +#include + +#include + +#include "util/gleasy/buffer.h" +#include "util/gleasy/texture.h" +#include "util/gleasy/program.h" +#include "util/math/vector.h" +#include "util/memory/memory.h" + +#include "./block.h" + +/* resources */ +#include "anysrc/drawer.vshader.h" +#include "anysrc/drawer.fshader.h" + +#define GLYPHAS_DRAWER_UNIFORM_TEX 0 + +#define GLYPHAS_DRAWER_VSHADER_IN_POS 0 +#define GLYPHAS_DRAWER_VSHADER_IN_SIZE 1 +#define GLYPHAS_DRAWER_VSHADER_IN_UV_POS 2 +#define GLYPHAS_DRAWER_VSHADER_IN_UV_SIZE 3 +#define GLYPHAS_DRAWER_VSHADER_IN_COLOR 4 + +struct glyphas_drawer_t { + gleasy_texture_2d_t tex; + + GLuint vao; + + gleasy_buffer_array_t instances; + size_t instances_reserved; + size_t instances_length; +}; + +#pragma pack(push, 1) +typedef struct { + vec2_t pos; + vec2_t size; + vec2_t uv_pos; + vec2_t uv_size; + vec4_t color; +} glyphas_drawer_instance_t; +#pragma pack(pop) + +static void glyphas_drawer_setup_vao_( + gleasy_buffer_array_t instances) { + assert(instances != 0); + + glBindBuffer(GL_ARRAY_BUFFER, instances); + +# define enable_attrib_(NAME, name, dim, type) do { \ + glEnableVertexAttribArray(GLYPHAS_DRAWER_VSHADER_IN_##NAME); \ + glVertexAttribPointer( \ + GLYPHAS_DRAWER_VSHADER_IN_##NAME, dim, type, GL_FALSE, \ + sizeof(glyphas_drawer_instance_t), \ + NULL + offsetof(glyphas_drawer_instance_t, name)); \ + glVertexAttribDivisor(GLYPHAS_DRAWER_VSHADER_IN_##NAME, 1); \ + } while (0) + + enable_attrib_(POS, pos, 2, GL_FLOAT); + enable_attrib_(SIZE, size, 2, GL_FLOAT); + enable_attrib_(UV_POS, uv_pos, 2, GL_FLOAT); + enable_attrib_(UV_SIZE, uv_size, 2, GL_FLOAT); + enable_attrib_(COLOR, color, 4, GL_FLOAT); + +# undef enable_attrib_ +} + +gleasy_program_t glyphas_drawer_create_default_program(void) { + return gleasy_program_new("", 0, + glyphas_drawer_vshader_, sizeof(glyphas_drawer_vshader_), + glyphas_drawer_fshader_, sizeof(glyphas_drawer_fshader_)); +} + +glyphas_drawer_t* glyphas_drawer_new(void) { + glyphas_drawer_t* drawer = memory_new(sizeof(*drawer)); + *drawer = (typeof(*drawer)) {0}; + + glCreateVertexArrays(1, &drawer->vao); + glBindVertexArray(drawer->vao); + + glGenBuffers(1, &drawer->instances); + glyphas_drawer_setup_vao_(drawer->instances); + + return drawer; +} + +void glyphas_drawer_delete(glyphas_drawer_t* drawer) { + if (drawer == NULL) return; + + glDeleteBuffers(1, &drawer->instances); + + glDeleteVertexArrays(1, &drawer->vao); + + memory_delete(drawer); +} + +void glyphas_drawer_clear( + glyphas_drawer_t* drawer, gleasy_texture_2d_t tex, size_t reserve) { + assert(drawer != NULL); + assert(tex != 0); + assert(reserve > 0); + + drawer->tex = tex; + + drawer->instances_length = 0; + if (drawer->instances_reserved < reserve) { + glBindBuffer(GL_ARRAY_BUFFER, drawer->instances); + glBufferData(GL_ARRAY_BUFFER, + reserve * sizeof(glyphas_drawer_instance_t), + NULL, GL_DYNAMIC_DRAW); + drawer->instances_reserved = reserve; + } +} + +void glyphas_drawer_add_block( + glyphas_drawer_t* drawer, const glyphas_block_t* block) { + assert(drawer != NULL); + assert(block != NULL); + + size_t len; + const glyphas_block_item_t* items = glyphas_block_get_items(block, &len); + + for (size_t i = 0; i < len; ++i) { + glyphas_drawer_add_block_item(drawer, &items[i]); + } +} + +void glyphas_drawer_add_block_item( + glyphas_drawer_t* drawer, const glyphas_block_item_t* item) { + assert(drawer != NULL); + assert(item != NULL); + + const glyphas_drawer_instance_t insta = { + .pos = item->pos, + .size = item->size, + .uv_pos = vec2(item->uv.left, item->uv.top), + .uv_size = vec2(item->uv.right-item->uv.left, item->uv.top-item->uv.bottom), + .color = item->color, + }; + + const size_t offset = drawer->instances_length * sizeof(insta); + glBindBuffer(GL_ARRAY_BUFFER, drawer->instances); + glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(insta), &insta); + + ++drawer->instances_length; +} + +void glyphas_drawer_draw(const glyphas_drawer_t* drawer) { + assert(drawer != NULL); + + if (drawer->instances_length == 0) return; + + glBindVertexArray(drawer->vao); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, drawer->tex); + glUniform1i(GLYPHAS_DRAWER_UNIFORM_TEX, 0); + + glDrawArraysInstanced(GL_TRIANGLES, 0, 6, drawer->instances_length); +} diff --git a/util/glyphas/drawer.fshader b/util/glyphas/drawer.fshader new file mode 100644 index 0000000..5b971b7 --- /dev/null +++ b/util/glyphas/drawer.fshader @@ -0,0 +1,14 @@ +#version 330 +#extension GL_ARB_explicit_uniform_location : enable + +layout (location = 0) uniform sampler2D u_tex; + +in vec2 v_uv; +in vec4 v_color; + +out vec4 o_color; + +void main(void) { + float a = texture(u_tex, v_uv).r; + o_color = vec4(v_color.rgb, v_color.a*a); +} diff --git a/util/glyphas/drawer.h b/util/glyphas/drawer.h new file mode 100644 index 0000000..20d4057 --- /dev/null +++ b/util/glyphas/drawer.h @@ -0,0 +1,51 @@ +#pragma once + +#include "util/gleasy/texture.h" +#include "util/gleasy/program.h" + +#include "./block.h" + +struct glyphas_drawer_t; +typedef struct glyphas_drawer_t glyphas_drawer_t; + +gleasy_program_t +glyphas_drawer_create_default_program( + void +); + +glyphas_drawer_t* +glyphas_drawer_new( + void +); + +void +glyphas_drawer_delete( + glyphas_drawer_t* drawer +); + +void +glyphas_drawer_clear( + glyphas_drawer_t* drawer, + gleasy_texture_2d_t tex, + size_t reserve +); + +void +glyphas_drawer_add_block( + glyphas_drawer_t* drawer, + const glyphas_block_t* block + /* The drawer doesn't hold the reference. */ +); + +void +glyphas_drawer_add_block_item( + glyphas_drawer_t* drawer, + const glyphas_block_item_t* item + /* The drawer doesn't hold the reference. */ +); + +/* bind the program before drawing */ +void +glyphas_drawer_draw( + const glyphas_drawer_t* drawer +); diff --git a/util/glyphas/drawer.vshader b/util/glyphas/drawer.vshader new file mode 100644 index 0000000..6442e62 --- /dev/null +++ b/util/glyphas/drawer.vshader @@ -0,0 +1,25 @@ +#version 330 + +layout (location = 0) in vec2 i_pos; +layout (location = 1) in vec2 i_size; +layout (location = 2) in vec2 i_uv_pos; +layout (location = 3) in vec2 i_uv_size; +layout (location = 4) in vec4 i_color; + +out vec2 v_uv; +out vec4 v_color; + +void main(void) { + vec2 p = + gl_VertexID == 0? vec2(0., 0.): + gl_VertexID == 1? vec2(0., -1.): + gl_VertexID == 2? vec2(1., -1.): + gl_VertexID == 3? vec2(0., 0.): + gl_VertexID == 4? vec2(1., -1.): + gl_VertexID == 5? vec2(1., 0.): + vec2(0.); + + gl_Position = vec4(p*i_size+i_pos, 0, 1); + v_uv = p*i_uv_size + i_uv_pos; + v_color = i_color; +} diff --git a/util/glyphas/face.c b/util/glyphas/face.c new file mode 100644 index 0000000..0f0d583 --- /dev/null +++ b/util/glyphas/face.c @@ -0,0 +1,78 @@ +#include "./face.h" + +#include +#include +#include +#include + +#include +#include FT_ERRORS_H +#include FT_FREETYPE_H + +#include "./context.h" + +void glyphas_face_initialize_from_file( + glyphas_face_t* face, + const glyphas_context_t* ctx, + const char* path, + size_t index) { + assert(face != NULL); + assert(ctx != NULL); + + *face = (typeof(*face)) {0}; + + const FT_Error err = FT_New_Face(ctx->ft, path, index, &face->ft); + if (err != FT_Err_Ok) { + fprintf(stderr, + "failed to load font file '%s': %s\n", path, FT_Error_String(err)); + abort(); + } +} + +void glyphas_face_initialize_from_buffer( + glyphas_face_t* face, + const glyphas_context_t* ctx, + const void* data, + size_t length, + size_t index) { + assert(face != NULL); + assert(ctx != NULL); + + *face = (typeof(*face)) {0}; + + const FT_Error err = + FT_New_Memory_Face(ctx->ft, data, length, index, &face->ft); + if (err != FT_Err_Ok) { + fprintf(stderr, + "failed to load font on memory: %s\n", FT_Error_String(err)); + abort(); + } +} + +void glyphas_face_deinitialize(glyphas_face_t* face) { + assert(face != NULL); + + FT_Done_Face(face->ft); +} + +bool glyphas_face_set_pixel_size( + glyphas_face_t* face, int32_t width, int32_t height) { + assert(face != NULL); + assert(width > 0); + assert(height > 0); + + const FT_Error err = FT_Set_Pixel_Sizes(face->ft, width, height); + return err == FT_Err_Ok; +} + +bool glyphas_face_render_glyph(glyphas_face_t* face, uint64_t unicode) { + assert(face != NULL); + + const FT_UInt index = FT_Get_Char_Index(face->ft, unicode); + if (index == 0) return false; + + const FT_Error err = FT_Load_Glyph(face->ft, index, FT_LOAD_RENDER); + if (err != FT_Err_Ok) return false; + + return glyphas_glyph_reset_from_ft_glyph_slot(&face->glyph, face->ft->glyph); +} diff --git a/util/glyphas/face.h b/util/glyphas/face.h new file mode 100644 index 0000000..3aed89f --- /dev/null +++ b/util/glyphas/face.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include + +#include +#include FT_FREETYPE_H + +#include "./context.h" +#include "./glyph.h" + +typedef struct { + FT_Face ft; + + glyphas_glyph_t glyph; +} glyphas_face_t; + +void +glyphas_face_initialize_from_file( + glyphas_face_t* face, + const glyphas_context_t* ctx, + const char* path, + size_t index +); + +void +glyphas_face_initialize_from_buffer( + glyphas_face_t* face, + const glyphas_context_t* ctx, + const void* data, + size_t length, + size_t index +); + +void +glyphas_face_deinitialize( + glyphas_face_t* face +); + +bool +glyphas_face_set_pixel_size( + glyphas_face_t* face, + int32_t width, + int32_t height +); + +/* Set sizes before rendering glyphs. */ +bool +glyphas_face_render_glyph( + glyphas_face_t* face, + uint64_t unicode +); diff --git a/util/glyphas/glyph.c b/util/glyphas/glyph.c new file mode 100644 index 0000000..6785409 --- /dev/null +++ b/util/glyphas/glyph.c @@ -0,0 +1,36 @@ +#include "./glyph.h" + +#include +#include +#include +#include + +#include +#include FT_FREETYPE_H + +bool glyphas_glyph_reset_from_ft_glyph_slot( + glyphas_glyph_t* glyph, FT_GlyphSlot slot) { + assert(glyph != NULL); + assert(slot != NULL); + + if (slot->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY) return false; + + *glyph = (typeof(*glyph)) { + .bitmap = { + .width = slot->bitmap.width, + .height = slot->bitmap.rows, + .buffer = slot->bitmap.buffer, + }, + .hmetrics = { + .bear_x = slot->metrics.horiBearingX/64, + .bear_y = slot->metrics.horiBearingY/64, + .advance = slot->metrics.horiAdvance /64, + }, + .vmetrics = { + .bear_x = slot->metrics.vertBearingX/64, + .bear_y = slot->metrics.vertBearingY/64, + .advance = slot->metrics.vertAdvance /64, + }, + }; + return true; +} diff --git a/util/glyphas/glyph.h b/util/glyphas/glyph.h new file mode 100644 index 0000000..a2e6584 --- /dev/null +++ b/util/glyphas/glyph.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +#include +#include FT_FREETYPE_H + +typedef struct { + int32_t width; + int32_t height; + uint8_t* buffer; /* gray-scale bitmap */ +} glyphas_glyph_bitmap_t; + +typedef struct { + int32_t bear_x; + int32_t bear_y; + int32_t advance; +} glyphas_glyph_metrics_t; + +typedef struct { + glyphas_glyph_bitmap_t bitmap; + glyphas_glyph_metrics_t vmetrics; + glyphas_glyph_metrics_t hmetrics; +} glyphas_glyph_t; + +/* The glyph's lifetime must be shorter than the slot. */ +bool +glyphas_glyph_reset_from_ft_glyph_slot( + glyphas_glyph_t* glyph, + FT_GlyphSlot slot +); diff --git a/util/glyphas/test.c b/util/glyphas/test.c new file mode 100644 index 0000000..a286842 --- /dev/null +++ b/util/glyphas/test.c @@ -0,0 +1,179 @@ +/* An expression in assert() can be expected to be executed absolutely. */ +#undef NDEBUG + +#define SDL_MAIN_HANDLED + +#include +#include +#include + +#include +#include + +#include "util/conv/charcode.h" +#include "util/gleasy/buffer.h" +#include "util/gleasy/program.h" +#include "util/math/vector.h" + +#include "./cache.h" +#include "./context.h" +#include "./drawer.h" +#include "./face.h" +#include "./glyph.h" + +typedef struct { + glyphas_context_t glyphas; + glyphas_face_t face; + + gleasy_atlas_t* atlas; + glyphas_cache_t* cache; + + gleasy_program_t prog; + glyphas_drawer_t* drawer; +} context_t; + +#define SCALE 0.01f + +static void align_text_(context_t* ctx, const char* str) { + assert(ctx != NULL); + + const size_t len = strlen(str); + glyphas_block_t* block = glyphas_block_new( + GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL, -20, 400, len); + + static vec4_t color = vec4(0, 0, 0, 1); + glyphas_block_add_characters(block, ctx->cache, &color, str, len); + + vec2_t offset, size; + glyphas_block_calculate_geometry(block, &offset, &size); + + offset = vec2(-offset.x, 0); + glyphas_block_translate(block, &offset); + + size = vec2(1.f/200, 1.f/200); + glyphas_block_scale(block, &size); + + static const vec2_t origin = vec2(.5f, -.5f); + glyphas_block_set_origin(block, &origin); + + glyphas_drawer_clear(ctx->drawer, gleasy_atlas_get_texture(ctx->atlas), len); + glyphas_drawer_add_block(ctx->drawer, block); + glyphas_block_delete(block); +} + +static void initialize_(context_t* ctx, const char* path, const char* str) { + assert(ctx != NULL); + + *ctx = (typeof(*ctx)) {0}; + glyphas_context_initialize(&ctx->glyphas); + + glyphas_face_initialize_from_file(&ctx->face, &ctx->glyphas, path, 0); + + ctx->atlas = gleasy_atlas_new(GL_RED, 256, 256, false /* anti-alias */); + ctx->cache = glyphas_cache_new(ctx->atlas, &ctx->face, 16, 16); + + ctx->prog = glyphas_drawer_create_default_program(); + ctx->drawer = glyphas_drawer_new(); + assert(glGetError() == GL_NO_ERROR); + + align_text_(ctx, str); + assert(glGetError() == GL_NO_ERROR); +} + +static void draw_(const context_t* ctx) { + assert(ctx != NULL); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glUseProgram(ctx->prog); + glyphas_drawer_draw(ctx->drawer); +} + +static void deinitialize_(context_t* ctx) { + assert(ctx != NULL); + + glyphas_drawer_delete(ctx->drawer); + glDeleteProgram(ctx->prog); + + glyphas_cache_delete(ctx->cache); + gleasy_atlas_delete(ctx->atlas); + + glyphas_face_deinitialize(&ctx->face); + glyphas_context_deinitialize(&ctx->glyphas); +} + +typedef struct { + SDL_Window* win; + SDL_GLContext gl; +} libs_t; + +static void initialize_libraries_(libs_t* libs) { + assert(libs != NULL); + + SDL_SetHint(SDL_HINT_VIDEO_HIGHDPI_DISABLED, "0"); + assert(SDL_Init(SDL_INIT_VIDEO) == 0); + + SDL_GL_SetAttribute( + SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); + SDL_GL_SetAttribute( + SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + + static const uint32_t win_flags = + SDL_WINDOW_ALLOW_HIGHDPI | + SDL_WINDOW_OPENGL | + SDL_WINDOW_SHOWN; + libs->win = SDL_CreateWindow( + "glyphas-test", /* title */ + SDL_WINDOWPOS_CENTERED, /* X position */ + SDL_WINDOWPOS_CENTERED, /* Y position */ + 400, + 400, + win_flags); + assert(libs->win != NULL); + + libs->gl = SDL_GL_CreateContext(libs->win); + + glewExperimental = 1; + assert(glewInit() == GLEW_OK); +} +static void deinitialize_libraries_(libs_t* libs) { + assert(libs != NULL); + + SDL_GL_DeleteContext(libs->gl); + SDL_DestroyWindow(libs->win); + SDL_Quit(); +} + +int main(int argc, char** argv) { + assert(argc == 3); + + libs_t libs; + initialize_libraries_(&libs); + + context_t ctx; + initialize_(&ctx, argv[1], argv[2]); + + bool alive = true; + while (alive) { + SDL_Delay(30); + + SDL_Event e; + while (SDL_PollEvent(&e)) { + if (e.type == SDL_QUIT) alive = false; + } + glClearColor(1, 1, 1, 1); + glClear(GL_COLOR_BUFFER_BIT); + draw_(&ctx); + SDL_GL_SwapWindow(libs.win); + } + + deinitialize_(&ctx); + deinitialize_libraries_(&libs); + return EXIT_SUCCESS; +} diff --git a/util/jukebox/CMakeLists.txt b/util/jukebox/CMakeLists.txt new file mode 100644 index 0000000..07d70ce --- /dev/null +++ b/util/jukebox/CMakeLists.txt @@ -0,0 +1,23 @@ +add_library(jukebox + amp.c + beep.c + composite.c + decoder.c + delay.c + effect.c + format.c + lowpass.c + mixer.c + sound.c +) +target_link_libraries(jukebox + miniaudio + + math + memory +) + +if (BUILD_TESTING) + add_executable(jukebox-test test.c) + target_link_libraries(jukebox-test jukebox) +endif() diff --git a/util/jukebox/amp.c b/util/jukebox/amp.c new file mode 100644 index 0000000..db0bac8 --- /dev/null +++ b/util/jukebox/amp.c @@ -0,0 +1,83 @@ +#include "./amp.h" + +#include +#include +#include +#include + +#include "util/math/algorithm.h" +#include "util/math/rational.h" + +#include "./effect.h" +#include "./format.h" + +/* FIXME: Whether this solution works fine, + depends on how long the operation is done. */ + +static void jukebox_amp_affect_( + jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) { + assert(effect != NULL); + assert(pcm != NULL); + + jukebox_amp_t* amp = (typeof(amp)) effect; + + const uint64_t easedur = atomic_load(&->ease_duration); + const uint64_t elapsed = atomic_load(&->elapsed); + + const float prev = atomic_load(&->prev_amount)/1000.0f; + const float next = atomic_load(&->next_amount)/1000.0f; + + float* ptr = pcm->ptr; + for (int32_t i = 0; i < pcm->frames; ++i) { + float a = next; + if (elapsed < easedur) { + a = (prev-next) * (easedur-elapsed) / easedur + next; + } + + for (int32_t j = 0; j < amp->format.channels; ++j) { + *(ptr++) *= a; + } + } + atomic_fetch_add(&->elapsed, pcm->frames); +} + +void jukebox_amp_initialize( + jukebox_amp_t* amp, const jukebox_format_t* format) { + assert(amp != NULL); + assert(jukebox_format_valid(format)); + + *amp = (typeof(*amp)) { + .super = { + .vtable = { + .affect = jukebox_amp_affect_, + }, + }, + .format = *format, + }; +} + +void jukebox_amp_deinitialize(jukebox_amp_t* amp) { + assert(amp != NULL); + +} + +void jukebox_amp_change_volume( + jukebox_amp_t* amp, float amount, const rational_t* duration) { + assert(amp != NULL); + assert(MATH_FLOAT_VALID(amount)); + assert(amount >= 0); + + const float srate = amp->format.sample_rate; + + atomic_store(&->ease_duration, 0); + + atomic_store(&->elapsed, 0); + atomic_store(&->prev_amount, amp->next_amount); + + rational_t dur = rational(1, 100); + if (rational_valid(duration)) rational_addeq(&dur, duration); + rational_normalize(&dur, srate); + + atomic_store(&->next_amount, (uint16_t) (amount*1000)); + atomic_store(&->ease_duration, dur.num); +} diff --git a/util/jukebox/amp.h b/util/jukebox/amp.h new file mode 100644 index 0000000..6a653b1 --- /dev/null +++ b/util/jukebox/amp.h @@ -0,0 +1,38 @@ +#pragma once + +#include + +#include "util/math/rational.h" + +#include "./effect.h" +#include "./format.h" + +typedef struct { + jukebox_effect_t super; + + jukebox_format_t format; + + atomic_uint_least64_t elapsed; + atomic_uint_least64_t ease_duration; + + atomic_uint_least16_t prev_amount; /* [1/1000] */ + atomic_uint_least16_t next_amount; /* [1/1000] */ +} jukebox_amp_t; + +void +jukebox_amp_initialize( + jukebox_amp_t* amp, + const jukebox_format_t* format +); + +void +jukebox_amp_deinitialize( + jukebox_amp_t* amp +); + +void +jukebox_amp_change_volume( + jukebox_amp_t* amp, + float amount, + const rational_t* duration /* NULLABLE */ +); diff --git a/util/jukebox/beep.c b/util/jukebox/beep.c new file mode 100644 index 0000000..8a572b4 --- /dev/null +++ b/util/jukebox/beep.c @@ -0,0 +1,77 @@ +#include "./beep.h" + +#include +#include +#include +#include +#include + +#include "util/math/algorithm.h" +#include "util/math/constant.h" + +#include "./effect.h" +#include "./format.h" + +static void jukebox_beep_affect_( + jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) { + assert(effect != NULL); + assert(pcm != NULL); + + jukebox_beep_t* b = (typeof(b)) effect; + if (!atomic_load(&b->playing)) return; + + const int32_t sample_rate = b->format.sample_rate; + const int32_t channels = b->format.channels; + const float omega = 2 * MATH_PI * b->hz; + + float* ptr = pcm->ptr; + for (int32_t i = 0; i < pcm->frames; ++i) { + const float a = fmod(i*omega/sample_rate, 2*MATH_PI); + const float x = sin(a + b->phase) * b->amp; + for (int32_t j = 0; j < channels; ++j) { + *(ptr++) += x; + } + } + + b->phase += pcm->frames*1.0f/sample_rate * omega; + b->phase = fmod(b->phase, 2*MATH_PI); +} + +void jukebox_beep_initialize(jukebox_beep_t* beep, const jukebox_format_t* format) { + assert(beep != NULL); + assert(jukebox_format_valid(format)); + + *beep = (typeof(*beep)) { + .super = { + .vtable = { + .affect = jukebox_beep_affect_, + }, + }, + .format = *format, + }; +} + +void jukebox_beep_deinitialize(jukebox_beep_t* beep) { + assert(beep != NULL); + +} + +void jukebox_beep_play(jukebox_beep_t* beep, float hz, float amp) { + assert(beep != NULL); + assert(MATH_FLOAT_VALID(hz) && hz > 0); + assert(MATH_FLOAT_VALID(amp) && amp > 0); + + if (beep->playing) return; + + beep->hz = hz; + beep->amp = amp; + beep->phase = 0; + atomic_store(&beep->playing, true); +} + +void jukebox_beep_stop(jukebox_beep_t* beep) { + assert(beep != NULL); + + if (!beep->playing) return; + atomic_store(&beep->playing, false); +} diff --git a/util/jukebox/beep.h b/util/jukebox/beep.h new file mode 100644 index 0000000..6efe461 --- /dev/null +++ b/util/jukebox/beep.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include "./effect.h" +#include "./format.h" + +typedef struct { + jukebox_effect_t super; + jukebox_format_t format; + + atomic_bool playing; + + float hz; + float amp; + float phase; +} jukebox_beep_t; + +void +jukebox_beep_initialize( + jukebox_beep_t* beep, + const jukebox_format_t* format +); + +void +jukebox_beep_deinitialize( + jukebox_beep_t* beep +); + +/* not thread-safe function */ +void +jukebox_beep_play( + jukebox_beep_t* beep, + float hz, + float amp +); + +/* not thread-safe function */ +void +jukebox_beep_stop( + jukebox_beep_t* beep +); diff --git a/util/jukebox/composite.c b/util/jukebox/composite.c new file mode 100644 index 0000000..e4c9593 --- /dev/null +++ b/util/jukebox/composite.c @@ -0,0 +1,113 @@ +#include "./composite.h" + +#include +#include +#include +#include +#include + +#include "util/math/algorithm.h" +#include "util/memory/memory.h" + +#include "./effect.h" +#include "./format.h" + +struct jukebox_composite_t { + jukebox_effect_t super; + + jukebox_format_t format; + + atomic_bool playing; + + size_t effects_reserved; + size_t effects_length; + jukebox_effect_t* effects[1]; +}; + +static void jukebox_composite_affect_( + jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) { + assert(effect != NULL); + assert(pcm != NULL); + +# define CHUNK_SIZE 256 + + jukebox_composite_t* c = (typeof(c)) effect; + if (!atomic_load(&c->playing)) return; + + const int32_t channels = c->format.channels; + const size_t chunk_frames = CHUNK_SIZE / channels; + + uint64_t read = 0; + while (read < (uint64_t) pcm->frames) { + float chunk[CHUNK_SIZE] = {0}; + + jukebox_effect_pcm_t chunk_pcm = { + .ptr = chunk, + .frames = MATH_MIN(chunk_frames, pcm->frames - read), + }; + for (size_t i = 0; i < c->effects_length; ++i) { + jukebox_effect_affect(c->effects[i], &chunk_pcm); + } + + const size_t len = chunk_pcm.frames * channels; + const float* src = chunk; + float* dst = pcm->ptr + read * channels; + for (uint64_t i = 0; i < len; ++i) { + *(dst++) += *(src++); + } + read += chunk_pcm.frames; + } + +# undef CHUNK_SIZE +} + +jukebox_composite_t* jukebox_composite_new( + const jukebox_format_t* format, size_t reserve) { + assert(jukebox_format_valid(format)); + assert(reserve > 0); + + jukebox_composite_t* compo = + memory_new(sizeof(*compo) + (reserve-1)*sizeof(compo->effects[0])); + *compo = (typeof(*compo)) { + .super = { + .vtable = { + .affect = jukebox_composite_affect_, + }, + }, + .format = *format, + .effects_reserved = reserve, + }; + return compo; +} + +void jukebox_composite_delete(jukebox_composite_t* compo) { + if (compo == NULL) return; + + memory_delete(compo); +} + +void jukebox_composite_add_effect( + jukebox_composite_t* compo, jukebox_effect_t* effect) { + assert(compo != NULL); + assert(effect != NULL); + + assert(!atomic_load(&compo->playing)); + + if (compo->effects_length >= compo->effects_reserved) { + fprintf(stderr, "jukebox: composite effect overflow\n"); + abort(); + } + compo->effects[compo->effects_length++] = effect; +} + +void jukebox_composite_play(jukebox_composite_t* compo) { + assert(compo != NULL); + + atomic_store(&compo->playing, true); +} + +void jukebox_composite_stop(jukebox_composite_t* compo) { + assert(compo != NULL); + + atomic_store(&compo->playing, false); +} diff --git a/util/jukebox/composite.h b/util/jukebox/composite.h new file mode 100644 index 0000000..86c5f52 --- /dev/null +++ b/util/jukebox/composite.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include "./effect.h" +#include "./format.h" + +struct jukebox_composite_t; +typedef struct jukebox_composite_t jukebox_composite_t; + +jukebox_composite_t* /* OWNERSHIP */ +jukebox_composite_new( + const jukebox_format_t* format, + size_t reserve +); + +void +jukebox_composite_delete( + jukebox_composite_t* compo /* OWNERSHIP */ +); + +void +jukebox_composite_add_effect( + jukebox_composite_t* compo, /* must be stopped */ + jukebox_effect_t* effect /* must be alive while the comp is alive */ +); + +void +jukebox_composite_play( + jukebox_composite_t* compo +); + +void +jukebox_composite_stop( + jukebox_composite_t* compo +); diff --git a/util/jukebox/decoder.c b/util/jukebox/decoder.c new file mode 100644 index 0000000..b573b78 --- /dev/null +++ b/util/jukebox/decoder.c @@ -0,0 +1,217 @@ +#include "./decoder.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "thirdparty/miniaudio/miniaudio.h" + +#include "util/math/algorithm.h" +#include "util/math/rational.h" +#include "util/memory/memory.h" + +#include "./effect.h" +#include "./format.h" + +# define JUKEBOX_DECODER_AFFECTION_CHUNK_SIZE 256 + +struct jukebox_decoder_t { + jukebox_effect_t super; + + jukebox_format_t format; + + ma_decoder ma; + + atomic_bool playing; + atomic_int_least64_t duration; /* negative = 0 */ + + atomic_uint_least64_t current; /* main thread doesn't modify when playing*/ + bool loop; + bool request_seek; +}; + +static void jukebox_decoder_seek_(jukebox_decoder_t* dec, uint64_t f) { + assert(dec != NULL); + assert(atomic_load(&dec->playing)); + + if (ma_decoder_seek_to_pcm_frame(&dec->ma, f) != MA_SUCCESS) { + fprintf(stderr, "jukebox: failed to seek ma_decoder\n"); + abort(); + } + atomic_store(&dec->current, f); + dec->request_seek = false; +} + +static void jukebox_decoder_affect_( + jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) { + assert(effect != NULL); + assert(pcm != NULL); + + jukebox_decoder_t* dec = (typeof(dec)) effect; + if (!atomic_load(&dec->playing)) return; + + if (dec->request_seek) { + jukebox_decoder_seek_(dec, atomic_load(&dec->current)); + } + + const int32_t ch = dec->format.channels; + + uint64_t read_frames = 0; + for (;;) { + const int64_t duration = atomic_load(&dec->duration); + const uint64_t requested_frames = pcm->frames - read_frames; + + if (duration <= 0) { + atomic_store(&dec->playing, false); + break; + } + if (read_frames >= (uint64_t) pcm->frames) break; + + uint64_t chunk_frames = JUKEBOX_DECODER_AFFECTION_CHUNK_SIZE / ch; + if (chunk_frames > (uint64_t) duration) chunk_frames = duration; + if (chunk_frames > requested_frames) chunk_frames = requested_frames; + + float chunk[JUKEBOX_DECODER_AFFECTION_CHUNK_SIZE]; + + const uint64_t decoded_frames = + ma_decoder_read_pcm_frames(&dec->ma, chunk, chunk_frames); + if (decoded_frames != chunk_frames) { + jukebox_decoder_seek_(dec, 0); + } + + assert(read_frames+decoded_frames <= (uint64_t) pcm->frames); + for (size_t i = 0; i < decoded_frames*ch; ++i) { + pcm->ptr[read_frames*ch+i] += chunk[i]; + } + read_frames += decoded_frames; + + atomic_fetch_add(&dec->duration, -decoded_frames); + atomic_fetch_add(&dec->current, decoded_frames); + } +} + +static jukebox_decoder_t* jukebox_decoder_new_( + const jukebox_format_t* format) { + assert(jukebox_format_valid(format)); + + jukebox_decoder_t* decoder = memory_new(sizeof(*decoder)); + *decoder = (typeof(*decoder)) { + .super = { + .vtable = { + .affect = jukebox_decoder_affect_, + }, + }, + .format = *format, + }; + return decoder; +} + +jukebox_decoder_t* jukebox_decoder_new_from_file( + const jukebox_format_t* format, const char* path) { + assert(jukebox_format_valid(format)); + + jukebox_decoder_t* decoder = jukebox_decoder_new_(format); + + const ma_decoder_config cfg = ma_decoder_config_init( + ma_format_f32, decoder->format.channels, decoder->format.sample_rate); + + if (ma_decoder_init_file(path, &cfg, &decoder->ma) != MA_SUCCESS) { + fprintf(stderr, "jukebox: invalid audio file '%s'\n", path); + abort(); + } + return decoder; +} + +jukebox_decoder_t* jukebox_decoder_new_from_memory_mp3( + const jukebox_format_t* format, const void* buf, size_t len) { + assert(jukebox_format_valid(format)); + assert(buf != NULL || len == 0); + + jukebox_decoder_t* decoder = jukebox_decoder_new_(format); + + const ma_decoder_config cfg = ma_decoder_config_init( + ma_format_f32, decoder->format.channels, decoder->format.sample_rate); + + if (ma_decoder_init_memory_mp3(buf, len, &cfg, &decoder->ma) != MA_SUCCESS) { + fprintf(stderr, "jukebox: invalid mp3 buffer\n"); + abort(); + } + return decoder; +} + +void jukebox_decoder_delete(jukebox_decoder_t* decoder) { + if (decoder == NULL) return; + + ma_decoder_uninit(&decoder->ma); + + memory_delete(decoder); +} + +void jukebox_decoder_play( + jukebox_decoder_t* decoder, const rational_t* st, bool loop) { + assert(decoder != NULL); + assert(rational_valid(st)); + + if (atomic_load(&decoder->playing)) return; + + rational_t t = *st; + rational_normalize(&t, decoder->format.sample_rate); + + atomic_store(&decoder->duration, INT64_MAX); + + decoder->loop = loop; + decoder->request_seek = true; + + atomic_store(&decoder->current, t.num); + atomic_store(&decoder->playing, true); +} + +void jukebox_decoder_resume(jukebox_decoder_t* decoder, bool loop) { + assert(decoder != NULL); + + if (atomic_load(&decoder->playing)) return; + + atomic_store(&decoder->duration, INT64_MAX); + + decoder->loop = loop; + + atomic_store(&decoder->playing, true); +} + +void jukebox_decoder_stop_after( + jukebox_decoder_t* decoder, const rational_t* dur) { + assert(decoder != NULL); + assert(rational_valid(dur)); + + if (!atomic_load(&decoder->playing)) return; + + rational_t d = *dur; + rational_normalize(&d, decoder->format.sample_rate); + + atomic_store(&decoder->duration, d.num); +} + +void jukebox_decoder_get_seek_position( + const jukebox_decoder_t* decoder, rational_t* time) { + assert(decoder != NULL); + assert(time != NULL); + + *time = (typeof(*time)) { + .num = atomic_load(&decoder->current), + .den = decoder->format.sample_rate, + }; +} + +void jukebox_decoder_get_duration( + const jukebox_decoder_t* decoder, rational_t* time) { + assert(decoder != NULL); + assert(time != NULL); + + *time = rational( + ma_decoder_get_length_in_pcm_frames((ma_decoder*) &decoder->ma), + decoder->format.sample_rate); +} diff --git a/util/jukebox/decoder.h b/util/jukebox/decoder.h new file mode 100644 index 0000000..474e848 --- /dev/null +++ b/util/jukebox/decoder.h @@ -0,0 +1,71 @@ +#pragma once + +#include + +#include "util/math/rational.h" + +#include "./format.h" + +struct jukebox_decoder_t; +typedef struct jukebox_decoder_t jukebox_decoder_t; + +jukebox_decoder_t* /* OWNERSHIP */ +jukebox_decoder_new_from_file( + const jukebox_format_t* format, + const char* path +); + +jukebox_decoder_t* /* OWNERSHIP */ +jukebox_decoder_new_from_memory_mp3( + const jukebox_format_t* format, + const void* buf, + size_t len +); + +void +jukebox_decoder_delete( + jukebox_decoder_t* decoder /* OWNERSHIP */ +); + +void +jukebox_decoder_play( + jukebox_decoder_t* decoder, + const rational_t* st, + bool loop +); + +void +jukebox_decoder_resume( + jukebox_decoder_t* decoder, + bool loop +); + +void +jukebox_decoder_stop_after( + jukebox_decoder_t* decoder, + const rational_t* dur +); + +void +jukebox_decoder_set_loop( + jukebox_decoder_t* decoder, + const rational_t* start, /* NULLABLE */ + const rational_t* end /* NULLABLE */ +); + +void +jukebox_decoder_unset_loop( + jukebox_decoder_t* decoder +); + +void +jukebox_decoder_get_seek_position( + const jukebox_decoder_t* decoder, + rational_t* time +); + +void +jukebox_decoder_get_duration( + const jukebox_decoder_t* decoder, + rational_t* time +); diff --git a/util/jukebox/delay.c b/util/jukebox/delay.c new file mode 100644 index 0000000..09fe3a0 --- /dev/null +++ b/util/jukebox/delay.c @@ -0,0 +1,94 @@ +#include "./delay.h" + +#include +#include +#include + +#include "util/math/algorithm.h" +#include "util/math/rational.h" +#include "util/memory/memory.h" + +#include "./effect.h" +#include "./format.h" + +struct jukebox_delay_t { + jukebox_effect_t super; + jukebox_format_t format; + + float source; + float feedback; + + size_t length; + size_t offset; + size_t cursor; + float buffer[1]; +}; + +static void jukebox_delay_affect_( + jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) { + assert(effect != NULL); + assert(pcm != NULL); + + jukebox_delay_t* d = (typeof(d)) effect; + + const int32_t ch = d->format.channels; + const size_t pcmlen = pcm->frames*ch; + + for (size_t i = 0; i < pcmlen; ++i) { + float* bufoff = &d->buffer[d->offset]; + float* bufcur = &d->buffer[d->cursor]; + float* pcmptr = &pcm->ptr[i]; + + *bufoff *= d->feedback; + *bufoff += *pcmptr * d->source; + + *pcmptr += *bufcur; + if (MATH_ABS(*pcmptr) >= 1.0f) { + *pcmptr = MATH_SIGN(*pcmptr) * 0.99f; + } + + if (++d->offset >= d->length) d->offset = 0; + if (++d->cursor >= d->length) d->cursor = 0; + } +} + +jukebox_delay_t* jukebox_delay_new( + const jukebox_format_t* format, + const rational_t* duration, + float source_attenuation, + float feedback_attenuation) { + assert(jukebox_format_valid(format)); + assert(rational_valid(duration)); + assert(MATH_FLOAT_VALID(source_attenuation)); + assert(MATH_FLOAT_VALID(feedback_attenuation)); + + rational_t dur_r = rational(format->channels*duration->num, duration->den); + rational_normalize(&dur_r, format->sample_rate); + + const size_t length = dur_r.num; + + jukebox_delay_t* d = + memory_new(sizeof(*d) + (length-1)*sizeof(d->buffer[0])); + *d = (typeof(*d)) { + .super = { + .vtable = { + .affect = jukebox_delay_affect_, + }, + }, + .format = *format, + .source = source_attenuation, + .feedback = feedback_attenuation, + .length = length, + .offset = dur_r.num, + .cursor = 0, + }; + + for (size_t i = 0; i < d->length; ++i) d->buffer[i] = 0; + return d; +} + +void jukebox_delay_delete(jukebox_delay_t* delay) { + if (delay == NULL) return; + + memory_delete(delay); +} diff --git a/util/jukebox/delay.h b/util/jukebox/delay.h new file mode 100644 index 0000000..27533ef --- /dev/null +++ b/util/jukebox/delay.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +#include "util/math/rational.h" + +#include "./format.h" + +struct jukebox_delay_t; +typedef struct jukebox_delay_t jukebox_delay_t; + +jukebox_delay_t* +jukebox_delay_new( + const jukebox_format_t* format, + const rational_t* duration, + float source_attenuation, + float feedback_attenuation +); + +void +jukebox_delay_delete( + jukebox_delay_t* delay +); diff --git a/util/jukebox/effect.c b/util/jukebox/effect.c new file mode 100644 index 0000000..a5b1438 --- /dev/null +++ b/util/jukebox/effect.c @@ -0,0 +1,13 @@ +#include "./effect.h" + +#include +#include + +void jukebox_effect_affect( + jukebox_effect_t* snd, const jukebox_effect_pcm_t* pcm) { + assert(snd != NULL); + assert(pcm != NULL); + + assert(snd->vtable.affect != NULL); + snd->vtable.affect(snd, pcm); +} diff --git a/util/jukebox/effect.h b/util/jukebox/effect.h new file mode 100644 index 0000000..0ee5116 --- /dev/null +++ b/util/jukebox/effect.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include "./format.h" + +struct jukebox_effect_t; +typedef struct jukebox_effect_t jukebox_effect_t; + +typedef struct { + float* ptr; + int32_t frames; +} jukebox_effect_pcm_t; + +typedef struct { + void + (*affect)( + jukebox_effect_t* snd, + const jukebox_effect_pcm_t* pcm + ); +} jukebox_effect_vtable_t; + +struct jukebox_effect_t { + jukebox_effect_vtable_t vtable; +}; + +void +jukebox_effect_affect( + jukebox_effect_t* snd, + const jukebox_effect_pcm_t* pcm +); diff --git a/util/jukebox/format.c b/util/jukebox/format.c new file mode 100644 index 0000000..7565aac --- /dev/null +++ b/util/jukebox/format.c @@ -0,0 +1,9 @@ +#include "./format.h" + +#include +#include +#include + +bool jukebox_format_valid(const jukebox_format_t* format) { + return format != NULL && format->sample_rate > 0 && format->channels > 0; +} diff --git a/util/jukebox/format.h b/util/jukebox/format.h new file mode 100644 index 0000000..944f344 --- /dev/null +++ b/util/jukebox/format.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +typedef struct { + int32_t sample_rate; + int32_t channels; +} jukebox_format_t; + +bool +jukebox_format_valid( + const jukebox_format_t* format +); diff --git a/util/jukebox/lowpass.c b/util/jukebox/lowpass.c new file mode 100644 index 0000000..9684333 --- /dev/null +++ b/util/jukebox/lowpass.c @@ -0,0 +1,57 @@ +#include "./lowpass.h" + +#include +#include +#include + +#include "util/math/algorithm.h" + +#include "./effect.h" +#include "./format.h" + +static void jukebox_lowpass_affect_( + jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) { + assert(effect != NULL); + assert(pcm != NULL); + + jukebox_lowpass_t* lp = (typeof(lp)) effect; + + const size_t ch = lp->format.channels; + const float f = atomic_load(&lp->factor) * JUKEBOX_LOWPASS_FACTOR_UNIT; + + assert(ch <= JUKEBOX_LOWPASS_SUPPORT_MAX_CHANNELS); + + const size_t pcmlen = pcm->frames * ch; + for (size_t i = 0; i < pcmlen; ++i) { + const float p = (ch+1 <= i? pcm->ptr[i-1-ch]: lp->prev[i]); + pcm->ptr[i] = (1.0f-f)*pcm->ptr[i] + f*p; + } + + if (pcmlen < ch) return; + for (size_t i = 0; i < ch; ++i) { + lp->prev[i] = pcm->ptr[pcmlen-ch+i]; + } +} + +void jukebox_lowpass_initialize( + jukebox_lowpass_t* lowpass, const jukebox_format_t* format, float factor) { + assert(lowpass != NULL); + assert(jukebox_format_valid(format)); + assert(MATH_FLOAT_VALID(factor)); + + *lowpass = (typeof(*lowpass)) { + .super = { + .vtable = { + .affect = jukebox_lowpass_affect_, + }, + }, + .format = *format, + .factor = factor*10000, + .prev = {0}, + }; +} + +void jukebox_lowpass_deinitialize(jukebox_lowpass_t* lowpass) { + assert(lowpass != NULL); + +} diff --git a/util/jukebox/lowpass.h b/util/jukebox/lowpass.h new file mode 100644 index 0000000..a9069fc --- /dev/null +++ b/util/jukebox/lowpass.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include "./effect.h" +#include "./format.h" + +#define JUKEBOX_LOWPASS_SUPPORT_MAX_CHANNELS 16 + +#define JUKEBOX_LOWPASS_FACTOR_UNIT (1.0f/10000) + +typedef struct { + jukebox_effect_t super; + jukebox_format_t format; + + atomic_uint_least32_t factor; + + float prev[JUKEBOX_LOWPASS_SUPPORT_MAX_CHANNELS]; +} jukebox_lowpass_t; + +void +jukebox_lowpass_initialize( + jukebox_lowpass_t* lowpass, + const jukebox_format_t* format, + float factor +); + +void +jukebox_lowpass_deinitialize( + jukebox_lowpass_t* lowpass +); diff --git a/util/jukebox/mixer.c b/util/jukebox/mixer.c new file mode 100644 index 0000000..a44c06c --- /dev/null +++ b/util/jukebox/mixer.c @@ -0,0 +1,98 @@ +#include "./mixer.h" + +#include +#include +#include +#include + +#include "thirdparty/miniaudio/miniaudio.h" + +#include "util/container/array.h" +#include "util/memory/memory.h" + +#include "./effect.h" + +struct jukebox_mixer_t { + ma_device device; + + jukebox_format_t format; + + size_t effects_reserved; + size_t effects_length; + jukebox_effect_t* effects[1]; +}; + +static void jukebox_mixer_device_callback_( + ma_device* device, void* out, const void* in, ma_uint32 frames) { + assert(device != NULL); + assert(out != NULL); + assert(frames > 0); + + jukebox_mixer_t* mixer = (typeof(mixer)) device->pUserData; + assert(mixer != NULL); + + const jukebox_effect_pcm_t pcm = { + .ptr = out, + .frames = frames, + }; + for (size_t i = 0; i < mixer->effects_length; ++i) { + jukebox_effect_affect(mixer->effects[i], &pcm); + } + + (void) in; +} + +jukebox_mixer_t* jukebox_mixer_new( + const jukebox_format_t* format, size_t reserve) { + assert(jukebox_format_valid(format)); + assert(reserve > 0); + + jukebox_mixer_t* mixer = memory_new( + sizeof(*mixer) + (reserve-1)*sizeof(mixer->effects[0])); + *mixer = (typeof(*mixer)) { + .format = *format, + .effects_reserved = reserve, + }; + + ma_device_config config = ma_device_config_init(ma_device_type_playback); + config.playback.format = ma_format_f32; + config.playback.channels = mixer->format.channels; + config.sampleRate = mixer->format.sample_rate; + config.dataCallback = jukebox_mixer_device_callback_; + config.pUserData = mixer; + + if (ma_device_init(NULL, &config, &mixer->device) != MA_SUCCESS) { + fprintf(stderr, "failed to open audio device\n"); + abort(); + } + + if (ma_device_start(&mixer->device) != MA_SUCCESS) { + fprintf(stderr, "failed to start audio device\n"); + ma_device_uninit(&mixer->device); + abort(); + } + + return mixer; +} + +void jukebox_mixer_delete(jukebox_mixer_t* mixer) { + if (mixer == NULL) return; + + ma_device_uninit(&mixer->device); + + memory_delete(mixer); +} + +void jukebox_mixer_add_effect( + jukebox_mixer_t* mixer, jukebox_effect_t* effect) { + assert(mixer != NULL); + assert(effect != NULL); + + ma_mutex_lock(&mixer->device.lock); + if (mixer->effects_length >= mixer->effects_reserved) { + fprintf(stderr, "mixer effects overflow\n"); + abort(); + } + mixer->effects[mixer->effects_length++] = effect; + ma_mutex_unlock(&mixer->device.lock); +} diff --git a/util/jukebox/mixer.h b/util/jukebox/mixer.h new file mode 100644 index 0000000..9ec94e9 --- /dev/null +++ b/util/jukebox/mixer.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include "./effect.h" +#include "./format.h" + +struct jukebox_mixer_t; +typedef struct jukebox_mixer_t jukebox_mixer_t; + +jukebox_mixer_t* /* OWNERSHIP */ +jukebox_mixer_new( + const jukebox_format_t* format, + size_t reserve +); + +void +jukebox_mixer_delete( + jukebox_mixer_t* mixer /* OWNERSHIP */ +); + +void +jukebox_mixer_add_effect( + jukebox_mixer_t* mixer, + jukebox_effect_t* effect /* must be alive while the mixer is alive */ +); diff --git a/util/jukebox/sound.c b/util/jukebox/sound.c new file mode 100644 index 0000000..7c138fc --- /dev/null +++ b/util/jukebox/sound.c @@ -0,0 +1,180 @@ +#include "./sound.h" + +#include +#include +#include +#include +#include + +#include "thirdparty/miniaudio/miniaudio.h" + +#include "util/math/rational.h" +#include "util/memory/memory.h" + +#include "./effect.h" +#include "./format.h" + +struct jukebox_sound_buffer_t { + jukebox_format_t format; + + uint64_t frames; + float ptr[1]; +}; + +struct jukebox_sound_t { + jukebox_effect_t super; + + const jukebox_sound_buffer_t* buffer; + + atomic_bool stop_all; + + size_t length; + atomic_uint_least64_t frames[1]; +}; + +static void jukebox_sound_affect_( + jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) { + assert(effect != NULL); + assert(pcm != NULL); + + jukebox_sound_t* s = (typeof(s)) effect; + + if (atomic_load(&s->stop_all)) { + for (size_t i = 0; i < s->length; ++i) { + atomic_store(&s->frames[i], s->buffer->frames); + } + atomic_store(&s->stop_all, false); + return; + } + + const int32_t ch = s->buffer->format.channels; + for (size_t i = 0; i < s->length; ++i) { + const uint64_t frame = atomic_load(&s->frames[i]); + if (frame >= s->buffer->frames) continue; + + const float* src = s->buffer->ptr; + float* dst = pcm->ptr; + + const float* src_ed = src + s->buffer->frames*ch; + const float* dst_ed = dst + pcm->frames*ch; + + src += frame*ch; + while (src < src_ed && dst < dst_ed) { + *(dst++) += *(src++); + } + atomic_fetch_add(&s->frames[i], (src - s->buffer->ptr)/ch - frame); + } +} + +static jukebox_sound_buffer_t* jukebox_sound_buffer_new_( + const jukebox_format_t* format, ma_decoder* decoder) { + assert(format != NULL); + assert(decoder != NULL); + + const uint64_t frames = ma_decoder_get_length_in_pcm_frames(decoder); + const size_t length = frames * format->channels; + + jukebox_sound_buffer_t* buf = + memory_new(sizeof(*buf) + (length-1)*sizeof(buf->ptr[0])); + *buf = (typeof(*buf)) { + .format = *format, + .frames = frames, + }; + + const uint64_t read_frames = + ma_decoder_read_pcm_frames(decoder, buf->ptr, frames); + + /* usually read_frames should be equal to frames */ + buf->frames = read_frames; + return buf; +} + +jukebox_sound_buffer_t* jukebox_sound_buffer_new_from_file( + const jukebox_format_t* format, const char* path) { + assert(jukebox_format_valid(format)); + + const ma_decoder_config cfg = ma_decoder_config_init( + ma_format_f32, format->channels, format->sample_rate); + + ma_decoder decoder; + if (ma_decoder_init_file(path, &cfg, &decoder) != MA_SUCCESS) { + fprintf(stderr, "jukebox: invalid audio file '%s'\n", path); + abort(); + } + jukebox_sound_buffer_t* sndbuf = jukebox_sound_buffer_new_(format, &decoder); + ma_decoder_uninit(&decoder); + return sndbuf; +} + +jukebox_sound_buffer_t* jukebox_sound_buffer_new_from_memory_mp3( + const jukebox_format_t* format, const void* buf, size_t len) { + assert(jukebox_format_valid(format)); + assert(buf != NULL || len == 0); + + const ma_decoder_config cfg = ma_decoder_config_init( + ma_format_f32, format->channels, format->sample_rate); + + ma_decoder decoder; + if (ma_decoder_init_memory_mp3(buf, len, &cfg, &decoder) != MA_SUCCESS) { + fprintf(stderr, "jukebox: invalid mp3 buffer\n"); + abort(); + } + jukebox_sound_buffer_t* sndbuf = jukebox_sound_buffer_new_(format, &decoder); + ma_decoder_uninit(&decoder); + return sndbuf; +} + +void jukebox_sound_buffer_delete(jukebox_sound_buffer_t* buf) { + if (buf == NULL) return; + + memory_delete(buf); +} + +jukebox_sound_t* jukebox_sound_new( + const jukebox_sound_buffer_t* buf, size_t max_concurrent) { + assert(buf != NULL); + assert(max_concurrent > 0); + + jukebox_sound_t* sound = + memory_new(sizeof(*sound) + (max_concurrent-1)*sizeof(sound->frames[0])); + *sound = (typeof(*sound)) { + .super = { + .vtable = { + .affect = jukebox_sound_affect_, + }, + }, + .buffer = buf, + .length = max_concurrent, + }; + + for (size_t i = 0; i < sound->length; ++i) { + sound->frames[i] = sound->buffer->frames; + } + return sound; +} + +void jukebox_sound_delete(jukebox_sound_t* sound) { + if (sound == NULL) return; + + memory_delete(sound); +} + +bool jukebox_sound_play(jukebox_sound_t* sound) { + assert(sound != NULL); + + if (atomic_load(&sound->stop_all)) return false; + + for (size_t i = 0; i < sound->length; ++i) { + if (atomic_load(&sound->frames[i]) >= sound->buffer->frames) { + atomic_store(&sound->frames[i], 0); + return true; + } + } + return false; +} + +void jukebox_sound_stop_all(jukebox_sound_t* sound) { + assert(sound != NULL); + + atomic_store(&sound->stop_all, true); +} diff --git a/util/jukebox/sound.h b/util/jukebox/sound.h new file mode 100644 index 0000000..ee0b325 --- /dev/null +++ b/util/jukebox/sound.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include + +#include "util/math/rational.h" + +#include "./format.h" + +struct jukebox_sound_buffer_t; +typedef struct jukebox_sound_buffer_t jukebox_sound_buffer_t; + +struct jukebox_sound_t; +typedef struct jukebox_sound_t jukebox_sound_t; + +jukebox_sound_buffer_t* +jukebox_sound_buffer_new_from_file( + const jukebox_format_t* format, + const char* path +); + +jukebox_sound_buffer_t* +jukebox_sound_buffer_new_from_memory_mp3( + const jukebox_format_t* format, + const void* buf, + size_t len +); + +void +jukebox_sound_buffer_delete( + jukebox_sound_buffer_t* buf +); + +jukebox_sound_t* +jukebox_sound_new( + const jukebox_sound_buffer_t* buf, + size_t max_concurrent +); + +void +jukebox_sound_delete( + jukebox_sound_t* sound +); + +bool +jukebox_sound_play( + jukebox_sound_t* sound +); + +void +jukebox_sound_stop_all( + jukebox_sound_t* sound +); diff --git a/util/jukebox/test.c b/util/jukebox/test.c new file mode 100644 index 0000000..3521a39 --- /dev/null +++ b/util/jukebox/test.c @@ -0,0 +1,53 @@ +#undef NDEBUG + +#include +#include +#include + +#include "util/math/rational.h" + +#include "./delay.h" +#include "./effect.h" +#include "./lowpass.h" +#include "./mixer.h" +#include "./sound.h" + +int main(int argc, char** argv) { + assert(argc == 2); + + static const jukebox_format_t format = { + .sample_rate = 48000, + .channels = 2, + }; + + jukebox_sound_buffer_t* sndbuf = + jukebox_sound_buffer_new_from_file(&format, argv[1]); + + jukebox_sound_t* snd = jukebox_sound_new(sndbuf, 16); + + static const rational_t delay_dur = rational(1, 10); + jukebox_delay_t* delay = jukebox_delay_new(&format, &delay_dur, 0.1f, 0.5f); + + jukebox_lowpass_t lowpass = {0}; + jukebox_lowpass_initialize(&lowpass, &format, 0.01f); + + jukebox_mixer_t* mixer = jukebox_mixer_new(&format, 16); + jukebox_mixer_add_effect(mixer, (jukebox_effect_t*) snd); + jukebox_mixer_add_effect(mixer, (jukebox_effect_t*) delay); + jukebox_mixer_add_effect(mixer, &lowpass.super); + + for (size_t i = 0; i < 32; ++i) { + jukebox_sound_play(snd); + printf("press enter to continue...\n"); + getchar(); + } + jukebox_sound_stop_all(snd); + + jukebox_mixer_delete(mixer); + jukebox_lowpass_deinitialize(&lowpass); + jukebox_delay_delete(delay); + jukebox_sound_delete(snd); + jukebox_sound_buffer_delete(sndbuf); + + return EXIT_SUCCESS; +} diff --git a/util/math/CMakeLists.txt b/util/math/CMakeLists.txt new file mode 100644 index 0000000..b09a5b9 --- /dev/null +++ b/util/math/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(math + rational.c + vector.c +) +target_source_of_source(math + algorithm.sos.c + matrix.sos.c +) +target_link_libraries(math m) + +if (BUILD_TESTING) + add_executable(math-test test.c) + target_link_libraries(math-test math) + + add_test(math-test math-test) +endif() diff --git a/util/math/algorithm.h b/util/math/algorithm.h new file mode 100644 index 0000000..6afca10 --- /dev/null +++ b/util/math/algorithm.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include + +#include "./constant.h" + +#define MATH_ABS(a) ((a) > 0? (a): -(a)) + +#define MATH_MAX(a, b) ((a) > (b)? (a): (b)) +#define MATH_MIN(a, b) ((a) < (b)? (a): (b)) + +#define MATH_DIFF(a, b) (MATH_MAX(a, b) - MATH_MIN(a, b)) + +#define MATH_CLAMP(x, min, max) ((x) < (min)? (min): (x) > (max)? (max): (x)) + +#define MATH_SIGN(a) (a < 0? -1: a > 0? 1: 0) + +#define MATH_FLOAT_EQUAL(a, b) (MATH_ABS((a) - (b)) < MATH_EPSILON) +#define MATH_FLOAT_VALID(a) (!isnan(a) && !isinf(a)) + + +#define decl_next_power2_(type) \ + type##_t math_##type##_next_power2(type##_t x) + +decl_next_power2_(int8); +decl_next_power2_(int16); +decl_next_power2_(int32); +decl_next_power2_(int64); +decl_next_power2_(uint8); +decl_next_power2_(uint16); +decl_next_power2_(uint32); +decl_next_power2_(uint64); + +#undef decl_next_power2_ + +#define decl_gcd_(type) \ + type##_t math_##type##_gcd(type##_t x, type##_t y) + +decl_gcd_(int8); +decl_gcd_(int16); +decl_gcd_(int32); +decl_gcd_(int64); +decl_gcd_(uint8); +decl_gcd_(uint16); +decl_gcd_(uint32); +decl_gcd_(uint64); + +#undef decl_gcd_ + +#define decl_lcm_(type) \ + type##_t math_##type##_lcm(type##_t x, type##_t y) + +decl_lcm_(int8); +decl_lcm_(int16); +decl_lcm_(int32); +decl_lcm_(int64); +decl_lcm_(uint8); +decl_lcm_(uint16); +decl_lcm_(uint32); +decl_lcm_(uint64); + +#undef decl_lcm_ diff --git a/util/math/algorithm.sos.c b/util/math/algorithm.sos.c new file mode 100644 index 0000000..9e18108 --- /dev/null +++ b/util/math/algorithm.sos.c @@ -0,0 +1,88 @@ +#include +#include +#include + +static void next_power2(const char* type, size_t N) { + assert(N > 0); + + /* https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 */ + + printf("%s%zu_t math_%s%zu_next_power2(%s%zu_t x) {", type, N, type, N, type, N); + + printf("--x;"); + for (size_t i = 1; i < N; i*=2) { + printf("x |= x >> %zu;", i); + } + printf("return ++x;"); + printf("}\n"); +} + +static void gcd(const char* type, size_t N) { + assert(N > 0); + + printf("%s%zu_t math_%s%zu_gcd(%s%zu_t x, %s%zu_t y) {", type, N, type, N, type, N, type, N); + + printf("assert(x > 0);"); + printf("assert(y > 0);"); + + printf("if (x < y) return math_%s%zu_gcd(y, x);", type, N); + + printf("%s%zu_t z;", type, N); + + printf("while (y) {"); + printf("z = x%%y;"); + printf("x = y;"); + printf("y = z;"); + printf("}"); + + printf("return x;}\n"); +} + +static void lcm(const char* type, size_t N) { + assert(N > 0); + + printf("%s%zu_t math_%s%zu_lcm(%s%zu_t x, %s%zu_t y) {", type, N, type, N, type, N, type, N); + + printf("assert(x > 0);"); + printf("assert(y > 0);"); + + printf("return x / math_%s%zu_gcd(x, y) * y;}\n", type, N); +} + +int main(int argc, char** argv) { + (void) argc, (void) argv; + + printf("#include \"./algorithm.h\"\n"); + + printf("#include \n"); + printf("#include \n"); + + next_power2("int", 8); + next_power2("int", 16); + next_power2("int", 32); + next_power2("int", 64); + next_power2("uint", 8); + next_power2("uint", 16); + next_power2("uint", 32); + next_power2("uint", 64); + + gcd("int", 8); + gcd("int", 16); + gcd("int", 32); + gcd("int", 64); + gcd("uint", 8); + gcd("uint", 16); + gcd("uint", 32); + gcd("uint", 64); + + lcm("int", 8); + lcm("int", 16); + lcm("int", 32); + lcm("int", 64); + lcm("uint", 8); + lcm("uint", 16); + lcm("uint", 32); + lcm("uint", 64); + + return EXIT_SUCCESS; +} diff --git a/util/math/constant.h b/util/math/constant.h new file mode 100644 index 0000000..44f1e89 --- /dev/null +++ b/util/math/constant.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +#define MATH_EPSILON FLT_EPSILON +#define MATH_INF 1e+10 +#define MATH_PI M_PI diff --git a/util/math/matrix.h b/util/math/matrix.h new file mode 100644 index 0000000..071e77f --- /dev/null +++ b/util/math/matrix.h @@ -0,0 +1,123 @@ +#pragma once + +#include +#include + +#include "./vector.h" + +typedef union { + vec2_t col[2]; + float elm[2][2]; + float ptr[4]; +} mat2_t; + +typedef union { + vec3_t col[3]; + float elm[3][3]; + float ptr[9]; +} mat3_t; + +typedef union { + vec4_t col[4]; + float elm[4][4]; + float ptr[16]; +} mat4_t; + +#define mat3_identity() (mat3_t) {{ \ + vec3(1, 0, 0), \ + vec3(0, 1, 0), \ + vec3(0, 0, 1), \ +}} +#define mat4_identity() (mat4_t) {{ \ + vec4(1, 0, 0, 0), \ + vec4(0, 1, 0, 0), \ + vec4(0, 0, 1, 0), \ + vec4(0, 0, 0, 1), \ +}} + +#define mat3_translation(x, y) (mat3_t) {{ \ + vec3(1, 0, 0), \ + vec3(0, 1, 0), \ + vec3(x, y, 1), \ +}} +#define mat4_translation(x, y, z) (mat4_t) {{ \ + vec4(1, 0, 0, 0), \ + vec4(0, 1, 0, 0), \ + vec4(0, 0, 1, 0), \ + vec4(x, y, z, 1), \ +}} + +#define mat3_scale(x, y) (mat3_t) {{ \ + vec3(x, 0, 0), \ + vec3(0, y, 0), \ + vec3(0, 0, 1), \ +}} +#define mat4_scale(x, y, z) (mat4_t) {{ \ + vec4(x, 0, 0, 0), \ + vec4(0, y, 0, 0), \ + vec4(0, 0, z, 0), \ + vec4(0, 0, 0, 1), \ +}} + +#define mat3_rotation_z(sin_theta, cos_theta) (mat3_t) {{ \ + vec3( cos_theta, sin_theta, 0), \ + vec3(-(sin_theta), cos_theta, 0), \ + vec3( 0, 0, 1), \ +}} + +#define mat4_rotation_x(sin_theta, cos_theta) (mat4_t) {{ \ + vec4(1, 0, 0, 0), \ + vec4(0, cos_theta, sin_theta, 0), \ + vec4(0, -(sin_theta), cos_theta, 0), \ + vec4(0, 0, 0, 1), \ +}} +#define mat4_rotation_y(sin_theta, cos_theta) (mat4_t) {{ \ + vec4(cos_theta, 0, -(sin_theta), 0), \ + vec4( 0, 1, 0, 0), \ + vec4(sin_theta, 0, cos_theta, 0), \ + vec4( 0, 0, 0, 1), \ +}} +#define mat4_rotation_z(sin_theta, cos_theta) (mat4_t) {{ \ + vec4( cos_theta, sin_theta, 0, 0), \ + vec4(-(sin_theta), cos_theta, 0, 0), \ + vec4( 0, 0, 1, 0), \ + vec4( 0, 0, 0, 1), \ +}} + +bool mat2_valid(const mat2_t* x); +bool mat3_valid(const mat3_t* x); +bool mat4_valid(const mat4_t* x); + +void mat2_add(mat2_t* x, const mat2_t* l, const mat2_t* r); +void mat3_add(mat3_t* x, const mat3_t* l, const mat3_t* r); +void mat4_add(mat4_t* x, const mat4_t* l, const mat4_t* r); + +void mat2_addeq(mat2_t* x, const mat2_t* r); +void mat3_addeq(mat3_t* x, const mat3_t* r); +void mat4_addeq(mat4_t* x, const mat4_t* r); + +void mat2_sub(mat2_t* x, const mat2_t* l, const mat2_t* r); +void mat3_sub(mat3_t* x, const mat3_t* l, const mat3_t* r); +void mat4_sub(mat4_t* x, const mat4_t* l, const mat4_t* r); + +void mat2_subeq(mat2_t* x, const mat2_t* r); +void mat3_subeq(mat3_t* x, const mat3_t* r); +void mat4_subeq(mat4_t* x, const mat4_t* r); + +void mat2_mul(mat2_t* x, const mat2_t* l, const mat2_t* r); +void mat3_mul(mat3_t* x, const mat3_t* l, const mat3_t* r); +void mat4_mul(mat4_t* x, const mat4_t* l, const mat4_t* r); + +void mat2_mul_vec2(vec2_t* x, const mat2_t* l, const vec2_t* r); +void mat3_mul_vec3(vec3_t* x, const mat3_t* l, const vec3_t* r); +void mat4_mul_vec4(vec4_t* x, const mat4_t* l, const vec4_t* r); + +float mat2_det(const mat2_t* x); +float mat3_det(const mat3_t* x); +float mat4_det(const mat4_t* x); + +void mat3_cofactor(mat2_t* x, const mat3_t* r, size_t row, size_t col); +void mat4_cofactor(mat3_t* x, const mat4_t* r, size_t row, size_t col); + +bool mat3_inv(mat3_t* x, const mat3_t* r); +bool mat4_inv(mat4_t* x, const mat4_t* r); diff --git a/util/math/matrix.sos.c b/util/math/matrix.sos.c new file mode 100644 index 0000000..62b35de --- /dev/null +++ b/util/math/matrix.sos.c @@ -0,0 +1,240 @@ +#include +#include +#include +#include +#include + +#define SWAP(a, b) do { \ + typeof(a) macro_SWAP_temp_ = a; \ + a = b; \ + b = macro_SWAP_temp_; \ +} while (0) + +static void next_permutation(size_t* p, size_t* c, size_t len) { + assert(p != NULL); + assert(c != NULL); + + size_t i = 0; + while (i < len) { + if (c[i] < i) { + if (i%2 == 0) { + SWAP(p[0], p[i]); + } else { + SWAP(p[c[i]], p[i]); + } + ++c[i]; + return; + } + c[i] = 0; + ++i; + } +} + +static void valid(size_t dim) { + assert(dim >= 2); + + printf("bool mat%zu_valid(const mat%zu_t* m) {", dim, dim); + + printf("return m != NULL && MATH_FLOAT_VALID(m->ptr[0])"); + for (size_t i = 1; i < dim*dim; ++i) { + printf("&& MATH_FLOAT_VALID(m->ptr[%zu])", i); + } + printf(";}\n"); +} + +static void each(size_t dim, const char* name, char op) { + assert(dim >= 2); + + printf("void mat%zu_%s(mat%zu_t* x, const mat%zu_t* l, const mat%zu_t* r) {", dim, name, dim, dim, dim); + + printf("assert(x != NULL);"); + printf("assert(mat%zu_valid(l));", dim); + printf("assert(mat%zu_valid(r));", dim); + + for (size_t i = 0; i < dim*dim; ++i) { + printf("x->ptr[%zu] = l->ptr[%zu] %c r->ptr[%zu];", i, i, op, i); + } + printf("}\n"); +} + +static void eacheq(size_t dim, const char* name, char op) { + assert(dim >= 2); + + printf("void mat%zu_%s(mat%zu_t* x, const mat%zu_t* r) {", dim, name, dim, dim); + + printf("assert(mat%zu_valid(x));", dim); + printf("assert(mat%zu_valid(r));", dim); + + for (size_t i = 0; i < dim*dim; ++i) { + printf("x->ptr[%zu] %c= r->ptr[%zu];", i, op, i); + } + printf("}\n"); +} + +static void mul(size_t dim) { + assert(dim >= 2); + + printf("void mat%zu_mul(mat%zu_t* x, const mat%zu_t* l, const mat%zu_t* r) {", dim, dim, dim, dim); + + printf("assert(x != NULL);"); + printf("assert(mat%zu_valid(l));", dim); + printf("assert(mat%zu_valid(r));", dim); + + for (size_t i = 0; i < dim; ++i) { + for (size_t j = 0; j < dim; ++j) { + printf("x->elm[%zu][%zu] = 0", j, i); + for (size_t k = 0; k < dim; ++k) { + printf("+ l->elm[%zu][%zu] * r->elm[%zu][%zu]", k, i, j, k); + } + printf(";"); + } + } + printf("}\n"); +} + +static void mul_vec(size_t dim) { + assert(dim >= 2); + + printf("void mat%zu_mul_vec%zu(vec%zu_t* x, const mat%zu_t* l, const vec%zu_t* r) {", dim, dim, dim, dim, dim); + + printf("assert(x != NULL);"); + printf("assert(mat%zu_valid(l));", dim); + printf("assert(vec%zu_valid(r));", dim); + + for (size_t i = 0; i < dim; ++i) { + printf("x->ptr[%zu] = 0", i); + for (size_t j = 0; j < dim; ++j) { + printf("+ l->elm[%zu][%zu] * r->ptr[%zu]", j, i, j); + } + printf(";"); + } + printf("}\n"); +} + +static void det(size_t dim) { + assert(dim >= 2); + + size_t p[dim]; + for (size_t i = 0; i < dim; ++i) p[i] = i; + + size_t c[dim]; + for (size_t i = 0; i < dim; ++i) c[i] = 0; + + printf("float mat%zu_det(const mat%zu_t* x) {", dim, dim); + printf("assert(mat%zu_valid(x));", dim); + + size_t f = 1; + for (size_t i = 1; i <= dim; ++i) f *= i; + + printf("return 0"); + + bool positive = true; + for (size_t i = 0; i < f; ++i) { + printf("%c1", positive? '+': '-'); + for (size_t j = 0; j < dim; ++j) { + printf("*x->elm[%zu][%zu]", p[j], j); + } + next_permutation(p, c, dim); + positive = !positive; + } + printf(";}\n"); +} + +static void cofactor(size_t dim) { + assert(dim >= 3); + + printf("void mat%zu_cofactor(mat%zu_t* x, const mat%zu_t* r, size_t row, size_t col) {", dim, dim-1, dim); + + printf("assert(x != NULL);"); + printf("assert(mat%zu_valid(r));", dim); + printf("assert(row < %zu);", dim); + printf("assert(col < %zu);", dim); + + for (size_t row = 0; row < dim; ++row) { + for (size_t col = 0; col < dim; ++col) { + printf("if (row == %zu && col == %zu) {", row, col); + for (size_t xi = 0; xi < dim-1; ++xi) { + const size_t ri = xi >= row? xi+1: xi; + for (size_t xj = 0; xj < dim-1; ++xj) { + const size_t rj = xj >= col? xj+1: xj; + printf("x->elm[%zu][%zu] = %s r->elm[%zu][%zu];", xj, xi, (xj+xi)%2 == 0? "": "-", rj, ri); + } + } + printf("return;}"); + } + } + printf("}\n"); +} + +static void inv(size_t dim) { + assert(dim >= 3); + + printf("bool mat%zu_inv(mat%zu_t* x, const mat%zu_t* r) {", dim, dim, dim); + + printf("assert(x != NULL);"); + printf("assert(mat%zu_valid(r));", dim); + + printf("const float d = mat%zu_det(r);", dim); + printf("if (d == 0) return false;"); + + printf("mat%zu_t co;", dim-1); + for (size_t i = 0; i < dim; ++i) { + for (size_t j = 0; j < dim; ++j) { + printf("mat%zu_cofactor(&co, r, %zu, %zu);", dim, i, j); + printf("x->elm[%zu][%zu] = mat%zu_det(&co) / d;", j, i, dim-1); + } + } + printf("return true;"); + printf("}\n"); +} + +int main(int argc, char** argv) { + (void) argc, (void) argv; + + printf("#include \"./matrix.h\"\n"); + + printf("#include \n"); + printf("#include \n"); + printf("#include \n"); + + printf("#include \"./algorithm.h\"\n"); + printf("#include \"./vector.h\"\n"); + + valid(2); + valid(3); + valid(4); + + each(2, "add", '+'); + each(3, "add", '+'); + each(4, "add", '+'); + + eacheq(2, "addeq", '+'); + eacheq(3, "addeq", '+'); + eacheq(4, "addeq", '+'); + + each(2, "sub", '-'); + each(3, "sub", '-'); + each(4, "sub", '-'); + + eacheq(2, "subeq", '-'); + eacheq(3, "subeq", '-'); + eacheq(4, "subeq", '-'); + + mul(2); + mul(3); + mul(4); + + mul_vec(2); + mul_vec(3); + mul_vec(4); + + det(2); + det(3); + det(4); + + cofactor(3); + cofactor(4); + + inv(3); + inv(4); +} diff --git a/util/math/rational.c b/util/math/rational.c new file mode 100644 index 0000000..68029c3 --- /dev/null +++ b/util/math/rational.c @@ -0,0 +1,110 @@ +#include "./rational.h" + +#include +#include + +#include "./algorithm.h" + +bool rational_valid(const rational_t* x) { + return x != NULL && x->den != 0; +} + +void rational_add(rational_t* x, const rational_t* a, const rational_t* b) { + assert(x != NULL); + assert(rational_valid(a)); + assert(rational_valid(b)); + + const int64_t d = math_int64_lcm(a->den, b->den); + + *x = (typeof(*x)) { + .num = a->num*(d/a->den) + b->num*(d/b->den), + .den = d, + }; +} +void rational_addeq(rational_t* x, const rational_t* a) { + assert(rational_valid(x)); + assert(rational_valid(a)); + + const int64_t d = math_int64_lcm(x->den, a->den); + x->num = x->num*(d/x->den) + a->num*(d/a->den); + x->den = d; +} + +void rational_sub(rational_t* x, const rational_t* a, const rational_t* b) { + assert(x != NULL); + assert(rational_valid(a)); + assert(rational_valid(b)); + + rational_t c = *b; + c.num *= -1; + rational_add(x, a, &c); +} +void rational_subeq(rational_t* x, const rational_t* a) { + assert(rational_valid(x)); + assert(rational_valid(a)); + + rational_t c = *a; + c.num *= -1; + rational_addeq(x, &c); +} + +void rational_mul(rational_t* x, const rational_t* a, const rational_t* b) { + assert(x != NULL); + assert(rational_valid(a)); + assert(rational_valid(b)); + + *x = (typeof(*x)) { + .num = a->num*b->num, + .den = a->den*b->den, + }; +} +void rational_muleq(rational_t* x, const rational_t* a) { + assert(rational_valid(x)); + assert(rational_valid(a)); + + x->num *= a->num; + x->den *= a->den; +} + +void rational_div(rational_t* x, const rational_t* a, const rational_t* b) { + assert(x != NULL); + assert(rational_valid(a)); + assert(rational_valid(b)); + assert(b->num != 0); + + *x = (typeof(*x)) { + .num = a->num*b->den, + .den = a->den*b->num, + }; +} +void rational_diveq(rational_t* x, const rational_t* a) { + assert(rational_valid(x)); + assert(rational_valid(a)); + assert(a->num != 0); + + x->num *= a->den; + x->den *= a->num; +} + +void rational_simplify(rational_t* x) { + assert(rational_valid(x)); + + const int64_t d = + x->num != 0? math_int64_gcd(MATH_ABS(x->num), MATH_ABS(x->den)): x->den; + + x->num /= d; + x->den /= d; +} +void rational_normalize(rational_t* x, int64_t den) { + assert(rational_valid(x)); + assert(den != 0); + + x->num = x->num * den / x->den; + x->den = den; +} + +float rational_calculate(const rational_t* x) { + assert(rational_valid(x)); + + return x->num*1.0f / x->den; +} diff --git a/util/math/rational.h b/util/math/rational.h new file mode 100644 index 0000000..0277e50 --- /dev/null +++ b/util/math/rational.h @@ -0,0 +1,84 @@ +#pragma once + +#include +#include + +typedef struct { + int64_t num; + int64_t den; +} rational_t; + +#define rational(n, d) ((rational_t) { \ + .num = n, \ + .den = d, \ + }) + +bool +rational_valid( + const rational_t* x +); + +void +rational_add( + rational_t* x, + const rational_t* a, + const rational_t* b +); +void +rational_addeq( + rational_t* x, + const rational_t* a +); + +void +rational_sub( + rational_t* x, + const rational_t* a, + const rational_t* b +); +void +rational_subeq( + rational_t* x, + const rational_t* a +); + +void +rational_mul( + rational_t* x, + const rational_t* a, + const rational_t* b +); +void +rational_muleq( + rational_t* x, + const rational_t* a +); + +void +rational_div( + rational_t* x, + const rational_t* a, + const rational_t* b +); +void +rational_diveq( + rational_t* x, + const rational_t* a +); + +void +rational_simplify( + rational_t* x +); + +/* Don't forget that this operation may cause a discrepancy. */ +void +rational_normalize( + rational_t* x, + int64_t den +); + +float +rational_calculate( + const rational_t* x +); diff --git a/util/math/test.c b/util/math/test.c new file mode 100644 index 0000000..af2b8e4 --- /dev/null +++ b/util/math/test.c @@ -0,0 +1,111 @@ +#undef NDEBUG + +#include +#include +#include +#include +#include + +#include "./algorithm.h" +#include "./matrix.h" +#include "./rational.h" +#include "./vector.h" + +static bool vec4_is_equal(vec4_t v1, vec4_t v2) { + return + MATH_FLOAT_EQUAL(v1.x, v2.x) && + MATH_FLOAT_EQUAL(v1.y, v2.y) && + MATH_FLOAT_EQUAL(v1.z, v2.z) && + MATH_FLOAT_EQUAL(v1.w, v2.w); +} + +static void test_matrix() { + vec4_t v1, v2; + mat4_t m1, m2, m3; + + v1 = vec4(1, 1, 1, 1); + m1 = mat4_identity(); + mat4_mul_vec4(&v2, &m1, &v1); + assert(vec4_is_equal(v2, v1)); + + v1 = vec4(1, 1, 1, 1); + m1 = mat4_translation(1, 1, 1); + mat4_mul_vec4(&v2, &m1, &v1); + assert(vec4_is_equal(v2, vec4(2, 2, 2, 1))); + + v1 = vec4(1, 1, 1, 1); + m1 = mat4_scale(.5f, 2, 1); + mat4_mul_vec4(&v2, &m1, &v1); + assert(vec4_is_equal(v2, vec4(.5f, 2, 1, 1))); + + v1 = vec4(0, 1, 0, 1); + m1 = mat4_rotation_x(-1 /* = sin -PI/2 */, 0 /* = cos -PI/2 */); + mat4_mul_vec4(&v2, &m1, &v1); + assert(vec4_is_equal(v2, vec4(0, 0, -1, 1))); + + v1 = vec4(1, 0, 0, 1); + m1 = mat4_rotation_y(1 /* = sin PI/2 */, 0 /* = cos PI/2 */); + mat4_mul_vec4(&v2, &m1, &v1); + assert(vec4_is_equal(v2, vec4(0, 0, -1, 1))); + + v1 = vec4(0, 0, -1, 1); + m1 = mat4_rotation_z(1 /* = sin PI/2 */, 0 /* = cos PI/2 */); + mat4_mul_vec4(&v2, &m1, &v1); + assert(vec4_is_equal(v2, vec4(0, 0, -1, 1))); + + v1 = vec4(-1, 1, 0, 1); + m1 = mat4_rotation_z(1 /* = sin PI/2 */, 0 /* = cos PI/2 */); + m2 = mat4_scale(.5f, .5f, .5f); + mat4_mul(&m3, &m2, &m1); + m1 = mat4_translation(1, 1, 0); + mat4_mul(&m2, &m1, &m3); + mat4_mul_vec4(&v2, &m2, &v1); + assert(vec4_is_equal(v2, vec4(.5f, .5f, 0, 1))); +} + +static void test_algorithm(void) { + assert(math_int32_next_power2(8) == 8); + assert(math_int32_next_power2(20) == 32); + assert(math_int32_next_power2(200) == 256); + + assert(math_int32_gcd(10, 5) == 5); + assert(math_int32_gcd(12, 6) == 6); + assert(math_int32_gcd(12, 9) == 3); + assert(math_int32_gcd(12, 11) == 1); + + assert(math_int32_lcm(10, 5) == 10); + assert(math_int32_lcm(12, 10) == 60); + assert(math_int32_lcm(7, 8) == 56); +} + +static void test_rational(void) { + rational_t x, y; + + x = rational(1, 2); + y = rational(1, 4); + rational_addeq(&x, &y); + assert(x.num == 3 && x.den == 4); + + x = rational(1, 2); + y = rational(1, 3); + rational_muleq(&x, &y); + assert(x.num == 1 && x.den == 6); + + x = rational(10, 20); + rational_simplify(&x); + assert(x.num == 1 && x.den == 2); + + x = rational(1, 2); + rational_normalize(&x, 100); + assert(x.num == 50 && x.den == 100); + + x = rational(1, 2); + assert(rational_calculate(&x) == 1.0f/2.0f); +} + +int main(void) { + test_matrix(); + test_algorithm(); + test_rational(); + return 0; +} diff --git a/util/math/vector.c b/util/math/vector.c new file mode 100644 index 0000000..7790ad8 --- /dev/null +++ b/util/math/vector.c @@ -0,0 +1,275 @@ +#include "./vector.h" + +#include +#include +#include +#include + +#include "./algorithm.h" + +bool vec2_valid(const vec2_t* x) { + return x != NULL && + MATH_FLOAT_VALID(x->ptr[0]) && + MATH_FLOAT_VALID(x->ptr[1]); +} +bool vec3_valid(const vec3_t* x) { + return x != NULL && + MATH_FLOAT_VALID(x->ptr[0]) && + MATH_FLOAT_VALID(x->ptr[1]) && + MATH_FLOAT_VALID(x->ptr[2]); +} +bool vec4_valid(const vec4_t* x) { + return x != NULL && + MATH_FLOAT_VALID(x->ptr[0]) && + MATH_FLOAT_VALID(x->ptr[1]) && + MATH_FLOAT_VALID(x->ptr[2]) && + MATH_FLOAT_VALID(x->ptr[3]); +} + +void vec2_add(vec2_t* x, const vec2_t* l, const vec2_t* r) { + assert(x != NULL); + assert(l != NULL); + assert(r != NULL); + + x->x = l->x + r->x; + x->y = l->y + r->y; +} +void vec3_add(vec3_t* x, const vec3_t* l, const vec3_t* r) { + assert(x != NULL); + assert(l != NULL); + assert(r != NULL); + + x->x = l->x + r->x; + x->y = l->y + r->y; + x->z = l->z + r->z; +} +void vec4_add(vec4_t* x, const vec4_t* l, const vec4_t* r) { + assert(x != NULL); + assert(l != NULL); + assert(r != NULL); + + x->x = l->x + r->x; + x->y = l->y + r->y; + x->z = l->z + r->z; + x->w = l->w + r->w; +} + +void vec2_addeq(vec2_t* x, const vec2_t* r) { + assert(x != NULL); + assert(r != NULL); + + x->x += r->x; + x->y += r->y; +} +void vec3_addeq(vec3_t* x, const vec3_t* r) { + assert(x != NULL); + assert(r != NULL); + + x->x += r->x; + x->y += r->y; + x->z += r->z; +} +void vec4_addeq(vec4_t* x, const vec4_t* r) { + assert(x != NULL); + assert(r != NULL); + + x->x += r->x; + x->y += r->y; + x->z += r->z; + x->w += r->w; +} + +void vec2_sub(vec2_t* x, const vec2_t* l, const vec2_t* r) { + assert(x != NULL); + assert(l != NULL); + assert(r != NULL); + + x->x = l->x - r->x; + x->y = l->y - r->y; +} +void vec3_sub(vec3_t* x, const vec3_t* l, const vec3_t* r) { + assert(x != NULL); + assert(l != NULL); + assert(r != NULL); + + x->x = l->x - r->x; + x->y = l->y - r->y; + x->z = l->z - r->z; +} +void vec4_sub(vec4_t* x, const vec4_t* l, const vec4_t* r) { + assert(x != NULL); + assert(l != NULL); + assert(r != NULL); + + x->x = l->x - r->x; + x->y = l->y - r->y; + x->z = l->z - r->z; + x->w = l->w - r->w; +} + +void vec2_subeq(vec2_t* x, const vec2_t* r) { + assert(x != NULL); + assert(r != NULL); + + x->x -= r->x; + x->y -= r->y; +} +void vec3_subeq(vec3_t* x, const vec3_t* r) { + assert(x != NULL); + assert(r != NULL); + + x->x -= r->x; + x->y -= r->y; + x->z -= r->z; +} +void vec4_subeq(vec4_t* x, const vec4_t* r) { + assert(x != NULL); + assert(r != NULL); + + x->x -= r->x; + x->y -= r->y; + x->z -= r->z; + x->w -= r->w; +} + +void vec2_mul(vec2_t* x, const vec2_t* l, float r) { + assert(x != NULL); + assert(l != NULL); + + x->x = l->x * r; + x->y = l->y * r; +} +void vec3_mul(vec3_t* x, const vec3_t* l, float r) { + assert(x != NULL); + assert(l != NULL); + + x->x = l->x * r; + x->y = l->y * r; + x->z = l->z * r; +} +void vec4_mul(vec4_t* x, const vec4_t* l, float r) { + assert(x != NULL); + assert(l != NULL); + + x->x = l->x * r; + x->y = l->y * r; + x->z = l->z * r; + x->w = l->w * r; +} + +void vec2_muleq(vec2_t* x, float r) { + assert(x != NULL); + + x->x *= r; + x->y *= r; +} +void vec3_muleq(vec3_t* x, float r) { + assert(x != NULL); + + x->x *= r; + x->y *= r; + x->z *= r; +} +void vec4_muleq(vec4_t* x, float r) { + assert(x != NULL); + + x->x *= r; + x->y *= r; + x->z *= r; + x->w *= r; +} + +void vec2_div(vec2_t* x, const vec2_t* l, float r) { + assert(x != NULL); + assert(l != NULL); + + x->x = l->x / r; + x->y = l->y / r; +} +void vec3_div(vec3_t* x, const vec3_t* l, float r) { + assert(x != NULL); + assert(l != NULL); + + x->x = l->x / r; + x->y = l->y / r; + x->z = l->z / r; +} +void vec4_div(vec4_t* x, const vec4_t* l, float r) { + assert(x != NULL); + assert(l != NULL); + + x->x = l->x / r; + x->y = l->y / r; + x->z = l->z / r; + x->w = l->w / r; +} + +void vec2_diveq(vec2_t* x, float r) { + assert(x != NULL); + + x->x /= r; + x->y /= r; +} +void vec3_diveq(vec3_t* x, float r) { + assert(x != NULL); + + x->x /= r; + x->y /= r; + x->z /= r; +} +void vec4_diveq(vec4_t* x, float r) { + assert(x != NULL); + + x->x /= r; + x->y /= r; + x->z /= r; + x->w /= r; +} + +float vec2_pow_length(const vec2_t* x) { + assert(x != NULL); + return x->x*x->x + x->y*x->y; +} +float vec3_pow_length(const vec3_t* x) { + assert(x != NULL); + return x->x*x->x + x->y*x->y + x->z*x->z; +} +float vec4_pow_length(const vec4_t* x) { + assert(x != NULL); + return x->x*x->x + x->y*x->y + x->z*x->z + x->w*x->w; +} + +float vec2_length(const vec2_t* x) { + assert(x != NULL); + return sqrtf(vec2_pow_length(x)); +} +float vec3_length(const vec3_t* x) { + assert(x != NULL); + return sqrtf(vec3_pow_length(x)); +} +float vec4_length(const vec4_t* x) { + assert(x != NULL); + return sqrtf(vec4_pow_length(x)); +} + +float vec2_dot(const vec2_t* l, const vec2_t* r) { + assert(l != NULL); + assert(r != NULL); + return l->x*r->x + l->y*r->y; +} +float vec3_dot(const vec3_t* l, const vec3_t* r) { + assert(l != NULL); + assert(r != NULL); + return l->x*r->x + l->y*r->y + l->z*r->z; +} +float vec4_dot(const vec4_t* l, const vec4_t* r) { + assert(l != NULL); + assert(r != NULL); + return l->x*r->x + l->y*r->y + l->z*r->z + l->w*r->w; +} + +float vec2_cross(const vec2_t* l, const vec2_t* r) { + assert(l != NULL); + assert(r != NULL); + return l->x*r->y - l->y*r->x; +} diff --git a/util/math/vector.h b/util/math/vector.h new file mode 100644 index 0000000..48c4b4f --- /dev/null +++ b/util/math/vector.h @@ -0,0 +1,78 @@ +#pragma once + +#include + +typedef union { + struct { float x, y; }; + float ptr[2]; +} vec2_t; + +typedef union { + struct { float x, y, z; }; + float ptr[3]; + + vec2_t xy; +} vec3_t; + +typedef union { + struct { float x, y, z, w; }; + float ptr[4]; + + vec2_t xy; + vec3_t xyz; +} vec4_t; + +#define vec2(x, y) (vec2_t) {{x, y}} +#define vec3(x, y, z) (vec3_t) {{x, y, z}} +#define vec4(x, y, z, w) (vec4_t) {{x, y, z, w}} + +bool vec2_valid(const vec2_t* x); +bool vec3_valid(const vec3_t* x); +bool vec4_valid(const vec4_t* x); + +void vec2_add(vec2_t* x, const vec2_t* l, const vec2_t* r); +void vec3_add(vec3_t* x, const vec3_t* l, const vec3_t* r); +void vec4_add(vec4_t* x, const vec4_t* l, const vec4_t* r); + +void vec2_addeq(vec2_t* x, const vec2_t* r); +void vec3_addeq(vec3_t* x, const vec3_t* r); +void vec4_addeq(vec4_t* x, const vec4_t* r); + +void vec2_sub(vec2_t* x, const vec2_t* l, const vec2_t* r); +void vec3_sub(vec3_t* x, const vec3_t* l, const vec3_t* r); +void vec4_sub(vec4_t* x, const vec4_t* l, const vec4_t* r); + +void vec2_subeq(vec2_t* x, const vec2_t* r); +void vec3_subeq(vec3_t* x, const vec3_t* r); +void vec4_subeq(vec4_t* x, const vec4_t* r); + +void vec2_mul(vec2_t* x, const vec2_t* l, float r); +void vec3_mul(vec3_t* x, const vec3_t* l, float r); +void vec4_mul(vec4_t* x, const vec4_t* l, float r); + +void vec2_muleq(vec2_t* x, float r); +void vec3_muleq(vec3_t* x, float r); +void vec4_muleq(vec4_t* x, float r); + +void vec2_div(vec2_t* x, const vec2_t* l, float r); +void vec3_div(vec3_t* x, const vec3_t* l, float r); +void vec4_div(vec4_t* x, const vec4_t* l, float r); + +void vec2_diveq(vec2_t* x, float r); +void vec3_diveq(vec3_t* x, float r); +void vec4_diveq(vec4_t* x, float r); + +float vec2_pow_length(const vec2_t* x); +float vec3_pow_length(const vec3_t* x); +float vec4_pow_length(const vec4_t* x); + +float vec2_length(const vec2_t* x); +float vec3_length(const vec3_t* x); +float vec4_length(const vec4_t* x); + +float vec2_dot(const vec2_t* l, const vec2_t* r); +float vec3_dot(const vec3_t* l, const vec3_t* r); +float vec4_dot(const vec4_t* l, const vec4_t* r); + +float vec2_cross(const vec2_t* l, const vec2_t* r); +/* TODO(catfoot): add vec3_cross function */ diff --git a/util/memory/CMakeLists.txt b/util/memory/CMakeLists.txt new file mode 100644 index 0000000..f0a52d8 --- /dev/null +++ b/util/memory/CMakeLists.txt @@ -0,0 +1,10 @@ +add_library(memory + memory.c +) + +if (BUILD_TESTING) + add_executable(memory-test test.c) + target_link_libraries(memory-test memory) + + add_test(memory-test memory-test) +endif() diff --git a/util/memory/memory.c b/util/memory/memory.c new file mode 100644 index 0000000..4731bcb --- /dev/null +++ b/util/memory/memory.c @@ -0,0 +1,81 @@ +#include "./memory.h" + +#include +#include +#include +#include +#include + +#ifdef __linux__ +# include +#endif + +#define MEMORY_LOG_FILE "memory-trace" + +void* memory_new(size_t size) { + if (size == 0) return NULL; + + void* ptr = malloc(size); + if (ptr == NULL) { + fprintf(stderr, "failed to allocate %zu bytes memory", size); + abort(); + } + + memory_log("new 0x%tx (%zu bytes)", (ptrdiff_t) ptr, size); + return ptr; +} + +void* memory_resize(void* ptr, size_t size) { + if (ptr == NULL) return memory_new(size); + if (size == 0) return ptr; + + void* newptr = realloc(ptr, size); + if (newptr == NULL) { + fprintf(stderr, + "failed to allocate %zu bytes memory for resizing 0x%tx", + size, (ptrdiff_t) ptr); + abort(); + } + + memory_log("resize 0x%tx -> 0x%tx (%zu bytes)", + (ptrdiff_t) ptr, (ptrdiff_t) newptr, size); + return newptr; +} + +void memory_delete(void* ptr) { + if (ptr == NULL) return; + free(ptr); + + memory_log("delete 0x%tx", (ptrdiff_t) ptr); +} + +#ifndef NDEBUG + void memory_log(const char* fmt, ...) { + static FILE* fp = NULL; + + if (fp == NULL) { + fp = fopen(MEMORY_LOG_FILE, "w"); + assert(fp != NULL); + } + + va_list args; + va_start(args, fmt); + vfprintf(fp, fmt, args); + va_end(args); + + fprintf(fp, "\n"); + +# if __linux__ + void* callstack[32]; + const size_t frames = backtrace(callstack, sizeof(callstack)); + char** symbols = backtrace_symbols(callstack, frames); + for (size_t i = 0; i < frames; ++i) { + fprintf(fp, " %s\n", symbols[i]); + } + free(symbols); +# endif /* __linux__ */ + + fprintf(fp, "\n"); + fflush(fp); + } +#endif /* NDEBUG */ diff --git a/util/memory/memory.h b/util/memory/memory.h new file mode 100644 index 0000000..4cf35ac --- /dev/null +++ b/util/memory/memory.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +void* +memory_new( + size_t size +); + +void* +memory_resize( + void* ptr, + size_t size +); + +void +memory_delete( + void* ptr +); + +#ifndef NDEBUG + void + memory_log( + const char* fmt, + ... + ); +#else +# define memory_log(fmt, ...) (void) fmt +#endif /* NDEBUG */ diff --git a/util/memory/test.c b/util/memory/test.c new file mode 100644 index 0000000..7642a94 --- /dev/null +++ b/util/memory/test.c @@ -0,0 +1,8 @@ +#include "./memory.h" + +int main(void) { + void* ptr = memory_new(0x1); + ptr = memory_resize(ptr, 0x2); + memory_delete(ptr); + return 0; +} diff --git a/util/mpkutil/CMakeLists.txt b/util/mpkutil/CMakeLists.txt new file mode 100644 index 0000000..40eacec --- /dev/null +++ b/util/mpkutil/CMakeLists.txt @@ -0,0 +1,10 @@ +add_library(mpkutil + file.c + get.c + pack.c +) +target_link_libraries(mpkutil + msgpackc + + math +) diff --git a/util/mpkutil/README.md b/util/mpkutil/README.md new file mode 100644 index 0000000..075bc0d --- /dev/null +++ b/util/mpkutil/README.md @@ -0,0 +1,4 @@ +mpkutil +==== + +utility library for msgpack-c diff --git a/util/mpkutil/file.c b/util/mpkutil/file.c new file mode 100644 index 0000000..9e2696b --- /dev/null +++ b/util/mpkutil/file.c @@ -0,0 +1,58 @@ +#include "./file.h" + +#include +#include +#include +#include +#include + +#include + +bool mpkutil_file_unpack_with_unpacker( + msgpack_unpacked* obj, + FILE* fp, + msgpack_unpacker* unpacker) { + assert(obj != NULL); + assert(fp != NULL); + assert(unpacker != NULL); + + for (;;) { + if (feof(fp)) return false; + + const size_t maxlen = msgpack_unpacker_buffer_capacity(unpacker); + if (maxlen == 0) { + fprintf(stderr, "unpacker buffer overflow\n"); + abort(); + } + + const size_t len = fread( + msgpack_unpacker_buffer(unpacker), 1, maxlen, fp); + if (ferror(fp)) return false; + + msgpack_unpacker_buffer_consumed(unpacker, len); + + size_t parsed_len; + switch (msgpack_unpacker_next_with_size(unpacker, obj, &parsed_len)) { + case MSGPACK_UNPACK_SUCCESS: + return fseek(fp, (long) parsed_len - len, SEEK_CUR) == 0; + case MSGPACK_UNPACK_CONTINUE: + break; + + case MSGPACK_UNPACK_PARSE_ERROR: + default: + return false; + } + } +} + +bool mpkutil_file_unpack(msgpack_unpacked* obj, FILE* fp) { + assert(fp != NULL); + assert(obj != NULL); + + msgpack_unpacker unpacker; + if (!msgpack_unpacker_init(&unpacker, 1024)) return false; + + const bool result = mpkutil_file_unpack_with_unpacker(obj, fp, &unpacker); + msgpack_unpacker_destroy(&unpacker); + return result; +} diff --git a/util/mpkutil/file.h b/util/mpkutil/file.h new file mode 100644 index 0000000..6f73daa --- /dev/null +++ b/util/mpkutil/file.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +#include + +bool +mpkutil_file_unpack_with_unpacker( + msgpack_unpacked* obj, + FILE* fp, + msgpack_unpacker* unpacker +); + +bool +mpkutil_file_unpack( + msgpack_unpacked* obj, + FILE* fp +); diff --git a/util/mpkutil/get.c b/util/mpkutil/get.c new file mode 100644 index 0000000..b88854b --- /dev/null +++ b/util/mpkutil/get.c @@ -0,0 +1,143 @@ +#include "./get.h" + +#include +#include +#include +#include + +#include + +#include "util/math/algorithm.h" +#include "util/math/vector.h" + +bool mpkutil_get_bool(const msgpack_object* obj, bool* b) { + if (obj == NULL) return false; + if (obj->type != MSGPACK_OBJECT_BOOLEAN) return false; + if (b != NULL) *b = obj->via.boolean; + return true; +} + +bool mpkutil_get_int(const msgpack_object* obj, intmax_t* i) { + if (obj == NULL) return false; + if (obj->type != MSGPACK_OBJECT_POSITIVE_INTEGER && + obj->type != MSGPACK_OBJECT_NEGATIVE_INTEGER) { + return false; + } + if (i != NULL) *i = obj->via.i64; + return true; +} + +#define define_mpkutil_get_intN_(N) \ + bool mpkutil_get_int##N(const msgpack_object* obj, int##N##_t* i) { \ + intmax_t temp; \ + if (!mpkutil_get_int(obj, &temp) || \ + temp < INT##N##_MIN || temp > INT##N##_MAX) { \ + return false; \ + } \ + if (i != NULL) *i = temp; \ + return true; \ + } +define_mpkutil_get_intN_(8); +define_mpkutil_get_intN_(16); +define_mpkutil_get_intN_(32); +define_mpkutil_get_intN_(64); +#undef define_mpkutil_get_intN_ + +bool mpkutil_get_uint(const msgpack_object* obj, uintmax_t* i) { + if (obj == NULL || obj->type != MSGPACK_OBJECT_POSITIVE_INTEGER) { + return false; + } + if (i != NULL) *i = obj->via.u64; + return true; +} + +#define define_mpkutil_get_uintN_(N) \ + bool mpkutil_get_uint##N(const msgpack_object* obj, uint##N##_t* i) { \ + uintmax_t temp; \ + if (!mpkutil_get_uint(obj, &temp) || temp > UINT##N##_MAX) { \ + return false; \ + } \ + if (i != NULL) *i = temp; \ + return true; \ + } +define_mpkutil_get_uintN_(8); +define_mpkutil_get_uintN_(16); +define_mpkutil_get_uintN_(32); +define_mpkutil_get_uintN_(64); +#undef define_mpkutil_get_uintN_ + +bool mpkutil_get_float(const msgpack_object* obj, float* f) { + if (obj == NULL || obj->type != MSGPACK_OBJECT_FLOAT64 || + !MATH_FLOAT_VALID(obj->via.f64)) { + return false; + } + if (f != NULL) *f = obj->via.f64; + return true; +} + +bool mpkutil_get_str(const msgpack_object* obj, const char** s, size_t* len) { + if (obj == NULL || obj->type != MSGPACK_OBJECT_STR) { + return false; + } + if (s != NULL) *s = obj->via.str.ptr; + if (len != NULL) *len = obj->via.str.size; + return true; +} + +bool mpkutil_get_vec2(const msgpack_object* obj, vec2_t* v) { + const msgpack_object_array* array = mpkutil_get_array(obj); + if (array == NULL || array->size != 2) return false; + for (size_t i = 0; i < 2; ++i) { + if (array->ptr[i].type != MSGPACK_OBJECT_FLOAT64) return false; + } + if (v != NULL) { + v->x = array->ptr[0].via.f64; + v->y = array->ptr[1].via.f64; + } + return true; +} + +bool mpkutil_get_vec4(const msgpack_object* obj, vec4_t* v) { + const msgpack_object_array* array = mpkutil_get_array(obj); + if (array == NULL || array->size != 4) return false; + for (size_t i = 0; i < 4; ++i) { + if (array->ptr[i].type != MSGPACK_OBJECT_FLOAT64) return false; + } + if (v != NULL) { + v->x = array->ptr[0].via.f64; + v->y = array->ptr[1].via.f64; + v->y = array->ptr[2].via.f64; + v->y = array->ptr[3].via.f64; + } + return true; +} + +const msgpack_object_array* mpkutil_get_array(const msgpack_object* obj) { + if (obj == NULL || obj->type != MSGPACK_OBJECT_ARRAY) { + return NULL; + } + return &obj->via.array; +} + +const msgpack_object_map* mpkutil_get_map(const msgpack_object* obj) { + if (obj == NULL || obj->type != MSGPACK_OBJECT_MAP) { + return NULL; + } + return &obj->via.map; +} + +const msgpack_object* mpkutil_get_map_item_by_str( + const msgpack_object_map* map, const char* name) { + if (map == NULL) return NULL; + + for (size_t i = 0; i < map->size; ++i) { + const msgpack_object* key_obj = &map->ptr[i].key; + if (key_obj->type != MSGPACK_OBJECT_STR) continue; + + const msgpack_object_str* key = &key_obj->via.str; + if (strncmp(key->ptr, name, key->size) == 0 && name[key->size] == 0) { + return &map->ptr[i].val; + } + } + return NULL; +} diff --git a/util/mpkutil/get.h b/util/mpkutil/get.h new file mode 100644 index 0000000..d37533f --- /dev/null +++ b/util/mpkutil/get.h @@ -0,0 +1,92 @@ +#pragma once + +#include +#include +#include + +#include + +#include "util/math/vector.h" + +bool +mpkutil_get_bool( + const msgpack_object* obj, /* NULLABLE */ + bool* b /* NULLABLE */ +); + +bool +mpkutil_get_int( + const msgpack_object* obj, /* NULLABLE */ + intmax_t* i /* NULLABLE */ +); + +#define decl_mpkutil_get_intN_(N) \ + bool \ + mpkutil_get_int##N( \ + const msgpack_object* obj, \ + int##N##_t* i \ + ); +decl_mpkutil_get_intN_(8); +decl_mpkutil_get_intN_(16); +decl_mpkutil_get_intN_(32); +decl_mpkutil_get_intN_(64); +#undef decl_mpkutil_get_intN_ + +bool +mpkutil_get_uint( + const msgpack_object* obj, /* NULLABLE */ + uintmax_t* i /* NULLABLE */ +); + +#define decl_mpkutil_get_uintN_(N) \ + bool \ + mpkutil_get_uint##N( \ + const msgpack_object* obj, \ + uint##N##_t* i \ + ); +decl_mpkutil_get_uintN_(8); +decl_mpkutil_get_uintN_(16); +decl_mpkutil_get_uintN_(32); +decl_mpkutil_get_uintN_(64); +#undef decl_mpkutil_get_uintN_ + +bool +mpkutil_get_float( + const msgpack_object* obj, /* NULLABLE */ + float* f /* NULLABLE */ +); + +bool +mpkutil_get_str( + const msgpack_object* obj, /* NULLABLE */ + const char** s, /* NULLABLE */ + size_t* len /* NULLABLE */ +); + +bool +mpkutil_get_vec2( + const msgpack_object* obj, /* NULLABLE */ + vec2_t* v /* NULLABLE */ +); + +bool +mpkutil_get_vec4( + const msgpack_object* obj, /* NULLABLE */ + vec4_t* v /* NULLABLE */ +); + +const msgpack_object_array* /* NULLABLE */ +mpkutil_get_array( + const msgpack_object* obj /* NULLABLE */ +); + +const msgpack_object_map* /* NULLABLE */ +mpkutil_get_map( + const msgpack_object* obj /* NULLABLE */ +); + +const msgpack_object* /* NULLABLE */ +mpkutil_get_map_item_by_str( + const msgpack_object_map* map, /* NULLABLE */ + const char* name /* NULL-terminated */ +); diff --git a/util/mpkutil/pack.c b/util/mpkutil/pack.c new file mode 100644 index 0000000..7d8a264 --- /dev/null +++ b/util/mpkutil/pack.c @@ -0,0 +1,49 @@ +#include "./pack.h" + +#include +#include + +#include + +#include "util/math/vector.h" + +void mpkutil_pack_bool(msgpack_packer* packer, bool b) { + assert(packer != NULL); + + (b? msgpack_pack_true: msgpack_pack_false)(packer); +} + +void mpkutil_pack_str(msgpack_packer* packer, const char* str) { + assert(packer != NULL); + assert(str != NULL); + + mpkutil_pack_strn(packer, str, strlen(str)); +} + +void mpkutil_pack_strn(msgpack_packer* packer, const char* str, size_t len) { + assert(packer != NULL); + assert(str != NULL); + + msgpack_pack_str(packer, len); + msgpack_pack_str_body(packer, str, len); +} + +void mpkutil_pack_vec2(msgpack_packer* packer, const vec2_t* v) { + assert(packer != NULL); + assert(v != NULL); + + msgpack_pack_array(packer, 2); + msgpack_pack_double(packer, v->x); + msgpack_pack_double(packer, v->y); +} + +void mpkutil_pack_vec4(msgpack_packer* packer, const vec4_t* v) { + assert(packer != NULL); + assert(v != NULL); + + msgpack_pack_array(packer, 4); + msgpack_pack_double(packer, v->x); + msgpack_pack_double(packer, v->y); + msgpack_pack_double(packer, v->z); + msgpack_pack_double(packer, v->w); +} diff --git a/util/mpkutil/pack.h b/util/mpkutil/pack.h new file mode 100644 index 0000000..953645c --- /dev/null +++ b/util/mpkutil/pack.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +#include + +#include "util/math/vector.h" + +void +mpkutil_pack_bool( + msgpack_packer* packer, + bool v +); + +void +mpkutil_pack_str( + msgpack_packer* packer, + const char* str /* NULL-terminated */ +); + +void +mpkutil_pack_strn( + msgpack_packer* packer, + const char* str, + size_t len +); + +void +mpkutil_pack_vec2( + msgpack_packer* packer, + const vec2_t* v +); + +void +mpkutil_pack_vec4( + msgpack_packer* packer, + const vec4_t* v +); diff --git a/util/parsarg/CMakeLists.txt b/util/parsarg/CMakeLists.txt new file mode 100644 index 0000000..dc6d0cf --- /dev/null +++ b/util/parsarg/CMakeLists.txt @@ -0,0 +1,12 @@ +add_library(parsarg + parsarg.c +) + +if (BUILD_TESTING) + add_executable(parsarg-test test.c) + target_link_libraries(parsarg-test + parsarg + + conv + ) +endif() diff --git a/util/parsarg/parsarg.c b/util/parsarg/parsarg.c new file mode 100644 index 0000000..f7122d7 --- /dev/null +++ b/util/parsarg/parsarg.c @@ -0,0 +1,79 @@ +#include "./parsarg.h" + +#include +#include + +void parsarg_initialize(parsarg_t* pa, int argc, char** argv) { + assert(pa != NULL); + + *pa = (typeof(*pa)) { + .argc = argc, + .argv = argv, + }; +} + +void parsarg_deinitialize(parsarg_t* pa) { + assert(pa != NULL); + +} + +char* parsarg_pop_name(parsarg_t* pa, size_t* len) { + assert(pa != NULL); + assert(len != NULL); + + *len = 0; + if (pa->argc <= 0 || pa->value != NULL || pa->after_option) { + return NULL; + } + + char* v = *pa->argv; + + size_t offset = 0; + while (v[offset] == '-') ++offset; + + if (offset == 0) return NULL; + v += offset; + + --pa->argc; + ++pa->argv; + + if (v[0] == 0) { + pa->after_option = true; + return NULL; + } + + while (v[*len] != '=' && v[*len] != 0) ++*len; + pa->value = (v[*len] == '='? &v[*len+1]: NULL); + + return v; +} + +bool parsarg_pop_value(parsarg_t* pa, char** str) { + assert(pa != NULL); + assert(str != NULL); + + *str = NULL; + if (pa->value != NULL) { + *str = pa->value; + pa->value = NULL; + return true; + } + + if (pa->argc <= 0) return false; + + char* v = *pa->argv; + if (!pa->after_option) { + if (v[0] == '-') return false; + } + --pa->argc; + ++pa->argv; + + *str = v; + return true; +} + +bool parsarg_finished(const parsarg_t* pa) { + assert(pa != NULL); + + return pa->argc <= 0; +} diff --git a/util/parsarg/parsarg.h b/util/parsarg/parsarg.h new file mode 100644 index 0000000..f80f2a2 --- /dev/null +++ b/util/parsarg/parsarg.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +typedef struct { + int argc; + char** argv; + + char* value; + + bool after_option; +} parsarg_t; + +void +parsarg_initialize( + parsarg_t* pa, + int argc, + char** argv +); + +void +parsarg_deinitialize( + parsarg_t* pa +); + +char* /* NULLABLE */ +parsarg_pop_name( + parsarg_t* pa, + size_t* len +); + +bool +parsarg_pop_value( + parsarg_t* pa, + char** str +); + +bool +parsarg_finished( + const parsarg_t* pa +); diff --git a/util/parsarg/test.c b/util/parsarg/test.c new file mode 100644 index 0000000..b3f01e0 --- /dev/null +++ b/util/parsarg/test.c @@ -0,0 +1,45 @@ +#include "./parsarg.h" + +#include +#include +#include +#include +#include + +int main(int argc, char** argv) { + parsarg_t pa; + parsarg_initialize(&pa, argc-1, argv+1); + + while (!parsarg_finished(&pa)) { + size_t nlen; + const char* n = parsarg_pop_name(&pa, &nlen); + + char* v; + parsarg_pop_value(&pa, &v); + + if (n == NULL && v == NULL) continue; + /* when the args ends with '--' */ + + if (n == NULL) { + printf("param: %s\n", v); + continue; + } + if (nlen == 3 && strncmp(n, "num", nlen) == 0) { + char* end; + const intmax_t i = strtoimax(v, &end, 0); + if (*end == 0 && 0 <= i && i <= 100) { + printf("num = %"PRIdMAX"\n", i); + } else { + printf("error: '%s' is not an valid integer for num (0~100)\n", v); + } + continue; + } + if (nlen == 3 && strncmp(n, "str", nlen) == 0) { + printf("str = '%s'\n", v); + continue; + } + printf("error: unknown option '%.*s'\n", (int) nlen, n); + } + + parsarg_deinitialize(&pa); +}