Compare commits

..

No commits in common. "e7ed72dfc0d5ae7d3fef0d176a0e83a8ede10793" and "446402c2e00ef9f80136b75a3af9c170d278b29a" have entirely different histories.

63 changed files with 1241 additions and 1919 deletions

2
.clangd Normal file
View File

@ -0,0 +1,2 @@
CompileFlags:
CompilationDatabase: "./.build/"

View File

@ -1,15 +0,0 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
trim_trailing_whitespace = true
[*.{c,h,py}]
indent_style = space
indent_size = 4
[CMakeListst.txt]
indent_style = space
indent_size = 8

2
.gitattributes vendored
View File

@ -2,5 +2,3 @@
*.ogg filter=lfs diff=lfs merge=lfs -text
*.xm filter=lfs diff=lfs merge=lfs -text
*.tga filter=lfs diff=lfs merge=lfs -text
text=auto eol=lf

4
.gitignore vendored
View File

@ -22,8 +22,8 @@
.vscode/
.idea/
.cache/
build/
build-web/
.build/
.build-web/
build/
out/

View File

@ -2,9 +2,6 @@ cmake_minimum_required(VERSION 3.21)
project(townengine LANGUAGES C)
set(CMAKE_MESSAGE_LOG_LEVEL "WARNING")
set(CMAKE_INSTALL_MESSAGE NEVER)
# SDL dependencies
# for whatever reason Emscripten has SDL2 config, but not actual SDL2 port by default
if(NOT EMSCRIPTEN)
@ -159,13 +156,14 @@ function(give_options_without_warnings target)
set(BUILD_FLAGS_RELEASE
-O3
-flto=$<IF:$<STREQUAL:${CMAKE_C_COMPILER_ID},Clang>,thin,auto>
-flto=auto
-mavx -mavx2
-Wl,--gc-sections
-fdata-sections
-ffunction-sections
-funroll-loops
-fomit-frame-pointer
$<$<STREQUAL:${CMAKE_C_COMPILER_ID},Gnu>:-s>)
-s)
set(BUILD_FLAGS_DEBUG
-O0
@ -175,19 +173,6 @@ function(give_options_without_warnings target)
$<$<BOOL:${TWN_SANITIZE}>:-fstack-protector-all -fsanitize=undefined -fsanitize=address>
$<$<BOOL:${EMSCRIPTEN}>:-gsource-map>)
if (CMAKE_C_COMPILER_LINKER_ID MATCHES GNU OR CMAKE_C_COMPILER_LINKER_ID MATCHES GNUgold)
set(THINLTO_USAGE "-plugin-opt,")
endif()
if (CMAKE_C_COMPILER_LINKER_ID MATCHES LLD)
set(THINLTO_USAGE "--thinlto-")
endif()
if (THINLTO_USAGE)
set(BUILD_SHARED_LIBRARY_FLAGS_RELEASE
$<$<STREQUAL:${CMAKE_C_COMPILER_ID},Clang>:-Wl,${THINLTO_USAGE}cache-dir=${CMAKE_CURRENT_BINARY_DIR}/linker-cache/>
$<$<STREQUAL:${CMAKE_C_COMPILER_ID},Clang>:-Wl,${THINLTO_USAGE}cache-policy=prune_after=30m>)
endif()
target_compile_options(${target} PUBLIC
${BUILD_FLAGS}
$<$<CONFIG:Release>:${BUILD_FLAGS_RELEASE}>
@ -201,15 +186,6 @@ function(give_options_without_warnings target)
-Bsymbolic-functions
$<$<BOOL:${LINUX}>:-Wl,--hash-style=gnu>)
get_target_property(target_type ${target} TYPE)
if (target_type MATCHES SHARED_LIBRARY)
target_compile_options(${target} PUBLIC
$<$<CONFIG:Release>:${BUILD_SHARED_LIBRARY_FLAGS_RELEASE}>)
target_link_options(${target} PUBLIC
$<$<CONFIG:Release>:${BUILD_SHARED_LIBRARY_FLAGS_RELEASE}>)
endif()
target_compile_definitions(${target} PRIVATE
$<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:TWN_FEATURE_DYNLIB_GAME>
$<$<BOOL:${LINUX}>:_GNU_SOURCE>)
@ -340,3 +316,8 @@ include_deps(${TWN_TARGET})
link_deps(${TWN_TARGET})
target_link_libraries(${TWN_TARGET} PUBLIC twn_third_parties)
target_include_directories(${TWN_TARGET} PRIVATE ${TWN_ROOT_DIR}/src)
# move compie_commands.json into root directory so that it plays nicer with code editors without any configuration
add_custom_target(copy-compile-commands ALL
${CMAKE_COMMAND} -E copy_if_different ${CMAKE_BINARY_DIR}/compile_commands.json
${TWN_ROOT_DIR})

View File

@ -20,13 +20,13 @@ static void handle_input(void)
if (ctx.mouse_position.y <= 60)
return;
if (input_action_pressed("add_a_bit"))
if (input_is_action_pressed("add_a_bit"))
{ // Left click
for (int i = 0; i < LEFT_CLICK_ADD; i++)
{
if (state->bunniesCount < MAX_BUNNIES)
{
state->bunnies[state->bunniesCount].position = input_action_position("add_a_bit");
state->bunnies[state->bunniesCount].position = input_get_action_position("add_a_bit");
state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0f;
state->bunnies[state->bunniesCount].speed.y = (float)(rand() % 500 - 250) / 60.0f;
state->bunnies[state->bunniesCount].color =
@ -38,13 +38,13 @@ static void handle_input(void)
}
}
if (input_action_pressed("add_a_lot"))
if (input_is_action_pressed("add_a_lot"))
{ // Right click
for (int i = 0; i < RIGHT_CLICK_ADD; i++)
{
if (state->bunniesCount < MAX_BUNNIES)
{
state->bunnies[state->bunniesCount].position = input_action_position("add_a_lot");
state->bunnies[state->bunniesCount].position = input_get_action_position("add_a_lot");
state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0f;
state->bunnies[state->bunniesCount].speed.y = (float)(rand() % 500 - 250) / 60.0f;
state->bunnies[state->bunniesCount].color =
@ -67,10 +67,13 @@ void game_tick(void)
// Allocating State struct to store data there
if (!ctx.udata)
ctx.udata = ccalloc(1, sizeof(State));
}
input_action("add_a_bit", CONTROL_LEFT_MOUSE);
input_action("add_a_lot", CONTROL_RIGHT_MOUSE);
input_add_action("add_a_bit");
input_bind_action_control("add_a_bit", CONTROL_LEFT_MOUSE);
input_add_action("add_a_lot");
input_bind_action_control("add_a_lot", CONTROL_RIGHT_MOUSE);
}
State *state = ctx.udata;
@ -94,7 +97,7 @@ void game_tick(void)
for (int i = 0; i < state->bunniesCount; i++)
{ // Draw each bunny based on their position and color, also scale accordingly
m_sprite(m_set(texture, "wabbit_alpha.png"),
m_sprite(m_set(path, "wabbit_alpha.png"),
m_set(rect, ((Rect){.x = state->bunnies[i].position.x,
.y = state->bunnies[i].position.y,
.w = BUNNY_W * SPRITE_SCALE,

View File

@ -18,18 +18,45 @@ void game_tick(void) {
state->ctx = &ctx;
state->scene = title_scene(state);
}
input_add_action("debug_toggle");
input_bind_action_control("debug_toggle", CONTROL_BACKSPACE);
input_add_action("debug_dump_atlases");
input_bind_action_control("debug_dump_atlases", CONTROL_HOME);
input_add_action("player_left");
input_bind_action_control("player_left", CONTROL_A);
input_add_action("player_right");
input_bind_action_control("player_right", CONTROL_D);
input_add_action("player_forward");
input_bind_action_control("player_forward", CONTROL_W);
input_add_action("player_backward");
input_bind_action_control("player_backward", CONTROL_S);
input_add_action("player_jump");
input_bind_action_control("player_jump", CONTROL_SPACE);
input_add_action("player_run");
input_bind_action_control("player_run", CONTROL_LSHIFT);
input_add_action("ui_accept");
input_bind_action_control("ui_accept", CONTROL_RETURN);
input_add_action("mouse_capture_toggle");
input_bind_action_control("mouse_capture_toggle", CONTROL_ESCAPE);
}
State *state = ctx.udata;
input_action("debug_toggle", CONTROL_BACKSPACE);
input_action("debug_dump_atlases", CONTROL_HOME);
if (input_action_just_pressed("debug_toggle")) {
if (input_is_action_just_pressed("debug_toggle")) {
ctx.debug = !ctx.debug;
}
if (input_action_just_pressed("debug_dump_atlases")) {
if (input_is_action_just_pressed("debug_dump_atlases")) {
textures_dump_atlases();
}

View File

@ -11,28 +11,28 @@
static void update_timers(Player *player) {
player->jump_air_timer = timer_tick_frames(player->jump_air_timer);
player->jump_coyote_timer = timer_tick_frames(player->jump_coyote_timer);
player->jump_buffer_timer = timer_tick_frames(player->jump_buffer_timer);
tick_timer(&player->jump_air_timer);
tick_timer(&player->jump_coyote_timer);
tick_timer(&player->jump_buffer_timer);
}
static void input_move(Player *player) {
/* apply horizontal damping when the player stops moving */
/* in other words, make it decelerate to a standstill */
if (!input_action_pressed("player_left") &&
!input_action_pressed("player_right"))
if (!input_is_action_pressed("player_left") &&
!input_is_action_pressed("player_right"))
{
player->dx *= player->horizontal_damping;
}
int input_dir = 0;
if (input_action_pressed("player_left"))
if (input_is_action_pressed("player_left"))
input_dir = -1;
if (input_action_pressed("player_right"))
if (input_is_action_pressed("player_right"))
input_dir = 1;
if (input_action_pressed("player_left") &&
input_action_pressed("player_right"))
if (input_is_action_pressed("player_left") &&
input_is_action_pressed("player_right"))
input_dir = 0;
player->dx += (float)input_dir * player->run_horizontal_speed;
@ -56,7 +56,7 @@ static void jump(Player *player) {
static void input_jump(Player *player) {
player->current_gravity_multiplier = player->jump_default_multiplier;
if (input_action_just_pressed("player_jump")) {
if (input_is_action_just_pressed("player_jump")) {
player->jump_air_timer = 0;
player->jump_buffer_timer = player->jump_buffer_ticks;
@ -65,7 +65,7 @@ static void input_jump(Player *player) {
}
}
if (input_action_pressed("player_jump")) {
if (input_is_action_pressed("player_jump")) {
if (player->action != PLAYER_ACTION_GROUND && player->jump_air_timer > 0) {
player->current_gravity_multiplier = player->jump_boosted_multiplier;
player->dy += player->jump_force_increase;
@ -147,7 +147,7 @@ static bool corner_correct(Player *player, Rect collision) {
static void calc_collisions_x(Player *player) {
Rect collision;
bool is_colliding = world_find_rect_intersects(player->world, player->collider_x, &collision);
bool is_colliding = world_find_intersect_frect(player->world, player->collider_x, &collision);
if (!is_colliding) return;
float player_center_x = player->collider_x.x + (player->collider_x.w / 2);
@ -164,7 +164,7 @@ static void calc_collisions_x(Player *player) {
static void calc_collisions_y(Player *player) {
Rect collision;
bool is_colliding = world_find_rect_intersects(player->world, player->collider_y, &collision);
bool is_colliding = world_find_intersect_frect(player->world, player->collider_y, &collision);
if (!is_colliding) return;
float player_center_y = player->collider_y.y + (player->collider_y.h / 2);
@ -255,10 +255,6 @@ static void drawdef(Player *player) {
draw_circle((Vec2) { 256, 128 },
24,
(Color) { 255, 0, 0, 255 });
draw_circle((Vec2) { 304, 128 },
24,
(Color) { 255, 0, 0, 255 });
}

View File

@ -11,15 +11,28 @@
static void ingame_tick(State *state) {
SceneIngame *scn = (SceneIngame *)state->scene;
input_action("player_left", CONTROL_A);
input_action("player_right", CONTROL_D);
input_action("player_forward", CONTROL_W);
input_action("player_backward", CONTROL_S);
input_action("player_jump", CONTROL_SPACE);
input_action("player_run", CONTROL_LSHIFT);
world_drawdef(scn->world);
player_calc(scn->player);
const Vec3 right = m_vec_norm(m_vec_cross(scn->cam.target, scn->cam.up));
const float speed = 0.04f; /* TODO: put this in a better place */
if (input_is_action_pressed("player_left"))
scn->cam.pos = vec3_sub(scn->cam.pos, m_vec_scale(right, speed));
if (input_is_action_pressed("player_right"))
scn->cam.pos = vec3_add(scn->cam.pos, m_vec_scale(right, speed));
if (input_is_action_pressed("player_forward"))
scn->cam.pos = vec3_add(scn->cam.pos, m_vec_scale(scn->cam.target, speed));
if (input_is_action_pressed("player_backward"))
scn->cam.pos = vec3_sub(scn->cam.pos, m_vec_scale(scn->cam.target, speed));
if (input_is_action_pressed("player_jump"))
scn->cam.pos.y += speed;
if (input_is_action_pressed("player_run"))
scn->cam.pos.y -= speed;
}
@ -41,5 +54,7 @@ Scene *ingame_scene(State *state) {
new_scene->world = world_create();
new_scene->player = player_create(new_scene->world);
new_scene->cam = (Camera){ .pos = { 32, 0, 1 }, .up = { 0, 1, 0 }, .fov = (float)M_PI_2 };
return (Scene *)new_scene;
}

View File

@ -15,6 +15,8 @@ typedef struct SceneIngame {
World *world;
Player *player;
Camera cam;
/* TODO: put this in a better place */
float yaw;
float pitch;

View File

@ -14,9 +14,7 @@ static void title_tick(State *state) {
SceneTitle *scn = (SceneTitle *)state->scene;
(void)scn;
input_action("ui_accept", CONTROL_RETURN);
if (input_action_just_pressed("ui_accept")) {
if (input_is_action_just_pressed("ui_accept")) {
switch_to(state, ingame_scene);
return;
}
@ -25,13 +23,13 @@ static void title_tick(State *state) {
((float)ctx.resolution.x / 2) - ((float)320 / 2), 64, 320, 128 }));
/* draw the tick count as an example of dynamic text */
size_t text_str_len = snprintf(NULL, 0, "%llu", (unsigned long long)state->ctx->frame_number) + 1;
size_t text_str_len = snprintf(NULL, 0, "%lu", state->ctx->frame_number) + 1;
char *text_str = cmalloc(text_str_len);
snprintf(text_str, text_str_len, "%llu", (unsigned long long)state->ctx->frame_number);
snprintf(text_str, text_str_len, "%lu", state->ctx->frame_number);
const char *font = "fonts/kenney-pixel.ttf";
float text_h = 32;
float text_w = draw_text_width(text_str, text_h, font);
int text_h = 32;
int text_w = draw_text_width(text_str, text_h, font);
draw_rectangle(
(Rect) {

View File

@ -12,11 +12,11 @@ static void update_tiles(struct World *world) {
for (size_t row = 0; row < world->tilemap_height; ++row) {
for (size_t col = 0; col < world->tilemap_width; ++col) {
world->tiles[(row * world->tilemap_width) + col] = (struct Tile) {
.rect = (Rect) {
.x = (float)(col * world->tile_size),
.y = (float)(row * world->tile_size),
.w = (float)world->tile_size,
.h = (float)world->tile_size,
.rect = (Recti) {
.x = (int)col * world->tile_size,
.y = (int)row * world->tile_size,
.w = world->tile_size,
.h = world->tile_size,
},
.type = world->tilemap[row][col],
};
@ -25,10 +25,10 @@ static void update_tiles(struct World *world) {
}
static Vec2 to_grid_location(struct World *world, float x, float y) {
return (Vec2) {
.x = floor(x / (float)world->tile_size),
.y = floor(y / (float)world->tile_size),
static Vec2i to_grid_location(struct World *world, float x, float y) {
return (Vec2i) {
.x = (int)floor(x / (float)world->tile_size),
.y = (int)floor(y / (float)world->tile_size),
};
}
@ -39,7 +39,8 @@ static void drawdef_debug(struct World *world) {
for (size_t i = 0; i < world->tilemap_height * world->tilemap_width; ++i) {
if (world->tiles[i].type == TILE_TYPE_VOID) continue;
draw_rectangle(world->tiles[i].rect, (Color) { 255, 0, 255, 128 });
draw_rectangle(to_frect(world->tiles[i].rect),
(Color) { 255, 0, 255, 128 });
}
}
@ -105,14 +106,14 @@ void world_drawdef(struct World *world) {
if (world->tiles[i].type == TILE_TYPE_VOID)
continue;
m_sprite("/assets/white.png", world->tiles[i].rect);
m_sprite("/assets/white.png", to_frect(world->tiles[i].rect));
}
drawdef_debug(world);
}
bool world_find_rect_intersects(struct World *world, Rect rect, Rect *intersection) {
bool world_find_intersect_frect(struct World *world, Rect rect, Rect *intersection) {
bool is_intersecting = false;
const size_t tile_count = world->tilemap_height * world->tilemap_width;
@ -120,12 +121,44 @@ bool world_find_rect_intersects(struct World *world, Rect rect, Rect *intersecti
if (world->tiles[i].type == TILE_TYPE_VOID)
continue;
Rect const tile_frect = world->tiles[i].rect;
Rect tile_frect = {
.x = (float)(world->tiles[i].rect.x),
.y = (float)(world->tiles[i].rect.y),
.w = (float)(world->tiles[i].rect.w),
.h = (float)(world->tiles[i].rect.h),
};
is_intersecting = rect_intersects(rect, tile_frect);
if (intersection == NULL) {
Rect temp;
is_intersecting = overlap_frect(&rect, &tile_frect, &temp);
} else {
is_intersecting = overlap_frect(&rect, &tile_frect, intersection);
}
if (intersection)
*intersection = rect_overlap(rect, tile_frect);
if (is_intersecting)
break;
}
return is_intersecting;
}
bool world_find_intersect_rect(struct World *world, Recti rect, Recti *intersection) {
bool is_intersecting = false;
const size_t tile_count = world->tilemap_height * world->tilemap_width;
for (size_t i = 0; i < tile_count; ++i) {
if (world->tiles[i].type == TILE_TYPE_VOID)
continue;
Recti *tile_rect = &world->tiles[i].rect;
if (intersection == NULL) {
Recti temp;
is_intersecting = overlap_rect(&rect, tile_rect, &temp);
} else {
is_intersecting = overlap_rect(&rect, tile_rect, intersection);
}
if (is_intersecting)
break;
@ -136,20 +169,20 @@ bool world_find_rect_intersects(struct World *world, Rect rect, Rect *intersecti
bool world_is_tile_at(struct World *world, float x, float y) {
Vec2 position_in_grid = to_grid_location(world, x, y);
return world->tilemap[(int32_t)position_in_grid.y][(int32_t)position_in_grid.x] != TILE_TYPE_VOID;
Vec2i position_in_grid = to_grid_location(world, x, y);
return world->tilemap[position_in_grid.y][position_in_grid.x] != TILE_TYPE_VOID;
}
void world_place_tile(struct World *world, float x, float y) {
Vec2 position_in_grid = to_grid_location(world, x, y);
world->tilemap[(int32_t)position_in_grid.y][(int32_t)position_in_grid.x] = TILE_TYPE_SOLID;
Vec2i position_in_grid = to_grid_location(world, x, y);
world->tilemap[position_in_grid.y][position_in_grid.x] = TILE_TYPE_SOLID;
update_tiles(world);
}
void world_remove_tile(struct World *world, float x, float y) {
Vec2 position_in_grid = to_grid_location(world, x, y);
world->tilemap[(int32_t)position_in_grid.y][(int32_t)position_in_grid.x] = TILE_TYPE_VOID;
Vec2i position_in_grid = to_grid_location(world, x, y);
world->tilemap[position_in_grid.y][position_in_grid.x] = TILE_TYPE_VOID;
update_tiles(world);
}

View File

@ -14,7 +14,7 @@ typedef enum TileType {
typedef struct Tile {
Rect rect;
Recti rect;
TileType type;
} Tile;
@ -34,7 +34,8 @@ typedef struct World {
World *world_create(void);
void world_destroy(World *world);
void world_drawdef(World *world);
bool world_find_rect_intersects(World *world, Rect rect, Rect *intersection);
bool world_find_intersect_frect(World *world, Rect rect, Rect *intersection);
bool world_find_intersect_rect(World *world, Recti rect, Recti *intersection);
bool world_is_tile_at(World *world, float x, float y);
void world_place_tile(World *world, float x, float y);
void world_remove_tile(World *world, float x, float y);

View File

@ -19,18 +19,45 @@ void game_tick(void) {
state->ctx = &ctx;
state->scene = title_scene(state);
}
input_add_action("debug_toggle");
input_bind_action_control("debug_toggle", CONTROL_BACKSPACE);
input_add_action("debug_dump_atlases");
input_bind_action_control("debug_dump_atlases", CONTROL_HOME);
input_add_action("player_left");
input_bind_action_control("player_left", CONTROL_A);
input_add_action("player_right");
input_bind_action_control("player_right", CONTROL_D);
input_add_action("player_forward");
input_bind_action_control("player_forward", CONTROL_W);
input_add_action("player_backward");
input_bind_action_control("player_backward", CONTROL_S);
input_add_action("player_jump");
input_bind_action_control("player_jump", CONTROL_SPACE);
input_add_action("player_run");
input_bind_action_control("player_run", CONTROL_LSHIFT);
input_add_action("ui_accept");
input_bind_action_control("ui_accept", CONTROL_RETURN);
input_add_action("mouse_capture_toggle");
input_bind_action_control("mouse_capture_toggle", CONTROL_ESCAPE);
}
State *state = ctx.udata;
input_action("debug_toggle", CONTROL_BACKSPACE);
input_action("debug_dump_atlases", CONTROL_HOME);
if (input_action_just_pressed("debug_toggle")) {
if (input_is_action_just_pressed("debug_toggle")) {
ctx.debug = !ctx.debug;
}
if (input_action_just_pressed("debug_dump_atlases")) {
if (input_is_action_just_pressed("debug_dump_atlases")) {
textures_dump_atlases();
}

View File

@ -15,59 +15,55 @@
static void ingame_tick(State *state) {
SceneIngame *scn = (SceneIngame *)state->scene;
input_action("player_left", CONTROL_A);
input_action("player_right", CONTROL_D);
input_action("player_forward", CONTROL_W);
input_action("player_backward", CONTROL_S);
input_action("player_jump", CONTROL_SPACE);
input_action("player_run", CONTROL_LSHIFT);
input_action("mouse_capture_toggle", CONTROL_ESCAPE);
if (scn->mouse_captured) {
const float sensitivity = 0.4f * (float)DEG2RAD; /* TODO: put this in a better place */
if (input_is_mouse_captured()) {
const float sensitivity = 0.6f; /* TODO: put this in a better place */
scn->yaw += (float)ctx.mouse_movement.x * sensitivity;
scn->pitch -= (float)ctx.mouse_movement.y * sensitivity;
scn->pitch = clampf(scn->pitch, (float)-M_PI * 0.49f, (float)M_PI * 0.49f);
scn->pitch = clampf(scn->pitch, -89.0f, 89.0f);
const float yaw_rad = scn->yaw * (float)DEG2RAD;
const float pitch_rad = scn->pitch * (float)DEG2RAD;
scn->cam.target = m_vec_norm(((Vec3){
cosf(yaw_rad) * cosf(pitch_rad),
sinf(pitch_rad),
sinf(yaw_rad) * cosf(pitch_rad)
}));
}
DrawCameraFromPrincipalAxesResult dir_and_up =
draw_camera_from_principal_axes(scn->pos, (float)M_PI_2, scn->roll, scn->pitch, scn->yaw);
const Vec3 right = m_vec_norm(m_vec_cross(dir_and_up.direction, dir_and_up.up));
const Vec3 right = m_vec_norm(m_vec_cross(scn->cam.target, scn->cam.up));
const float speed = 0.04f; /* TODO: put this in a better place */
if (input_action_pressed("player_left"))
scn->pos = vec3_sub(scn->pos, m_vec_scale(right, speed));
if (input_is_action_pressed("player_left"))
scn->cam.pos = vec3_sub(scn->cam.pos, m_vec_scale(right, speed));
if (input_action_pressed("player_right"))
scn->pos = vec3_add(scn->pos, m_vec_scale(right, speed));
if (input_is_action_pressed("player_right"))
scn->cam.pos = vec3_add(scn->cam.pos, m_vec_scale(right, speed));
if (input_action_pressed("player_forward"))
scn->pos = vec3_add(scn->pos, m_vec_scale(dir_and_up.direction, speed));
if (input_is_action_pressed("player_forward"))
scn->cam.pos = vec3_add(scn->cam.pos, m_vec_scale(scn->cam.target, speed));
if (input_action_pressed("player_backward"))
scn->pos = vec3_sub(scn->pos, m_vec_scale(dir_and_up.direction, speed));
if (input_is_action_pressed("player_backward"))
scn->cam.pos = vec3_sub(scn->cam.pos, m_vec_scale(scn->cam.target, speed));
if (input_action_pressed("player_jump"))
scn->pos.y += speed;
if (input_is_action_pressed("player_jump"))
scn->cam.pos.y += speed;
if (input_action_pressed("player_run"))
scn->pos.y -= speed;
if (input_is_action_pressed("player_run"))
scn->cam.pos.y -= speed;
/* toggle mouse capture with end key */
if (input_action_just_pressed("mouse_capture_toggle"))
scn->mouse_captured = !scn->mouse_captured;
if (input_is_action_just_pressed("mouse_capture_toggle")) {
input_set_mouse_captured(!input_is_mouse_captured());
}
input_mouse_captured(scn->mouse_captured);
draw_camera(&scn->cam);
#define TERRAIN_FREQUENCY 0.1f
#define TERRAIN_DISTANCE 64
float const half_terrain_distance = (float)TERRAIN_DISTANCE / 2;
for (int ly = TERRAIN_DISTANCE; ly--;) {
for (int lx = TERRAIN_DISTANCE; lx--;) {
float x = SDL_truncf(scn->pos.x + half_terrain_distance - (float)lx);
float y = SDL_truncf(scn->pos.z + half_terrain_distance - (float)ly);
for (int ly = 64; ly--;) {
for (int lx = 64; lx--;) {
float x = SDL_truncf(scn->cam.pos.x + 32 - (float)lx);
float y = SDL_truncf(scn->cam.pos.z + 32 - (float)ly);
float d0 = stb_perlin_noise3((float)x * TERRAIN_FREQUENCY, (float)y * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6;
float d1 = stb_perlin_noise3((float)(x + 1) * TERRAIN_FREQUENCY, (float)y * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6;
@ -109,11 +105,13 @@ Scene *ingame_scene(State *state) {
new_scene->base.tick = ingame_tick;
new_scene->base.end = ingame_end;
new_scene->mouse_captured = true;
new_scene->cam = (Camera){ .pos = { 32, 0, 1 }, .up = { 0, 1, 0 }, .fov = (float)M_PI_2 };
m_audio(m_set(path, "music/mod65.xm"),
m_opt(channel, "soundtrack"),
m_opt(repeat, true));
input_set_mouse_captured(true);
return (Scene *)new_scene;
}

View File

@ -6,18 +6,16 @@
#include "../state.h"
#include "scene.h"
#include <stdbool.h>
typedef struct SceneIngame {
Scene base;
Vec3 pos;
Camera cam;
/* TODO: put this in a better place */
float yaw;
float pitch;
float roll;
bool mouse_captured;
} SceneIngame;

View File

@ -11,9 +11,7 @@ static void title_tick(State *state) {
SceneTitle *scn = (SceneTitle *)state->scene;
(void)scn;
input_action("ui_accept", CONTROL_RETURN);
if (input_action_just_pressed("ui_accept")) {
if (input_is_action_just_pressed("ui_accept")) {
switch_to(state, ingame_scene);
return;
}
@ -22,20 +20,20 @@ static void title_tick(State *state) {
((float)ctx.resolution.x / 2) - ((float)320 / 2), 64, 320, 128 }));
/* draw the tick count as an example of dynamic text */
size_t text_str_len = snprintf(NULL, 0, "%llu", (unsigned long long)state->ctx->frame_number) + 1;
size_t text_str_len = snprintf(NULL, 0, "%llu", state->ctx->frame_number) + 1;
char *text_str = cmalloc(text_str_len);
snprintf(text_str, text_str_len, "%llu", (unsigned long long)state->ctx->frame_number);
snprintf(text_str, text_str_len, "%llu", state->ctx->frame_number);
const char *font = "/fonts/kenney-pixel.ttf";
float text_h = 32;
float text_w = draw_text_width(text_str, text_h, font);
int text_h = 32;
int text_w = draw_text_width(text_str, text_h, font);
draw_rectangle(
(Rect) {
.x = 0,
.y = 0,
.w = text_w,
.h = text_h,
.w = (float)text_w,
.h = (float)text_h,
},
(Color) { 0, 0, 0, 255 }
);

View File

@ -8,19 +8,10 @@ endif()
add_subdirectory($ENV{TWNROOT} $ENV{TWNBUILDDIR})
add_custom_command(
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/luabind.c
COMMAND ${PYTHON3} ${CMAKE_CURRENT_SOURCE_DIR}/bindgen.py $ENV{TWNROOT}/share/twn_api.json > ${CMAKE_CURRENT_SOURCE_DIR}/luabind.c
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/bindgen.py
)
set(SOURCE_FILES
game.c
state.h
${CMAKE_CURRENT_SOURCE_DIR}/luabind.c
lua/src/lapi.c
lua/src/lapi.h
lua/src/lauxlib.c

View File

@ -1,112 +0,0 @@
#!/bin/env python3
import sys, json
with open(sys.argv[1], 'r') if sys.argv[1] != "-" else sys.stdin as f:
api_source = f.read()
api = json.loads(api_source)
def default(parameter):
basetype = parameter["type"].rsplit(' *', 1)[0]
if parameter["type"] == "float":
return parameter["default"]
elif parameter["type"] == "bool":
return "true" if parameter["default"] else "false"
elif parameter["type"] == "char *":
if parameter["default"] == {}:
return "NULL"
else: return '"' + parameter["default"] + '"'
elif basetype in api["types"]:
if parameter["type"].endswith("*"):
if parameter["default"] == {}:
return "NULL"
else:
return "&(%s){\n%s\n }" % \
(parameter["type"], ", \n".join(" .%s = %s" % (n, v) for n, v in parameter["default"].items()))
else:
return "(%s){\n%s\n }" % \
(parameter["type"], ", \n".join(" .%s = %s" % (n, v) for n, v in parameter["default"].items()))
raise BaseException("Unhandled default value of type '%s'" % parameter["type"])
print('#include "twn_game_api.h"')
print('#include <lua.h>')
print('#include <lualib.h>')
print('#include <lauxlib.h>\n')
for typename, typedesc in api["types"].items():
converter = "static %s table_to_%s(lua_State *L, int idx) {\n" % (typename, typename.lower())
converter += " %s %s;\n" % (typename, typename.lower());
if not "fields" in typedesc:
continue
for field in typedesc["fields"]:
converter += " lua_getfield(L, idx, \"%s\");\n" % (field["name"]);
if field["type"] == "float":
converter += " %s.%s = (float)lua_tonumber(L, -1);\n" % (typename.lower(), field["name"]);
elif field["type"] == "uint8_t":
converter += " %s.%s = (uint8_t)lua_tointeger(L, -1);\n" % (typename.lower(), field["name"]);
else:
raise BaseException("Unhandled converter field type '%s'" % (field["type"]))
converter += " lua_pop(L, 1);\n";
converter += " return %s;\n}\n" % (typename.lower())
print(converter)
for procedure, procedure_desc in api["procedures"].items():
binding = "static int binding_%s(lua_State *L) {\n" % procedure
binding += " luaL_checktype(L, 1, LUA_TTABLE);\n"
if "params" in procedure_desc:
for parameter in procedure_desc["params"]:
basetype = parameter["type"].rsplit(' *', 1)[0]
if parameter["type"].endswith("*"):
binding += " %s %s_value;\n" % (basetype, parameter["name"])
binding += " %s %s;\n" % (parameter["type"], parameter["name"])
binding += " lua_getfield(L, 1, \"%s\");\n" % parameter["name"]
if "default" in parameter:
binding += " if (lua_isnoneornil(L, -1))\n"
binding += " %s = %s;\n" % (parameter["name"], default(parameter))
binding += " else\n "
if parameter["type"] == "float":
binding += " %s = (float)lua_tonumber(L, -1);\n" % (parameter["name"]);
elif parameter["type"] == "bool":
binding += " %s = lua_toboolean(L, -1);\n" % (parameter["name"]);
elif parameter["type"] == "char *":
binding += " %s = lua_tostring(L, -1);\n" % (parameter["name"]);
elif basetype in api["types"]:
if "enums" in api["types"][basetype]:
binding += " %s = lua_tointeger(L, -1);\n" % (parameter["name"]);
elif parameter["type"].endswith("*"):
binding += " { %s_value = table_to_%s(L, -1); %s = &%s_value; }\n" % (parameter["name"], basetype.lower(), parameter["name"], parameter["name"]);
else:
binding += " %s = table_to_%s(L, -1);\n" % (parameter["name"], basetype.lower());
else:
raise BaseException("Unhandled parameter type '%s'" % (parameter["type"]))
binding += " %s(%s);\n" % (procedure, ", ".join(param["name"] for param in procedure_desc["params"]))
binding += "}\n"
print(binding)
loader = "void bindgen_load_%s(lua_State *L) {\n" % api["name"]
modules = set(api["procedures"][procedure]["module"] for procedure in api["procedures"])
for module in modules:
loader += " lua_newtable(L);\n"
for procedure, procedure_desc in api["procedures"].items():
if procedure_desc["module"] == module:
loader += " lua_pushstring(L, \"%s\");\n" % procedure_desc["symbol"]
loader += " lua_pushcfunction(L, binding_%s);\n" % procedure
loader += " lua_settable(L, -3);\n"
loader += " lua_setglobal(L, \"%s\");\n" % module
loader += "}"
print(loader)

View File

@ -4,12 +4,12 @@ offset = { x = 0, y = 0 }
angle = 0
function game_tick()
draw.rectangle {
rectangle {
rect = { x = 0, y = 0, w = 640, h = 360 },
color = { r = 127, g = 0, b = 127, a = 255 },
}
draw.sprite {
sprite {
path = "/assets/title.png",
rect = {
x = 320 - (320 / 2),
@ -19,8 +19,8 @@ function game_tick()
},
}
draw.text {
string = "it never happened",
text {
string = "IT KEEPS HAPPENING",
position = offset,
font = "/fonts/kenney-pixel.ttf",
}

View File

@ -10,10 +10,6 @@
#include <malloc.h>
/* generated by bindgen.py */
void bindgen_load_twn(lua_State *L);
/* require will go through physicsfs exclusively so that scripts can be in the data dir */
static int physfs_loader(lua_State *L) {
const char *name = luaL_checkstring(L, 1);
@ -41,6 +37,201 @@ static int physfs_loader(lua_State *L) {
}
static Rect table_to_rect(lua_State *L, int idx, const char *name) {
/* types are checked here to help prevent unexpected results */
Rect rect;
int is_num;
lua_getfield(L, idx, "x");
rect.x = (float)lua_tonumberx(L, -1, &is_num);
if (!is_num)
luaL_error(L, "bad field 'x' in '%s' (number expected, got %s)", name, luaL_typename(L, -1));
lua_pop(L, 1);
lua_getfield(L, idx, "y");
rect.y = (float)lua_tonumberx(L, -1, &is_num);
if (!is_num)
luaL_error(L, "bad field 'y' in '%s' (number expected, got %s)", name, luaL_typename(L, -1));
lua_pop(L, 1);
lua_getfield(L, idx, "w");
rect.w = (float)lua_tonumberx(L, -1, &is_num);
if (!is_num)
luaL_error(L, "bad field 'w' in '%s' (number expected, got %s)", name, luaL_typename(L, -1));
lua_pop(L, 1);
lua_getfield(L, idx, "h");
rect.h = (float)lua_tonumberx(L, -1, &is_num);
if (!is_num)
luaL_error(L, "bad field 'h' in '%s' (number expected, got %s)", name, luaL_typename(L, -1));
lua_pop(L, 1);
return rect;
}
static Color table_to_color(lua_State *L, int idx) {
Color color;
lua_getfield(L, idx, "r");
color.r = (uint8_t)lua_tointeger(L, -1);
lua_pop(L, 1);
lua_getfield(L, idx, "g");
color.g = (uint8_t)lua_tointeger(L, -1);
lua_pop(L, 1);
lua_getfield(L, idx, "b");
color.b = (uint8_t)lua_tointeger(L, -1);
lua_pop(L, 1);
lua_getfield(L, idx, "a");
color.a = (uint8_t)lua_tointeger(L, -1);
lua_pop(L, 1);
return color;
}
/* sprite(data [table]) */
/* data should contain the following fields: */
/*
* path [string]
* rect [table]
* texture_region [table], optional
* color [table], optional
* rotation [number], optional
* flip_x [boolean], optional
* flip_y [boolean], optional
* stretch [boolean], optional
*/
static int b_sprite(lua_State *L) {
luaL_checktype(L, 1, LUA_TTABLE);
DrawSpriteArgs args = { 0 };
lua_getfield(L, 1, "path");
const char *field_path = lua_tostring(L, -1);
if (field_path == NULL)
luaL_error(L, "bad field 'path' in 'data' (string expected, got %s)", luaL_typename(L, -1));
args.path = field_path;
lua_getfield(L, 1, "rect");
if (!lua_istable(L, -1))
luaL_error(L, "bad field 'rect' in 'data' (table expected, got %s)", luaL_typename(L, -1));
args.rect = table_to_rect(L, -1, "data.rect");
lua_getfield(L, 1, "texture_region");
if (lua_istable(L, -1)) {
args.texture_region_opt = table_to_rect(L, -1, "data.texture_region");
args.texture_region_opt_set = true;
}
lua_getfield(L, 1, "color");
if (lua_istable(L, -1)) {
args.color_opt = table_to_color(L, -1);
args.color_opt_set = true;
}
lua_getfield(L, 1, "rotation");
if (lua_isnumber(L, -1)) {
args.rotation_opt = (float)lua_tonumber(L, -1);
args.rotation_opt_set = true;
}
lua_getfield(L, 1, "flip_x");
if (lua_isboolean(L, -1)) {
args.flip_x_opt = lua_toboolean(L, -1);
args.flip_x_opt_set = true;
}
lua_getfield(L, 1, "flip_y");
if (lua_isboolean(L, -1)) {
args.flip_y_opt = lua_toboolean(L, -1);
args.flip_y_opt_set = true;
}
lua_getfield(L, 1, "stretch");
if (lua_isboolean(L, -1)) {
args.stretch_opt = lua_toboolean(L, -1);
args.stretch_opt_set = true;
}
draw_sprite_args(args);
return 0;
}
/* rectangle(data [table]) */
/* data should contain the following fields: */
/*
* rect [table]
* color [table], optional, defaults to white
*/
static int b_rectangle(lua_State *L) {
luaL_checktype(L, 1, LUA_TTABLE);
lua_getfield(L, 1, "rect");
if (!lua_istable(L, -1))
luaL_error(L, "bad field 'rect' in 'data' (table expected, got %s)", luaL_typename(L, -1));
Rect rect = table_to_rect(L, -1, "data.rect");
Color color = { 255, 255, 255, 255 };
lua_getfield(L, 1, "color");
if (lua_istable(L, -1))
color = table_to_color(L, -1);
draw_rectangle(rect, color);
return 0;
}
/* text(data [table]) */
/* data should contain the following fields: */
/*
* string [string]
* position [table]
* height_px [number], optional, defaults to 22
* color [table], optional, defaults to black
* font [string]
*/
static int b_text(lua_State *L) {
luaL_checktype(L, 1, LUA_TTABLE);
lua_getfield(L, 1, "string");
const char *string = lua_tostring(L, -1);
if (string == NULL)
luaL_error(L, "bad field 'string' in 'data' (string expected, got %s)", luaL_typename(L, -1));
lua_getfield(L, 1, "position");
if (!lua_istable(L, -1))
luaL_error(L, "bad field 'position' in 'data' (table expected, got %s)", luaL_typename(L, -1));
lua_getfield(L, -1, "x");
float x = (float)lua_tonumber(L, -1);
lua_getfield(L, -2, "y");
float y = (float)lua_tonumber(L, -1);
lua_getfield(L, 1, "height_px");
int is_num;
int height_px = (int)lua_tointegerx(L, -1, &is_num);
if (!is_num)
height_px = 22;
lua_getfield(L, 1, "color");
Color color = { 0, 0, 0, 255 };
if (lua_istable(L, -1))
color = table_to_color(L, -1);
lua_getfield(L, 1, "font");
const char *font = lua_tostring(L, -1);
if (font == NULL)
luaL_error(L, "bad field 'font' in 'data' (string expected, got %s)", luaL_typename(L, -1));
draw_text(string, (Vec2) { x, y }, height_px, color, font);
return 0;
}
void game_tick(void) {
if (ctx.initialization_needed) {
if (!ctx.udata)
@ -55,13 +246,14 @@ void game_tick(void) {
}
state->L = luaL_newstate();
/* fakey version of luaL_openlibs() that excludes file i/o and os stuff */
/* fakey version of luaL_openlibs() that excludes file i/o */
{
static const luaL_Reg loaded_libs[] = {
{ LUA_GNAME, luaopen_base },
{ LUA_LOADLIBNAME, luaopen_package },
{ LUA_COLIBNAME, luaopen_coroutine },
{ LUA_TABLIBNAME, luaopen_table },
{ LUA_OSLIBNAME, luaopen_os },
{ LUA_STRLIBNAME, luaopen_string },
{ LUA_MATHLIBNAME, luaopen_math },
{ LUA_UTF8LIBNAME, luaopen_utf8 },
@ -88,11 +280,9 @@ void game_tick(void) {
lua_pop(state->L, 2);
/* binding */
// lua_register(state->L, "sprite", b_sprite);
// lua_register(state->L, "rectangle", b_rectangle);
// lua_register(state->L, "text", b_text);
bindgen_load_twn(state->L);
lua_register(state->L, "sprite", b_sprite);
lua_register(state->L, "rectangle", b_rectangle);
lua_register(state->L, "text", b_text);
/* now finally get to running the code */
unsigned char *game_buf = NULL;

View File

@ -1,6 +1,8 @@
#ifndef STATE_H
#define STATE_H
#include "twn_game_api.h"
#include <lua.h>

View File

@ -5,13 +5,8 @@ if [ -x "$(command -v ninja)" ]; then
generator="-G Ninja"
fi
# check whether clang is around (it's just better)
if [ -x "$(command -v clang)" ]; then
cc="-DCMAKE_C_COMPILER=clang"
fi
if [ "$1" = "web" ]; then
emcmake cmake $generator $cc -B build-web "${@:2}" && cmake --build build-web --parallel
emcmake cmake $generator -B .build-web "${@:2}" && cmake --build .build-web --parallel
else
cmake $generator $cc -B build "$@" && cmake --build build --parallel
cmake $generator -B .build "$@" && cmake --build .build --parallel
fi

View File

@ -6,7 +6,7 @@ set +e
exe="$(basename $PWD)"
toolpath="$(dirname -- "${BASH_SOURCE[0]}")"
export TWNROOT=$(realpath "$toolpath"/../)
export TWNBUILDDIR=$(realpath "$toolpath"/../build)
export TWNBUILDDIR=$(realpath "$toolpath"/../.build)
case "$1" in
build ) "$toolpath"/build.sh "${@:2}"

View File

@ -1,15 +0,0 @@
# interoperability
api needs to facilitate easy interoperability with other languages and tools,
for that certain considerations are taken:
* number of public api calls is kept at the minimum
* procedure parameters can only use basic types, no aggregates, with exception of Vec/Matrix types and alike,
with no expectation on new additions (see [/include/twn_types.h](../include/twn_types.h))
* optionals can be expressed via pointer passage of value primitives, assumed immutable, with the NULL expressing default
* no opaque types, only keys
* when mutation on input is done, - it shouldn't be achieved from a mutable pointer, but the return value
* return value could be a simple aggregate that is translatable to pure data dictionary
* module prefix is used for namespacing, actual symbols come after the prefix (`<module>_<symbol>`)
* symbols should not contain numerics at the start nor after the namespace prefix
* 32 bit floating point is the only numeric type
* [/include/twn_api.json](../include/twn_api.json) file is hand-kept with a schema to aid automatic binding generation and tooling

10
docs/interop.txt Normal file
View File

@ -0,0 +1,10 @@
api needs to facilitate easy interoperability with other languages and tools,
for that certain steps are taken:
* number of public api calls is kept at the minimum
* procedure signatures can only use basic types, no aggregates, with exception of Vec/Matrix types and alike,
with no expectation on new additions (see /include/twn_types.h)
* optionals can be expressed via pointer passage of value primitives, with NULL expressive default
* /include/twn_game_api.json file is hand-kept with a schema to aid automatic generation and other tooling
one of main inspirations for that is opengl model

View File

@ -15,8 +15,13 @@ TWN_API void audio_play(const char *path,
float volume, /* default: 1.0f, range: 0.0f to 1.0f */
float panning); /* default: 0.0f, range: -1.0 to 1.0f */
/* possible parameter options: "volume", "panning", "repeat" */
TWN_API void audio_parameter(const char *channel, const char *parameter, float value);
typedef enum {
AUDIO_PARAM_REPEAT,
AUDIO_PARAM_VOLUME,
AUDIO_PARAM_PANNING,
} AudioParam;
TWN_API void audio_set(const char *channel, AudioParam param, float value);
/* TODO */
// TWN_API bool audio_ended(const char *channel);

View File

@ -2,6 +2,7 @@
#define TWN_CAMERA_H
#include "twn_types.h"
#include "twn_engine_api.h"
/* TODO: make it cached? */
/* for example, perspective matrix only needs recaluclation on FOV change */
@ -14,8 +15,8 @@ typedef struct Camera {
float fov; /* field of view, in radians */
} Camera;
Matrix4 camera_look_at(const Camera *camera);
TWN_API Matrix4 camera_look_at(const Camera *camera);
Matrix4 camera_perspective(const Camera *const camera);
TWN_API Matrix4 camera_perspective(const Camera *const camera);
#endif

View File

@ -25,9 +25,9 @@ typedef struct Context {
/* resolution is set from config and dictates both logical and drawing space, as they're related */
/* even if scaling is done, game logic should never change over that */
Vec2 resolution;
Vec2 mouse_position;
Vec2 mouse_movement;
Vec2i resolution;
Vec2i mouse_position;
Vec2i mouse_movement;
/* is set on startup, should be used as source of randomness */
uint64_t random_seed;

View File

@ -3,19 +3,20 @@
#include "twn_types.h"
#include "twn_option.h"
#include "twn_camera.h"
#include "twn_engine_api.h"
#include <stdbool.h>
/* pushes a sprite onto the sprite render queue */
TWN_API void draw_sprite(char const *texture,
TWN_API void draw_sprite(char const *path,
Rect rect,
Rect const *texture_region, /* optional, default: NULL */
Color color, /* optional, default: all 255 */
float rotation, /* optional, default: 0 */
bool flip_x, /* optional, default: false */
bool flip_y, /* optional, default: false */
bool stretch); /* optional, default: true */
bool stretch); /* optional, default: false */
/* pushes a filled rectangle onto the rectangle render queue */
TWN_API void draw_rectangle(Rect rect, Color color);
@ -27,24 +28,26 @@ TWN_API void draw_circle(Vec2 position, float radius, Color color);
/* TODO: have font optional, with something minimal coming embedded */
TWN_API void draw_text(char const *string,
Vec2 position,
float height, /* optional, default: 22 */
int height_px, /* optional, default: 22 */
Color color, /* optional, default: all 0 */
char const *font); /* optional, default: NULL */
char const *font);
TWN_API float draw_text_width(char const *string,
float height, /* optional, default: 22 */
char const *font); /* optional, default: NULL */
TWN_API int draw_text_width(char const *string,
int height_px, /* TODO: make optional */
char const *font);
TWN_API void draw_nine_slice(char const *texture,
Vec2 corners,
TWN_API void draw_9slice(char const *texture_path,
int texture_w,
int texture_h,
int border_thickness,
Rect rect,
float border_thickness, /* optional, default: 0 */
Color color); /* optional, default: all 255 */
Color color); /* TODO: make optional */
/* pushes a textured 3d triangle onto the render queue */
/* vertices are in absolute coordinates, relative to world origin */
/* texture coordinates are in pixels */
TWN_API void draw_triangle(char const *texture,
TWN_API void draw_triangle(char const *path,
Vec3 v0,
Vec3 v1,
Vec3 v2,
@ -66,25 +69,15 @@ TWN_API void draw_triangle(char const *texture,
// Color c1,
// Color c2);
TWN_API void draw_billboard(const char *path,
Vec3 position,
Vec2 size);
// TODO:
// http://www.lighthouse3d.com/opengl/billboarding/index.php?billCheat2
// void unfurl_billboard(const char *path,
// Vec2 position,
// Vec2 scaling,
// Rect uvs);
/* sets a perspective 3d camera to be used for all 3d commands */
TWN_API void draw_camera(Vec3 position, float fov, Vec3 up, Vec3 direction);
/* same as draw_camera(), but with specific use case */
/* direction and up vectors are inferred from roll, pitch and yaw parameters (in radians) */
/* return value is direction and up vectors, so that you can use them in logic (such as controllers) */
typedef struct DrawCameraFromPrincipalAxesResult {
Vec3 direction;
Vec3 up;
} DrawCameraFromPrincipalAxesResult;
TWN_API DrawCameraFromPrincipalAxesResult draw_camera_from_principal_axes(Vec3 position,
float fov,
float roll,
float pitch,
float yaw);
/* pushes a camera state to be used for all future unfurl_* commands */
TWN_API void draw_camera(const Camera *camera);
/* expects '*' masks that will be expanded to 6 names: 'up', 'down', 'east', 'west', 'north' and 'south' */
TWN_API void draw_skybox(const char *paths);
@ -95,7 +88,7 @@ TWN_API void draw_fog(float start, float end, float density, Color color);
#ifndef TWN_NOT_C
typedef struct DrawSpriteArgs {
char const *texture;
char const *path;
Rect rect;
m_option_list(

View File

@ -1,9 +1,7 @@
#ifndef TWN_ENGINE_API_H
#define TWN_ENGINE_API_H
#if defined(TWN_NOT_C)
#define TWN_API
#elif defined(__WIN32)
#if defined(__WIN32)
#define TWN_API __declspec(dllexport)
#else
#define TWN_API __attribute__((visibility("default")))

View File

@ -3,11 +3,11 @@
#define TWN_GAME_API_H
#include "twn_input.h"
#include "twn_context.h"
#include "twn_draw.h"
#include "twn_audio.h"
#include "twn_engine_api.h"
#include "twn_util.h"
#include "twn_context.h"
#ifndef TWN_NOT_C

View File

@ -10,11 +10,19 @@
#include <stddef.h>
TWN_API void input_action(const char *name, Control control);
TWN_API bool input_action_pressed(const char *name);
TWN_API bool input_action_just_pressed(const char *name);
TWN_API bool input_action_just_released(const char *name);
TWN_API Vec2 input_action_position(const char *name);
TWN_API void input_mouse_captured(bool enabled);
TWN_API void input_bind_action_control(const char *action_name, Control control);
TWN_API void input_unbind_action_control(const char *action_name, Control control);
TWN_API void input_add_action(const char *action_name);
TWN_API void input_delete_action(const char *action_name);
TWN_API bool input_is_action_pressed(const char *action_name);
TWN_API bool input_is_action_just_pressed(const char *action_name);
TWN_API bool input_is_action_just_released(const char *action_name);
TWN_API Vec2 input_get_action_position(const char *action_name);
TWN_API void input_set_mouse_captured(bool enabled);
TWN_API bool input_is_mouse_captured(void);
#endif

View File

@ -9,7 +9,6 @@ typedef enum TextureMode {
TEXTURE_MODE_OPAQUE, /* all pixels are solid */
TEXTURE_MODE_SEETHROUGH, /* some pixels are alpha zero */
TEXTURE_MODE_GHOSTLY, /* arbitrary alpha values */
TEXTURE_MODE_UNKNOWN = -1, /* a sentinel */
} TextureMode;
#endif

View File

@ -6,8 +6,17 @@
/* plain data aggregates that are accepted between public procedure boundaries */
/* a point in some space (integer) */
typedef struct Vec2i {
_Alignas(8)
int32_t x;
int32_t y;
} Vec2i;
/* a point in some space (floating point) */
typedef struct Vec2 {
_Alignas(8)
float x;
float y;
} Vec2;
@ -16,6 +25,7 @@ typedef struct Vec2 {
/* a point in some three dimension space (floating point) */
/* y goes up, x goes to the right */
typedef struct Vec3 {
_Alignas(16)
float x;
float y;
float z;
@ -25,6 +35,7 @@ typedef struct Vec3 {
/* a point in some three dimension space (floating point) */
/* y goes up, x goes to the right */
typedef struct Vec4 {
_Alignas(16)
float x;
float y;
float z;
@ -34,6 +45,7 @@ typedef struct Vec4 {
/* 32-bit color data */
typedef struct Color {
_Alignas(4)
uint8_t r;
uint8_t g;
uint8_t b;
@ -41,8 +53,19 @@ typedef struct Color {
} Color;
/* a rectangle with the origin at the upper left (integer) */
typedef struct Recti {
_Alignas(16)
int32_t x;
int32_t y;
int32_t w;
int32_t h;
} Recti;
/* a rectangle with the origin at the upper left (floating point) */
typedef struct Rect {
_Alignas(16)
float x;
float y;
float w;

View File

@ -24,60 +24,74 @@
TWN_API void *crealloc(void *ptr, size_t size);
TWN_API void *ccalloc(size_t num, size_t size);
TWN_API void log_info(const char *restrict format, ...);
TWN_API void log_critical(const char *restrict format, ...);
TWN_API void log_warn(const char *restrict format, ...);
/* saves all texture atlases as BMP files in the write directory */
TWN_API void textures_dump_atlases(void);
/* returns true if str ends with suffix */
TWN_API bool strends(const char *str, const char *suffix);
/* TODO: this is why generics were invented. sorry, i'm tired today */
TWN_API double clamp(double d, double min, double max);
TWN_API float clampf(float f, float min, float max);
TWN_API int clampi(int i, int min, int max);
/* sets buf_out to a pointer to a byte buffer which must be freed. */
/* returns the size of this buffer. */
TWN_API int64_t file_to_bytes(const char *path, unsigned char **buf_out);
/* returns a pointer to a string which must be freed */
TWN_API char *file_to_str(const char *path);
/* returns true if the file exists in the filesystem */
TWN_API bool file_exists(const char *path);
#endif /* TWN_NOT_C */
/* calculates the overlap of two rectangles */
TWN_API Rect rect_overlap(Rect a, Rect b);
/* returns true if two rectangles are intersecting */
TWN_API bool rect_intersects(Rect a, Rect b);
TWN_API Vec2 rect_center(Rect rect);
/* decrements an integer value, stopping at 0 */
TWN_API void log_info(const char *restrict format, ...);
TWN_API void log_critical(const char *restrict format, ...);
TWN_API void log_warn(const char *restrict format, ...);
/* TODO: this is why generics were invented. sorry, i'm tired today */
TWN_API double clamp(double d, double min, double max);
TWN_API float clampf(float f, float min, float max);
TWN_API int clampi(int i, int min, int max);
/* sets buf_out to a pointer to a byte buffer which must be freed. */
/* returns the size of this buffer. */
TWN_API int64_t file_to_bytes(const char *path, unsigned char **buf_out);
/* returns a pointer to a string which must be freed */
TWN_API char *file_to_str(const char *path);
/* returns true if the file exists in the filesystem */
TWN_API bool file_exists(const char *path);
/* saves all texture atlases as BMP files in the write directory */
TWN_API void textures_dump_atlases(void);
/* returns true if str ends with suffix */
TWN_API TWN_API bool strends(const char *str, const char *suffix);
/* */
/* GAME LOGIC UTILITIES */
/* */
/* calculates the overlap of two rectangles and places it in result. */
/* result may be NULL. if this is the case, it will simply be ignored. */
/* returns true if the rectangles are indeed intersecting. */
TWN_API bool overlap_rect(const Recti *a, const Recti *b, Recti *result);
TWN_API bool overlap_frect(const Rect *a, const Rect *b, Rect *result);
/* returns true if two rectangles are intersecting */
TWN_API bool intersect_rect(const Recti *a, const Recti *b);
TWN_API bool intersect_frect(const Rect *a, const Rect *b);
/* TODO: generics and specials (see m_vec2_from() for an example)*/
TWN_API Rect to_frect(Recti rect);
TWN_API Vec2 frect_center(Rect rect);
/* decrements an lvalue (which should be an int), stopping at 0 */
/* meant for tick-based timers in game logic */
/*
* example:
* tick_timer(&player->jump_air_timer);
*/
TWN_API int32_t timer_tick_frames(int32_t frames_left);
TWN_API void tick_timer(int *value);
/* decrements a floating point second-based timer, stopping at 0.0f */
/* decrements a floating point second-based timer, stopping at 0.0 */
/* meant for poll based real time logic in game logic */
/* note that it should be decremented only on the next tick after its creation */
TWN_API float timer_tick_seconds(float seconds_left);
TWN_API void tick_ftimer(float *value);
typedef struct TimerElapseFramesResult {
bool elapsed; int32_t frames_left;
} TimerElapseFramesResult;
TWN_API TimerElapseFramesResult timer_elapse_frames(int32_t frames_left, int32_t interval);
typedef struct TimerElapseSecondsResult {
bool elapsed; float seconds_left;
} TimerElapseSecondsResult;
TWN_API TimerElapseSecondsResult timer_elapse_seconds(float seconds_left, float interval);
/* same as `tick_ftimer` but instead of clamping it repeats */
/* returns true if value was cycled */
TWN_API bool repeat_ftimer(float *value, float at);
#endif

View File

@ -9,6 +9,15 @@
#include <math.h>
/* aren't macros to prevent double evaluation with side effects */
/* maybe could be inlined? i hope LTO will resolve this */
static inline Vec2 vec2_from_vec2i(Vec2i vec) {
return (Vec2) {
.x = (float)vec.x,
.y = (float)vec.y,
};
}
static inline Vec3 vec3_add(Vec3 a, Vec3 b) {
return (Vec3) { a.x + b.x, a.y + b.y, a.z + b.z };
}

View File

@ -1,406 +0,0 @@
{
"name": "twn",
"procedures": {
"input_action": {
"module": "input",
"symbol": "bind_action",
"header": "twn_input.h",
"params": [
{ "name": "name", "type": "char *" },
{ "name": "control", "type": "Control" }
]
},
"input_action_pressed": {
"module": "input",
"symbol": "action_pressed",
"header": "twn_input.h",
"params": [
{ "name": "name", "type": "char *" }
],
"return": "bool"
},
"input_action_just_pressed": {
"module": "input",
"symbol": "action_just_pressed",
"header": "twn_input.h",
"params": [
{ "name": "name", "type": "char *" }
],
"return": "bool"
},
"input_action_just_released": {
"module": "input",
"symbol": "action_just_released",
"header": "twn_input.h",
"params": [
{ "name": "name", "type": "char *" }
],
"return": "bool"
},
"input_action_position": {
"module": "input",
"symbol": "get_action_position",
"header": "twn_input.h",
"params": [
{ "name": "name", "type": "char *" }
],
"return": "Vec2"
},
"input_mouse_captured": {
"module": "input",
"symbol": "set_mouse_captured",
"header": "twn_input.h",
"params": [
{ "name": "enabled", "type": "bool" }
]
},
"draw_sprite": {
"module": "draw",
"symbol": "sprite",
"header": "twn_draw.h",
"params": [
{ "name": "path", "type": "char *" },
{ "name": "rect", "type": "Rect" },
{ "name": "texture_region", "type": "Rect *", "default": {} },
{ "name": "color", "type": "Color", "default": { "r": 255, "g": 255, "b": 255, "a": 255 } },
{ "name": "rotation", "type": "float", "default": 0.0 },
{ "name": "flip_x", "type": "bool", "default": false },
{ "name": "flip_y", "type": "bool", "default": false },
{ "name": "stretch", "type": "bool", "default": true }
]
},
"draw_rectangle": {
"module": "draw",
"symbol": "rectangle",
"header": "twn_draw.h",
"params": [
{ "name": "rect", "type": "Rect" },
{ "name": "color", "type": "Color", "default": { "r": 255, "g": 255, "b": 255, "a": 255 } }
]
},
"draw_circle": {
"module": "draw",
"symbol": "circle",
"header": "twn_draw.h",
"params": [
{ "name": "position", "type": "Vec2" },
{ "name": "radius", "type": "float" },
{ "name": "color", "type": "Color", "default": { "r": 255, "g": 255, "b": 255, "a": 255 } }
]
},
"draw_text": {
"module": "draw",
"symbol": "text",
"header": "twn_draw.h",
"params": [
{ "name": "string", "type": "char *" },
{ "name": "position", "type": "Vec2" },
{ "name": "height", "type": "float", "default": 22 },
{ "name": "color", "type": "Color", "default": { "r": 255, "g": 255, "b": 255, "a": 255 } },
{ "name": "font", "type": "char *", "default": {} }
]
},
"draw_text_width": {
"module": "draw",
"symbol": "text_width",
"header": "twn_draw.h",
"params": [
{ "name": "string", "type": "char *" },
{ "name": "height", "type": "float", "default": 22 },
{ "name": "font", "type": "char *", "default": {} }
],
"return": "float"
}
},
"types": {
"Vec2": {
"fields": [
{ "name": "x", "type": "float" },
{ "name": "y", "type": "float" }
]
},
"Vec3": {
"fields": [
{ "name": "x", "type": "float" },
{ "name": "y", "type": "float" },
{ "name": "z", "type": "float" }
]
},
"Vec4": {
"fields": [
{ "name": "x", "type": "float" },
{ "name": "y", "type": "float" },
{ "name": "z", "type": "float" },
{ "name": "w", "type": "float" }
]
},
"Color": {
"fields": [
{ "name": "r", "type": "uint8_t" },
{ "name": "g", "type": "uint8_t" },
{ "name": "b", "type": "uint8_t" },
{ "name": "a", "type": "uint8_t" }
]
},
"Rect": {
"fields": [
{ "name": "x", "type": "float" },
{ "name": "y", "type": "float" },
{ "name": "w", "type": "float" },
{ "name": "h", "type": "float" }
]
},
"Control": {
"enums": {
"A": 4,
"B": 5,
"C": 6,
"D": 7,
"E": 8,
"F": 9,
"G": 10,
"H": 11,
"I": 12,
"J": 13,
"K": 14,
"L": 15,
"M": 16,
"N": 17,
"O": 18,
"P": 19,
"Q": 20,
"R": 21,
"S": 22,
"T": 23,
"U": 24,
"V": 25,
"W": 26,
"X": 27,
"Y": 28,
"Z": 29,
"1": 30,
"2": 31,
"3": 32,
"4": 33,
"5": 34,
"6": 35,
"7": 36,
"8": 37,
"9": 38,
"0": 39,
"RETURN": 40,
"ESCAPE": 41,
"BACKSPACE": 42,
"TAB": 43,
"SPACE": 44,
"MINUS": 45,
"EQUALS": 46,
"LEFTBRACKET": 47,
"RIGHTBRACKET": 48,
"BACKSLASH": 49,
"NONUSHASH": 50,
"SEMICOLON": 51,
"APOSTROPHE": 52,
"GRAVE": 53,
"COMMA": 54,
"PERIOD": 55,
"SLASH": 56,
"CAPSLOCK": 57,
"F1": 58,
"F2": 59,
"F3": 60,
"F4": 61,
"F5": 62,
"F6": 63,
"F7": 64,
"F8": 65,
"F9": 66,
"F10": 67,
"F11": 68,
"F12": 69,
"PRINTSCREEN": 70,
"SCROLLLOCK": 71,
"PAUSE": 72,
"INSERT": 73,
"HOME": 74,
"PAGEUP": 75,
"DELETE": 76,
"END": 77,
"PAGEDOWN": 78,
"RIGHT": 79,
"LEFT": 80,
"DOWN": 81,
"UP": 82,
"NUMLOCKCLEAR": 83,
"KP_DIVIDE": 84,
"KP_MULTIPLY": 85,
"KP_MINUS": 86,
"KP_PLUS": 87,
"KP_ENTER": 88,
"KP_1": 89,
"KP_2": 90,
"KP_3": 91,
"KP_4": 92,
"KP_5": 93,
"KP_6": 94,
"KP_7": 95,
"KP_8": 96,
"KP_9": 97,
"KP_0": 98,
"KP_PERIOD": 99,
"NONUSBACKSLASH": 100,
"APPLICATION": 101,
"POWER": 102,
"KP_EQUALS": 103,
"F13": 104,
"F14": 105,
"F15": 106,
"F16": 107,
"F17": 108,
"F18": 109,
"F19": 110,
"F20": 111,
"F21": 112,
"F22": 113,
"F23": 114,
"F24": 115,
"EXECUTE": 116,
"HELP": 117,
"MENU": 118,
"SELECT": 119,
"STOP": 120,
"AGAIN": 121,
"UNDO": 122,
"CUT": 123,
"COPY": 124,
"PASTE": 125,
"FIND": 126,
"MUTE": 127,
"VOLUMEUP": 128,
"VOLUMEDOWN": 129,
"KP_COMMA": 133,
"KP_EQUALSAS400": 134,
"INTERNATIONAL1": 135,
"INTERNATIONAL2": 136,
"INTERNATIONAL3": 137,
"INTERNATIONAL4": 138,
"INTERNATIONAL5": 139,
"INTERNATIONAL6": 140,
"INTERNATIONAL7": 141,
"INTERNATIONAL8": 142,
"INTERNATIONAL9": 143,
"LANG1": 144,
"LANG2": 145,
"LANG3": 146,
"LANG4": 147,
"LANG5": 148,
"LANG6": 149,
"LANG7": 150,
"LANG8": 151,
"LANG9": 152,
"ALTERASE": 153,
"SYSREQ": 154,
"CANCEL": 155,
"CLEAR": 156,
"PRIOR": 157,
"RETURN2": 158,
"SEPARATOR": 159,
"OUT": 160,
"OPER": 161,
"CLEARAGAIN": 162,
"CRSEL": 163,
"EXSEL": 164,
"KP_00": 176,
"KP_000": 177,
"THOUSANDSSEPARATOR": 178,
"DECIMALSEPARATOR": 179,
"CURRENCYUNIT": 180,
"CURRENCYSUBUNIT": 181,
"KP_LEFTPAREN": 182,
"KP_RIGHTPAREN": 183,
"KP_LEFTBRACE": 184,
"KP_RIGHTBRACE": 185,
"KP_TAB": 186,
"KP_BACKSPACE": 187,
"KP_A": 188,
"KP_B": 189,
"KP_C": 190,
"KP_D": 191,
"KP_E": 192,
"KP_F": 193,
"KP_XOR": 194,
"KP_POWER": 195,
"KP_PERCENT": 196,
"KP_LESS": 197,
"KP_GREATER": 198,
"KP_AMPERSAND": 199,
"KP_DBLAMPERSAND": 200,
"KP_VERTICALBAR": 201,
"KP_DBLVERTICALBAR": 202,
"KP_COLON": 203,
"KP_HASH": 204,
"KP_SPACE": 205,
"KP_AT": 206,
"KP_EXCLAM": 207,
"KP_MEMSTORE": 208,
"KP_MEMRECALL": 209,
"KP_MEMCLEAR": 210,
"KP_MEMADD": 211,
"KP_MEMSUBTRACT": 212,
"KP_MEMMULTIPLY": 213,
"KP_MEMDIVIDE": 214,
"KP_PLUSMINUS": 215,
"KP_CLEAR": 216,
"KP_CLEARENTRY": 217,
"KP_BINARY": 218,
"KP_OCTAL": 219,
"KP_DECIMAL": 220,
"KP_HEXADECIMAL": 221,
"LCTRL": 224,
"LSHIFT": 225,
"LALT": 226,
"LGUI": 227,
"RCTRL": 228,
"RSHIFT": 229,
"RALT": 230,
"RGUI": 231,
"MODE": 257,
"KBDILLUMTOGGLE": 278,
"KBDILLUMDOWN": 279,
"KBDILLUMUP": 280,
"EJECT": 281,
"SLEEP": 282,
"APP1": 283,
"APP2": 284,
"AUDIOREWIND": 285,
"AUDIOFASTFORWARD": 286,
"SOFTLEFT": 287,
"SOFTRIGHT": 288,
"CALL": 289,
"ENDCALL": 290,
"LEFT_MOUSE": 513,
"RIGHT_MOUSE": 515,
"MIDDLE_MOUSE": 514,
"X1": 516,
"X2": 517
}
}
}
}

View File

@ -1,7 +1,6 @@
#include "twn_game_object_c.h"
#include "twn_engine_context_c.h"
#include "twn_util_c.h"
#include "twn_util.h"
#include <x-watcher.h>
#include <SDL2/SDL.h>

View File

@ -1,7 +1,6 @@
#include "twn_game_object_c.h"
#include "twn_engine_context_c.h"
#include "twn_util_c.h"
#include "twn_util.h"
#include <errhandlingapi.h>
#include <libloaderapi.h>

View File

@ -1,3 +1,4 @@
#include "twn_util.h"
#include "twn_engine_context_c.h"
#include "twn_draw_c.h"
#include "twn_draw.h"
@ -23,46 +24,58 @@ void draw_circle(Vec2 position, float radius, Color color) {
void create_circle_geometry(Vec2 position,
Color color,
float radius,
size_t num_vertices,
Vec2 vertices[])
SDL_Vertex vertices[],
int indices[])
{
SDL_assert(num_vertices <= CIRCLE_VERTICES_MAX);
/* the angle (in radians) to rotate by on each iteration */
float seg_rotation_angle = (360.0f / (float)(num_vertices - 2)) * ((float)M_PI / 180);
float seg_rotation_angle = (360.0f / (float)num_vertices) * ((float)M_PI / 180);
vertices[0].x = (float)position.x;
vertices[0].y = (float)position.y;
vertices[0].position.x = (float)position.x;
vertices[0].position.y = (float)position.y;
vertices[0].color.r = color.r;
vertices[0].color.g = color.g;
vertices[0].color.b = color.b;
vertices[0].color.a = color.a;
vertices[0].tex_coord = (SDL_FPoint){ 0, 0 };
/* this point will rotate around the center */
float start_x = 0.0f - radius;
float start_y = 0.0f;
for (size_t i = 1; i < num_vertices - 1; ++i) {
for (size_t i = 1; i < num_vertices + 1; ++i) {
float final_seg_rotation_angle = (float)i * seg_rotation_angle;
float c, s;
sincosf(final_seg_rotation_angle, &s, &c);
vertices[i].position.x =
cosf(final_seg_rotation_angle) * start_x -
sinf(final_seg_rotation_angle) * start_y;
vertices[i].position.y =
cosf(final_seg_rotation_angle) * start_y +
sinf(final_seg_rotation_angle) * start_x;
vertices[i].x = c * start_x - s * start_y;
vertices[i].y = c * start_y + s * start_x;
vertices[i].position.x += position.x;
vertices[i].position.y += position.y;
vertices[i].x += position.x;
vertices[i].y += position.y;
}
vertices[i].color.r = color.r;
vertices[i].color.g = color.g;
vertices[i].color.b = color.b;
vertices[i].color.a = color.a;
// place a redundant vertex to make proper circling over shared element buffer
{
float final_seg_rotation_angle = (float)1 * seg_rotation_angle;
vertices[i].tex_coord = (SDL_FPoint){ 0, 0 };
float c, s;
sincosf(final_seg_rotation_angle, &s, &c);
vertices[num_vertices - 1].x = c * start_x - s * start_y;
vertices[num_vertices - 1].y = c * start_y + s * start_x;
size_t triangle_offset = 3 * (i - 1);
vertices[num_vertices - 1].x += position.x;
vertices[num_vertices - 1].y += position.y;
/* center point index */
indices[triangle_offset] = 0;
/* generated point index */
indices[triangle_offset + 1] = (int)i;
size_t index = (i + 1) % num_vertices;
if (index == 0)
index = num_vertices;
indices[triangle_offset + 2] = (int)index;
}
}

View File

@ -1,9 +1,8 @@
#include "twn_draw_c.h"
#include "twn_draw.h"
#include "twn_engine_context_c.h"
#include "twn_camera_c.h"
#include "twn_camera.h"
#include "twn_types.h"
#include "twn_vec.h"
#include <SDL2/SDL.h>
#include <stb_ds.h>
@ -36,8 +35,8 @@ void render_queue_clear(void) {
}
void draw_nine_slice(const char *texture, Vec2 corners, Rect rect, float border_thickness, Color color) {
const float bt = border_thickness;
void draw_9slice(const char *texture_path, int texture_w, int texture_h, int border_thickness, Rect rect, Color color) {
const float bt = (float)border_thickness; /* i know! */
const float bt2 = bt * 2; /* combined size of the two borders in an axis */
@ -49,7 +48,7 @@ void draw_nine_slice(const char *texture, Vec2 corners, Rect rect, float border_
};
m_sprite(
m_set(texture, texture),
m_set(path, texture_path),
m_set(rect, top_left),
m_opt(texture_region, ((Rect) { 0, 0, bt, bt })),
m_opt(color, color),
@ -64,9 +63,9 @@ void draw_nine_slice(const char *texture, Vec2 corners, Rect rect, float border_
};
m_sprite(
m_set(texture, texture),
m_set(path, texture_path),
m_set(rect, top_center),
m_opt(texture_region, ((Rect) { bt, 0, corners.x - bt2, bt })),
m_opt(texture_region, ((Rect) { bt, 0, (float)texture_w - bt2, bt })),
m_opt(color, color),
);
@ -79,9 +78,9 @@ void draw_nine_slice(const char *texture, Vec2 corners, Rect rect, float border_
};
m_sprite(
m_set(texture, texture),
m_set(path, texture_path),
m_set(rect, top_right),
m_opt(texture_region, ((Rect) { corners.x - bt, 0, bt, bt })),
m_opt(texture_region, ((Rect) { (float)texture_w - bt, 0, bt, bt })),
m_opt(color, color),
);
@ -94,9 +93,9 @@ void draw_nine_slice(const char *texture, Vec2 corners, Rect rect, float border_
};
m_sprite(
m_set(texture, texture),
m_set(path, texture_path),
m_set(rect, center_left),
m_opt(texture_region, ((Rect) { 0, bt, bt, corners.y - bt2 })),
m_opt(texture_region, ((Rect) { 0, bt, bt, (float)texture_h - bt2 })),
m_opt(color, color),
);
@ -109,9 +108,9 @@ void draw_nine_slice(const char *texture, Vec2 corners, Rect rect, float border_
};
m_sprite(
m_set(texture, texture),
m_set(path, texture_path),
m_set(rect, center_right),
m_opt(texture_region, ((Rect) { corners.x - bt, bt, bt, corners.y - bt2 })),
m_opt(texture_region, ((Rect) { (float)texture_w - bt, bt, bt, (float)texture_h - bt2 })),
m_opt(color, color),
);
@ -124,9 +123,9 @@ void draw_nine_slice(const char *texture, Vec2 corners, Rect rect, float border_
};
m_sprite(
m_set(texture, texture),
m_set(path, texture_path),
m_set(rect, bottom_left),
m_opt(texture_region, ((Rect) { 0, corners.y - bt, bt, bt })),
m_opt(texture_region, ((Rect) { 0, (float)texture_h - bt, bt, bt })),
m_opt(color, color),
);
@ -139,9 +138,9 @@ void draw_nine_slice(const char *texture, Vec2 corners, Rect rect, float border_
};
m_sprite(
m_set(texture, texture),
m_set(path, texture_path),
m_set(rect, bottom_center),
m_opt(texture_region, ((Rect) { bt, corners.y - bt, corners.x - bt2, bt })),
m_opt(texture_region, ((Rect) { bt, (float)texture_h - bt, (float)texture_w - bt2, bt })),
m_opt(color, color),
);
@ -154,9 +153,9 @@ void draw_nine_slice(const char *texture, Vec2 corners, Rect rect, float border_
};
m_sprite(
m_set(texture, texture),
m_set(path, texture_path),
m_set(rect, bottom_right),
m_opt(texture_region, ((Rect) { corners.x - bt, corners.y - bt, bt, bt })),
m_opt(texture_region, ((Rect) { (float)texture_w - bt, (float)texture_h - bt, bt, bt })),
m_opt(color, color),
);
@ -169,9 +168,9 @@ void draw_nine_slice(const char *texture, Vec2 corners, Rect rect, float border_
};
m_sprite(
m_set(texture, texture),
m_set(path, texture_path),
m_set(rect, center),
m_opt(texture_region, ((Rect) { bt, bt, corners.x - bt2, corners.y - bt2 })),
m_opt(texture_region, ((Rect) { bt, bt, (float)texture_w - bt2, (float)texture_h - bt2 })),
m_opt(color, color),
);
}
@ -358,64 +357,33 @@ void render(void) {
float ratio = (float)ctx.window_dims.y / (float)ctx.base_render_height;
int w = (int)((float)ctx.base_render_width * ratio);
setup_viewport(
(int)ctx.window_dims.x / 2 - w / 2,
ctx.window_dims.x / 2 - w / 2,
0,
w,
(int)ctx.window_dims.y
ctx.window_dims.y
);
} else {
float ratio = (float)ctx.window_dims.x / (float)ctx.base_render_width;
int h = (int)((float)ctx.base_render_height * ratio);
setup_viewport(
0,
(int)ctx.window_dims.y / 2 - h / 2,
(int)ctx.window_dims.x,
ctx.window_dims.y / 2 - h / 2,
ctx.window_dims.x,
h
);
}
}
start_render_frame(); {
render_space();
render_skybox(); /* after space, as to use depth buffer for early z rejection */
render_skybox(); /* after space, as to use depth buffer for early rejection */
render_2d();
} end_render_frame();
swap_buffers();
clear_draw_buffer();
}
void draw_camera(Vec3 position, float fov, Vec3 up, Vec3 direction) {
Camera const camera = {
.fov = fov,
.pos = position,
.target = direction,
.up = up,
};
camera_projection_matrix = camera_perspective(&camera);
camera_look_at_matrix = camera_look_at(&camera);
}
/* TODO: https://stackoverflow.com/questions/62493770/how-to-add-roll-in-camera-class */
DrawCameraFromPrincipalAxesResult draw_camera_from_principal_axes(Vec3 position, float fov, float roll, float pitch, float yaw) {
(void)roll;
float yawc, yaws, pitchc, pitchs;
sincosf(yaw, &yaws, &yawc);
sincosf(pitch, &pitchs, &pitchc);
Camera const camera = {
.fov = fov,
.pos = position,
.target = m_vec_norm(((Vec3){
yawc * pitchc,
pitchs,
yaws * pitchc,
})),
.up = (Vec3){0, 1, 0},
};
camera_projection_matrix = camera_perspective(&camera);
camera_look_at_matrix = camera_look_at(&camera);
return (DrawCameraFromPrincipalAxesResult) {
.direction = camera.target,
.up = camera.up,
};
void draw_camera(const Camera *const camera) {
/* TODO: skip recaulculating if it's the same? */
camera_projection_matrix = camera_perspective(camera);
camera_look_at_matrix = camera_look_at(camera);
}

View File

@ -3,6 +3,7 @@
#include "twn_textures_c.h"
#include "twn_text_c.h"
#include "twn_util.h"
#include "twn_option.h"
#include <SDL2/SDL.h>
@ -19,9 +20,7 @@
extern Matrix4 camera_projection_matrix;
extern Matrix4 camera_look_at_matrix;
#define QUAD_ELEMENT_BUFFER_LENGTH (65536 / 6)
#define CIRCLE_VERTICES_MAX 2048
typedef GLuint VertexBuffer;
@ -83,22 +82,34 @@ typedef struct Primitive2D {
} Primitive2D;
/* union for in-place recalculation of texture coordinates */
/* needs to be later resolved in texture atlas */
typedef struct UncoloredSpaceTriangle {
union UncoloredSpaceTriangle {
/* pending for sending, uvs are not final as texture atlases could update */
struct UncoloredSpaceTrianglePrimitive {
Vec3 v0;
Vec2 uv0; /* in pixels */
Vec3 v1;
Vec2 uv1; /* in pixels */
Vec3 v2;
Vec2 uv2; /* in pixels */
} UncoloredSpaceTriangle;
} primitive;
/* TODO: have it packed? */
/* structure that is passed in opengl vertex array */
struct UncoloredSpaceTrianglePayload {
Vec3 v0;
Vec2 uv0;
Vec3 v1;
Vec2 uv1;
Vec3 v2;
Vec2 uv2;
} payload;
};
/* batch of primitives with overlapping properties */
typedef struct MeshBatch {
uint8_t *primitives; /* note: interpretation of it is arbitrary */
uint8_t *primitives;
} MeshBatch;
/* TODO: use atlas id instead */
typedef struct MeshBatchItem {
TextureKey key;
struct MeshBatch value;
@ -118,13 +129,14 @@ void render_queue_clear(void);
/* fills two existing arrays with the geometry data of a circle */
/* the size of indices must be at least 3 times the number of vertices */
void create_circle_geometry(Vec2 position,
Color color,
float radius,
size_t num_vertices,
Vec2 vertices[]);
SDL_Vertex vertices[],
int indices[]);
struct QuadBatch {
size_t size; /* how many primitives are in current batch */
TextureKey texture_key;
TextureMode mode; /* how color should be applied */
bool constant_colored; /* whether colored batch is uniformly colored */
bool repeat; /* whether repeat is needed */
@ -153,8 +165,6 @@ void text_cache_reset_arena(TextCache *cache);
VertexBuffer create_vertex_buffer(void);
VertexBuffer get_scratch_vertex_array(void);
void delete_vertex_buffer(VertexBuffer buffer);
void specify_vertex_buffer(VertexBuffer buffer, void const *data, size_t bytes);
@ -177,9 +187,7 @@ void swap_buffers(void);
void set_depth_range(double low, double high);
VertexBuffer get_quad_element_buffer(void);
VertexBuffer get_circle_element_buffer(void);
void bind_quad_element_buffer(void);
void render_circle(const CirclePrimitive *circle);
@ -230,8 +238,4 @@ void pop_fog(void);
void finally_pop_fog(void);
void start_render_frame(void);
void end_render_frame(void);
#endif

View File

@ -1,5 +1,6 @@
#include "twn_draw.h"
#include "twn_draw_c.h"
#include "twn_util.h"
#include <stdbool.h>

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,6 @@
#include "twn_draw_c.h"
#include "twn_engine_context_c.h"
#include "twn_util_c.h"
#ifdef EMSCRIPTEN
#include <GLES2/gl2.h>
@ -30,57 +32,65 @@ void specify_vertex_buffer(VertexBuffer buffer, void const *data, size_t bytes)
}
VertexBuffer get_quad_element_buffer(void) {
static VertexBuffer buffer = 0;
void bind_quad_element_buffer(void) {
static GLuint buffer = 0;
/* it's only generated once at runtime */
/* TODO: use builder interface, not direct calls (glMapBuffer isn't portable) */
if (buffer == 0) {
buffer = create_vertex_buffer();
VertexBufferBuilder builder = build_vertex_buffer(buffer, sizeof (GLshort) * QUAD_ELEMENT_BUFFER_LENGTH * 6 );
glGenBuffers(1, &buffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
QUAD_ELEMENT_BUFFER_LENGTH * 6 * sizeof(uint16_t),
NULL,
GL_STATIC_DRAW);
for (size_t i = 0; i < QUAD_ELEMENT_BUFFER_LENGTH; ++i) {
GLshort indices[6];
indices[0] = (GLshort)(i * 4 + 0);
indices[1] = (GLshort)(i * 4 + 1);
indices[2] = (GLshort)(i * 4 + 2);
indices[3] = (GLshort)(i * 4 + 2);
indices[4] = (GLshort)(i * 4 + 3);
indices[5] = (GLshort)(i * 4 + 0);
uint16_t *const indices = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER,
GL_WRITE_ONLY);
if (!indices)
CRY("Quad indices generation", "glMapBuffer() failed");
push_to_vertex_buffer_builder(&builder, indices, sizeof indices);
}
for (uint16_t i = 0; i < QUAD_ELEMENT_BUFFER_LENGTH; ++i) {
indices[i * 6 + 0] = (uint16_t)(i * 4 + 0);
indices[i * 6 + 1] = (uint16_t)(i * 4 + 1);
indices[i * 6 + 2] = (uint16_t)(i * 4 + 2);
indices[i * 6 + 3] = (uint16_t)(i * 4 + 2);
indices[i * 6 + 4] = (uint16_t)(i * 4 + 3);
indices[i * 6 + 5] = (uint16_t)(i * 4 + 0);
}
SDL_assert_always(buffer);
glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
return buffer;
} else
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
}
VertexBuffer get_circle_element_buffer(void) {
static VertexBuffer buffer = 0;
void clear_draw_buffer(void) {
/* TODO: we can optimize a rectangle drawn over whole window to a clear color call*/
glClearColor((1.0f / 255) * 230,
(1.0f / 255) * 230,
(1.0f / 255) * 230, 1);
if (buffer == 0) {
buffer = create_vertex_buffer();
VertexBufferBuilder builder = build_vertex_buffer(buffer, sizeof (GLshort) * (CIRCLE_VERTICES_MAX - 2) * 3);
glDepthRange(0.0, 1.0);
glDepthMask(GL_TRUE);
for (size_t i = 1; i < CIRCLE_VERTICES_MAX - 1; ++i) {
/* first one is center point index, always zero */
GLshort indices[3];
indices[0] = 0;
/* generated point index */
indices[1] = (GLshort)i;
indices[2] = (GLshort)i + 1;
push_to_vertex_buffer_builder(&builder, indices, sizeof indices);
}
}
SDL_assert_always(buffer);
return buffer;
/* TODO: don't clear color when skybox is applied? */
/* for that window should match framebuffer */
/* also it is driver dependent, from what i can gather */
/* INFO: also, based on below, driver might prefer it staying this way */
/* https://gamedev.stackexchange.com/questions/90344/render-with-const-depth-value */
/* we could optionally load ARB_invalidate_subdata extension if it's available instead */
glClear(GL_COLOR_BUFFER_BIT |
GL_DEPTH_BUFFER_BIT |
GL_STENCIL_BUFFER_BIT);
}
void swap_buffers(void) {
SDL_GL_SwapWindow(ctx.window);
}
void set_depth_range(double low, double high) {
glDepthRange(low, high);
}

View File

@ -1,6 +1,10 @@
#include "twn_draw.h"
#include "twn_draw_c.h"
#include "twn_engine_context_c.h"
#include "twn_util.h"
#include "twn_util_c.h"
#include "twn_textures_c.h"
#include "twn_option.h"
#include <stb_ds.h>
@ -32,7 +36,7 @@ struct QuadBatch collect_rect_batch(const Primitive2D primitives[], size_t len)
.constant_colored = true,
};
const uint32_t uniform_color = *(const uint32_t *)(void const *)&primitives[0].rect.color;
const uint32_t uniform_color = *(const uint32_t *)&primitives[0].rect.color;
/* batch size is clamped so that reallocated short indices could be used */
if (len >= QUAD_ELEMENT_BUFFER_LENGTH)
@ -50,7 +54,7 @@ struct QuadBatch collect_rect_batch(const Primitive2D primitives[], size_t len)
break;
/* if all are modulated the same we can skip sending the color data */
if (*(const uint32_t *)(void const *)&current->rect.color != uniform_color)
if (*(const uint32_t *)&current->rect.color != uniform_color)
batch.constant_colored = false;
++batch.size;
@ -68,7 +72,9 @@ void render_rect_batch(const Primitive2D primitives[],
SDL_assert(primitives[0].type == PRIMITIVE_2D_RECT);
/* single vertex array is used for every batch with NULL glBufferData() trick at the end */
VertexBuffer const vertex_array = get_scratch_vertex_array();
static VertexBuffer vertex_array = 0;
if (vertex_array == 0)
vertex_array = create_vertex_buffer();
use_texture_mode(batch.mode);

View File

@ -56,7 +56,7 @@ void draw_sprite_args(const DrawSpriteArgs args) {
bool const stretch = m_or(args, stretch, false);
Rect const *texture_region = m_is_set(args, texture_region) ? &args.texture_region_opt : NULL;
draw_sprite(args.texture, args.rect, texture_region, color, rotation, flip_x, flip_y, stretch);
draw_sprite(args.path, args.rect, texture_region, color, rotation, flip_x, flip_y, stretch);
}
@ -69,13 +69,12 @@ struct QuadBatch collect_sprite_batch(const Primitive2D primitives[], size_t len
struct QuadBatch batch = {
.mode = textures_get_mode(&ctx.texture_cache, primitives[0].sprite.texture_key),
.texture_key = primitives[0].sprite.texture_key,
.constant_colored = true,
.repeat = primitives[0].sprite.repeat,
.textured = true,
};
const uint32_t uniform_color = *(const uint32_t *)(void const*)&primitives[0].sprite.color;
const uint32_t uniform_color = *(const uint32_t *)&primitives[0].sprite.color;
/* batch size is clamped so that reallocated short indices could be used */
if (len >= QUAD_ELEMENT_BUFFER_LENGTH)
@ -109,7 +108,7 @@ struct QuadBatch collect_sprite_batch(const Primitive2D primitives[], size_t len
}
/* if all are modulated the same we can skip sending the color data */
if (*(const uint32_t *)(void const *)&current->sprite.color != uniform_color)
if (*(const uint32_t *)&current->sprite.color != uniform_color)
batch.constant_colored = false;
++batch.size;
@ -126,15 +125,16 @@ void render_sprite_batch(const Primitive2D primitives[],
SDL_assert(primitives && batch.size != 0);
SDL_assert(primitives[0].type == PRIMITIVE_2D_SPRITE);
VertexBuffer const vertex_array = get_scratch_vertex_array();
/* single vertex array is used for every batch with NULL glBufferData() trick at the end */
static VertexBuffer vertex_array = 0;
if (vertex_array == 0)
vertex_array = create_vertex_buffer();
use_texture_mode(batch.mode);
const Rect dims =
textures_get_dims(&ctx.texture_cache, primitives->sprite.texture_key);
/* cached srcrect */
Rect cached_srcrect = {0};
TextureKey cached_srcrect_key = TEXTURE_KEY_INVALID;
/* vertex population over a vertex buffer builder interface */
{
VertexBufferBuilder payload = build_vertex_buffer(vertex_array, get_quad_payload_size(batch) * batch.size);
@ -144,16 +144,13 @@ void render_sprite_batch(const Primitive2D primitives[],
const size_t cur = batch.mode == TEXTURE_MODE_GHOSTLY ? i : batch.size - i - 1;
const SpritePrimitive sprite = primitives[cur].sprite;
if (primitives[cur].sprite.texture_key.id != cached_srcrect_key.id) {
cached_srcrect = textures_get_srcrect(&ctx.texture_cache, primitives[cur].sprite.texture_key);
cached_srcrect_key = primitives[cur].sprite.texture_key;
}
Rect const srcrect = cached_srcrect;
/* TODO: try caching it */
const Rect srcrect =
textures_get_srcrect(&ctx.texture_cache, primitives[cur].sprite.texture_key);
Vec2 uv0, uv1, uv2, uv3;
if (!batch.repeat) {
if (!sprite.repeat) {
const float wr = srcrect.w / dims.w;
const float hr = srcrect.h / dims.h;
const float xr = srcrect.x / dims.w;
@ -216,7 +213,7 @@ void render_sprite_batch(const Primitive2D primitives[],
#pragma GCC diagnostic pop
const Vec2 c = rect_center(sprite.rect);
const Vec2 c = frect_center(sprite.rect);
const Vec2 t = fast_cossine(sprite.rotation + (float)M_PI_4);
const Vec2 d = {
.x = t.x * sprite.rect.w * (float)M_SQRT1_2,
@ -231,7 +228,7 @@ void render_sprite_batch(const Primitive2D primitives[],
} else {
/* rotated non-square case*/
const Vec2 c = rect_center(sprite.rect);
const Vec2 c = frect_center(sprite.rect);
const Vec2 t = fast_cossine(sprite.rotation);
const Vec2 h = { sprite.rect.w / 2, sprite.rect.h / 2 };

View File

@ -171,7 +171,9 @@ static void text_destroy_font_data(FontData *font_data) {
static void text_draw_with(FontData* font_data, char* text, Vec2 position, Color color) {
VertexBuffer const vertex_array = get_scratch_vertex_array();
VertexBuffer vertex_array = 0;
if (vertex_array == 0)
vertex_array = create_vertex_buffer();
const size_t len = SDL_strlen(text);
@ -275,8 +277,8 @@ void text_cache_reset_arena(TextCache *cache) {
}
void draw_text(const char *string, Vec2 position, float height, Color color, const char *font) {
ensure_font_cache(font, (int)height);
void draw_text(const char *string, Vec2 position, int height_px, Color color, const char *font_path) {
ensure_font_cache(font_path, height_px);
/* the original string might not be around by the time it's used, so copy it */
size_t str_length = SDL_strlen(string) + 1;
@ -290,8 +292,8 @@ void draw_text(const char *string, Vec2 position, float height, Color color, con
.color = color,
.position = position,
.text = dup_string,
.font = font,
.height_px = (int)height,
.font = font_path,
.height_px = height_px,
};
Primitive2D primitive = {
@ -303,9 +305,9 @@ void draw_text(const char *string, Vec2 position, float height, Color color, con
}
float draw_text_width(const char *string, float height, const char *font) {
ensure_font_cache(font, (int)height);
FontData *font_data = get_font_data(font, (int)height);
int draw_text_width(const char *string, int height_px, const char *font_path) {
ensure_font_cache(font_path, height_px);
FontData *font_data = get_font_data(font_path, height_px);
int length = 0;
for (const char *p = string; *p != '\0'; ++p) {
@ -316,5 +318,5 @@ float draw_text_width(const char *string, float height, const char *font) {
length += advance_width;
}
return (float)length * font_data->scale_factor;
return (int)((float)length * font_data->scale_factor);
}

View File

@ -23,19 +23,19 @@ void draw_triangle(const char *path,
if (!batch_p) {
struct MeshBatch item = {0};
hmput(ctx.uncolored_mesh_batches, texture_key, item);
batch_p = &ctx.uncolored_mesh_batches[hmlenu(ctx.uncolored_mesh_batches) - 1];
batch_p = &ctx.uncolored_mesh_batches[hmlenu(ctx.uncolored_mesh_batches) - 1]; /* TODO: can last index be used? */
}
UncoloredSpaceTriangle const triangle = {
union UncoloredSpaceTriangle triangle = { .primitive = {
.v0 = v0,
.v1 = v1,
.v2 = v2,
.uv1 = uv1,
.uv0 = uv0,
.uv2 = uv2,
};
}};
UncoloredSpaceTriangle *triangles = (UncoloredSpaceTriangle *)(void *)batch_p->value.primitives;
union UncoloredSpaceTriangle *triangles = (union UncoloredSpaceTriangle *)(void *)batch_p->value.primitives;
arrpush(triangles, triangle);
batch_p->value.primitives = (uint8_t *)triangles;
@ -45,7 +45,38 @@ void draw_triangle(const char *path,
void draw_uncolored_space_traingle_batch(struct MeshBatch *batch,
TextureKey texture_key)
{
VertexBuffer const vertex_array = get_scratch_vertex_array();
static VertexBuffer vertex_array = 0;
if (vertex_array == 0)
vertex_array = create_vertex_buffer();
const size_t primitives_len = arrlenu(batch->primitives);
/* nothing to do */
if (primitives_len == 0)
return;
const Rect srcrect = textures_get_srcrect(&ctx.texture_cache, texture_key);
const Rect dims = textures_get_dims(&ctx.texture_cache, texture_key);
const float wr = srcrect.w / dims.w;
const float hr = srcrect.h / dims.h;
const float xr = srcrect.x / dims.w;
const float yr = srcrect.y / dims.h;
/* update pixel-based uvs to correspond with texture atlases */
for (size_t i = 0; i < primitives_len; ++i) {
struct UncoloredSpaceTrianglePayload *payload =
&((union UncoloredSpaceTriangle *)(void *)batch->primitives)[i].payload;
payload->uv0.x = xr + ((float)payload->uv0.x / srcrect.w) * wr;
payload->uv0.y = yr + ((float)payload->uv0.y / srcrect.h) * hr;
payload->uv1.x = xr + ((float)payload->uv1.x / srcrect.w) * wr;
payload->uv1.y = yr + ((float)payload->uv1.y / srcrect.h) * hr;
payload->uv2.x = xr + ((float)payload->uv2.x / srcrect.w) * wr;
payload->uv2.y = yr + ((float)payload->uv2.y / srcrect.h) * hr;
}
specify_vertex_buffer(vertex_array, batch->primitives, primitives_len * sizeof (struct UncoloredSpaceTrianglePayload));
finally_draw_uncolored_space_traingle_batch(batch, texture_key, vertex_array);
}

View File

@ -2,7 +2,6 @@
#include "twn_engine_context_c.h"
#include "twn_util.h"
#include "twn_util_c.h"
#include "twn_audio.h"
#include <SDL2/SDL.h>
#include <stb_ds.h>
@ -191,32 +190,42 @@ void audio_play(const char *path,
}
TWN_API void audio_parameter(const char *channel, const char *param, float value) {
TWN_API void audio_set(const char *channel, AudioParam param, float value) {
AudioChannelItem *pair = shgetp_null(ctx.audio_channels, channel);
if (!pair) {
log_warn("No channel by the name of %s to set a parameter for", channel);
return;
}
if (SDL_strcmp(param, "repeat") == 0) {
switch (param) {
case AUDIO_PARAM_REPEAT:
pair->value.repeat = (bool)value;
} else if (SDL_strcmp(param, "volume") == 0) {
if (value > 1.0f || value < 0.0f) {
break;
case AUDIO_PARAM_VOLUME:
if (value > 1.0f) {
log_warn("Out of range volume for channel %s set", channel);
value = clampf(value, 0.0f, 1.0f);
value = 1.0f;
}
if (value < 0.0f) {
log_warn("Out of range volume for channel %s set", channel);
value = 0.0f;
}
pair->value.volume = value;
} else if (SDL_strcmp(param, "panning") == 0) {
if (value > 1.0f || value < -1.0f) {
break;
case AUDIO_PARAM_PANNING:
if (value > 1.0f) {
log_warn("Out of range panning for channel %s set", channel);
value = clampf(value, -1.0f, +1.0f);
value = 1.0f;
}
if (value < -1.0f) {
log_warn("Out of range panning for channel %s set", channel);
value = -1.0f;
}
pair->value.panning = value;
} else
break;
default:
CRY("Audio channel parameter setting failed", "Invalid parameter enum given");
}
}

View File

@ -1,6 +1,8 @@
#ifndef TWN_AUDIO_C_H
#define TWN_AUDIO_C_H
#include "twn_audio.h"
#include <SDL2/SDL_audio.h>
#define STB_VORBIS_HEADER_ONLY

View File

@ -1,4 +1,4 @@
#include "twn_camera_c.h"
#include "twn_camera.h"
#include "twn_vec.h"
#include "twn_engine_context_c.h"

View File

@ -29,7 +29,7 @@ typedef struct EngineContext {
/* where the app was run from, used as the root for packs */
char *base_dir;
Vec2 window_dims;
Vec2i window_dims;
/* configuration */
toml_table_t *config_table;
@ -64,20 +64,19 @@ typedef struct EngineContext {
int64_t delta_averager_residual;
int64_t time_averager[4];
SDL_GLContext *gl_context;
SDL_Window *window;
uint32_t window_id;
/* this should be a multiple of the current ticks per second */
/* use it to simulate low framerate (e.g. at 60 tps, set to 2 for 30 fps) */
/* it can be changed at runtime; any resulting logic anomalies are bugs */
uint32_t update_multiplicity;
SDL_GLContext *gl_context;
SDL_Window *window;
uint32_t window_id;
bool is_running;
bool window_size_has_changed;
bool resync_flag;
bool was_successful;
bool render_double_buffered;
} EngineContext;
/* TODO: does it need to be marked with TWN_API? */

View File

@ -1,14 +1,13 @@
#include "twn_input_c.h"
#include "twn_util.h"
#include "twn_util_c.h"
#include "twn_control.h"
#include "twn_engine_context_c.h"
#include "twn_input.h"
#include <SDL2/SDL.h>
#include <stb_ds.h>
#include <stdbool.h>
#include <stdlib.h>
static void update_action_pressed_state(InputState *input, Action *action) {
@ -70,40 +69,20 @@ static void update_action_pressed_state(InputState *input, Action *action) {
}
static ActionHashItem *input_add_action(char const *action_name) {
SDL_assert(action_name);
Action new_action = { 0 };
new_action.bindings = SDL_calloc(ctx.keybind_slots, sizeof *new_action.bindings);
shput(ctx.input.action_hash, action_name, new_action);
return shgetp(ctx.input.action_hash, action_name);
}
static void input_delete_action(char const *action_name) {
SDL_assert(action_name);
ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
SDL_assert(action);
SDL_free(action->value.bindings);
shdel(ctx.input.action_hash, action_name);
}
static void input_bind_code_to_action(InputState *input,
char const *action_name,
ButtonSource source,
union ButtonCode code)
{
ActionHashItem *action_item = shgetp_null(input->action_hash, action_name);
if (!action_item)
action_item = input_add_action(action_name);
if (action_item == NULL) {
log_warn("(%s) Action \"%s\" does not exist.", __func__, action_name);
return;
}
Action *action = &action_item->value;
/* check every binding to make sure this code isn't already bound */
for (size_t i = 0; i < action->num_bindings; ++i) {
for (size_t i = 0; i < (uint64_t)ctx.keybind_slots; ++i) {
Button *binding = &action->bindings[i];
if (binding->source != source)
@ -130,8 +109,7 @@ static void input_bind_code_to_action(InputState *input,
}
if (is_already_bound) {
/* keep it alive */
binding->in_use = true;
log_warn("(%s) Code already bound to action \"%s\".", __func__, action_name);
return;
}
}
@ -139,14 +117,13 @@ static void input_bind_code_to_action(InputState *input,
/* if we're at max bindings, forget the first element and shift the rest */
if (action->num_bindings == (uint64_t)ctx.keybind_slots) {
--action->num_bindings;
size_t shifted_size = sizeof action->bindings[0] * (ctx.keybind_slots - 1);
size_t shifted_size = (sizeof action->bindings) - (sizeof action->bindings[0]);
SDL_memmove(action->bindings, action->bindings + 1, shifted_size);
}
action->bindings[action->num_bindings++] = (Button) {
.source = source,
.code = code,
.in_use = true,
};
}
@ -157,15 +134,16 @@ static void input_unbind_code_from_action(InputState *input,
union ButtonCode code)
{
ActionHashItem *action_item = shgetp_null(input->action_hash, action_name);
if (!action_item)
action_item = input_add_action(action_name);
if (action_item == NULL) {
log_warn("(%s) Action \"%s\" does not exist.", __func__, action_name);
return;
}
Action *action = &action_item->value;
/* check every binding to make sure this code is bound */
size_t index = 0;
bool is_bound = false;
for (index = 0; index < action->num_bindings; ++index) {
for (index = 0; index < (uint64_t)ctx.keybind_slots; ++index) {
Button *binding = &action->bindings[index];
if (binding->source != source)
@ -195,8 +173,10 @@ static void input_unbind_code_from_action(InputState *input,
break;
}
if (!is_bound)
if (!is_bound) {
log_warn("(%s) Code is not bound to action \"%s\".", __func__, action_name);
return;
}
/* remove the element to unbind and shift the rest so there isn't a gap */
size_t elements_after_index = action->num_bindings - index;
@ -217,45 +197,24 @@ void input_state_deinit(InputState *input) {
void input_state_update(InputState *input) {
/* TODO: don't spam it if it happens */
if (SDL_SetRelativeMouseMode(input->mouse_captured) != 0)
log_warn("(%s) Mouse capture isn't supported.", __func__);
int x, y;
input->keyboard_state = SDL_GetKeyboardState(NULL);
input->mouse_state = SDL_GetMouseState(&x, &y);
input->mouse_window_position = (Vec2){ (float)x, (float)y };
input->mouse_state = SDL_GetMouseState(&input->mouse_window_position.x,
&input->mouse_window_position.y);
SDL_GetRelativeMouseState(&x, &y);
input->mouse_relative_position = (Vec2){ (float)x, (float)y };
SDL_GetRelativeMouseState(&input->mouse_relative_position.x,
&input->mouse_relative_position.y);
ctx.game.mouse_position = input->mouse_window_position;
ctx.game.mouse_movement = input->mouse_relative_position;
for (size_t i = 0; i < shlenu(input->action_hash); ++i) {
Action *action = &input->action_hash[i].value;
/* collect unused */
for (size_t u = 0; u < action->num_bindings; ++u) {
Button *button = &action->bindings[u];
if (!button->in_use)
input_unbind_code_from_action(input, input->action_hash[i].key, button->source, button->code);
else
button->in_use = false;
}
update_action_pressed_state(input, action);
}
size_t removed = 0;
for (size_t i = 0; i < shlenu(input->action_hash); ++i) {
if (input->action_hash[i - removed].value.num_bindings == 0)
input_delete_action(input->action_hash[i - removed].key);
}
}
void input_action(char const *action_name,
void input_bind_action_control(char const *action_name,
Control control)
{
SDL_assert_always(action_name);
@ -277,7 +236,57 @@ void input_action(char const *action_name,
}
bool input_action_pressed(char const *action_name) {
void input_unbind_action_control(char const *action_name,
Control control)
{
SDL_assert_always(action_name);
if (CONTROL_SCANCODE_START <= control && control < CONTROL_SCANCODE_LIMIT)
input_unbind_code_from_action(&ctx.input,
action_name,
BUTTON_SOURCE_KEYBOARD_PHYSICAL,
(union ButtonCode) { .scancode = (SDL_Scancode)control });
else if (CONTROL_MOUSECODE_START <= control && control < CONTROL_MOUSECODE_LIMIT) {
uint8_t const mouse_button = (uint8_t)(control - CONTROL_MOUSECODE_START);
input_unbind_code_from_action(&ctx.input,
action_name,
BUTTON_SOURCE_MOUSE,
(union ButtonCode) { .mouse_button = (uint8_t)SDL_BUTTON(mouse_button)});
} else
log_warn("(%s) Invalid control value given: %i.", __func__, control);
}
void input_add_action(char const *action_name) {
SDL_assert_always(action_name);
if (shgeti(ctx.input.action_hash, action_name) >= 0) {
log_warn("(%s) Action \"%s\" is already registered.", __func__, action_name);
return;
}
Action new_action = { 0 };
new_action.bindings = ccalloc(ctx.keybind_slots, sizeof *new_action.bindings);
shput(ctx.input.action_hash, action_name, new_action);
}
void input_delete_action(char const *action_name) {
SDL_assert_always(action_name);
ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
if (action == NULL) {
log_warn("(%s) Action \"%s\" is not registered.", __func__, action_name);
return;
}
SDL_free(action->value.bindings);
shdel(ctx.input.action_hash, action_name);
}
bool input_is_action_pressed(char const *action_name) {
SDL_assert_always(action_name);
ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
@ -289,7 +298,7 @@ bool input_action_pressed(char const *action_name) {
}
bool input_action_just_pressed(char const *action_name) {
bool input_is_action_just_pressed(char const *action_name) {
SDL_assert_always(action_name);
ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
@ -301,7 +310,7 @@ bool input_action_just_pressed(char const *action_name) {
}
bool input_action_just_released(char const *action_name) {
bool input_is_action_just_released(char const *action_name) {
SDL_assert_always(action_name);
ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
@ -313,7 +322,7 @@ bool input_action_just_released(char const *action_name) {
}
Vec2 input_action_position(char const *action_name) {
Vec2 input_get_action_position(char const *action_name) {
SDL_assert_always(action_name);
ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
@ -326,8 +335,14 @@ Vec2 input_action_position(char const *action_name) {
}
void input_mouse_captured(bool enabled) {
ctx.input.mouse_captured = enabled;
void input_set_mouse_captured(bool enabled) {
if (SDL_SetRelativeMouseMode(enabled) != 0)
log_warn("(%s) Mouse capture isn't supported.", __func__);
}
bool input_is_mouse_captured(void) {
return SDL_GetRelativeMouseMode();
}

View File

@ -1,12 +1,11 @@
#ifndef TWN_INPUT_C_H
#define TWN_INPUT_C_H
#include "twn_types.h"
#include "twn_input.h"
#include "twn_vec.h"
#include <SDL2/SDL.h>
#include <stdbool.h>
#define KEYBIND_SLOTS_DEFAULT 3
@ -33,7 +32,6 @@ typedef enum ButtonSource {
typedef struct Button {
enum ButtonSource source;
union ButtonCode code;
bool in_use;
} Button;
@ -61,8 +59,8 @@ typedef struct ActionHashItem {
typedef struct InputState {
const uint8_t *keyboard_state; /* array of booleans indexed by scancode */
ActionHashItem *action_hash;
Vec2 mouse_window_position;
Vec2 mouse_relative_position;
Vec2i mouse_window_position;
Vec2i mouse_relative_position;
uint32_t mouse_state; /* SDL mouse button bitmask */
ButtonSource last_active_source;
bool is_anything_just_pressed;

View File

@ -37,8 +37,8 @@ static int event_callback(void *userdata, SDL_Event *event) {
switch (event->window.event) {
case SDL_WINDOWEVENT_SIZE_CHANGED:
ctx.window_dims.x = (float)event->window.data1;
ctx.window_dims.y = (float)event->window.data2;
ctx.window_dims.x = event->window.data1;
ctx.window_dims.y = event->window.data2;
ctx.resync_flag = true;
break;
@ -204,8 +204,8 @@ static void main_loop(void) {
/* TODO: disable rendering pushes on not-last ? */
render_queue_clear();
poll_events();
game_object_tick();
input_state_update(&ctx.input);
game_object_tick();
preserve_persistent_ctx_fields();
ctx.frame_accumulator -= ctx.desired_frametime;
@ -444,7 +444,7 @@ static bool initialize(void) {
goto fail;
}
ctx.base_render_width = datum_base_render_width.u.i;
ctx.game.resolution.x = (float)ctx.base_render_width;
ctx.game.resolution.x = (int)ctx.base_render_width;
toml_datum_t datum_base_render_height = toml_int_in(game, "base_render_height");
if (!datum_base_render_height.ok) {
@ -452,7 +452,7 @@ static bool initialize(void) {
goto fail;
}
ctx.base_render_height = datum_base_render_height.u.i;
ctx.game.resolution.y = (float)ctx.base_render_height;
ctx.game.resolution.y = (int)ctx.base_render_height;
ctx.window = SDL_CreateWindow(datum_title.u.s,
SDL_WINDOWPOS_CENTERED,
@ -508,8 +508,8 @@ static bool initialize(void) {
/* TODO: */
// SDL_GetRendererOutputSize(ctx.renderer, &ctx.window_w, &ctx.window_h);
ctx.window_dims.x = (float)ctx.base_render_width;
ctx.window_dims.y = (float)ctx.base_render_height;
ctx.window_dims.x = (int)ctx.base_render_width;
ctx.window_dims.y = (int)ctx.base_render_height;
/* add a watcher for immediate updates on window size */
SDL_AddEventWatch(event_callback, NULL);
@ -658,8 +658,6 @@ static bool initialize(void) {
}
*/
ctx.render_double_buffered = true;
return true;
fail:
@ -788,8 +786,6 @@ int enter_loop(int argc, char **argv) {
main_loop();
}
profile_list_stats();
/* loop is over */
game_object_unload();

View File

@ -1,7 +1,7 @@
#ifndef TWN_TEXTURES_C_H
#define TWN_TEXTURES_C_H
#include "twn_types.h"
#include "twn_util.h"
#include "twn_texture_modes.h"
#include "rendering/twn_gpu_texture_c.h"
@ -50,7 +50,6 @@ typedef struct TextureCache {
typedef struct TextureKey { uint16_t id; } TextureKey;
/* tests whether given key structure corresponds to any texture */
#define TEXTURE_KEY_INVALID (TextureKey) { (uint16_t)-1 }
#define m_texture_key_is_valid(p_key) ((p_key).id != (uint16_t)-1)
void textures_cache_init(struct TextureCache *cache, SDL_Window *window);

View File

@ -9,16 +9,6 @@
#include <stdarg.h>
static struct ProfileItem {
char const *key;
struct Profile {
uint64_t tick_start;
uint64_t tick_accum;
uint64_t sample_count;
} value;
} *profiles;
void cry_impl(const char *file, const int line, const char *title, const char *text) {
SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION,
"TEARS AT %s:%d: %s ... %s", file, line, title, text);
@ -207,27 +197,59 @@ bool strends(const char *str, const char *suffix) {
}
/* TODO: have our own */
Rect rect_overlap(const Rect a, const Rect b) {
SDL_FRect a_sdl = { a.x, a.y, a.w, a.h };
SDL_FRect b_sdl = { b.x, b.y, b.w, b.h };
SDL_FRect result_sdl = { 0 };
bool overlap_rect(const Recti *a, const Recti *b, Recti *result) {
SDL_Rect a_sdl = { a->x, a->y, a->w, a->h };
SDL_Rect b_sdl = { b->x, b->y, b->w, b->h };
SDL_Rect result_sdl = { 0 };
(void)SDL_IntersectFRect(&a_sdl, &b_sdl, &result_sdl);
bool intersection = SDL_IntersectRect(&a_sdl, &b_sdl, &result_sdl);
return (Rect){ result_sdl.x, result_sdl.y, result_sdl.w, result_sdl.h };
if (result != NULL)
*result = (Recti){ result_sdl.x, result_sdl.y, result_sdl.w, result_sdl.h };
return intersection;
}
/* TODO: have our own */
bool rect_intersects(const Rect a, const Rect b) {
SDL_FRect a_sdl = { a.x, a.y, a.w, a.h };
SDL_FRect b_sdl = { b.x, b.y, b.w, b.h };
bool overlap_frect(const Rect *a, const Rect *b, Rect *result) {
SDL_FRect a_sdl = { a->x, a->y, a->w, a->h };
SDL_FRect b_sdl = { b->x, b->y, b->w, b->h };
SDL_FRect result_sdl = { 0 };
bool intersection = SDL_IntersectFRect(&a_sdl, &b_sdl, &result_sdl);
if (result != NULL)
*result = (Rect){ result_sdl.x, result_sdl.y, result_sdl.w, result_sdl.h };
return intersection;
}
bool intersect_rect(const Recti *a, const Recti *b) {
SDL_Rect a_sdl = { a->x, a->y, a->w, a->h };
SDL_Rect b_sdl = { b->x, b->y, b->w, b->h };
return SDL_HasIntersection(&a_sdl, &b_sdl);
}
bool intersect_frect(const Rect *a, const Rect *b) {
SDL_FRect a_sdl = { a->x, a->y, a->w, a->h };
SDL_FRect b_sdl = { b->x, b->y, b->w, b->h };
return SDL_HasIntersectionF(&a_sdl, &b_sdl);
}
Vec2 rect_center(Rect rect) {
Rect to_frect(Recti rect) {
return (Rect) {
.h = (float)rect.h,
.w = (float)rect.w,
.x = (float)rect.x,
.y = (float)rect.y,
};
}
Vec2 frect_center(Rect rect) {
return (Vec2){
.x = rect.x + rect.w / 2,
.y = rect.y + rect.h / 2,
@ -235,52 +257,25 @@ Vec2 rect_center(Rect rect) {
}
int32_t timer_tick_frames(int32_t frames_left) {
SDL_assert(frames_left >= 0);
return MAX(frames_left - 1, 0);
void tick_timer(int *value) {
*value = MAX(*value - 1, 0);
}
float timer_tick_seconds(float seconds_left) {
SDL_assert(seconds_left >= 0);
return MAX(seconds_left - ((float)ctx.delta_time / (float)ctx.clocks_per_second), 0.0f);
void tick_ftimer(float *value) {
*value = MAX(*value - ((float)ctx.delta_time / (float)ctx.clocks_per_second), 0.0f);
}
TimerElapseFramesResult timer_elapse_frames(int32_t frames_left, int32_t interval) {
SDL_assert(frames_left >= 0);
SDL_assert(interval > 0);
frames_left -= 1;
bool elapsed = false;
if (frames_left <= 0) {
elapsed = true;
frames_left += interval;
bool repeat_ftimer(float *value, float at) {
*value -= (float)ctx.delta_time / (float)ctx.clocks_per_second;
if (*value < 0.0f) {
*value += at;
return true;
}
return (TimerElapseFramesResult) {
.elapsed = elapsed,
.frames_left = frames_left
};
return false;
}
TimerElapseSecondsResult timer_elapse_seconds(float seconds_left, float interval) {
SDL_assert(seconds_left >= 0);
SDL_assert(interval > 0);
seconds_left -= (float)ctx.delta_time / (float)ctx.clocks_per_second;
bool elapsed = false;
if (seconds_left <= 0.0f) {
elapsed = true;
seconds_left += interval;
}
return (TimerElapseSecondsResult) {
.elapsed = elapsed,
.seconds_left = seconds_left
};
}
/* TODO: handle utf8 */
char *expand_asterisk(const char *mask, const char *to) {
const char *offset = SDL_strchr(mask, '*');
@ -297,43 +292,3 @@ char *expand_asterisk(const char *mask, const char *to) {
SDL_memcpy(str + (offset - mask) + to_len, offset + 1, mask_len - (offset - mask));
return str;
}
void profile_start(char profile[const static 1]) {
uint64_t tick_accum = 0, sample_count = 0;
struct ProfileItem const *p = shgetp_null(profiles, profile);
if (p) {
tick_accum = p->value.tick_accum;
sample_count = p->value.sample_count;
}
shput(profiles, profile, ((struct Profile) {
.tick_start = SDL_GetPerformanceCounter(),
.tick_accum = tick_accum,
.sample_count = sample_count,
}));
}
void profile_end(char profile[const static 1]) {
struct ProfileItem *p = shgetp_null(profiles, profile);
if (!p) {
log_warn("profile %s wasn't started!", profile);
return;
}
p->value.sample_count++;
p->value.tick_accum += SDL_GetPerformanceCounter() - p->value.tick_start;
}
void profile_list_stats(void) {
for (size_t i = 0; i < shlenu(profiles); ++i) {
log_info("profile '%s' on average took: %f seconds",
profiles[i].key,
(double)profiles[i].value.tick_accum /
(double)profiles[i].value.sample_count /
(double)(SDL_GetPerformanceFrequency()));
}
}

View File

@ -6,7 +6,7 @@
#include <SDL2/SDL.h>
#include <stdbool.h>
#include <math.h>
#define MAX SDL_max
#define MIN SDL_min
@ -23,10 +23,6 @@ _Noreturn void die_abruptly(void);
/* note: you must free the returned string */
char *expand_asterisk(const char *mask, const char *to);
void profile_start(char profile[const static 1]);
void profile_end(char profile[const static 1]);
void profile_list_stats(void);
/* http://www.azillionmonkeys.com/qed/sqroot.html */
static inline float fast_sqrt(float x)
{
@ -43,7 +39,7 @@ static inline float fast_sqrt(float x)
static inline Vec2 fast_cossine(float a) {
const float s = sinf(a);
const float s = SDL_sinf(a);
return (Vec2){
.x = fast_sqrt(1.0f - s * s) * (a >= (float)M_PI_2 && a < (float)(M_PI + M_PI_2) ? -1 : 1),
.y = s

View File

@ -1,4 +1,4 @@
CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
PROJECT(libxm C)
FUNCTION(OPTION_AND_DEFINE name description default_value)

View File

@ -11,7 +11,7 @@
set(PHYSFS_VERSION 3.2.0)
cmake_minimum_required(VERSION 3.5)
cmake_minimum_required(VERSION 3.0)
project(PhysicsFS VERSION ${PHYSFS_VERSION} LANGUAGES C )

View File

@ -548,7 +548,7 @@ extern void * stbds_shmode_func(size_t elemsize, int mode);
#define stbds_arraddnindex(a,n)(stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), stbds_header(a)->length-(n)) : stbds_arrlen(a))
#define stbds_arraddnoff stbds_arraddnindex
#define stbds_arrlast(a) ((a)[stbds_header(a)->length-1])
#define stbds_arrfree(a) ((void) ((a) ? stbds_arrfreef(a) : (void)0), (a)=NULL)
#define stbds_arrfree(a) ((void) ((a) ? STBDS_FREE(NULL,stbds_header(a)) : (void)0), (a)=NULL)
#define stbds_arrdel(a,i) stbds_arrdeln(a,i,1)
#define stbds_arrdeln(a,i,n) (memmove(&(a)[i], &(a)[(i)+(n)], sizeof *(a) * (stbds_header(a)->length-(n)-(i))), stbds_header(a)->length -= (n))
#define stbds_arrdelswap(a,i) ((a)[i] = stbds_arrlast(a), stbds_header(a)->length -= 1)