Compare commits
134 Commits
double
...
ab3c032313
Author | SHA1 | Date | |
---|---|---|---|
ab3c032313 | |||
34a3de73c6 | |||
597168c282 | |||
37cd8cf2cf | |||
cb5f207761 | |||
6e421543c4 | |||
c97d9b2568 | |||
e281ba593c | |||
a20be2c523 | |||
b20e7202fe | |||
045d2764fa | |||
53917b05b7 | |||
0dc0a18019 | |||
2df5616410 | |||
3f9906a918 | |||
8a5d639f95 | |||
40aef0a1f9 | |||
2de536210b | |||
449d4d3c32 | |||
8233f31269 | |||
45b8b21ec3 | |||
4659cf2aef | |||
db530ca3a0 | |||
f0dfd5627a | |||
0da1e413aa | |||
8c165974c7 | |||
1ba33bdc26 | |||
760515c551 | |||
9d0a2cab81 | |||
d5b42fa242 | |||
851ab80292 | |||
688d71953a | |||
63abf3d374 | |||
80c77424e2 | |||
82d4f21a4b | |||
3990f78a74 | |||
f0ad9b9a8a | |||
ea0af5159f | |||
5059802d09 | |||
b7cb37c06a | |||
664f123a85 | |||
2351d4114c | |||
86bf16b680 | |||
dbe6217e24 | |||
b037d7a0b9 | |||
e984e95fa8 | |||
4ed3764c1d | |||
6d19d2d819 | |||
5bce3e5238 | |||
6298394957 | |||
eefd53a630 | |||
87ae1a7312 | |||
3052bb693a | |||
5f6c8dd8e6 | |||
c694dfff82 | |||
b6ca9bedb4 | |||
8d67e44009 | |||
192907a0db | |||
e8b02570a2 | |||
46e077ba63 | |||
41d0e24780 | |||
777a06a002 | |||
313108092b | |||
83e2dc5468 | |||
951d9c76c8 | |||
f3848d2d52 | |||
8c401eda75 | |||
5c89c55b3e | |||
5b05386bb0 | |||
b0549612a9 | |||
6463ac3dd7 | |||
e914cad0dd | |||
a9d9936cb7 | |||
4dd028aeae | |||
d7a119a592 | |||
cb6c1df0be | |||
3bfa86066e | |||
7c0bf39f12 | |||
4f2b8ccd01 | |||
472a0657f3 | |||
0f368e2700 | |||
33471b4c46 | |||
edcb7fc39c | |||
f9a8448782 | |||
6d5732cc2b | |||
62d738cbbe | |||
f4a3298906 | |||
8ec5a96333 | |||
4277852fc5 | |||
dc2535358e | |||
190eb1f107 | |||
e06c879869 | |||
e7ed72dfc0 | |||
c4c097f050 | |||
1d34c91106 | |||
0d81236331 | |||
f9bb6412b7 | |||
b18f6f1d87 | |||
2f94e17852 | |||
26a2bf293f | |||
4b0d584b7e | |||
19215d5795 | |||
1c97053675 | |||
ee7fc42fbc | |||
cd9c65212d | |||
ccaef34d61 | |||
833f7dbc53 | |||
a7feb7b61b | |||
4be27816c2 | |||
d794ca862f | |||
26c75ffd7c | |||
e4da4a8b7f | |||
963d549eed | |||
9121da0675 | |||
6464d14b3e | |||
eff9fe6918 | |||
d11143ac86 | |||
1d35a3859b | |||
9da26638c8 | |||
a22bcfd97e | |||
a527036436 | |||
b390e9db23 | |||
5a08c01208 | |||
eff2d9c5e1 | |||
8aecc2bd06 | |||
1296d41ad7 | |||
48f63fc9df | |||
c49789f1f4 | |||
a7b09b9f39 | |||
399b199266 | |||
73b6ab047d | |||
024f17de91 | |||
92de2c00c0 | |||
b683594013 |
15
.editorconfig
Normal file
15
.editorconfig
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
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
2
.gitattributes
vendored
@ -2,3 +2,5 @@
|
|||||||
*.ogg filter=lfs diff=lfs merge=lfs -text
|
*.ogg filter=lfs diff=lfs merge=lfs -text
|
||||||
*.xm filter=lfs diff=lfs merge=lfs -text
|
*.xm filter=lfs diff=lfs merge=lfs -text
|
||||||
*.tga filter=lfs diff=lfs merge=lfs -text
|
*.tga filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|
||||||
|
text=auto eol=lf
|
||||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -22,8 +22,8 @@
|
|||||||
.vscode/
|
.vscode/
|
||||||
.idea/
|
.idea/
|
||||||
.cache/
|
.cache/
|
||||||
.build/
|
build/
|
||||||
.build-web/
|
build-web/
|
||||||
build/
|
build/
|
||||||
out/
|
out/
|
||||||
|
|
||||||
|
@ -2,6 +2,9 @@ cmake_minimum_required(VERSION 3.21)
|
|||||||
|
|
||||||
project(townengine LANGUAGES C)
|
project(townengine LANGUAGES C)
|
||||||
|
|
||||||
|
set(CMAKE_MESSAGE_LOG_LEVEL "WARNING")
|
||||||
|
set(CMAKE_INSTALL_MESSAGE NEVER)
|
||||||
|
|
||||||
# SDL dependencies
|
# SDL dependencies
|
||||||
# for whatever reason Emscripten has SDL2 config, but not actual SDL2 port by default
|
# for whatever reason Emscripten has SDL2 config, but not actual SDL2 port by default
|
||||||
if(NOT EMSCRIPTEN)
|
if(NOT EMSCRIPTEN)
|
||||||
@ -24,6 +27,7 @@ set(TWN_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "")
|
|||||||
|
|
||||||
# feature configuration, set them with -DFEATURE=ON/OFF in cli
|
# feature configuration, set them with -DFEATURE=ON/OFF in cli
|
||||||
option(TWN_FEATURE_DYNLIB_GAME "Enable dynamic library loading support" ON)
|
option(TWN_FEATURE_DYNLIB_GAME "Enable dynamic library loading support" ON)
|
||||||
|
option(TWN_FEATURE_PUSH_AUDIO "Enable frame based audio push for easy realtime audio" ON)
|
||||||
option(TWN_USE_AMALGAM "Enable use of twn_amalgam.c as a single compilation unit" ON)
|
option(TWN_USE_AMALGAM "Enable use of twn_amalgam.c as a single compilation unit" ON)
|
||||||
|
|
||||||
# todo: figure out how to compile for dynamic linking instead
|
# todo: figure out how to compile for dynamic linking instead
|
||||||
@ -81,8 +85,7 @@ endif()
|
|||||||
if(TWN_RENDERING_API MATCHES OPENGL_15)
|
if(TWN_RENDERING_API MATCHES OPENGL_15)
|
||||||
set(SYSTEM_SOURCE_FILES ${SYSTEM_SOURCE_FILES}
|
set(SYSTEM_SOURCE_FILES ${SYSTEM_SOURCE_FILES}
|
||||||
src/rendering/twn_gl_any_rendering.c
|
src/rendering/twn_gl_any_rendering.c
|
||||||
src/rendering/twn_gl_15_rendering.c
|
src/rendering/twn_gl_15_rendering.c)
|
||||||
src/rendering/twn_gl_15_gpu_texture.c)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(TWN_THIRD_PARTY_SOURCE_FILES
|
set(TWN_THIRD_PARTY_SOURCE_FILES
|
||||||
@ -99,17 +102,18 @@ set(TWN_NONOPT_SOURCE_FILES
|
|||||||
src/twn_audio.c include/twn_audio.h
|
src/twn_audio.c include/twn_audio.h
|
||||||
src/twn_util.c include/twn_util.h
|
src/twn_util.c include/twn_util.h
|
||||||
src/twn_input.c include/twn_input.h
|
src/twn_input.c include/twn_input.h
|
||||||
src/twn_camera.c include/twn_camera.h
|
src/twn_camera.c src/twn_camera_c.h
|
||||||
src/twn_textures.c src/twn_textures_c.h
|
src/twn_textures.c src/twn_textures_c.h
|
||||||
|
|
||||||
src/rendering/twn_draw.c src/rendering/twn_draw_c.h
|
src/rendering/twn_draw.c src/rendering/twn_draw_c.h
|
||||||
|
src/rendering/twn_quads.c
|
||||||
src/rendering/twn_sprites.c
|
src/rendering/twn_sprites.c
|
||||||
src/rendering/twn_rects.c
|
src/rendering/twn_rects.c
|
||||||
src/rendering/twn_text.c
|
src/rendering/twn_text.c
|
||||||
src/rendering/twn_triangles.c
|
src/rendering/twn_triangles.c
|
||||||
|
src/rendering/twn_billboards.c
|
||||||
src/rendering/twn_circles.c
|
src/rendering/twn_circles.c
|
||||||
src/rendering/twn_skybox.c
|
src/rendering/twn_skybox.c)
|
||||||
src/rendering/twn_fog.c)
|
|
||||||
|
|
||||||
set(TWN_SOURCE_FILES
|
set(TWN_SOURCE_FILES
|
||||||
$<IF:$<BOOL:${TWN_USE_AMALGAM}>,src/twn_amalgam.c src/twn_stb.c,${TWN_NONOPT_SOURCE_FILES}>
|
$<IF:$<BOOL:${TWN_USE_AMALGAM}>,src/twn_amalgam.c src/twn_stb.c,${TWN_NONOPT_SOURCE_FILES}>
|
||||||
@ -137,6 +141,8 @@ set_target_properties(${TWN_TARGET} PROPERTIES
|
|||||||
C_STANDARD_REQUIRED ON
|
C_STANDARD_REQUIRED ON
|
||||||
C_EXTENSIONS ON) # extensions are required by stb_ds.h
|
C_EXTENSIONS ON) # extensions are required by stb_ds.h
|
||||||
|
|
||||||
|
target_compile_definitions(${TWN_TARGET} PRIVATE $<$<BOOL:${TWN_FEATURE_PUSH_AUDIO}>:TWN_FEATURE_PUSH_AUDIO>)
|
||||||
|
|
||||||
# precompile commonly used not-so-small headers
|
# precompile commonly used not-so-small headers
|
||||||
target_precompile_headers(${TWN_TARGET} PRIVATE
|
target_precompile_headers(${TWN_TARGET} PRIVATE
|
||||||
$<$<NOT:$<BOOL:${EMSCRIPTEN}>>:third-party/glad/include/glad/glad.h>
|
$<$<NOT:$<BOOL:${EMSCRIPTEN}>>:third-party/glad/include/glad/glad.h>
|
||||||
@ -156,14 +162,13 @@ function(give_options_without_warnings target)
|
|||||||
|
|
||||||
set(BUILD_FLAGS_RELEASE
|
set(BUILD_FLAGS_RELEASE
|
||||||
-O3
|
-O3
|
||||||
-flto=auto
|
-flto=$<IF:$<STREQUAL:${CMAKE_C_COMPILER_ID},Clang>,thin,auto>
|
||||||
-mavx -mavx2
|
-mavx -mavx2
|
||||||
-Wl,--gc-sections
|
|
||||||
-fdata-sections
|
-fdata-sections
|
||||||
-ffunction-sections
|
-ffunction-sections
|
||||||
-funroll-loops
|
-funroll-loops
|
||||||
-fomit-frame-pointer
|
-fomit-frame-pointer
|
||||||
-s)
|
$<$<STREQUAL:${CMAKE_C_COMPILER_ID},Gnu>:-s>)
|
||||||
|
|
||||||
set(BUILD_FLAGS_DEBUG
|
set(BUILD_FLAGS_DEBUG
|
||||||
-O0
|
-O0
|
||||||
@ -173,6 +178,19 @@ function(give_options_without_warnings target)
|
|||||||
$<$<BOOL:${TWN_SANITIZE}>:-fstack-protector-all -fsanitize=undefined -fsanitize=address>
|
$<$<BOOL:${TWN_SANITIZE}>:-fstack-protector-all -fsanitize=undefined -fsanitize=address>
|
||||||
$<$<BOOL:${EMSCRIPTEN}>:-gsource-map>)
|
$<$<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
|
target_compile_options(${target} PUBLIC
|
||||||
${BUILD_FLAGS}
|
${BUILD_FLAGS}
|
||||||
$<$<CONFIG:Release>:${BUILD_FLAGS_RELEASE}>
|
$<$<CONFIG:Release>:${BUILD_FLAGS_RELEASE}>
|
||||||
@ -186,6 +204,15 @@ function(give_options_without_warnings target)
|
|||||||
-Bsymbolic-functions
|
-Bsymbolic-functions
|
||||||
$<$<BOOL:${LINUX}>:-Wl,--hash-style=gnu>)
|
$<$<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
|
target_compile_definitions(${target} PRIVATE
|
||||||
$<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:TWN_FEATURE_DYNLIB_GAME>
|
$<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:TWN_FEATURE_DYNLIB_GAME>
|
||||||
$<$<BOOL:${LINUX}>:_GNU_SOURCE>)
|
$<$<BOOL:${LINUX}>:_GNU_SOURCE>)
|
||||||
@ -316,8 +343,3 @@ include_deps(${TWN_TARGET})
|
|||||||
link_deps(${TWN_TARGET})
|
link_deps(${TWN_TARGET})
|
||||||
target_link_libraries(${TWN_TARGET} PUBLIC twn_third_parties)
|
target_link_libraries(${TWN_TARGET} PUBLIC twn_third_parties)
|
||||||
target_include_directories(${TWN_TARGET} PRIVATE ${TWN_ROOT_DIR}/src)
|
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})
|
|
||||||
|
@ -6,7 +6,7 @@ if(NOT CMAKE_BUILD_TYPE)
|
|||||||
set(CMAKE_BUILD_TYPE Debug)
|
set(CMAKE_BUILD_TYPE Debug)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNBUILDDIR})
|
add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build)
|
||||||
|
|
||||||
set(SOURCE_FILES
|
set(SOURCE_FILES
|
||||||
game.c
|
game.c
|
||||||
|
@ -5,7 +5,6 @@ app_id = "bunnymark"
|
|||||||
dev_id = "morshy"
|
dev_id = "morshy"
|
||||||
|
|
||||||
[game]
|
[game]
|
||||||
base_render_width = 640
|
resolution = [ 640, 480 ]
|
||||||
base_render_height = 480
|
|
||||||
|
|
||||||
[engine]
|
[engine]
|
||||||
|
@ -20,13 +20,13 @@ static void handle_input(void)
|
|||||||
if (ctx.mouse_position.y <= 60)
|
if (ctx.mouse_position.y <= 60)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (input_is_action_pressed("add_a_bit"))
|
if (input_action_pressed("add_a_bit"))
|
||||||
{ // Left click
|
{ // Left click
|
||||||
for (int i = 0; i < LEFT_CLICK_ADD; i++)
|
for (int i = 0; i < LEFT_CLICK_ADD; i++)
|
||||||
{
|
{
|
||||||
if (state->bunniesCount < MAX_BUNNIES)
|
if (state->bunniesCount < MAX_BUNNIES)
|
||||||
{
|
{
|
||||||
state->bunnies[state->bunniesCount].position = input_get_action_position("add_a_bit");
|
state->bunnies[state->bunniesCount].position = input_action_position("add_a_bit");
|
||||||
state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0f;
|
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].speed.y = (float)(rand() % 500 - 250) / 60.0f;
|
||||||
state->bunnies[state->bunniesCount].color =
|
state->bunnies[state->bunniesCount].color =
|
||||||
@ -38,13 +38,13 @@ static void handle_input(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input_is_action_pressed("add_a_lot"))
|
if (input_action_pressed("add_a_lot"))
|
||||||
{ // Right click
|
{ // Right click
|
||||||
for (int i = 0; i < RIGHT_CLICK_ADD; i++)
|
for (int i = 0; i < RIGHT_CLICK_ADD; i++)
|
||||||
{
|
{
|
||||||
if (state->bunniesCount < MAX_BUNNIES)
|
if (state->bunniesCount < MAX_BUNNIES)
|
||||||
{
|
{
|
||||||
state->bunnies[state->bunniesCount].position = input_get_action_position("add_a_lot");
|
state->bunnies[state->bunniesCount].position = input_action_position("add_a_lot");
|
||||||
state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0f;
|
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].speed.y = (float)(rand() % 500 - 250) / 60.0f;
|
||||||
state->bunnies[state->bunniesCount].color =
|
state->bunnies[state->bunniesCount].color =
|
||||||
@ -67,14 +67,11 @@ void game_tick(void)
|
|||||||
// Allocating State struct to store data there
|
// Allocating State struct to store data there
|
||||||
if (!ctx.udata)
|
if (!ctx.udata)
|
||||||
ctx.udata = ccalloc(1, sizeof(State));
|
ctx.udata = ccalloc(1, sizeof(State));
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input_action("add_a_bit", CONTROL_LEFT_MOUSE);
|
||||||
|
input_action("add_a_lot", CONTROL_RIGHT_MOUSE);
|
||||||
|
|
||||||
State *state = ctx.udata;
|
State *state = ctx.udata;
|
||||||
|
|
||||||
for (int i = 0; i < state->bunniesCount; i++)
|
for (int i = 0; i < state->bunniesCount; i++)
|
||||||
@ -97,7 +94,7 @@ void game_tick(void)
|
|||||||
|
|
||||||
for (int i = 0; i < state->bunniesCount; i++)
|
for (int i = 0; i < state->bunniesCount; i++)
|
||||||
{ // Draw each bunny based on their position and color, also scale accordingly
|
{ // Draw each bunny based on their position and color, also scale accordingly
|
||||||
m_sprite(m_set(path, "wabbit_alpha.png"),
|
m_sprite(m_set(texture, "wabbit_alpha.png"),
|
||||||
m_set(rect, ((Rect){.x = state->bunnies[i].position.x,
|
m_set(rect, ((Rect){.x = state->bunnies[i].position.x,
|
||||||
.y = state->bunnies[i].position.y,
|
.y = state->bunnies[i].position.y,
|
||||||
.w = BUNNY_W * SPRITE_SCALE,
|
.w = BUNNY_W * SPRITE_SCALE,
|
||||||
|
@ -6,7 +6,7 @@ if(NOT CMAKE_BUILD_TYPE)
|
|||||||
set(CMAKE_BUILD_TYPE Debug)
|
set(CMAKE_BUILD_TYPE Debug)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNBUILDDIR})
|
add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build)
|
||||||
|
|
||||||
set(SOURCE_FILES
|
set(SOURCE_FILES
|
||||||
game.c
|
game.c
|
||||||
|
@ -5,7 +5,6 @@ app_id = "platformer-demo"
|
|||||||
dev_id = "townengine-team"
|
dev_id = "townengine-team"
|
||||||
|
|
||||||
[game]
|
[game]
|
||||||
base_render_width = 640
|
resolution = [ 640, 360 ]
|
||||||
base_render_height = 360
|
|
||||||
|
|
||||||
[engine]
|
[engine]
|
||||||
|
@ -18,45 +18,18 @@ void game_tick(void) {
|
|||||||
state->ctx = &ctx;
|
state->ctx = &ctx;
|
||||||
state->scene = title_scene(state);
|
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;
|
State *state = ctx.udata;
|
||||||
|
|
||||||
if (input_is_action_just_pressed("debug_toggle")) {
|
input_action("debug_toggle", CONTROL_BACKSPACE);
|
||||||
|
input_action("debug_dump_atlases", CONTROL_HOME);
|
||||||
|
|
||||||
|
if (input_action_just_pressed("debug_toggle")) {
|
||||||
ctx.debug = !ctx.debug;
|
ctx.debug = !ctx.debug;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input_is_action_just_pressed("debug_dump_atlases")) {
|
if (input_action_just_pressed("debug_dump_atlases")) {
|
||||||
textures_dump_atlases();
|
textures_dump_atlases();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,28 +11,28 @@
|
|||||||
|
|
||||||
|
|
||||||
static void update_timers(Player *player) {
|
static void update_timers(Player *player) {
|
||||||
tick_timer(&player->jump_air_timer);
|
player->jump_air_timer = timer_tick_frames(player->jump_air_timer);
|
||||||
tick_timer(&player->jump_coyote_timer);
|
player->jump_coyote_timer = timer_tick_frames(player->jump_coyote_timer);
|
||||||
tick_timer(&player->jump_buffer_timer);
|
player->jump_buffer_timer = timer_tick_frames(player->jump_buffer_timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void input_move(Player *player) {
|
static void input_move(Player *player) {
|
||||||
/* apply horizontal damping when the player stops moving */
|
/* apply horizontal damping when the player stops moving */
|
||||||
/* in other words, make it decelerate to a standstill */
|
/* in other words, make it decelerate to a standstill */
|
||||||
if (!input_is_action_pressed("player_left") &&
|
if (!input_action_pressed("player_left") &&
|
||||||
!input_is_action_pressed("player_right"))
|
!input_action_pressed("player_right"))
|
||||||
{
|
{
|
||||||
player->dx *= player->horizontal_damping;
|
player->dx *= player->horizontal_damping;
|
||||||
}
|
}
|
||||||
|
|
||||||
int input_dir = 0;
|
int input_dir = 0;
|
||||||
if (input_is_action_pressed("player_left"))
|
if (input_action_pressed("player_left"))
|
||||||
input_dir = -1;
|
input_dir = -1;
|
||||||
if (input_is_action_pressed("player_right"))
|
if (input_action_pressed("player_right"))
|
||||||
input_dir = 1;
|
input_dir = 1;
|
||||||
if (input_is_action_pressed("player_left") &&
|
if (input_action_pressed("player_left") &&
|
||||||
input_is_action_pressed("player_right"))
|
input_action_pressed("player_right"))
|
||||||
input_dir = 0;
|
input_dir = 0;
|
||||||
|
|
||||||
player->dx += (float)input_dir * player->run_horizontal_speed;
|
player->dx += (float)input_dir * player->run_horizontal_speed;
|
||||||
@ -56,7 +56,7 @@ static void jump(Player *player) {
|
|||||||
static void input_jump(Player *player) {
|
static void input_jump(Player *player) {
|
||||||
player->current_gravity_multiplier = player->jump_default_multiplier;
|
player->current_gravity_multiplier = player->jump_default_multiplier;
|
||||||
|
|
||||||
if (input_is_action_just_pressed("player_jump")) {
|
if (input_action_just_pressed("player_jump")) {
|
||||||
player->jump_air_timer = 0;
|
player->jump_air_timer = 0;
|
||||||
player->jump_buffer_timer = player->jump_buffer_ticks;
|
player->jump_buffer_timer = player->jump_buffer_ticks;
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ static void input_jump(Player *player) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input_is_action_pressed("player_jump")) {
|
if (input_action_pressed("player_jump")) {
|
||||||
if (player->action != PLAYER_ACTION_GROUND && player->jump_air_timer > 0) {
|
if (player->action != PLAYER_ACTION_GROUND && player->jump_air_timer > 0) {
|
||||||
player->current_gravity_multiplier = player->jump_boosted_multiplier;
|
player->current_gravity_multiplier = player->jump_boosted_multiplier;
|
||||||
player->dy += player->jump_force_increase;
|
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) {
|
static void calc_collisions_x(Player *player) {
|
||||||
Rect collision;
|
Rect collision;
|
||||||
bool is_colliding = world_find_intersect_frect(player->world, player->collider_x, &collision);
|
bool is_colliding = world_find_rect_intersects(player->world, player->collider_x, &collision);
|
||||||
if (!is_colliding) return;
|
if (!is_colliding) return;
|
||||||
|
|
||||||
float player_center_x = player->collider_x.x + (player->collider_x.w / 2);
|
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) {
|
static void calc_collisions_y(Player *player) {
|
||||||
Rect collision;
|
Rect collision;
|
||||||
bool is_colliding = world_find_intersect_frect(player->world, player->collider_y, &collision);
|
bool is_colliding = world_find_rect_intersects(player->world, player->collider_y, &collision);
|
||||||
if (!is_colliding) return;
|
if (!is_colliding) return;
|
||||||
|
|
||||||
float player_center_y = player->collider_y.y + (player->collider_y.h / 2);
|
float player_center_y = player->collider_y.y + (player->collider_y.h / 2);
|
||||||
@ -255,6 +255,10 @@ static void drawdef(Player *player) {
|
|||||||
draw_circle((Vec2) { 256, 128 },
|
draw_circle((Vec2) { 256, 128 },
|
||||||
24,
|
24,
|
||||||
(Color) { 255, 0, 0, 255 });
|
(Color) { 255, 0, 0, 255 });
|
||||||
|
|
||||||
|
draw_circle((Vec2) { 304, 128 },
|
||||||
|
24,
|
||||||
|
(Color) { 255, 0, 0, 255 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,28 +11,15 @@
|
|||||||
static void ingame_tick(State *state) {
|
static void ingame_tick(State *state) {
|
||||||
SceneIngame *scn = (SceneIngame *)state->scene;
|
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);
|
world_drawdef(scn->world);
|
||||||
player_calc(scn->player);
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -54,7 +41,5 @@ Scene *ingame_scene(State *state) {
|
|||||||
new_scene->world = world_create();
|
new_scene->world = world_create();
|
||||||
new_scene->player = player_create(new_scene->world);
|
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;
|
return (Scene *)new_scene;
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,6 @@ typedef struct SceneIngame {
|
|||||||
World *world;
|
World *world;
|
||||||
Player *player;
|
Player *player;
|
||||||
|
|
||||||
Camera cam;
|
|
||||||
|
|
||||||
/* TODO: put this in a better place */
|
/* TODO: put this in a better place */
|
||||||
float yaw;
|
float yaw;
|
||||||
float pitch;
|
float pitch;
|
||||||
|
@ -14,7 +14,9 @@ static void title_tick(State *state) {
|
|||||||
SceneTitle *scn = (SceneTitle *)state->scene;
|
SceneTitle *scn = (SceneTitle *)state->scene;
|
||||||
(void)scn;
|
(void)scn;
|
||||||
|
|
||||||
if (input_is_action_just_pressed("ui_accept")) {
|
input_action("ui_accept", CONTROL_RETURN);
|
||||||
|
|
||||||
|
if (input_action_just_pressed("ui_accept")) {
|
||||||
switch_to(state, ingame_scene);
|
switch_to(state, ingame_scene);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -23,13 +25,13 @@ static void title_tick(State *state) {
|
|||||||
((float)ctx.resolution.x / 2) - ((float)320 / 2), 64, 320, 128 }));
|
((float)ctx.resolution.x / 2) - ((float)320 / 2), 64, 320, 128 }));
|
||||||
|
|
||||||
/* draw the tick count as an example of dynamic text */
|
/* draw the tick count as an example of dynamic text */
|
||||||
size_t text_str_len = snprintf(NULL, 0, "%lu", state->ctx->frame_number) + 1;
|
size_t text_str_len = snprintf(NULL, 0, "%llu", (unsigned long long)state->ctx->frame_number) + 1;
|
||||||
char *text_str = cmalloc(text_str_len);
|
char *text_str = cmalloc(text_str_len);
|
||||||
snprintf(text_str, text_str_len, "%lu", state->ctx->frame_number);
|
snprintf(text_str, text_str_len, "%llu", (unsigned long long)state->ctx->frame_number);
|
||||||
|
|
||||||
const char *font = "fonts/kenney-pixel.ttf";
|
const char *font = "fonts/kenney-pixel.ttf";
|
||||||
int text_h = 32;
|
float text_h = 32;
|
||||||
int text_w = draw_text_width(text_str, text_h, font);
|
float text_w = draw_text_width(text_str, text_h, font);
|
||||||
|
|
||||||
draw_rectangle(
|
draw_rectangle(
|
||||||
(Rect) {
|
(Rect) {
|
||||||
|
@ -12,11 +12,11 @@ static void update_tiles(struct World *world) {
|
|||||||
for (size_t row = 0; row < world->tilemap_height; ++row) {
|
for (size_t row = 0; row < world->tilemap_height; ++row) {
|
||||||
for (size_t col = 0; col < world->tilemap_width; ++col) {
|
for (size_t col = 0; col < world->tilemap_width; ++col) {
|
||||||
world->tiles[(row * world->tilemap_width) + col] = (struct Tile) {
|
world->tiles[(row * world->tilemap_width) + col] = (struct Tile) {
|
||||||
.rect = (Recti) {
|
.rect = (Rect) {
|
||||||
.x = (int)col * world->tile_size,
|
.x = (float)(col * world->tile_size),
|
||||||
.y = (int)row * world->tile_size,
|
.y = (float)(row * world->tile_size),
|
||||||
.w = world->tile_size,
|
.w = (float)world->tile_size,
|
||||||
.h = world->tile_size,
|
.h = (float)world->tile_size,
|
||||||
},
|
},
|
||||||
.type = world->tilemap[row][col],
|
.type = world->tilemap[row][col],
|
||||||
};
|
};
|
||||||
@ -25,10 +25,10 @@ static void update_tiles(struct World *world) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static Vec2i to_grid_location(struct World *world, float x, float y) {
|
static Vec2 to_grid_location(struct World *world, float x, float y) {
|
||||||
return (Vec2i) {
|
return (Vec2) {
|
||||||
.x = (int)floor(x / (float)world->tile_size),
|
.x = floor(x / (float)world->tile_size),
|
||||||
.y = (int)floor(y / (float)world->tile_size),
|
.y = floor(y / (float)world->tile_size),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,8 +39,7 @@ static void drawdef_debug(struct World *world) {
|
|||||||
for (size_t i = 0; i < world->tilemap_height * world->tilemap_width; ++i) {
|
for (size_t i = 0; i < world->tilemap_height * world->tilemap_width; ++i) {
|
||||||
if (world->tiles[i].type == TILE_TYPE_VOID) continue;
|
if (world->tiles[i].type == TILE_TYPE_VOID) continue;
|
||||||
|
|
||||||
draw_rectangle(to_frect(world->tiles[i].rect),
|
draw_rectangle(world->tiles[i].rect, (Color) { 255, 0, 255, 128 });
|
||||||
(Color) { 255, 0, 255, 128 });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,14 +105,14 @@ void world_drawdef(struct World *world) {
|
|||||||
if (world->tiles[i].type == TILE_TYPE_VOID)
|
if (world->tiles[i].type == TILE_TYPE_VOID)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
m_sprite("/assets/white.png", to_frect(world->tiles[i].rect));
|
m_sprite("/assets/white.png", world->tiles[i].rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
drawdef_debug(world);
|
drawdef_debug(world);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool world_find_intersect_frect(struct World *world, Rect rect, Rect *intersection) {
|
bool world_find_rect_intersects(struct World *world, Rect rect, Rect *intersection) {
|
||||||
bool is_intersecting = false;
|
bool is_intersecting = false;
|
||||||
|
|
||||||
const size_t tile_count = world->tilemap_height * world->tilemap_width;
|
const size_t tile_count = world->tilemap_height * world->tilemap_width;
|
||||||
@ -121,44 +120,12 @@ bool world_find_intersect_frect(struct World *world, Rect rect, Rect *intersecti
|
|||||||
if (world->tiles[i].type == TILE_TYPE_VOID)
|
if (world->tiles[i].type == TILE_TYPE_VOID)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Rect tile_frect = {
|
Rect const tile_frect = world->tiles[i].rect;
|
||||||
.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),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (intersection == NULL) {
|
is_intersecting = rect_intersects(rect, tile_frect);
|
||||||
Rect temp;
|
|
||||||
is_intersecting = overlap_frect(&rect, &tile_frect, &temp);
|
|
||||||
} else {
|
|
||||||
is_intersecting = overlap_frect(&rect, &tile_frect, intersection);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_intersecting)
|
if (intersection)
|
||||||
break;
|
*intersection = rect_overlap(rect, tile_frect);
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
if (is_intersecting)
|
||||||
break;
|
break;
|
||||||
@ -169,20 +136,20 @@ bool world_find_intersect_rect(struct World *world, Recti rect, Recti *intersect
|
|||||||
|
|
||||||
|
|
||||||
bool world_is_tile_at(struct World *world, float x, float y) {
|
bool world_is_tile_at(struct World *world, float x, float y) {
|
||||||
Vec2i position_in_grid = to_grid_location(world, x, y);
|
Vec2 position_in_grid = to_grid_location(world, x, y);
|
||||||
return world->tilemap[position_in_grid.y][position_in_grid.x] != TILE_TYPE_VOID;
|
return world->tilemap[(int32_t)position_in_grid.y][(int32_t)position_in_grid.x] != TILE_TYPE_VOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void world_place_tile(struct World *world, float x, float y) {
|
void world_place_tile(struct World *world, float x, float y) {
|
||||||
Vec2i position_in_grid = to_grid_location(world, x, y);
|
Vec2 position_in_grid = to_grid_location(world, x, y);
|
||||||
world->tilemap[position_in_grid.y][position_in_grid.x] = TILE_TYPE_SOLID;
|
world->tilemap[(int32_t)position_in_grid.y][(int32_t)position_in_grid.x] = TILE_TYPE_SOLID;
|
||||||
update_tiles(world);
|
update_tiles(world);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void world_remove_tile(struct World *world, float x, float y) {
|
void world_remove_tile(struct World *world, float x, float y) {
|
||||||
Vec2i position_in_grid = to_grid_location(world, x, y);
|
Vec2 position_in_grid = to_grid_location(world, x, y);
|
||||||
world->tilemap[position_in_grid.y][position_in_grid.x] = TILE_TYPE_VOID;
|
world->tilemap[(int32_t)position_in_grid.y][(int32_t)position_in_grid.x] = TILE_TYPE_VOID;
|
||||||
update_tiles(world);
|
update_tiles(world);
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ typedef enum TileType {
|
|||||||
|
|
||||||
|
|
||||||
typedef struct Tile {
|
typedef struct Tile {
|
||||||
Recti rect;
|
Rect rect;
|
||||||
TileType type;
|
TileType type;
|
||||||
} Tile;
|
} Tile;
|
||||||
|
|
||||||
@ -34,8 +34,7 @@ typedef struct World {
|
|||||||
World *world_create(void);
|
World *world_create(void);
|
||||||
void world_destroy(World *world);
|
void world_destroy(World *world);
|
||||||
void world_drawdef(World *world);
|
void world_drawdef(World *world);
|
||||||
bool world_find_intersect_frect(World *world, Rect rect, Rect *intersection);
|
bool world_find_rect_intersects(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);
|
bool world_is_tile_at(World *world, float x, float y);
|
||||||
void world_place_tile(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);
|
void world_remove_tile(World *world, float x, float y);
|
||||||
|
@ -6,7 +6,7 @@ if(NOT CMAKE_BUILD_TYPE)
|
|||||||
set(CMAKE_BUILD_TYPE Debug)
|
set(CMAKE_BUILD_TYPE Debug)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNBUILDDIR})
|
add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build)
|
||||||
|
|
||||||
set(SOURCE_FILES
|
set(SOURCE_FILES
|
||||||
game.c
|
game.c
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
[about]
|
[about]
|
||||||
title = "Serene Scenery"
|
title = "Serene Scenery"
|
||||||
developer = "Townengine Team"
|
developer = "Townengine Team"
|
||||||
app_id = "platformer-demo"
|
app_id = "scenery-demo"
|
||||||
dev_id = "townengine-team"
|
dev_id = "townengine-team"
|
||||||
|
|
||||||
[game]
|
[game]
|
||||||
base_render_width = 640
|
resolution = [ 640, 360 ]
|
||||||
base_render_height = 360
|
|
||||||
|
|
||||||
[engine]
|
[engine]
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include "state.h"
|
#include "state.h"
|
||||||
#include "scenes/scene.h"
|
#include "scenes/scene.h"
|
||||||
#include "scenes/title.h"
|
#include "scenes/title.h"
|
||||||
|
#include "scenes/ingame.h"
|
||||||
|
|
||||||
#include "twn_game_api.h"
|
#include "twn_game_api.h"
|
||||||
|
|
||||||
@ -17,47 +18,20 @@ void game_tick(void) {
|
|||||||
|
|
||||||
State *state = ctx.udata;
|
State *state = ctx.udata;
|
||||||
state->ctx = &ctx;
|
state->ctx = &ctx;
|
||||||
state->scene = title_scene(state);
|
state->scene = ingame_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;
|
State *state = ctx.udata;
|
||||||
|
|
||||||
if (input_is_action_just_pressed("debug_toggle")) {
|
input_action("debug_toggle", CONTROL_BACKSPACE);
|
||||||
|
input_action("debug_dump_atlases", CONTROL_HOME);
|
||||||
|
|
||||||
|
if (input_action_just_pressed("debug_toggle")) {
|
||||||
ctx.debug = !ctx.debug;
|
ctx.debug = !ctx.debug;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input_is_action_just_pressed("debug_dump_atlases")) {
|
if (input_action_just_pressed("debug_dump_atlases")) {
|
||||||
textures_dump_atlases();
|
textures_dump_atlases();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,63 +12,147 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
|
||||||
static void ingame_tick(State *state) {
|
#define TERRAIN_FREQUENCY 0.15f
|
||||||
|
#define TERRAIN_DISTANCE 64
|
||||||
|
#define HALF_TERRAIN_DISTANCE ((float)TERRAIN_DISTANCE / 2)
|
||||||
|
#define PLAYER_HEIGHT 0.6f
|
||||||
|
|
||||||
|
static float heightmap[TERRAIN_DISTANCE][TERRAIN_DISTANCE];
|
||||||
|
|
||||||
|
|
||||||
|
static void process_fly_mode(State *state) {
|
||||||
SceneIngame *scn = (SceneIngame *)state->scene;
|
SceneIngame *scn = (SceneIngame *)state->scene;
|
||||||
|
|
||||||
if (input_is_mouse_captured()) {
|
DrawCameraFromPrincipalAxesResult dir_and_up =
|
||||||
const float sensitivity = 0.6f; /* TODO: put this in a better place */
|
draw_camera_from_principal_axes(scn->pos, (float)M_PI_2 * 0.8f, scn->roll, scn->pitch, scn->yaw);
|
||||||
scn->yaw += (float)ctx.mouse_movement.x * sensitivity;
|
|
||||||
scn->pitch -= (float)ctx.mouse_movement.y * sensitivity;
|
|
||||||
scn->pitch = clampf(scn->pitch, -89.0f, 89.0f);
|
|
||||||
|
|
||||||
const float yaw_rad = scn->yaw * (float)DEG2RAD;
|
const Vec3 right = m_vec_norm(m_vec_cross(dir_and_up.direction, dir_and_up.up));
|
||||||
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)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
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 */
|
const float speed = 0.04f; /* TODO: put this in a better place */
|
||||||
if (input_is_action_pressed("player_left"))
|
if (input_action_pressed("player_left"))
|
||||||
scn->cam.pos = vec3_sub(scn->cam.pos, m_vec_scale(right, speed));
|
scn->pos = vec3_sub(scn->pos, m_vec_scale(right, speed));
|
||||||
|
|
||||||
if (input_is_action_pressed("player_right"))
|
if (input_action_pressed("player_right"))
|
||||||
scn->cam.pos = vec3_add(scn->cam.pos, m_vec_scale(right, speed));
|
scn->pos = vec3_add(scn->pos, m_vec_scale(right, speed));
|
||||||
|
|
||||||
if (input_is_action_pressed("player_forward"))
|
if (input_action_pressed("player_forward"))
|
||||||
scn->cam.pos = vec3_add(scn->cam.pos, m_vec_scale(scn->cam.target, speed));
|
scn->pos = vec3_add(scn->pos, m_vec_scale(dir_and_up.direction, speed));
|
||||||
|
|
||||||
if (input_is_action_pressed("player_backward"))
|
if (input_action_pressed("player_backward"))
|
||||||
scn->cam.pos = vec3_sub(scn->cam.pos, m_vec_scale(scn->cam.target, speed));
|
scn->pos = vec3_sub(scn->pos, m_vec_scale(dir_and_up.direction, speed));
|
||||||
|
|
||||||
if (input_is_action_pressed("player_jump"))
|
if (input_action_pressed("player_jump"))
|
||||||
scn->cam.pos.y += speed;
|
scn->pos.y += speed;
|
||||||
|
|
||||||
if (input_is_action_pressed("player_run"))
|
if (input_action_pressed("player_run"))
|
||||||
scn->cam.pos.y -= speed;
|
scn->pos.y -= speed;
|
||||||
|
}
|
||||||
|
|
||||||
/* toggle mouse capture with end key */
|
|
||||||
if (input_is_action_just_pressed("mouse_capture_toggle")) {
|
static float height_at(SceneIngame *scn, Vec2 position) {
|
||||||
input_set_mouse_captured(!input_is_mouse_captured());
|
float height0, height1, height2, weight0, weight1, weight2;
|
||||||
|
|
||||||
|
int const x = (int)(HALF_TERRAIN_DISTANCE + (position.x - scn->pos.x));
|
||||||
|
int const y = (int)(HALF_TERRAIN_DISTANCE + (position.y - scn->pos.z));
|
||||||
|
|
||||||
|
height0 = heightmap[x][y];
|
||||||
|
height1 = heightmap[x + 1][y + 1];
|
||||||
|
|
||||||
|
Vec2 incell = { position.x - floorf(position.x), position.y - floorf(position.y) };
|
||||||
|
|
||||||
|
/* who needs barycentric coordinates, am i right? */
|
||||||
|
weight0 = 1 / sqrtf(powf(incell.x, 2) + powf(incell.y, 2));
|
||||||
|
weight1 = 1 / sqrtf(powf(1 - incell.x, 2) + powf(1 - incell.y, 2));
|
||||||
|
|
||||||
|
/* find which triangle we're directly under */
|
||||||
|
/* for this manhattan distance is sufficient */
|
||||||
|
if (incell.x + (1 - incell.y) < (1 - incell.x) + incell.y) {
|
||||||
|
height2 = heightmap[x][y + 1];
|
||||||
|
weight2 = 1 / sqrtf(powf(incell.x, 2) + powf(1 - incell.y, 2));
|
||||||
|
} else {
|
||||||
|
height2 = heightmap[x + 1][y];
|
||||||
|
weight2 = 1 / sqrtf(powf(1 - incell.x, 2) + powf(incell.y, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_camera(&scn->cam);
|
return (height0 * weight0 + height1 * weight1 + height2 * weight2) / (weight0 + weight1 + weight2);
|
||||||
|
}
|
||||||
|
|
||||||
#define TERRAIN_FREQUENCY 0.1f
|
|
||||||
|
|
||||||
for (int ly = 64; ly--;) {
|
static void process_ground_mode(State *state) {
|
||||||
for (int lx = 64; lx--;) {
|
SceneIngame *scn = (SceneIngame *)state->scene;
|
||||||
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;
|
DrawCameraFromPrincipalAxesResult dir_and_up =
|
||||||
float d1 = stb_perlin_noise3((float)(x + 1) * TERRAIN_FREQUENCY, (float)y * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6;
|
draw_camera_from_principal_axes(scn->pos, (float)M_PI_2 * 0.8f, scn->roll, scn->pitch, scn->yaw);
|
||||||
float d2 = stb_perlin_noise3((float)(x + 1) * TERRAIN_FREQUENCY, (float)(y - 1) * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6;
|
|
||||||
float d3 = stb_perlin_noise3((float)x * TERRAIN_FREQUENCY, (float)(y - 1) * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6;
|
dir_and_up.direction.y = 0;
|
||||||
|
dir_and_up.direction = vec3_norm(dir_and_up.direction);
|
||||||
|
|
||||||
|
const Vec3 right = m_vec_norm(m_vec_cross(dir_and_up.direction, dir_and_up.up));
|
||||||
|
const float speed = 0.18f; /* TODO: put this in a better place */
|
||||||
|
|
||||||
|
Vec3 target = scn->pos;
|
||||||
|
|
||||||
|
/* gravity */
|
||||||
|
{
|
||||||
|
float const height = height_at(scn, (Vec2){scn->pos.x, scn->pos.z});
|
||||||
|
|
||||||
|
if (target.y > height + PLAYER_HEIGHT)
|
||||||
|
target.y = target.y - 0.4f;
|
||||||
|
|
||||||
|
if (target.y < height + PLAYER_HEIGHT)
|
||||||
|
target.y = height + PLAYER_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* movement */
|
||||||
|
{
|
||||||
|
Vec3 direction = {0, 0, 0};
|
||||||
|
|
||||||
|
if (input_action_pressed("player_left"))
|
||||||
|
direction = m_vec_sub(direction, m_vec_scale(right, speed));
|
||||||
|
|
||||||
|
if (input_action_pressed("player_right"))
|
||||||
|
direction = m_vec_add(direction, m_vec_scale(right, speed));
|
||||||
|
|
||||||
|
if (input_action_pressed("player_forward"))
|
||||||
|
direction = m_vec_add(direction, m_vec_scale(dir_and_up.direction, speed));
|
||||||
|
|
||||||
|
if (input_action_pressed("player_backward"))
|
||||||
|
direction = m_vec_sub(direction, m_vec_scale(dir_and_up.direction, speed));
|
||||||
|
|
||||||
|
target = m_vec_add(target, direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* interpolate */
|
||||||
|
scn->pos.x = (target.x - scn->pos.x) * (0.13f / 0.9f) + scn->pos.x;
|
||||||
|
scn->pos.y = (target.y - scn->pos.y) * (0.13f / 0.9f) + scn->pos.y;
|
||||||
|
scn->pos.z = (target.z - scn->pos.z) * (0.13f / 0.9f) + scn->pos.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void generate_terrain(SceneIngame *scn) {
|
||||||
|
for (int ly = 0; ly < TERRAIN_DISTANCE; ly++) {
|
||||||
|
for (int lx = 0; lx < TERRAIN_DISTANCE; lx++) {
|
||||||
|
float x = floorf(scn->pos.x - HALF_TERRAIN_DISTANCE + (float)lx);
|
||||||
|
float y = floorf(scn->pos.z - HALF_TERRAIN_DISTANCE + (float)ly);
|
||||||
|
|
||||||
|
float height = stb_perlin_noise3((float)x * TERRAIN_FREQUENCY, (float)y * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 1;
|
||||||
|
height += stb_perlin_noise3((float)x * TERRAIN_FREQUENCY / 10, (float)y * TERRAIN_FREQUENCY / 10, 0, 0, 0, 0) * 10 - 1;
|
||||||
|
|
||||||
|
heightmap[lx][ly] = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void draw_terrain(SceneIngame *scn) {
|
||||||
|
for (int ly = TERRAIN_DISTANCE - 1; ly > 0; ly--) {
|
||||||
|
for (int lx = 0; lx < TERRAIN_DISTANCE - 1; lx++) {
|
||||||
|
int32_t x = (int32_t)(floorf(scn->pos.x - HALF_TERRAIN_DISTANCE + (float)lx));
|
||||||
|
int32_t y = (int32_t)(floorf(scn->pos.z - HALF_TERRAIN_DISTANCE + (float)ly));
|
||||||
|
|
||||||
|
float d0 = heightmap[lx][ly];
|
||||||
|
float d1 = heightmap[lx + 1][ly];
|
||||||
|
float d2 = heightmap[lx + 1][ly - 1];
|
||||||
|
float d3 = heightmap[lx][ly - 1];
|
||||||
|
|
||||||
draw_triangle("/assets/grass.png",
|
draw_triangle("/assets/grass.png",
|
||||||
(Vec3){ (float)x, d0, (float)y },
|
(Vec3){ (float)x, d0, (float)y },
|
||||||
@ -76,7 +160,10 @@ static void ingame_tick(State *state) {
|
|||||||
(Vec3){ (float)x, d3, (float)y - 1 },
|
(Vec3){ (float)x, d3, (float)y - 1 },
|
||||||
(Vec2){ 128, 128 },
|
(Vec2){ 128, 128 },
|
||||||
(Vec2){ 128, 0 },
|
(Vec2){ 128, 0 },
|
||||||
(Vec2){ 0, 128 });
|
(Vec2){ 0, 128 },
|
||||||
|
(Color){255, 255, 255, 255},
|
||||||
|
(Color){255, 255, 255, 255},
|
||||||
|
(Color){255, 255, 255, 255});
|
||||||
|
|
||||||
draw_triangle("/assets/grass.png",
|
draw_triangle("/assets/grass.png",
|
||||||
(Vec3){ (float)x + 1, d1, (float)y },
|
(Vec3){ (float)x + 1, d1, (float)y },
|
||||||
@ -84,12 +171,61 @@ static void ingame_tick(State *state) {
|
|||||||
(Vec3){ (float)x, d3, (float)y - 1 },
|
(Vec3){ (float)x, d3, (float)y - 1 },
|
||||||
(Vec2){ 128, 0 },
|
(Vec2){ 128, 0 },
|
||||||
(Vec2){ 0, 0 },
|
(Vec2){ 0, 0 },
|
||||||
(Vec2){ 0, 128 });
|
(Vec2){ 0, 128 },
|
||||||
|
(Color){255, 255, 255, 255},
|
||||||
|
(Color){255, 255, 255, 255},
|
||||||
|
(Color){255, 255, 255, 255});
|
||||||
|
|
||||||
|
draw_billboard("/assets/grasses/10.png",
|
||||||
|
(Vec3){ (float)x, d0 + 0.15f, (float)y },
|
||||||
|
(Vec2){0.3f, 0.3f},
|
||||||
|
(Color){255, 255, 255, 255}, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
input_action("toggle_camera_mode", CONTROL_C);
|
||||||
|
|
||||||
|
if (scn->mouse_captured) {
|
||||||
|
const float sensitivity = 0.4f * (float)DEG2RAD; /* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input_action_just_pressed("toggle_camera_mode"))
|
||||||
|
scn->flying_camera = !scn->flying_camera;
|
||||||
|
|
||||||
|
if (scn->flying_camera) {
|
||||||
|
process_fly_mode(state);
|
||||||
|
} else {
|
||||||
|
process_ground_mode(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* toggle mouse capture with end key */
|
||||||
|
if (input_action_just_pressed("mouse_capture_toggle"))
|
||||||
|
scn->mouse_captured = !scn->mouse_captured;
|
||||||
|
|
||||||
|
ctx.mouse_capture = scn->mouse_captured;
|
||||||
|
|
||||||
|
generate_terrain(scn);
|
||||||
|
draw_terrain(scn);
|
||||||
|
|
||||||
draw_skybox("/assets/miramar/miramar_*.tga");
|
draw_skybox("/assets/miramar/miramar_*.tga");
|
||||||
draw_fog(0.9f, 1.0f, 0.05f, (Color){ 140, 147, 160, 255 });
|
|
||||||
|
ctx.fog_color = (Color){ 140, 147, 160, 255 };
|
||||||
|
ctx.fog_density = 0.03f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -105,13 +241,13 @@ Scene *ingame_scene(State *state) {
|
|||||||
new_scene->base.tick = ingame_tick;
|
new_scene->base.tick = ingame_tick;
|
||||||
new_scene->base.end = ingame_end;
|
new_scene->base.end = ingame_end;
|
||||||
|
|
||||||
new_scene->cam = (Camera){ .pos = { 32, 0, 1 }, .up = { 0, 1, 0 }, .fov = (float)M_PI_2 };
|
new_scene->mouse_captured = true;
|
||||||
|
|
||||||
m_audio(m_set(path, "music/mod65.xm"),
|
m_audio(m_set(path, "music/mod65.xm"),
|
||||||
m_opt(channel, "soundtrack"),
|
m_opt(channel, "soundtrack"),
|
||||||
m_opt(repeat, true));
|
m_opt(repeat, true));
|
||||||
|
|
||||||
input_set_mouse_captured(true);
|
new_scene->pos = (Vec3){ 0.1f, 0.0, 0.1f };
|
||||||
|
|
||||||
return (Scene *)new_scene;
|
return (Scene *)new_scene;
|
||||||
}
|
}
|
||||||
|
@ -6,16 +6,19 @@
|
|||||||
#include "../state.h"
|
#include "../state.h"
|
||||||
#include "scene.h"
|
#include "scene.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
|
||||||
typedef struct SceneIngame {
|
typedef struct SceneIngame {
|
||||||
Scene base;
|
Scene base;
|
||||||
|
|
||||||
Camera cam;
|
Vec3 pos;
|
||||||
|
|
||||||
/* TODO: put this in a better place */
|
|
||||||
float yaw;
|
float yaw;
|
||||||
float pitch;
|
float pitch;
|
||||||
float roll;
|
float roll;
|
||||||
|
|
||||||
|
bool mouse_captured;
|
||||||
|
bool flying_camera;
|
||||||
} SceneIngame;
|
} SceneIngame;
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,7 +11,9 @@ static void title_tick(State *state) {
|
|||||||
SceneTitle *scn = (SceneTitle *)state->scene;
|
SceneTitle *scn = (SceneTitle *)state->scene;
|
||||||
(void)scn;
|
(void)scn;
|
||||||
|
|
||||||
if (input_is_action_just_pressed("ui_accept")) {
|
input_action("ui_accept", CONTROL_RETURN);
|
||||||
|
|
||||||
|
if (input_action_just_pressed("ui_accept")) {
|
||||||
switch_to(state, ingame_scene);
|
switch_to(state, ingame_scene);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -20,20 +22,20 @@ static void title_tick(State *state) {
|
|||||||
((float)ctx.resolution.x / 2) - ((float)320 / 2), 64, 320, 128 }));
|
((float)ctx.resolution.x / 2) - ((float)320 / 2), 64, 320, 128 }));
|
||||||
|
|
||||||
/* draw the tick count as an example of dynamic text */
|
/* draw the tick count as an example of dynamic text */
|
||||||
size_t text_str_len = snprintf(NULL, 0, "%llu", state->ctx->frame_number) + 1;
|
size_t text_str_len = snprintf(NULL, 0, "%llu", (unsigned long long)state->ctx->frame_number) + 1;
|
||||||
char *text_str = cmalloc(text_str_len);
|
char *text_str = cmalloc(text_str_len);
|
||||||
snprintf(text_str, text_str_len, "%llu", state->ctx->frame_number);
|
snprintf(text_str, text_str_len, "%llu", (unsigned long long)state->ctx->frame_number);
|
||||||
|
|
||||||
const char *font = "/fonts/kenney-pixel.ttf";
|
const char *font = "/fonts/kenney-pixel.ttf";
|
||||||
int text_h = 32;
|
float text_h = 32;
|
||||||
int text_w = draw_text_width(text_str, text_h, font);
|
float text_w = draw_text_width(text_str, text_h, font);
|
||||||
|
|
||||||
draw_rectangle(
|
draw_rectangle(
|
||||||
(Rect) {
|
(Rect) {
|
||||||
.x = 0,
|
.x = 0,
|
||||||
.y = 0,
|
.y = 0,
|
||||||
.w = (float)text_w,
|
.w = text_w,
|
||||||
.h = (float)text_h,
|
.h = text_h,
|
||||||
},
|
},
|
||||||
(Color) { 0, 0, 0, 255 }
|
(Color) { 0, 0, 0, 255 }
|
||||||
);
|
);
|
||||||
|
16
apps/examples/circle-raster/CMakeLists.txt
Normal file
16
apps/examples/circle-raster/CMakeLists.txt
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.21)
|
||||||
|
|
||||||
|
project(circle-raster LANGUAGES C)
|
||||||
|
|
||||||
|
if(NOT CMAKE_BUILD_TYPE)
|
||||||
|
set(CMAKE_BUILD_TYPE Debug)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build)
|
||||||
|
|
||||||
|
set(SOURCE_FILES
|
||||||
|
game.c
|
||||||
|
state.h
|
||||||
|
)
|
||||||
|
|
||||||
|
use_townengine(${PROJECT_NAME} "${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR})
|
26
apps/examples/circle-raster/data/twn.toml
Normal file
26
apps/examples/circle-raster/data/twn.toml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# This file contains everything about the engine and your game that can be
|
||||||
|
# configured before it runs.
|
||||||
|
#
|
||||||
|
# Optional settings are commented out, with their default values shown.
|
||||||
|
# Invalid values in these settings will be ignored.
|
||||||
|
|
||||||
|
# Data about your game as an application
|
||||||
|
[about]
|
||||||
|
title = "Template"
|
||||||
|
developer = "You"
|
||||||
|
app_id = "template"
|
||||||
|
dev_id = "you"
|
||||||
|
|
||||||
|
# Game runtime details
|
||||||
|
[game]
|
||||||
|
resolution = [ 640, 480 ]
|
||||||
|
#debug = true
|
||||||
|
|
||||||
|
# Engine tweaks. You probably don't need to change these
|
||||||
|
[engine]
|
||||||
|
#ticks_per_second = 60 # minimum of 8
|
||||||
|
#keybind_slots = 3 # minimum of 1
|
||||||
|
#texture_atlas_size = 2048 # minimum of 32
|
||||||
|
#font_texture_size = 2048 # minimum of 1024
|
||||||
|
#font_oversampling = 4 # minimum of 0
|
||||||
|
#font_filtering = "linear" # possible values: "nearest", "linear"
|
126
apps/examples/circle-raster/game.c
Normal file
126
apps/examples/circle-raster/game.c
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
#include "twn_game_api.h"
|
||||||
|
#include "state.h"
|
||||||
|
|
||||||
|
#include <malloc.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
|
||||||
|
/* Emits `x` and `y` for every intersecting cell */
|
||||||
|
/* We snap position to the nearest corner, which means there's no aliasing */
|
||||||
|
/* It works great for integer radii */
|
||||||
|
#define m_iter_circle_pixels(p_center_x, p_center_y, p_radius) \
|
||||||
|
for (float y = (p_center_y + ceilf(p_radius)) - 1; y > (p_center_y - ceilf(p_radius)) - 1; --y) \
|
||||||
|
for (float x = p_center_x - ceilf(sqrtf(p_radius * p_radius - (y - p_center_y + (y <= p_center_y)) * (y - p_center_y + (y <= p_center_y)))); x < p_center_x + ceilf(sqrtf(p_radius * p_radius - (y - p_center_y + (y <= p_center_y)) * (y - p_center_y + (y <= p_center_y)))); ++x)
|
||||||
|
|
||||||
|
|
||||||
|
static int32_t ceil_sqrt(int32_t const n) {
|
||||||
|
int32_t res = 1;
|
||||||
|
#pragma clang loop unroll_count(8)
|
||||||
|
while(res * res < n)
|
||||||
|
res++;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void benchmark(struct state *state) {
|
||||||
|
volatile float x, y;
|
||||||
|
|
||||||
|
profile_start("float");
|
||||||
|
|
||||||
|
for (int i = 0; i < 1000; ++i) {
|
||||||
|
float const rs = state->r * state->r;
|
||||||
|
float const cr = ceilf(state->r);
|
||||||
|
for (float iy = -cr; iy <= cr - 1; ++iy) {
|
||||||
|
float const dx = ceilf(sqrtf(rs - (iy + (iy <= 0)) * (iy + (iy <= 0))));
|
||||||
|
for (float ix = -dx; ix < dx; ++ix) {
|
||||||
|
x = ix;
|
||||||
|
y = iy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
profile_end("float");
|
||||||
|
|
||||||
|
profile_start("int32_t");
|
||||||
|
|
||||||
|
for (int i = 0; i < 1000; ++i) {
|
||||||
|
int32_t const rsi = (int32_t)state->r * (int32_t)state->r;
|
||||||
|
for (int32_t iy = -(int32_t)state->r; iy <= (int32_t)state->r - 1; ++iy) {
|
||||||
|
int32_t const dx = ceil_sqrt(rsi - (iy + (iy <= 0)) * (iy + (iy <= 0)));
|
||||||
|
for (int32_t ix = -dx; ix < dx; ++ix) {
|
||||||
|
x = (float)ix;
|
||||||
|
y = (float)iy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
profile_end("int32_t");
|
||||||
|
|
||||||
|
profile_start("int32_t acc");
|
||||||
|
|
||||||
|
for (int i = 0; i < 1000; ++i) {
|
||||||
|
int32_t const rsi = (int32_t)state->r * (int32_t)state->r;
|
||||||
|
int32_t acc = 1;
|
||||||
|
for (int32_t iy = (int32_t)state->r - 1; iy >= 0; --iy) {
|
||||||
|
while (acc * acc < rsi - iy * iy) acc++;
|
||||||
|
for (int32_t ix = -acc; ix < acc; ++ix) {
|
||||||
|
/* lower portion */
|
||||||
|
x = (float)ix;
|
||||||
|
y = (float)iy;
|
||||||
|
/* upper portion */
|
||||||
|
x = (float)ix;
|
||||||
|
y = (float)-iy - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
profile_end("int32_t acc");
|
||||||
|
|
||||||
|
(void)x; (void)y;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void game_tick(void) {
|
||||||
|
if (ctx.initialization_needed) {
|
||||||
|
if (!ctx.udata) {
|
||||||
|
ctx.udata = ccalloc(1, sizeof (struct state));
|
||||||
|
struct state *state = ctx.udata;
|
||||||
|
state->r = 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct state *state = ctx.udata;
|
||||||
|
|
||||||
|
Vec2 const mouse_snap = {floorf(ctx.mouse_position.x / 8) * 8, floorf(ctx.mouse_position.y / 8) * 8};
|
||||||
|
|
||||||
|
input_action("up", CONTROL_LEFT_MOUSE);
|
||||||
|
input_action("down", CONTROL_RIGHT_MOUSE);
|
||||||
|
|
||||||
|
if (input_action_just_pressed("up"))
|
||||||
|
state->r += 1;
|
||||||
|
if (input_action_just_pressed("down"))
|
||||||
|
state->r -= 1;
|
||||||
|
|
||||||
|
int32_t const rsi = (int32_t)state->r * (int32_t)state->r;
|
||||||
|
int32_t acc = 1;
|
||||||
|
for (int32_t iy = (int32_t)state->r - 1; iy >= 0; --iy) {
|
||||||
|
while (acc * acc < rsi - iy * iy) acc++;
|
||||||
|
for (int32_t ix = -acc; ix < acc; ++ix) {
|
||||||
|
/* lower portion */
|
||||||
|
draw_box((Rect){mouse_snap.x + (float)ix * 8, mouse_snap.y + (float)iy * 8, 8, 8}, 1, (Color){125, 125, 0, 255});
|
||||||
|
/* upper portion */
|
||||||
|
draw_box((Rect){mouse_snap.x + (float)ix * 8, mouse_snap.y + (float)(-iy - 1) * 8, 8, 8}, 1, (Color){125, 125, 0, 255});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_circle(mouse_snap, state->r * 8, (Color){125, 125, 125, 125});
|
||||||
|
|
||||||
|
benchmark(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void game_end(void) {
|
||||||
|
/* do your deinitialization here */
|
||||||
|
struct state *state = ctx.udata;
|
||||||
|
free(state);
|
||||||
|
}
|
11
apps/examples/circle-raster/state.h
Normal file
11
apps/examples/circle-raster/state.h
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#ifndef STATE_H
|
||||||
|
#define STATE_H
|
||||||
|
|
||||||
|
#include "twn_game_api.h"
|
||||||
|
|
||||||
|
struct state {
|
||||||
|
float r;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
@ -6,7 +6,7 @@ if(NOT CMAKE_BUILD_TYPE)
|
|||||||
set(CMAKE_BUILD_TYPE Debug)
|
set(CMAKE_BUILD_TYPE Debug)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNBUILDDIR})
|
add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build)
|
||||||
|
|
||||||
set(SOURCE_FILES
|
set(SOURCE_FILES
|
||||||
game.c
|
game.c
|
||||||
|
@ -13,8 +13,7 @@ dev_id = "you"
|
|||||||
|
|
||||||
# Game runtime details
|
# Game runtime details
|
||||||
[game]
|
[game]
|
||||||
base_render_width = 640
|
resolution = [ 640, 480 ]
|
||||||
base_render_height = 480
|
|
||||||
#debug = true
|
#debug = true
|
||||||
|
|
||||||
# Engine tweaks. You probably don't need to change these
|
# Engine tweaks. You probably don't need to change these
|
||||||
|
1
apps/twnlua/.gitignore
vendored
Normal file
1
apps/twnlua/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
luabind.c
|
@ -6,12 +6,21 @@ if(NOT CMAKE_BUILD_TYPE)
|
|||||||
set(CMAKE_BUILD_TYPE Debug)
|
set(CMAKE_BUILD_TYPE Debug)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNBUILDDIR})
|
add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build)
|
||||||
|
|
||||||
|
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 $ENV{TWNROOT}/share/twn_api.json
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
set(SOURCE_FILES
|
set(SOURCE_FILES
|
||||||
game.c
|
game.c
|
||||||
state.h
|
state.h
|
||||||
|
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/luabind.c
|
||||||
|
|
||||||
lua/src/lapi.c
|
lua/src/lapi.c
|
||||||
lua/src/lapi.h
|
lua/src/lapi.h
|
||||||
lua/src/lauxlib.c
|
lua/src/lauxlib.c
|
||||||
@ -74,4 +83,4 @@ set(SOURCE_FILES
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
use_townengine(${PROJECT_NAME} "${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR})
|
use_townengine(${PROJECT_NAME} "${SOURCE_FILES}" ${CMAKE_SOURCE_DIR})
|
||||||
|
211
apps/twnlua/bindgen.py
Executable file
211
apps/twnlua/bindgen.py
Executable file
@ -0,0 +1,211 @@
|
|||||||
|
#!/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"])
|
||||||
|
|
||||||
|
|
||||||
|
def to_table(typedesc, variable, indent = 0):
|
||||||
|
binding = ' ' * indent + "lua_createtable(L, 0, %i);\n" % len(typedesc["fields"])
|
||||||
|
for field in typedesc["fields"]:
|
||||||
|
if field["type"] == "float" or field["type"] == "uint8_t":
|
||||||
|
binding += ' ' * indent + "lua_pushnumber(L, (double)(%s));\n" % (variable + ".%s" % field["name"])
|
||||||
|
elif field["type"] == "bool":
|
||||||
|
binding += ' ' * indent + "lua_pushboolean(L, (%s));\n" % (variable + ".%s" % field["name"])
|
||||||
|
elif field["type"] in api["types"]:
|
||||||
|
binding += to_table(api["types"][field["type"]], variable + ".%s" % field["name"], indent + 4)
|
||||||
|
else:
|
||||||
|
raise BaseException("Unhandled return field type '%s'" % (field["type"]))
|
||||||
|
binding += ' ' * indent + "lua_setfield(L, -2, \"%s\");\n" % field["name"]
|
||||||
|
return binding
|
||||||
|
|
||||||
|
|
||||||
|
def from_table(typedesc, variable, indent = 0):
|
||||||
|
binding = ""
|
||||||
|
for field in typedesc["fields"]:
|
||||||
|
binding += ' ' * indent + "lua_getfield(L, -1, \"%s\");\n" % field["name"]
|
||||||
|
if field["type"] == "float" or field["type"] == "uint8_t":
|
||||||
|
binding += ' ' * indent + "%s = (%s)lua_tonumber(L, -1);\n" % (variable + ".%s" % field["name"], field["type"])
|
||||||
|
elif field["type"] == "bool":
|
||||||
|
binding += ' ' * indent + "%s = lua_toboolean(L, -1);\n" % (variable + ".%s" % field["name"])
|
||||||
|
elif field["type"] in api["types"]:
|
||||||
|
binding += from_table(api["types"][field["type"]], variable + ".%s" % field["name"], indent + 4)
|
||||||
|
else:
|
||||||
|
raise BaseException("Unhandled return field type '%s'" % (field["type"]))
|
||||||
|
binding += ' ' * indent + "lua_pop(L, 1);\n"
|
||||||
|
return binding
|
||||||
|
|
||||||
|
|
||||||
|
print('#include "twn_game_api.h"\n')
|
||||||
|
# TODO: reuse implementation from the engine, this also breaks with statically compiled build
|
||||||
|
print('#define STB_DS_IMPLEMENTATION')
|
||||||
|
print('#include <stb_ds.h>')
|
||||||
|
print('#include <lua.h>')
|
||||||
|
print('#include <lualib.h>')
|
||||||
|
print('#include <lauxlib.h>\n')
|
||||||
|
|
||||||
|
bindings, used_converters = [], {}
|
||||||
|
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("*") and not parameter["type"] == "char *":
|
||||||
|
binding += " %s %s_value;\n" % (basetype, parameter["name"])
|
||||||
|
binding += " %s %s;\n" % (parameter["type"] if not parameter["type"].endswith("*") else 'const ' + parameter["type"], parameter["name"])
|
||||||
|
binding += " lua_getfield(L, 1, \"%s\");\n" % parameter["name"]
|
||||||
|
|
||||||
|
if "default" in parameter and parameter["type"] != "float":
|
||||||
|
binding += " if (lua_isnoneornil(L, -1))\n"
|
||||||
|
binding += " %s = %s;\n" % (parameter["name"], default(parameter))
|
||||||
|
binding += " else\n "
|
||||||
|
|
||||||
|
if parameter["type"] == "float":
|
||||||
|
if "default" in parameter:
|
||||||
|
binding += " int is_%s_num;\n" % parameter["name"]
|
||||||
|
binding += " %s = (float)lua_tonumberx(L, -1, &is_%s_num);\n" % (parameter["name"], parameter["name"]);
|
||||||
|
binding += " if (!is_%s_num) %s = %s;\n" % (parameter["name"], parameter["name"], default(parameter))
|
||||||
|
else:
|
||||||
|
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"]:
|
||||||
|
used_converters[basetype] = api["types"][basetype]
|
||||||
|
if parameter["type"].endswith(" *"):
|
||||||
|
binding += " { %s_value = to_%s(L); %s = &%s_value; }\n" % (parameter["name"], basetype.lower(), parameter["name"], parameter["name"]);
|
||||||
|
else:
|
||||||
|
binding += " %s = to_%s(L);\n" % (parameter["name"], basetype.lower());
|
||||||
|
else:
|
||||||
|
raise BaseException("Unhandled parameter type '%s'" % (parameter["type"]))
|
||||||
|
|
||||||
|
if "return" in procedure_desc:
|
||||||
|
if procedure_desc["return"] == "bool":
|
||||||
|
binding += " lua_pushboolean(L, (int)(%s(%s)));\n" % (procedure, ", ".join(param["name"] for param in procedure_desc["params"]))
|
||||||
|
elif procedure_desc["return"] == "float":
|
||||||
|
binding += " lua_pushnumber(L, (double)(%s(%s)));\n" % (procedure, ", ".join(param["name"] for param in procedure_desc["params"]))
|
||||||
|
elif procedure_desc["return"] == "char *":
|
||||||
|
binding += " lua_pushstring(L, %s(%s));\n" % (procedure, ", ".join(param["name"] for param in procedure_desc["params"]))
|
||||||
|
elif type(procedure_desc["return"]) is dict or procedure_desc["return"] in api["types"]:
|
||||||
|
# TODO: handle enums
|
||||||
|
type_desc = procedure_desc["return"] if type(procedure_desc["return"]) is dict else api["types"][procedure_desc["return"]]
|
||||||
|
binding += " %s result = %s(%s);\n" % (type_desc["c_type"], procedure, ", ".join(param["name"] for param in procedure_desc["params"]))
|
||||||
|
binding += to_table(type_desc, "result", 4)
|
||||||
|
else:
|
||||||
|
raise BaseException("Unhandled return type '%s'" % (procedure_desc["return"]))
|
||||||
|
binding += " return 1;\n}\n"
|
||||||
|
else:
|
||||||
|
binding += " %s(%s);\n" % (procedure, ", ".join(param["name"] for param in procedure_desc["params"]))
|
||||||
|
binding += " return 0;\n}\n"
|
||||||
|
|
||||||
|
bindings += [binding]
|
||||||
|
|
||||||
|
|
||||||
|
storages, converters, initializers, deinitializers = [], [], [], []
|
||||||
|
|
||||||
|
for typename, typedesc in used_converters.items():
|
||||||
|
if "no_convert" in typedesc and typedesc["no_convert"]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
converter = "static %s to_%s(lua_State *L) {\n" % (typename, typename.lower())
|
||||||
|
converter += " %s %s;\n" % (typename, typename.lower());
|
||||||
|
|
||||||
|
if "fields" in typedesc:
|
||||||
|
for field in typedesc["fields"]:
|
||||||
|
converter += " lua_getfield(L, -1, \"%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";
|
||||||
|
|
||||||
|
# TODO: wild idea: use compile time built hash table
|
||||||
|
elif "enums" in typedesc:
|
||||||
|
storages += ["struct %sHashItem { char *key; %s value; };\nstatic struct %sHashItem *%s_map = NULL;\n" % (typename, typename, typename, typename.lower())]
|
||||||
|
|
||||||
|
# TODO: use arena
|
||||||
|
for enum in typedesc["enums"]:
|
||||||
|
initializer = " shput(%s_map, \"%s\", %s);" % (typename.lower(), enum, typedesc["enums"][enum])
|
||||||
|
initializers += [initializer]
|
||||||
|
|
||||||
|
deinitializers += [" shfree(%s_map);" % typename.lower()]
|
||||||
|
|
||||||
|
converter += " char const *value = lua_tostring(L, -1);\n";
|
||||||
|
converter += " %s = shget(%s_map, value);\n" % (typename.lower(), typename.lower())
|
||||||
|
converter += " lua_pop(L, 1);\n";
|
||||||
|
|
||||||
|
converter += " return %s;\n}\n" % (typename.lower())
|
||||||
|
converters += [converter]
|
||||||
|
|
||||||
|
|
||||||
|
print('\n'.join(storages))
|
||||||
|
print("extern void bindgen_init(void);\n")
|
||||||
|
print("void bindgen_init(void) {\n" + '\n'.join(initializers) + "\n}\n")
|
||||||
|
print('\n'.join(converters))
|
||||||
|
print('\n'.join(bindings))
|
||||||
|
|
||||||
|
|
||||||
|
loader = "extern void bindgen_load_%s(lua_State *L);\n" % api["name"]
|
||||||
|
loader += "void bindgen_load_%s(lua_State *L) {\n" % api["name"]
|
||||||
|
loader += " bindgen_init();\n"
|
||||||
|
for procedure, procedure_desc in api["procedures"].items():
|
||||||
|
loader += " lua_pushcfunction(L, binding_%s);\n" % procedure
|
||||||
|
loader += " lua_setglobal(L, \"%s\");\n" % procedure
|
||||||
|
|
||||||
|
loader += "}\n"
|
||||||
|
print(loader)
|
||||||
|
|
||||||
|
|
||||||
|
unloader = "extern void bindgen_unload_%s(lua_State *L);\n" % api["name"]
|
||||||
|
unloader += "void bindgen_unload_%s(lua_State *L) {\n (void)L;\n" % api["name"]
|
||||||
|
unloader += '\n'.join(deinitializers)
|
||||||
|
unloader += "\n}\n"
|
||||||
|
print(unloader)
|
||||||
|
|
||||||
|
|
||||||
|
# exceptions for the base townengine api
|
||||||
|
# TODO: is there a way to generalize it? or rather, is there any need to do so?
|
||||||
|
if api["name"] == "twn":
|
||||||
|
contexter = "extern void bindgen_build_context(lua_State *L);\n"
|
||||||
|
contexter += "void bindgen_build_context(lua_State *L) {\n"
|
||||||
|
contexter += to_table(api["types"]["Context"], "ctx", 4)
|
||||||
|
contexter += "}\n\n"
|
||||||
|
|
||||||
|
contexter += "extern void bindgen_upload_context(lua_State *L);\n"
|
||||||
|
contexter += "void bindgen_upload_context(lua_State *L) {\n"
|
||||||
|
contexter += from_table(api["types"]["Context"], "ctx", 4)
|
||||||
|
contexter += "}"
|
||||||
|
|
||||||
|
print(contexter)
|
@ -4,13 +4,18 @@ offset = { x = 0, y = 0 }
|
|||||||
angle = 0
|
angle = 0
|
||||||
|
|
||||||
function game_tick()
|
function game_tick()
|
||||||
rectangle {
|
input_action {
|
||||||
|
name = "press",
|
||||||
|
control = "A"
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_rectangle {
|
||||||
rect = { x = 0, y = 0, w = 640, h = 360 },
|
rect = { x = 0, y = 0, w = 640, h = 360 },
|
||||||
color = { r = 127, g = 0, b = 127, a = 255 },
|
color = { r = 127, g = 0, b = 127, a = 255 },
|
||||||
}
|
}
|
||||||
|
|
||||||
sprite {
|
draw_sprite {
|
||||||
path = "/assets/title.png",
|
texture = "/assets/title.png",
|
||||||
rect = {
|
rect = {
|
||||||
x = 320 - (320 / 2),
|
x = 320 - (320 / 2),
|
||||||
y = 180 - (128 / 2),
|
y = 180 - (128 / 2),
|
||||||
@ -19,11 +24,13 @@ function game_tick()
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
text {
|
if input_action_pressed { name = "press" } then
|
||||||
string = "IT KEEPS HAPPENING",
|
draw_text {
|
||||||
|
string = "it never happened",
|
||||||
position = offset,
|
position = offset,
|
||||||
font = "/fonts/kenney-pixel.ttf",
|
font = "/fonts/kenney-pixel.ttf",
|
||||||
}
|
}
|
||||||
|
end
|
||||||
|
|
||||||
offset.x = ORIGIN.x + (math.cos(angle) * RADIUS)
|
offset.x = ORIGIN.x + (math.cos(angle) * RADIUS)
|
||||||
offset.y = ORIGIN.y + (math.sin(angle) * RADIUS)
|
offset.y = ORIGIN.y + (math.sin(angle) * RADIUS)
|
||||||
|
@ -5,7 +5,6 @@ app_id = "twnlua"
|
|||||||
dev_id = "somebody"
|
dev_id = "somebody"
|
||||||
|
|
||||||
[game]
|
[game]
|
||||||
base_render_width = 640
|
resolution = [ 640, 360 ]
|
||||||
base_render_height = 360
|
|
||||||
|
|
||||||
[engine]
|
[engine]
|
||||||
|
@ -10,6 +10,13 @@
|
|||||||
#include <malloc.h>
|
#include <malloc.h>
|
||||||
|
|
||||||
|
|
||||||
|
/* generated by bindgen.py */
|
||||||
|
void bindgen_load_twn(lua_State *L);
|
||||||
|
void bindgen_unload_twn(lua_State *L);
|
||||||
|
void bindgen_build_context(lua_State *L);
|
||||||
|
void bindgen_upload_context(lua_State *L);
|
||||||
|
|
||||||
|
|
||||||
/* require will go through physicsfs exclusively so that scripts can be in the data dir */
|
/* require will go through physicsfs exclusively so that scripts can be in the data dir */
|
||||||
static int physfs_loader(lua_State *L) {
|
static int physfs_loader(lua_State *L) {
|
||||||
const char *name = luaL_checkstring(L, 1);
|
const char *name = luaL_checkstring(L, 1);
|
||||||
@ -37,198 +44,46 @@ static int physfs_loader(lua_State *L) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static Rect table_to_rect(lua_State *L, int idx, const char *name) {
|
/* WARN! experimental and will probably be removed */
|
||||||
/* types are checked here to help prevent unexpected results */
|
/* it is an attempt to ease memory usage problems posed by using lua, partially successful */
|
||||||
Rect rect;
|
static void *custom_alloc(void *ud, void *ptr, size_t osize, size_t nsize) {
|
||||||
int is_num;
|
(void)ud;
|
||||||
|
|
||||||
lua_getfield(L, idx, "x");
|
/* small allocations are placed in slots, as there's a big chance they will not need to be resized */
|
||||||
rect.x = (float)lua_tonumberx(L, -1, &is_num);
|
static char slots[1024][128];
|
||||||
if (!is_num)
|
static int16_t free_slots[1024] = { [0] = -1 };
|
||||||
luaL_error(L, "bad field 'x' in '%s' (number expected, got %s)", name, luaL_typename(L, -1));
|
static size_t free_slot_count = 1024;
|
||||||
lua_pop(L, 1);
|
|
||||||
|
|
||||||
lua_getfield(L, idx, "y");
|
if (free_slots[0] == -1)
|
||||||
rect.y = (float)lua_tonumberx(L, -1, &is_num);
|
for (int i = 0; i < 1024; i++)
|
||||||
if (!is_num)
|
free_slots[i] = (int16_t)i;
|
||||||
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");
|
if (nsize == 0) {
|
||||||
rect.w = (float)lua_tonumberx(L, -1, &is_num);
|
if (ptr && (char *)ptr >= &slots[0][0] && (char *)ptr <= &slots[1024-1][128-1])
|
||||||
if (!is_num)
|
free_slots[free_slot_count++] = (int16_t)(((uintptr_t)ptr - (uintptr_t)slots) / 128);
|
||||||
luaL_error(L, "bad field 'w' in '%s' (number expected, got %s)", name, luaL_typename(L, -1));
|
else if (osize)
|
||||||
lua_pop(L, 1);
|
SDL_free(ptr);
|
||||||
|
return NULL;
|
||||||
lua_getfield(L, idx, "h");
|
} else {
|
||||||
rect.h = (float)lua_tonumberx(L, -1, &is_num);
|
if (!ptr && nsize <= 128 && free_slot_count > 0) {
|
||||||
if (!is_num)
|
/* use a slot */
|
||||||
luaL_error(L, "bad field 'h' in '%s' (number expected, got %s)", name, luaL_typename(L, -1));
|
return slots[free_slots[--free_slot_count]];
|
||||||
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 ((char *)ptr >= &slots[0][0] && (char *)ptr <= &slots[1024-1][128-1]) {
|
||||||
if (lua_istable(L, -1)) {
|
/* still fits */
|
||||||
args.color_opt = table_to_color(L, -1);
|
if (nsize <= 128)
|
||||||
args.color_opt_set = true;
|
return ptr;
|
||||||
|
|
||||||
|
/* move from slot to dynamic memory */
|
||||||
|
void *mem = SDL_malloc(nsize);
|
||||||
|
SDL_memcpy(mem, ptr, osize);
|
||||||
|
free_slots[free_slot_count++] = (int16_t)(((uintptr_t)ptr - (uintptr_t)slots) / 128);
|
||||||
|
return mem;
|
||||||
}
|
}
|
||||||
|
|
||||||
lua_getfield(L, 1, "rotation");
|
return SDL_realloc(ptr, nsize);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -246,14 +101,15 @@ void game_tick(void) {
|
|||||||
}
|
}
|
||||||
state->L = luaL_newstate();
|
state->L = luaL_newstate();
|
||||||
|
|
||||||
/* fakey version of luaL_openlibs() that excludes file i/o */
|
lua_setallocf(state->L, custom_alloc, NULL);
|
||||||
|
|
||||||
|
/* fakey version of luaL_openlibs() that excludes file i/o and os stuff */
|
||||||
{
|
{
|
||||||
static const luaL_Reg loaded_libs[] = {
|
static const luaL_Reg loaded_libs[] = {
|
||||||
{ LUA_GNAME, luaopen_base },
|
{ LUA_GNAME, luaopen_base },
|
||||||
{ LUA_LOADLIBNAME, luaopen_package },
|
{ LUA_LOADLIBNAME, luaopen_package },
|
||||||
{ LUA_COLIBNAME, luaopen_coroutine },
|
{ LUA_COLIBNAME, luaopen_coroutine },
|
||||||
{ LUA_TABLIBNAME, luaopen_table },
|
{ LUA_TABLIBNAME, luaopen_table },
|
||||||
{ LUA_OSLIBNAME, luaopen_os },
|
|
||||||
{ LUA_STRLIBNAME, luaopen_string },
|
{ LUA_STRLIBNAME, luaopen_string },
|
||||||
{ LUA_MATHLIBNAME, luaopen_math },
|
{ LUA_MATHLIBNAME, luaopen_math },
|
||||||
{ LUA_UTF8LIBNAME, luaopen_utf8 },
|
{ LUA_UTF8LIBNAME, luaopen_utf8 },
|
||||||
@ -269,7 +125,7 @@ void game_tick(void) {
|
|||||||
|
|
||||||
/* package.searchers = { physfs_loader } */
|
/* package.searchers = { physfs_loader } */
|
||||||
lua_getglobal(state->L, "package");
|
lua_getglobal(state->L, "package");
|
||||||
lua_newtable(state->L);
|
lua_createtable(state->L, 0, 1);
|
||||||
lua_setfield(state->L, -2, "searchers");
|
lua_setfield(state->L, -2, "searchers");
|
||||||
|
|
||||||
lua_getfield(state->L, -1, "searchers");
|
lua_getfield(state->L, -1, "searchers");
|
||||||
@ -280,9 +136,11 @@ void game_tick(void) {
|
|||||||
lua_pop(state->L, 2);
|
lua_pop(state->L, 2);
|
||||||
|
|
||||||
/* binding */
|
/* binding */
|
||||||
lua_register(state->L, "sprite", b_sprite);
|
// lua_register(state->L, "sprite", b_sprite);
|
||||||
lua_register(state->L, "rectangle", b_rectangle);
|
// lua_register(state->L, "rectangle", b_rectangle);
|
||||||
lua_register(state->L, "text", b_text);
|
// lua_register(state->L, "text", b_text);
|
||||||
|
|
||||||
|
bindgen_load_twn(state->L);
|
||||||
|
|
||||||
/* now finally get to running the code */
|
/* now finally get to running the code */
|
||||||
unsigned char *game_buf = NULL;
|
unsigned char *game_buf = NULL;
|
||||||
@ -299,16 +157,29 @@ void game_tick(void) {
|
|||||||
|
|
||||||
State *state = ctx.udata;
|
State *state = ctx.udata;
|
||||||
|
|
||||||
|
bindgen_build_context(state->L);
|
||||||
|
lua_getglobal(state->L, "ctx");
|
||||||
|
if (!lua_isnoneornil(state->L, -1)) {
|
||||||
|
lua_getfield(state->L, -1, "udata");
|
||||||
|
lua_setfield(state->L, -3, "udata");
|
||||||
|
}
|
||||||
|
lua_pop(state->L, 1);
|
||||||
|
lua_setglobal(state->L, "ctx");
|
||||||
|
|
||||||
lua_getglobal(state->L, "game_tick");
|
lua_getglobal(state->L, "game_tick");
|
||||||
if (lua_pcall(state->L, 0, 0, 0) != LUA_OK) {
|
if (lua_pcall(state->L, 0, 0, 0) != LUA_OK) {
|
||||||
log_critical("%s", lua_tostring(state->L, -1));
|
log_critical("%s", lua_tostring(state->L, -1));
|
||||||
lua_pop(state->L, 1);
|
lua_pop(state->L, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lua_getglobal(state->L, "ctx");
|
||||||
|
bindgen_upload_context(state->L);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void game_end(void) {
|
void game_end(void) {
|
||||||
State *state = ctx.udata;
|
State *state = ctx.udata;
|
||||||
|
bindgen_unload_twn(state->L);
|
||||||
lua_close(state->L);
|
lua_close(state->L);
|
||||||
free(state);
|
free(state);
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
#ifndef STATE_H
|
#ifndef STATE_H
|
||||||
#define STATE_H
|
#define STATE_H
|
||||||
|
|
||||||
#include "twn_game_api.h"
|
|
||||||
|
|
||||||
#include <lua.h>
|
#include <lua.h>
|
||||||
|
|
||||||
|
|
||||||
|
15
bin/build.sh
15
bin/build.sh
@ -1,12 +1,19 @@
|
|||||||
#!/bin/env sh
|
#!/bin/env sh
|
||||||
|
|
||||||
|
set +e
|
||||||
|
|
||||||
# check whether ninja is around (you better start running)
|
# check whether ninja is around (you better start running)
|
||||||
if [ -x "$(command -v ninja)" ]; then
|
if [ -x "$(command -v ninja)" ]; then
|
||||||
generator="-G Ninja"
|
generator="-G Ninja"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$1" = "web" ]; then
|
# check whether clang is around (it's just better)
|
||||||
emcmake cmake $generator -B .build-web "${@:2}" && cmake --build .build-web --parallel
|
if [ -x "$(command -v clang)" ]; then
|
||||||
else
|
cc="-DCMAKE_C_COMPILER=clang"
|
||||||
cmake $generator -B .build "$@" && cmake --build .build --parallel
|
fi
|
||||||
|
|
||||||
|
if [ "$1" = "web" ]; then
|
||||||
|
emcmake cmake $generator $cc -B build-web "${@:2}" && cmake --build build-web --parallel
|
||||||
|
else
|
||||||
|
cmake $generator $cc -B build "$@" && cmake --build build --parallel
|
||||||
fi
|
fi
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
#!/bin/env sh
|
#!/bin/env sh
|
||||||
# single header api generator with clang
|
# single header api generator with clang
|
||||||
|
|
||||||
|
set +e
|
||||||
|
|
||||||
clang -I./ -P -E -nostdinc -nobuiltininc -DTWN_NOT_C $TWNROOT/include/twn_game_api.h 2> /dev/null | clang-format
|
clang -I./ -P -E -nostdinc -nobuiltininc -DTWN_NOT_C $TWNROOT/include/twn_game_api.h 2> /dev/null | clang-format
|
||||||
|
1
bin/twn
1
bin/twn
@ -6,7 +6,6 @@ set +e
|
|||||||
exe="$(basename $PWD)"
|
exe="$(basename $PWD)"
|
||||||
toolpath="$(dirname -- "${BASH_SOURCE[0]}")"
|
toolpath="$(dirname -- "${BASH_SOURCE[0]}")"
|
||||||
export TWNROOT=$(realpath "$toolpath"/../)
|
export TWNROOT=$(realpath "$toolpath"/../)
|
||||||
export TWNBUILDDIR=$(realpath "$toolpath"/../.build)
|
|
||||||
|
|
||||||
case "$1" in
|
case "$1" in
|
||||||
build ) "$toolpath"/build.sh "${@:2}"
|
build ) "$toolpath"/build.sh "${@:2}"
|
||||||
|
BIN
common-data/assets/grasses/10.png
(Stored with Git LFS)
Normal file
BIN
common-data/assets/grasses/10.png
(Stored with Git LFS)
Normal file
Binary file not shown.
16
docs/interop.md
Normal file
16
docs/interop.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# 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 new additions, ever (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 value
|
||||||
|
* no opaque types, only keys if needed
|
||||||
|
* pointers to pointers aren't allowed
|
||||||
|
* when mutation on input is done, - it shouldn't be achieved by a mutable pointer, but the return value
|
||||||
|
* return value could be a simple aggregate that is translatable to a dictionary of primitives
|
||||||
|
* 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](../share/twn_api.json) file is hand-kept with a schema to aid automatic binding generation and tooling
|
@ -1,10 +0,0 @@
|
|||||||
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
|
|
1
hooks
1
hooks
@ -6,3 +6,4 @@ set +e
|
|||||||
|
|
||||||
# TODO: prevent double hooking
|
# TODO: prevent double hooking
|
||||||
export PATH=$PATH:$(realpath $(dirname -- "${BASH_SOURCE[0]}")/bin/)
|
export PATH=$PATH:$(realpath $(dirname -- "${BASH_SOURCE[0]}")/bin/)
|
||||||
|
export TWNROOT=$(realpath $(dirname -- "${BASH_SOURCE[0]}"))
|
||||||
|
@ -9,19 +9,14 @@
|
|||||||
/* plays audio file at specified channel or at scratch channel if NULL is passed, without ability to refer to it later */
|
/* plays audio file at specified channel or at scratch channel if NULL is passed, without ability to refer to it later */
|
||||||
/* path path must contain valid file extension to infer which file format it is */
|
/* path path must contain valid file extension to infer which file format it is */
|
||||||
/* supported formats: .ogg, .xm */
|
/* supported formats: .ogg, .xm */
|
||||||
TWN_API void audio_play(const char *path,
|
TWN_API void audio_play(const char *audio,
|
||||||
const char *channel, /* optional */
|
const char *channel, /* optional */
|
||||||
bool repeat, /* default: false */
|
bool repeat, /* default: false */
|
||||||
float volume, /* default: 1.0f, range: 0.0f to 1.0f */
|
float volume, /* default: 1.0f, range: 0.0f to 1.0f */
|
||||||
float panning); /* default: 0.0f, range: -1.0 to 1.0f */
|
float panning); /* default: 0.0f, range: -1.0 to 1.0f */
|
||||||
|
|
||||||
typedef enum {
|
/* possible parameter options: "volume", "panning", "repeat" */
|
||||||
AUDIO_PARAM_REPEAT,
|
TWN_API void audio_parameter(const char *channel, const char *parameter, float value);
|
||||||
AUDIO_PARAM_VOLUME,
|
|
||||||
AUDIO_PARAM_PANNING,
|
|
||||||
} AudioParam;
|
|
||||||
|
|
||||||
TWN_API void audio_set(const char *channel, AudioParam param, float value);
|
|
||||||
|
|
||||||
/* TODO */
|
/* TODO */
|
||||||
// TWN_API bool audio_ended(const char *channel);
|
// TWN_API bool audio_ended(const char *channel);
|
||||||
|
@ -16,21 +16,25 @@ typedef struct Context {
|
|||||||
void *udata;
|
void *udata;
|
||||||
|
|
||||||
/* which frame is it, starting from 0 at startup */
|
/* which frame is it, starting from 0 at startup */
|
||||||
uint64_t frame_number;
|
float frame_number;
|
||||||
|
|
||||||
/* real time spent on one frame (in seconds) */
|
/* real time spent on one frame (in seconds) */
|
||||||
/* townengine is fixed step based, so you don't have */
|
/* townengine is fixed step based, so you don't have to use delta */
|
||||||
/* TODO: actually set it */
|
/* TODO: actually set it */
|
||||||
float frame_duration;
|
float frame_duration;
|
||||||
|
|
||||||
|
/* it is disabled by having fog_density approximately equal to zero */
|
||||||
|
float fog_density;
|
||||||
|
Color fog_color;
|
||||||
|
|
||||||
/* resolution is set from config and dictates both logical and drawing space, as they're related */
|
/* 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 */
|
/* even if scaling is done, game logic should never change over that */
|
||||||
Vec2i resolution;
|
Vec2 resolution;
|
||||||
Vec2i mouse_position;
|
Vec2 mouse_position;
|
||||||
Vec2i mouse_movement;
|
Vec2 mouse_movement;
|
||||||
|
|
||||||
/* is set on startup, should be used as source of randomness */
|
/* is set on startup, should be used as source of randomness */
|
||||||
uint64_t random_seed;
|
float random_seed;
|
||||||
|
|
||||||
/* whether debugging logic should be enabled in user code */
|
/* whether debugging logic should be enabled in user code */
|
||||||
bool debug;
|
bool debug;
|
||||||
@ -38,6 +42,7 @@ typedef struct Context {
|
|||||||
/* is set to true when state is invalidated and needs to be rebuilt */
|
/* is set to true when state is invalidated and needs to be rebuilt */
|
||||||
/* watch for it and handle properly! */
|
/* watch for it and handle properly! */
|
||||||
bool initialization_needed;
|
bool initialization_needed;
|
||||||
|
bool mouse_capture;
|
||||||
} Context;
|
} Context;
|
||||||
|
|
||||||
/* when included after twn_engine_context there's an 'ctx' defined already */
|
/* when included after twn_engine_context there's an 'ctx' defined already */
|
||||||
|
@ -3,20 +3,19 @@
|
|||||||
|
|
||||||
#include "twn_types.h"
|
#include "twn_types.h"
|
||||||
#include "twn_option.h"
|
#include "twn_option.h"
|
||||||
#include "twn_camera.h"
|
|
||||||
#include "twn_engine_api.h"
|
#include "twn_engine_api.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
/* pushes a sprite onto the sprite render queue */
|
/* pushes a sprite onto the sprite render queue */
|
||||||
TWN_API void draw_sprite(char const *path,
|
TWN_API void draw_sprite(char const *texture,
|
||||||
Rect rect,
|
Rect rect,
|
||||||
Rect const *texture_region, /* optional, default: NULL */
|
Rect const *texture_region, /* optional, default: NULL */
|
||||||
Color color, /* optional, default: all 255 */
|
Color color, /* optional, default: all 255 */
|
||||||
float rotation, /* optional, default: 0 */
|
float rotation, /* optional, default: 0 */
|
||||||
bool flip_x, /* optional, default: false */
|
bool flip_x, /* optional, default: false */
|
||||||
bool flip_y, /* optional, default: false */
|
bool flip_y, /* optional, default: false */
|
||||||
bool stretch); /* optional, default: false */
|
bool stretch); /* optional, default: true */
|
||||||
|
|
||||||
/* pushes a filled rectangle onto the rectangle render queue */
|
/* pushes a filled rectangle onto the rectangle render queue */
|
||||||
TWN_API void draw_rectangle(Rect rect, Color color);
|
TWN_API void draw_rectangle(Rect rect, Color color);
|
||||||
@ -28,67 +27,81 @@ TWN_API void draw_circle(Vec2 position, float radius, Color color);
|
|||||||
/* TODO: have font optional, with something minimal coming embedded */
|
/* TODO: have font optional, with something minimal coming embedded */
|
||||||
TWN_API void draw_text(char const *string,
|
TWN_API void draw_text(char const *string,
|
||||||
Vec2 position,
|
Vec2 position,
|
||||||
int height_px, /* optional, default: 22 */
|
float height, /* optional, default: 22 */
|
||||||
Color color, /* optional, default: all 0 */
|
Color color, /* optional, default: all 0 */
|
||||||
char const *font);
|
char const *font); /* optional, default: NULL */
|
||||||
|
|
||||||
|
|
||||||
TWN_API int draw_text_width(char const *string,
|
TWN_API float draw_text_width(char const *string,
|
||||||
int height_px, /* TODO: make optional */
|
float height, /* optional, default: 22 */
|
||||||
char const *font);
|
char const *font); /* optional, default: NULL */
|
||||||
|
|
||||||
TWN_API void draw_9slice(char const *texture_path,
|
TWN_API void draw_nine_slice(char const *texture,
|
||||||
int texture_w,
|
Vec2 corners,
|
||||||
int texture_h,
|
|
||||||
int border_thickness,
|
|
||||||
Rect rect,
|
Rect rect,
|
||||||
Color color); /* TODO: make optional */
|
float border_thickness, /* optional, default: 0 */
|
||||||
|
Color color); /* optional, default: all 255 */
|
||||||
|
|
||||||
|
TWN_API void draw_line(Vec2 start,
|
||||||
|
Vec2 finish,
|
||||||
|
float thickness, /* optional, default: 1 */
|
||||||
|
Color color); /* optional, default: all 255 */
|
||||||
|
|
||||||
|
TWN_API void draw_box(Rect rect,
|
||||||
|
float thickness, /* optional, default: 1 */
|
||||||
|
Color color); /* optional, default: all 255 */
|
||||||
|
|
||||||
/* pushes a textured 3d triangle onto the render queue */
|
/* pushes a textured 3d triangle onto the render queue */
|
||||||
/* vertices are in absolute coordinates, relative to world origin */
|
|
||||||
/* texture coordinates are in pixels */
|
/* texture coordinates are in pixels */
|
||||||
TWN_API void draw_triangle(char const *path,
|
TWN_API void draw_triangle(char const *texture,
|
||||||
Vec3 v0,
|
Vec3 v0,
|
||||||
Vec3 v1,
|
Vec3 v1,
|
||||||
Vec3 v2,
|
Vec3 v2,
|
||||||
Vec2 uv0,
|
Vec2 uv0,
|
||||||
Vec2 uv1,
|
Vec2 uv1,
|
||||||
Vec2 uv2);
|
Vec2 uv2,
|
||||||
|
Color c0, /* optional, default: all 255 */
|
||||||
|
Color c1, /* optional, default: all 255 */
|
||||||
|
Color c2); /* optional, default: all 255 */
|
||||||
|
|
||||||
// TODO: decide whether it's needed to begin with?
|
TWN_API void draw_quad(char const *texture,
|
||||||
// intended usage for it is baked lighting, i would think.
|
Vec3 v0, /* upper-left */
|
||||||
/* pushes a colored textured 3d triangle onto the render queue */
|
Vec3 v1, /* bottom-left */
|
||||||
// void unfurl_colored_triangle(const char *path,
|
Vec3 v2, /* bottom-right */
|
||||||
// Vec3 v0,
|
Vec3 v3, /* upper-right */
|
||||||
// Vec3 v1,
|
Rect texture_region,
|
||||||
// Vec3 v2,
|
Color color); /* optional, default: all 255 */
|
||||||
// Vec2sh uv0,
|
|
||||||
// Vec2sh uv1,
|
|
||||||
// Vec2sh uv2,
|
|
||||||
// Color c0,
|
|
||||||
// Color c1,
|
|
||||||
// Color c2);
|
|
||||||
|
|
||||||
// TODO:
|
TWN_API void draw_billboard(const char *texture,
|
||||||
// http://www.lighthouse3d.com/opengl/billboarding/index.php?billCheat2
|
Vec3 position,
|
||||||
// void unfurl_billboard(const char *path,
|
Vec2 size,
|
||||||
// Vec2 position,
|
Color color, /* optional, default: all 255 */
|
||||||
// Vec2 scaling,
|
bool cylindrical); /* optional, default: false */
|
||||||
// Rect uvs);
|
|
||||||
|
|
||||||
/* pushes a camera state to be used for all future unfurl_* commands */
|
/* sets a perspective 3d camera to be used for all 3d commands */
|
||||||
TWN_API void draw_camera(const Camera *camera);
|
TWN_API void draw_camera(Vec3 position, float fov, Vec3 up, Vec3 direction);
|
||||||
|
|
||||||
|
/* same as draw_camera(), but with first person controller in mind */
|
||||||
|
/* 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);
|
||||||
|
|
||||||
/* expects '*' masks that will be expanded to 6 names: 'up', 'down', 'east', 'west', 'north' and 'south' */
|
/* expects '*' masks that will be expanded to 6 names: 'up', 'down', 'east', 'west', 'north' and 'south' */
|
||||||
TWN_API void draw_skybox(const char *paths);
|
TWN_API void draw_skybox(const char *textures);
|
||||||
|
|
||||||
TWN_API void draw_fog(float start, float end, float density, Color color);
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef TWN_NOT_C
|
#ifndef TWN_NOT_C
|
||||||
|
|
||||||
typedef struct DrawSpriteArgs {
|
typedef struct DrawSpriteArgs {
|
||||||
char const *path;
|
char const *texture;
|
||||||
Rect rect;
|
Rect rect;
|
||||||
|
|
||||||
m_option_list(
|
m_option_list(
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
#ifndef TWN_ENGINE_API_H
|
#ifndef TWN_ENGINE_API_H
|
||||||
#define TWN_ENGINE_API_H
|
#define TWN_ENGINE_API_H
|
||||||
|
|
||||||
#if defined(__WIN32)
|
#if defined(TWN_NOT_C)
|
||||||
|
#define TWN_API
|
||||||
|
#elif defined(__WIN32)
|
||||||
#define TWN_API __declspec(dllexport)
|
#define TWN_API __declspec(dllexport)
|
||||||
#else
|
#else
|
||||||
#define TWN_API __attribute__((visibility("default")))
|
#define TWN_API __attribute__((visibility("default")))
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
#define TWN_GAME_API_H
|
#define TWN_GAME_API_H
|
||||||
|
|
||||||
#include "twn_input.h"
|
#include "twn_input.h"
|
||||||
#include "twn_context.h"
|
|
||||||
#include "twn_draw.h"
|
#include "twn_draw.h"
|
||||||
#include "twn_audio.h"
|
#include "twn_audio.h"
|
||||||
#include "twn_engine_api.h"
|
#include "twn_engine_api.h"
|
||||||
#include "twn_util.h"
|
#include "twn_util.h"
|
||||||
|
#include "twn_context.h"
|
||||||
|
|
||||||
#ifndef TWN_NOT_C
|
#ifndef TWN_NOT_C
|
||||||
|
|
||||||
|
@ -10,19 +10,10 @@
|
|||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
|
|
||||||
TWN_API void input_bind_action_control(const char *action_name, Control control);
|
TWN_API void input_action(const char *name, Control control);
|
||||||
TWN_API void input_unbind_action_control(const char *action_name, Control control);
|
TWN_API bool input_action_pressed(const char *name);
|
||||||
|
TWN_API bool input_action_just_pressed(const char *name);
|
||||||
TWN_API void input_add_action(const char *action_name);
|
TWN_API bool input_action_just_released(const char *name);
|
||||||
TWN_API void input_delete_action(const char *action_name);
|
TWN_API Vec2 input_action_position(const char *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
|
#endif
|
||||||
|
@ -9,6 +9,8 @@ typedef enum TextureMode {
|
|||||||
TEXTURE_MODE_OPAQUE, /* all pixels are solid */
|
TEXTURE_MODE_OPAQUE, /* all pixels are solid */
|
||||||
TEXTURE_MODE_SEETHROUGH, /* some pixels are alpha zero */
|
TEXTURE_MODE_SEETHROUGH, /* some pixels are alpha zero */
|
||||||
TEXTURE_MODE_GHOSTLY, /* arbitrary alpha values */
|
TEXTURE_MODE_GHOSTLY, /* arbitrary alpha values */
|
||||||
|
TEXTURE_MODE_COUNT,
|
||||||
|
TEXTURE_MODE_UNKNOWN = -1, /* a sentinel */
|
||||||
} TextureMode;
|
} TextureMode;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -6,13 +6,6 @@
|
|||||||
/* plain data aggregates that are accepted between public procedure boundaries */
|
/* plain data aggregates that are accepted between public procedure boundaries */
|
||||||
|
|
||||||
|
|
||||||
/* a point in some space (integer) */
|
|
||||||
typedef struct Vec2i {
|
|
||||||
int32_t x;
|
|
||||||
int32_t y;
|
|
||||||
} Vec2i;
|
|
||||||
|
|
||||||
|
|
||||||
/* a point in some space (floating point) */
|
/* a point in some space (floating point) */
|
||||||
typedef struct Vec2 {
|
typedef struct Vec2 {
|
||||||
float x;
|
float x;
|
||||||
@ -29,16 +22,6 @@ typedef struct Vec3 {
|
|||||||
} Vec3;
|
} Vec3;
|
||||||
|
|
||||||
|
|
||||||
/* a point in some three dimension space (floating point) */
|
|
||||||
/* y goes up, x goes to the right */
|
|
||||||
typedef struct Vec4 {
|
|
||||||
float x;
|
|
||||||
float y;
|
|
||||||
float z;
|
|
||||||
float w;
|
|
||||||
} Vec4;
|
|
||||||
|
|
||||||
|
|
||||||
/* 32-bit color data */
|
/* 32-bit color data */
|
||||||
typedef struct Color {
|
typedef struct Color {
|
||||||
uint8_t r;
|
uint8_t r;
|
||||||
@ -48,15 +31,6 @@ typedef struct Color {
|
|||||||
} Color;
|
} Color;
|
||||||
|
|
||||||
|
|
||||||
/* a rectangle with the origin at the upper left (integer) */
|
|
||||||
typedef struct Recti {
|
|
||||||
int32_t x;
|
|
||||||
int32_t y;
|
|
||||||
int32_t w;
|
|
||||||
int32_t h;
|
|
||||||
} Recti;
|
|
||||||
|
|
||||||
|
|
||||||
/* a rectangle with the origin at the upper left (floating point) */
|
/* a rectangle with the origin at the upper left (floating point) */
|
||||||
typedef struct Rect {
|
typedef struct Rect {
|
||||||
float x;
|
float x;
|
||||||
@ -66,9 +40,4 @@ typedef struct Rect {
|
|||||||
} Rect;
|
} Rect;
|
||||||
|
|
||||||
|
|
||||||
typedef struct Matrix4 {
|
|
||||||
Vec4 row[4];
|
|
||||||
} Matrix4;
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -24,74 +24,68 @@
|
|||||||
TWN_API void *crealloc(void *ptr, size_t size);
|
TWN_API void *crealloc(void *ptr, size_t size);
|
||||||
TWN_API void *ccalloc(size_t num, 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 */
|
#endif /* TWN_NOT_C */
|
||||||
|
|
||||||
|
/* calculates the overlap of two rectangles */
|
||||||
TWN_API void log_info(const char *restrict format, ...);
|
TWN_API Rect rect_overlap(Rect a, Rect b);
|
||||||
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 */
|
/* returns true if two rectangles are intersecting */
|
||||||
TWN_API bool intersect_rect(const Recti *a, const Recti *b);
|
TWN_API bool rect_intersects(Rect a, Rect b);
|
||||||
TWN_API bool intersect_frect(const Rect *a, const Rect *b);
|
TWN_API Vec2 rect_center(Rect rect);
|
||||||
|
|
||||||
/* TODO: generics and specials (see m_vec2_from() for an example)*/
|
/* decrements an integer value, stopping at 0 */
|
||||||
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 */
|
/* meant for tick-based timers in game logic */
|
||||||
/*
|
/*
|
||||||
* example:
|
* example:
|
||||||
* tick_timer(&player->jump_air_timer);
|
* tick_timer(&player->jump_air_timer);
|
||||||
*/
|
*/
|
||||||
TWN_API void tick_timer(int *value);
|
TWN_API int32_t timer_tick_frames(int32_t frames_left);
|
||||||
|
|
||||||
/* decrements a floating point second-based timer, stopping at 0.0 */
|
/* decrements a floating point second-based timer, stopping at 0.0f */
|
||||||
/* meant for poll based real time logic in game logic */
|
/* meant for poll based real time logic in game logic */
|
||||||
/* note that it should be decremented only on the next tick after its creation */
|
/* note that it should be decremented only on the next tick after its creation */
|
||||||
TWN_API void tick_ftimer(float *value);
|
TWN_API float timer_tick_seconds(float seconds_left);
|
||||||
|
|
||||||
/* same as `tick_ftimer` but instead of clamping it repeats */
|
typedef struct TimerElapseFramesResult {
|
||||||
/* returns true if value was cycled */
|
bool elapsed; int32_t frames_left;
|
||||||
TWN_API bool repeat_ftimer(float *value, float at);
|
} 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);
|
||||||
|
|
||||||
|
TWN_API void log_vec2(Vec2 vector, char const *message);
|
||||||
|
TWN_API void log_vec3(Vec3 vector, char const *message);
|
||||||
|
TWN_API void log_rect(Rect rect, char const *message);
|
||||||
|
|
||||||
|
TWN_API void profile_start(char profile[const static 1]);
|
||||||
|
TWN_API void profile_end(char profile[const static 1]);
|
||||||
|
TWN_API void profile_list_stats(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -9,13 +9,28 @@
|
|||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
|
|
||||||
/* aren't macros to prevent double evaluation with side effects */
|
static inline Vec2 vec2_add(Vec2 a, Vec2 b) {
|
||||||
/* maybe could be inlined? i hope LTO will resolve this */
|
return (Vec2) { a.x + b.x, a.y + b.y };
|
||||||
static inline Vec2 vec2_from_vec2i(Vec2i vec) {
|
}
|
||||||
return (Vec2) {
|
|
||||||
.x = (float)vec.x,
|
static inline Vec2 vec2_sub(Vec2 a, Vec2 b) {
|
||||||
.y = (float)vec.y,
|
return (Vec2) { a.x - b.x, a.y - b.y };
|
||||||
};
|
}
|
||||||
|
|
||||||
|
static inline Vec2 vec2_div(Vec2 a, Vec2 b) {
|
||||||
|
return (Vec2) { a.x / b.x, a.y / b.y };
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline Vec2 vec2_mul(Vec2 a, Vec2 b) {
|
||||||
|
return (Vec2) { a.x * b.x, a.y * b.y };
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline Vec2 vec2_scale(Vec2 a, float s) {
|
||||||
|
return (Vec2) { a.x * s, a.y * s };
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline float vec2_length(Vec2 a) {
|
||||||
|
return sqrtf(a.x * a.x + a.y * a.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline Vec3 vec3_add(Vec3 a, Vec3 b) {
|
static inline Vec3 vec3_add(Vec3 a, Vec3 b) {
|
||||||
@ -26,12 +41,12 @@ static inline Vec3 vec3_sub(Vec3 a, Vec3 b) {
|
|||||||
return (Vec3) { a.x - b.x, a.y - b.y, a.z - b.z };
|
return (Vec3) { a.x - b.x, a.y - b.y, a.z - b.z };
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline Vec2 vec2_div(Vec2 a, Vec2 b) {
|
static inline Vec3 vec3_div(Vec3 a, Vec3 b) {
|
||||||
return (Vec2) { a.x / b.x, a.y / b.y };
|
return (Vec3) { a.x / b.x, a.y / b.y, a.z / b.z };
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline Vec2 vec2_scale(Vec2 a, float s) {
|
static inline Vec3 vec3_mul(Vec3 a, Vec3 b) {
|
||||||
return (Vec2) { a.x * s, a.y * s };
|
return (Vec3) { a.x * b.x, a.y * b.y, a.z * b.z };
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline Vec3 vec3_scale(Vec3 a, float s) {
|
static inline Vec3 vec3_scale(Vec3 a, float s) {
|
||||||
@ -42,6 +57,10 @@ static inline float vec3_dot(Vec3 a, Vec3 b) {
|
|||||||
return a.x * b.x + a.y * b.y + a.z * b.z;
|
return a.x * b.x + a.y * b.y + a.z * b.z;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline float vec3_length(Vec3 a) {
|
||||||
|
return sqrtf(a.x * a.x + a.y * a.y + a.z * a.z);
|
||||||
|
}
|
||||||
|
|
||||||
static inline Vec3 vec3_cross(Vec3 a, Vec3 b) {
|
static inline Vec3 vec3_cross(Vec3 a, Vec3 b) {
|
||||||
return (Vec3) {
|
return (Vec3) {
|
||||||
a.y * b.z - a.z * b.y,
|
a.y * b.z - a.z * b.y,
|
||||||
@ -83,16 +102,26 @@ static inline Vec3 vec3_rotate(Vec3 v, float angle, Vec3 axis) {
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define m_vec2_from(p_any_vec2) (_Generic((p_any_vec2), \
|
|
||||||
Vec2i: vec2_from_vec2i, \
|
/* TODO: remove. */
|
||||||
)(p_any_vec2))
|
#define m_vec_add(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \
|
||||||
|
Vec2: vec2_add, \
|
||||||
|
Vec3: vec3_add \
|
||||||
|
)(p_any_vec0, p_any_vec1))
|
||||||
|
|
||||||
#define m_vec_sub(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \
|
#define m_vec_sub(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \
|
||||||
|
Vec2: vec2_sub, \
|
||||||
Vec3: vec3_sub \
|
Vec3: vec3_sub \
|
||||||
)(p_any_vec0, p_any_vec1))
|
)(p_any_vec0, p_any_vec1))
|
||||||
|
|
||||||
#define m_vec_div(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \
|
#define m_vec_div(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \
|
||||||
Vec2: vec2_div \
|
Vec2: vec2_div, \
|
||||||
|
Vec3: vec3_div \
|
||||||
|
)(p_any_vec0, p_any_vec1))
|
||||||
|
|
||||||
|
#define m_vec_mul(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \
|
||||||
|
Vec2: vec2_mul, \
|
||||||
|
Vec3: vec3_mul \
|
||||||
)(p_any_vec0, p_any_vec1))
|
)(p_any_vec0, p_any_vec1))
|
||||||
|
|
||||||
#define m_vec_scale(p_any_vec, p_any_scalar) (_Generic((p_any_vec), \
|
#define m_vec_scale(p_any_vec, p_any_scalar) (_Generic((p_any_vec), \
|
||||||
|
533
share/twn_api.json
Normal file
533
share/twn_api.json
Normal file
@ -0,0 +1,533 @@
|
|||||||
|
{
|
||||||
|
"name": "twn",
|
||||||
|
|
||||||
|
"procedures": {
|
||||||
|
"input_action": {
|
||||||
|
"module": "input",
|
||||||
|
"symbol": "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"
|
||||||
|
},
|
||||||
|
|
||||||
|
"draw_sprite": {
|
||||||
|
"module": "draw",
|
||||||
|
"symbol": "sprite",
|
||||||
|
"header": "twn_draw.h",
|
||||||
|
"params": [
|
||||||
|
{ "name": "texture", "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"
|
||||||
|
},
|
||||||
|
|
||||||
|
"draw_nine_slice": {
|
||||||
|
"module": "draw",
|
||||||
|
"symbol": "nine_slice",
|
||||||
|
"header": "twn_draw.h",
|
||||||
|
"params": [
|
||||||
|
{ "name": "texture", "type": "char *" },
|
||||||
|
{ "name": "corners", "type": "Vec2" },
|
||||||
|
{ "name": "rect", "type": "Rect" },
|
||||||
|
{ "name": "border_thickness", "type": "float", "default": 0 },
|
||||||
|
{ "name": "color", "type": "Color", "default": { "r": 255, "g": 255, "b": 255, "a": 255 } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"draw_triangle": {
|
||||||
|
"module": "draw",
|
||||||
|
"symbol": "triangle",
|
||||||
|
"header": "twn_draw.h",
|
||||||
|
"params": [
|
||||||
|
{ "name": "texture", "type": "char *" },
|
||||||
|
{ "name": "v0", "type": "Vec3" },
|
||||||
|
{ "name": "v1", "type": "Vec3" },
|
||||||
|
{ "name": "v2", "type": "Vec3" },
|
||||||
|
{ "name": "uv0", "type": "Vec2" },
|
||||||
|
{ "name": "uv1", "type": "Vec2" },
|
||||||
|
{ "name": "uv2", "type": "Vec2" },
|
||||||
|
{ "name": "c0", "type": "Color", "default": { "r": 255, "g": 255, "b": 255, "a": 255 } },
|
||||||
|
{ "name": "c1", "type": "Color", "default": { "r": 255, "g": 255, "b": 255, "a": 255 } },
|
||||||
|
{ "name": "c2", "type": "Color", "default": { "r": 255, "g": 255, "b": 255, "a": 255 } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"draw_quad": {
|
||||||
|
"module": "draw",
|
||||||
|
"symbol": "quad",
|
||||||
|
"header": "twn_draw.h",
|
||||||
|
"params": [
|
||||||
|
{ "name": "texture", "type": "char *" },
|
||||||
|
{ "name": "v0", "type": "Vec3" },
|
||||||
|
{ "name": "v1", "type": "Vec3" },
|
||||||
|
{ "name": "v2", "type": "Vec3" },
|
||||||
|
{ "name": "v3", "type": "Vec3" },
|
||||||
|
{ "name": "texture_region", "type": "Rect" },
|
||||||
|
{ "name": "color", "type": "Color", "default": { "r": 255, "g": 255, "b": 255, "a": 255 } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"draw_billboard": {
|
||||||
|
"module": "draw",
|
||||||
|
"symbol": "billboard",
|
||||||
|
"header": "twn_draw.h",
|
||||||
|
"params": [
|
||||||
|
{ "name": "texture", "type": "char *" },
|
||||||
|
{ "name": "position", "type": "Vec3" },
|
||||||
|
{ "name": "size", "type": "Vec2" },
|
||||||
|
{ "name": "color", "type": "Color", "default": { "r": 255, "g": 255, "b": 255, "a": 255 } },
|
||||||
|
{ "name": "cylindrical", "type": "bool", "default": false }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"draw_camera": {
|
||||||
|
"module": "draw",
|
||||||
|
"symbol": "camera",
|
||||||
|
"header": "twn_draw.h",
|
||||||
|
"params": [
|
||||||
|
{ "name": "position", "type": "Vec3" },
|
||||||
|
{ "name": "fov", "type": "float" },
|
||||||
|
{ "name": "up", "type": "Vec3" },
|
||||||
|
{ "name": "direction", "type": "Vec3" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"draw_camera_from_principal_axes": {
|
||||||
|
"module": "draw",
|
||||||
|
"symbol": "camera_from_principal_axes",
|
||||||
|
"header": "twn_draw.h",
|
||||||
|
"params": [
|
||||||
|
{ "name": "position", "type": "Vec3" },
|
||||||
|
{ "name": "fov", "type": "float" },
|
||||||
|
{ "name": "roll", "type": "float" },
|
||||||
|
{ "name": "pitch", "type": "float" },
|
||||||
|
{ "name": "yaw", "type": "float" }
|
||||||
|
],
|
||||||
|
"return": {
|
||||||
|
"fields": [
|
||||||
|
{ "name": "direction", "type": "Vec3" },
|
||||||
|
{ "name": "up", "type": "Vec3" }
|
||||||
|
],
|
||||||
|
"c_type": "DrawCameraFromPrincipalAxesResult"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"draw_skybox": {
|
||||||
|
"module": "draw",
|
||||||
|
"symbol": "skybox",
|
||||||
|
"header": "twn_draw.h",
|
||||||
|
"params": [
|
||||||
|
{ "name": "textures", "type": "char *", "default": {} }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"audio_play": {
|
||||||
|
"module": "audio",
|
||||||
|
"symbol": "play",
|
||||||
|
"header": "twn_audio.h",
|
||||||
|
"params": [
|
||||||
|
{ "name": "audio", "type": "char *" },
|
||||||
|
{ "name": "channel", "type": "char *", "default": {} },
|
||||||
|
{ "name": "repeat", "type": "bool", "default": false },
|
||||||
|
{ "name": "volume", "type": "float", "default": 1.0 },
|
||||||
|
{ "name": "panning", "type": "float", "default": 0.0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"audio_parameter": {
|
||||||
|
"module": "audio",
|
||||||
|
"symbol": "parameter",
|
||||||
|
"header": "twn_audio.h",
|
||||||
|
"params": [
|
||||||
|
{ "name": "channel", "type": "char *" },
|
||||||
|
{ "name": "parameter", "type": "char *" },
|
||||||
|
{ "name": "value", "type": "float" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"types": {
|
||||||
|
"Vec2": {
|
||||||
|
"fields": [
|
||||||
|
{ "name": "x", "type": "float" },
|
||||||
|
{ "name": "y", "type": "float" }
|
||||||
|
],
|
||||||
|
"c_type": "Vec2"
|
||||||
|
},
|
||||||
|
|
||||||
|
"Vec3": {
|
||||||
|
"fields": [
|
||||||
|
{ "name": "x", "type": "float" },
|
||||||
|
{ "name": "y", "type": "float" },
|
||||||
|
{ "name": "z", "type": "float" }
|
||||||
|
],
|
||||||
|
"c_type": "Vec3"
|
||||||
|
},
|
||||||
|
|
||||||
|
"Color": {
|
||||||
|
"fields": [
|
||||||
|
{ "name": "r", "type": "uint8_t" },
|
||||||
|
{ "name": "g", "type": "uint8_t" },
|
||||||
|
{ "name": "b", "type": "uint8_t" },
|
||||||
|
{ "name": "a", "type": "uint8_t" }
|
||||||
|
],
|
||||||
|
"c_type": "Color"
|
||||||
|
},
|
||||||
|
|
||||||
|
"Rect": {
|
||||||
|
"fields": [
|
||||||
|
{ "name": "x", "type": "float" },
|
||||||
|
{ "name": "y", "type": "float" },
|
||||||
|
{ "name": "w", "type": "float" },
|
||||||
|
{ "name": "h", "type": "float" }
|
||||||
|
],
|
||||||
|
"c_type": "Rect"
|
||||||
|
},
|
||||||
|
|
||||||
|
"Context": {
|
||||||
|
"fields": [
|
||||||
|
{ "name": "frame_number", "type": "float" },
|
||||||
|
{ "name": "frame_duration", "type": "float" },
|
||||||
|
{ "name": "fog_density", "type": "float" },
|
||||||
|
{ "name": "fog_color", "type": "Color" },
|
||||||
|
{ "name": "resolution", "type": "Vec2" },
|
||||||
|
{ "name": "mouse_position", "type": "Vec2" },
|
||||||
|
{ "name": "mouse_movement", "type": "Vec2" },
|
||||||
|
{ "name": "random_seed", "type": "float" },
|
||||||
|
{ "name": "debug", "type": "bool" },
|
||||||
|
{ "name": "initialization_needed", "type": "bool" },
|
||||||
|
{ "name": "mouse_capture", "type": "bool" }
|
||||||
|
],
|
||||||
|
"c_type": "Context"
|
||||||
|
},
|
||||||
|
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
#include "twn_game_object_c.h"
|
#include "twn_game_object_c.h"
|
||||||
#include "twn_engine_context_c.h"
|
#include "twn_engine_context_c.h"
|
||||||
#include "twn_util_c.h"
|
#include "twn_util_c.h"
|
||||||
|
#include "twn_util.h"
|
||||||
|
|
||||||
#include <x-watcher.h>
|
#include <x-watcher.h>
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
@ -18,7 +19,7 @@ static void (*game_end_callback)(void);
|
|||||||
static x_watcher *watcher;
|
static x_watcher *watcher;
|
||||||
static void *handle = NULL;
|
static void *handle = NULL;
|
||||||
|
|
||||||
static uint64_t last_tick_modified;
|
static float last_tick_modified;
|
||||||
static bool loaded_after_modification = true;
|
static bool loaded_after_modification = true;
|
||||||
static SDL_mutex *lock;
|
static SDL_mutex *lock;
|
||||||
|
|
||||||
@ -58,7 +59,7 @@ static void load_game_object(void) {
|
|||||||
|
|
||||||
handle = new_handle;
|
handle = new_handle;
|
||||||
|
|
||||||
if (ctx.game.frame_number != 0)
|
if (fabsf(0.0f - ctx.game.frame_number) > 0.00001f)
|
||||||
log_info("Game object was reloaded\n");
|
log_info("Game object was reloaded\n");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include "twn_game_object_c.h"
|
#include "twn_game_object_c.h"
|
||||||
#include "twn_engine_context_c.h"
|
#include "twn_engine_context_c.h"
|
||||||
#include "twn_util_c.h"
|
#include "twn_util_c.h"
|
||||||
|
#include "twn_util.h"
|
||||||
|
|
||||||
#include <errhandlingapi.h>
|
#include <errhandlingapi.h>
|
||||||
#include <libloaderapi.h>
|
#include <libloaderapi.h>
|
||||||
|
185
src/rendering/twn_billboards.c
Normal file
185
src/rendering/twn_billboards.c
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
#include "twn_draw.h"
|
||||||
|
#include "twn_draw_c.h"
|
||||||
|
#include "twn_engine_context_c.h"
|
||||||
|
#include "twn_textures_c.h"
|
||||||
|
#include "twn_types.h"
|
||||||
|
#include "twn_util_c.h"
|
||||||
|
#include "twn_vec.h"
|
||||||
|
|
||||||
|
#include <stb_ds.h>
|
||||||
|
|
||||||
|
|
||||||
|
void draw_billboard(const char *texture,
|
||||||
|
Vec3 position,
|
||||||
|
Vec2 size,
|
||||||
|
Color color,
|
||||||
|
bool cylindrical)
|
||||||
|
{
|
||||||
|
// TODO: order drawing by atlas id as well, so that texture rebinding is not as common
|
||||||
|
const TextureKey texture_key = textures_get_key(&ctx.texture_cache, texture);
|
||||||
|
|
||||||
|
struct MeshBatchItem *batch_p = hmgetp_null(ctx.billboard_batches, texture_key);
|
||||||
|
if (!batch_p) {
|
||||||
|
struct MeshBatch item = {0};
|
||||||
|
hmput(ctx.billboard_batches, texture_key, item);
|
||||||
|
batch_p = &ctx.billboard_batches[hmlenu(ctx.billboard_batches) - 1]; /* TODO: can last index be used? */
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SpaceBillboard billboard = {
|
||||||
|
.color = color,
|
||||||
|
.cylindrical = cylindrical,
|
||||||
|
.position = position,
|
||||||
|
.size = size,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SpaceBillboard *billboards = (struct SpaceBillboard *)(void *)batch_p->value.primitives;
|
||||||
|
|
||||||
|
arrpush(billboards, billboard);
|
||||||
|
batch_p->value.primitives = (uint8_t *)billboards;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* reused for all billboards */
|
||||||
|
static Vec3 right_plus_up;
|
||||||
|
static Vec3 right_minus_up;
|
||||||
|
static Vec3 right_plus_up_cylindrical;
|
||||||
|
static Vec3 right_minus_up_cylindrical;
|
||||||
|
|
||||||
|
/* precalculate (right + up) and (right - up) that are used with all batches */
|
||||||
|
static void calculate_intermediates(void) {
|
||||||
|
Vec3 const right = { camera_look_at_matrix.row[0].x, camera_look_at_matrix.row[1].x, camera_look_at_matrix.row[2].x };
|
||||||
|
Vec3 const up = { camera_look_at_matrix.row[0].y, camera_look_at_matrix.row[1].y, camera_look_at_matrix.row[2].y };
|
||||||
|
|
||||||
|
right_plus_up = m_vec_add(right, up);
|
||||||
|
right_minus_up = m_vec_sub(right, up);
|
||||||
|
|
||||||
|
Vec3 const up_cylindrical = { 0, 1, 0 };
|
||||||
|
|
||||||
|
right_plus_up_cylindrical = m_vec_add(right, up_cylindrical);
|
||||||
|
right_minus_up_cylindrical = m_vec_sub(right, up_cylindrical);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* http://www.lighthouse3d.com/opengl/billboarding/index.php?billCheat2 */
|
||||||
|
void finally_draw_billboard_batch(struct MeshBatch const *batch,
|
||||||
|
TextureKey texture_key)
|
||||||
|
{
|
||||||
|
const size_t primitives_len = arrlenu(batch->primitives);
|
||||||
|
|
||||||
|
/* nothing to do */
|
||||||
|
if (primitives_len == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* TODO: only do it once per frame */
|
||||||
|
calculate_intermediates();
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
const Vec2 uv0 = { xr + wr, yr };
|
||||||
|
const Vec2 uv1 = { xr + wr, yr + hr };
|
||||||
|
const Vec2 uv2 = { xr, yr + hr };
|
||||||
|
const Vec2 uv3 = { xr, yr };
|
||||||
|
|
||||||
|
for (size_t batch_n = 0; batch_n <= (primitives_len - 1) / QUAD_ELEMENT_BUFFER_LENGTH; batch_n++) {
|
||||||
|
|
||||||
|
/* emit vertex data */
|
||||||
|
VertexBuffer const buffer = get_scratch_vertex_array();
|
||||||
|
VertexBufferBuilder builder = build_vertex_buffer(
|
||||||
|
buffer,
|
||||||
|
sizeof (ElementIndexedBillboard) * MIN(primitives_len - batch_n * QUAD_ELEMENT_BUFFER_LENGTH, QUAD_ELEMENT_BUFFER_LENGTH));
|
||||||
|
|
||||||
|
for (size_t i = 0; i < primitives_len; ++i) {
|
||||||
|
struct SpaceBillboard const billboard = ((SpaceBillboard *)(void *)batch->primitives)[batch_n * QUAD_ELEMENT_BUFFER_LENGTH + i];
|
||||||
|
|
||||||
|
/* a = (right + up) * size, b = (right - up) * size*/
|
||||||
|
Vec3 a, b;
|
||||||
|
if (billboard.cylindrical) {
|
||||||
|
a = vec3_mul(right_plus_up_cylindrical, ((Vec3){billboard.size.x, billboard.size.y, billboard.size.x }));
|
||||||
|
b = vec3_mul(right_minus_up_cylindrical, ((Vec3){billboard.size.x, billboard.size.y, billboard.size.x }));
|
||||||
|
} else {
|
||||||
|
a = vec3_mul(right_plus_up, ((Vec3){billboard.size.x, billboard.size.y, billboard.size.x }));
|
||||||
|
b = vec3_mul(right_minus_up, ((Vec3){billboard.size.x, billboard.size.y, billboard.size.x }));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ElementIndexedBillboard const payload = {
|
||||||
|
/* flat shading is assumed, so we can skip setting the duplicates */
|
||||||
|
.c0 = billboard.color,
|
||||||
|
// .c1 = billboard.color,
|
||||||
|
.c2 = billboard.color,
|
||||||
|
// .c3 = billboard.color,
|
||||||
|
|
||||||
|
.uv0 = uv0,
|
||||||
|
.uv1 = uv1,
|
||||||
|
.uv2 = uv2,
|
||||||
|
.uv3 = uv3,
|
||||||
|
|
||||||
|
.v0 = vec3_sub(billboard.position, b),
|
||||||
|
.v1 = vec3_sub(billboard.position, a),
|
||||||
|
.v2 = vec3_add(billboard.position, b),
|
||||||
|
.v3 = vec3_add(billboard.position, a),
|
||||||
|
};
|
||||||
|
|
||||||
|
((struct ElementIndexedBillboard *)builder.base)[i] = payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
finish_vertex_builder(&builder);
|
||||||
|
|
||||||
|
/* commit to drawing */
|
||||||
|
DeferredCommandDraw command = {0};
|
||||||
|
|
||||||
|
command.vertices = (AttributeArrayPointer) {
|
||||||
|
.arity = 3,
|
||||||
|
.type = TWN_FLOAT,
|
||||||
|
.stride = offsetof(ElementIndexedBillboard, v1),
|
||||||
|
.offset = offsetof(ElementIndexedBillboard, v0),
|
||||||
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
command.texcoords = (AttributeArrayPointer) {
|
||||||
|
.arity = 2,
|
||||||
|
.type = TWN_FLOAT,
|
||||||
|
.stride = offsetof(ElementIndexedBillboard, v1),
|
||||||
|
.offset = offsetof(ElementIndexedBillboard, uv0),
|
||||||
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
command.colors = (AttributeArrayPointer) {
|
||||||
|
.arity = 4,
|
||||||
|
.type = TWN_UNSIGNED_BYTE,
|
||||||
|
.stride = offsetof(ElementIndexedBillboard, v1),
|
||||||
|
.offset = offsetof(ElementIndexedBillboard, c0),
|
||||||
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
command.textured = true;
|
||||||
|
command.texture_key = texture_key;
|
||||||
|
|
||||||
|
command.element_buffer = get_quad_element_buffer();
|
||||||
|
command.element_count = 6 * (uint32_t)primitives_len;
|
||||||
|
command.range_end = 6 * (uint32_t)primitives_len;
|
||||||
|
|
||||||
|
/* TODO: support alpha blended case, with distance sort */
|
||||||
|
TextureMode mode = textures_get_mode(&ctx.texture_cache, texture_key);
|
||||||
|
if (mode == TEXTURE_MODE_GHOSTLY)
|
||||||
|
mode = TEXTURE_MODE_SEETHROUGH;
|
||||||
|
|
||||||
|
command.texture_mode = mode;
|
||||||
|
command.pipeline = PIPELINE_SPACE;
|
||||||
|
|
||||||
|
command.depth_range_high = depth_range_high;
|
||||||
|
command.depth_range_low = depth_range_low;
|
||||||
|
|
||||||
|
DeferredCommand final_command = {
|
||||||
|
.type = DEFERRED_COMMAND_TYPE_DRAW,
|
||||||
|
.draw = command
|
||||||
|
};
|
||||||
|
|
||||||
|
arrpush(deferred_commands, final_command);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
#include "twn_engine_context_c.h"
|
#include "twn_engine_context_c.h"
|
||||||
#include "twn_draw_c.h"
|
#include "twn_draw_c.h"
|
||||||
#include "twn_draw.h"
|
#include "twn_draw.h"
|
||||||
|
#include "twn_util_c.h"
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <stb_ds.h>
|
#include <stb_ds.h>
|
||||||
@ -27,8 +28,10 @@ void create_circle_geometry(Vec2 position,
|
|||||||
size_t num_vertices,
|
size_t num_vertices,
|
||||||
Vec2 vertices[])
|
Vec2 vertices[])
|
||||||
{
|
{
|
||||||
|
SDL_assert(num_vertices <= CIRCLE_VERTICES_MAX);
|
||||||
|
|
||||||
/* the angle (in radians) to rotate by on each iteration */
|
/* the angle (in radians) to rotate by on each iteration */
|
||||||
float seg_rotation_angle = (360.0f / (float)num_vertices) * ((float)M_PI / 180);
|
float seg_rotation_angle = (360.0f / (float)(num_vertices - 2)) * ((float)M_PI / 180);
|
||||||
|
|
||||||
vertices[0].x = (float)position.x;
|
vertices[0].x = (float)position.x;
|
||||||
vertices[0].y = (float)position.y;
|
vertices[0].y = (float)position.y;
|
||||||
@ -37,7 +40,7 @@ void create_circle_geometry(Vec2 position,
|
|||||||
float start_x = 0.0f - radius;
|
float start_x = 0.0f - radius;
|
||||||
float start_y = 0.0f;
|
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 final_seg_rotation_angle = (float)i * seg_rotation_angle;
|
||||||
|
|
||||||
float c, s;
|
float c, s;
|
||||||
@ -49,4 +52,76 @@ void create_circle_geometry(Vec2 position,
|
|||||||
vertices[i].x += position.x;
|
vertices[i].x += position.x;
|
||||||
vertices[i].y += position.y;
|
vertices[i].y += position.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// place a redundant vertex to make proper circling over shared element buffer
|
||||||
|
{
|
||||||
|
float final_seg_rotation_angle = (float)1 * seg_rotation_angle;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
vertices[num_vertices - 1].x += position.x;
|
||||||
|
vertices[num_vertices - 1].y += position.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void render_circle(const CirclePrimitive *circle) {
|
||||||
|
static Vec2 vertices[CIRCLE_VERTICES_MAX];
|
||||||
|
static int prev_num_vertices = 0;
|
||||||
|
static Vec2 prev_position = {0};
|
||||||
|
|
||||||
|
int const num_vertices = MIN((int)circle->radius, CIRCLE_VERTICES_MAX);
|
||||||
|
|
||||||
|
if (prev_num_vertices != num_vertices) {
|
||||||
|
create_circle_geometry(circle->position,
|
||||||
|
circle->radius,
|
||||||
|
num_vertices,
|
||||||
|
vertices);
|
||||||
|
prev_num_vertices = num_vertices;
|
||||||
|
prev_position = circle->position;
|
||||||
|
} else {
|
||||||
|
/* reuse the data, but offset it by difference with previously generated position */
|
||||||
|
/* no evil cos sin ops this way, if radius is shared in sequential calls */
|
||||||
|
Vec2 const d = { prev_position.x - circle->position.x, prev_position.y - circle->position.y };
|
||||||
|
for (int i = 0; i < num_vertices; ++i)
|
||||||
|
vertices[i] = (Vec2){ vertices[i].x - d.x, vertices[i].y - d.y };
|
||||||
|
prev_position = circle->position;
|
||||||
|
}
|
||||||
|
|
||||||
|
VertexBuffer buffer = get_scratch_vertex_array();
|
||||||
|
specify_vertex_buffer(buffer, vertices, sizeof (Vec2) * num_vertices);
|
||||||
|
|
||||||
|
DeferredCommandDraw command = {0};
|
||||||
|
|
||||||
|
command.vertices = (AttributeArrayPointer) {
|
||||||
|
.arity = 2,
|
||||||
|
.type = TWN_FLOAT,
|
||||||
|
.stride = sizeof (Vec2),
|
||||||
|
.offset = 0,
|
||||||
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
command.constant_colored = true;
|
||||||
|
command.color = circle->color;
|
||||||
|
|
||||||
|
command.element_buffer = get_circle_element_buffer();
|
||||||
|
command.element_count = (num_vertices - 2) * 3;
|
||||||
|
command.range_end = (num_vertices - 2) * 3;
|
||||||
|
|
||||||
|
command.texture_mode = circle->color.a == 255 ? TEXTURE_MODE_OPAQUE : TEXTURE_MODE_GHOSTLY;
|
||||||
|
command.pipeline = PIPELINE_2D;
|
||||||
|
|
||||||
|
command.depth_range_high = depth_range_high;
|
||||||
|
command.depth_range_low = depth_range_low;
|
||||||
|
|
||||||
|
DeferredCommand final_command = {
|
||||||
|
.type = DEFERRED_COMMAND_TYPE_DRAW,
|
||||||
|
.draw = command
|
||||||
|
};
|
||||||
|
|
||||||
|
arrpush(deferred_commands, final_command);
|
||||||
}
|
}
|
||||||
|
87
src/rendering/twn_deferred_commands.h
Normal file
87
src/rendering/twn_deferred_commands.h
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
#ifndef TWN_DEFERRED_COMMANDS_H
|
||||||
|
#define TWN_DEFERRED_COMMANDS_H
|
||||||
|
|
||||||
|
#include "twn_types.h"
|
||||||
|
#include "twn_gpu_texture_c.h"
|
||||||
|
#include "twn_textures_c.h"
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
PIPELINE_NO,
|
||||||
|
PIPELINE_SPACE,
|
||||||
|
PIPELINE_2D, /* TODO: rename to PIPELINE_PLANE? */
|
||||||
|
} Pipeline;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
size_t offset;
|
||||||
|
uint32_t type;
|
||||||
|
uint32_t stride;
|
||||||
|
uint32_t buffer;
|
||||||
|
uint8_t arity; /* leave at 0 to signal pointer as unused */
|
||||||
|
} AttributeArrayPointer;
|
||||||
|
|
||||||
|
|
||||||
|
/* allows us to have generic way to issue draws as well as */
|
||||||
|
/* deferring new draw calls while previous frame is still being drawn */
|
||||||
|
typedef struct {
|
||||||
|
AttributeArrayPointer vertices;
|
||||||
|
AttributeArrayPointer texcoords;
|
||||||
|
union {
|
||||||
|
AttributeArrayPointer colors;
|
||||||
|
Color color;
|
||||||
|
};
|
||||||
|
|
||||||
|
double depth_range_low, depth_range_high;
|
||||||
|
|
||||||
|
Pipeline pipeline;
|
||||||
|
TextureMode texture_mode;
|
||||||
|
|
||||||
|
TextureKey texture_key;
|
||||||
|
GPUTexture gpu_texture;
|
||||||
|
|
||||||
|
/* could be either `element_count` with supplied `element_buffer`, or this, but not both */
|
||||||
|
uint32_t primitive_count;
|
||||||
|
uint32_t element_buffer;
|
||||||
|
uint32_t element_count;
|
||||||
|
uint32_t range_start, range_end;
|
||||||
|
|
||||||
|
bool constant_colored;
|
||||||
|
bool textured, texture_repeat, uses_gpu_key;
|
||||||
|
} DeferredCommandDraw;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *paths;
|
||||||
|
} DeferredCommandDrawSkybox;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Color color;
|
||||||
|
bool clear_color;
|
||||||
|
bool clear_depth;
|
||||||
|
bool clear_stencil;
|
||||||
|
} DeferredCommandClear;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
enum DeferredCommandType {
|
||||||
|
DEFERRED_COMMAND_TYPE_DRAW,
|
||||||
|
DEFERRED_COMMAND_TYPE_DRAW_SKYBOX,
|
||||||
|
DEFERRED_COMMAND_TYPE_CLEAR,
|
||||||
|
} type;
|
||||||
|
|
||||||
|
union {
|
||||||
|
DeferredCommandDraw draw;
|
||||||
|
DeferredCommandDrawSkybox draw_skybox;
|
||||||
|
DeferredCommandClear clear;
|
||||||
|
};
|
||||||
|
} DeferredCommand;
|
||||||
|
|
||||||
|
|
||||||
|
extern DeferredCommand *deferred_commands;
|
||||||
|
|
||||||
|
#endif
|
@ -1,26 +1,29 @@
|
|||||||
#include "twn_draw_c.h"
|
#include "twn_draw_c.h"
|
||||||
#include "twn_draw.h"
|
#include "twn_draw.h"
|
||||||
#include "twn_engine_context_c.h"
|
#include "twn_engine_context_c.h"
|
||||||
#include "twn_camera.h"
|
#include "twn_camera_c.h"
|
||||||
#include "twn_types.h"
|
#include "twn_types.h"
|
||||||
|
#include "twn_util.h"
|
||||||
|
#include "twn_vec.h"
|
||||||
|
#include "twn_deferred_commands.h"
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <stb_ds.h>
|
#include <stb_ds.h>
|
||||||
|
|
||||||
#ifdef EMSCRIPTEN
|
|
||||||
#include <GLES2/gl2.h>
|
|
||||||
#else
|
|
||||||
#include <glad/glad.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include <math.h>
|
||||||
#include <tgmath.h>
|
#include <tgmath.h>
|
||||||
|
|
||||||
|
|
||||||
|
DeferredCommand *deferred_commands;
|
||||||
|
|
||||||
/* TODO: have a default initialized one */
|
/* TODO: have a default initialized one */
|
||||||
|
/* TODO: with buffered render, don't we use camera of wrong frame right now ? */
|
||||||
Matrix4 camera_projection_matrix;
|
Matrix4 camera_projection_matrix;
|
||||||
Matrix4 camera_look_at_matrix;
|
Matrix4 camera_look_at_matrix;
|
||||||
|
|
||||||
|
double depth_range_low, depth_range_high;
|
||||||
|
|
||||||
|
|
||||||
void render_queue_clear(void) {
|
void render_queue_clear(void) {
|
||||||
text_cache_reset_arena(&ctx.text_cache);
|
text_cache_reset_arena(&ctx.text_cache);
|
||||||
@ -30,13 +33,17 @@ void render_queue_clear(void) {
|
|||||||
/* and start overwriting the existing data */
|
/* and start overwriting the existing data */
|
||||||
arrsetlen(ctx.render_queue_2d, 0);
|
arrsetlen(ctx.render_queue_2d, 0);
|
||||||
|
|
||||||
|
/* TODO: free memory if it isn't used for a while */
|
||||||
for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i)
|
for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i)
|
||||||
arrsetlen(ctx.uncolored_mesh_batches[i].value.primitives, 0);
|
arrsetlen(ctx.uncolored_mesh_batches[i].value.primitives, 0);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < hmlenu(ctx.billboard_batches); ++i)
|
||||||
|
arrsetlen(ctx.billboard_batches[i].value.primitives, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void draw_9slice(const char *texture_path, int texture_w, int texture_h, int border_thickness, Rect rect, Color color) {
|
void draw_nine_slice(const char *texture, Vec2 corners, Rect rect, float border_thickness, Color color) {
|
||||||
const float bt = (float)border_thickness; /* i know! */
|
const float bt = border_thickness;
|
||||||
const float bt2 = bt * 2; /* combined size of the two borders in an axis */
|
const float bt2 = bt * 2; /* combined size of the two borders in an axis */
|
||||||
|
|
||||||
|
|
||||||
@ -48,7 +55,7 @@ void draw_9slice(const char *texture_path, int texture_w, int texture_h, int bor
|
|||||||
};
|
};
|
||||||
|
|
||||||
m_sprite(
|
m_sprite(
|
||||||
m_set(path, texture_path),
|
m_set(texture, texture),
|
||||||
m_set(rect, top_left),
|
m_set(rect, top_left),
|
||||||
m_opt(texture_region, ((Rect) { 0, 0, bt, bt })),
|
m_opt(texture_region, ((Rect) { 0, 0, bt, bt })),
|
||||||
m_opt(color, color),
|
m_opt(color, color),
|
||||||
@ -63,9 +70,9 @@ void draw_9slice(const char *texture_path, int texture_w, int texture_h, int bor
|
|||||||
};
|
};
|
||||||
|
|
||||||
m_sprite(
|
m_sprite(
|
||||||
m_set(path, texture_path),
|
m_set(texture, texture),
|
||||||
m_set(rect, top_center),
|
m_set(rect, top_center),
|
||||||
m_opt(texture_region, ((Rect) { bt, 0, (float)texture_w - bt2, bt })),
|
m_opt(texture_region, ((Rect) { bt, 0, corners.x - bt2, bt })),
|
||||||
m_opt(color, color),
|
m_opt(color, color),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -78,9 +85,9 @@ void draw_9slice(const char *texture_path, int texture_w, int texture_h, int bor
|
|||||||
};
|
};
|
||||||
|
|
||||||
m_sprite(
|
m_sprite(
|
||||||
m_set(path, texture_path),
|
m_set(texture, texture),
|
||||||
m_set(rect, top_right),
|
m_set(rect, top_right),
|
||||||
m_opt(texture_region, ((Rect) { (float)texture_w - bt, 0, bt, bt })),
|
m_opt(texture_region, ((Rect) { corners.x - bt, 0, bt, bt })),
|
||||||
m_opt(color, color),
|
m_opt(color, color),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -93,9 +100,9 @@ void draw_9slice(const char *texture_path, int texture_w, int texture_h, int bor
|
|||||||
};
|
};
|
||||||
|
|
||||||
m_sprite(
|
m_sprite(
|
||||||
m_set(path, texture_path),
|
m_set(texture, texture),
|
||||||
m_set(rect, center_left),
|
m_set(rect, center_left),
|
||||||
m_opt(texture_region, ((Rect) { 0, bt, bt, (float)texture_h - bt2 })),
|
m_opt(texture_region, ((Rect) { 0, bt, bt, corners.y - bt2 })),
|
||||||
m_opt(color, color),
|
m_opt(color, color),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -108,9 +115,9 @@ void draw_9slice(const char *texture_path, int texture_w, int texture_h, int bor
|
|||||||
};
|
};
|
||||||
|
|
||||||
m_sprite(
|
m_sprite(
|
||||||
m_set(path, texture_path),
|
m_set(texture, texture),
|
||||||
m_set(rect, center_right),
|
m_set(rect, center_right),
|
||||||
m_opt(texture_region, ((Rect) { (float)texture_w - bt, bt, bt, (float)texture_h - bt2 })),
|
m_opt(texture_region, ((Rect) { corners.x - bt, bt, bt, corners.y - bt2 })),
|
||||||
m_opt(color, color),
|
m_opt(color, color),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -123,9 +130,9 @@ void draw_9slice(const char *texture_path, int texture_w, int texture_h, int bor
|
|||||||
};
|
};
|
||||||
|
|
||||||
m_sprite(
|
m_sprite(
|
||||||
m_set(path, texture_path),
|
m_set(texture, texture),
|
||||||
m_set(rect, bottom_left),
|
m_set(rect, bottom_left),
|
||||||
m_opt(texture_region, ((Rect) { 0, (float)texture_h - bt, bt, bt })),
|
m_opt(texture_region, ((Rect) { 0, corners.y - bt, bt, bt })),
|
||||||
m_opt(color, color),
|
m_opt(color, color),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -138,9 +145,9 @@ void draw_9slice(const char *texture_path, int texture_w, int texture_h, int bor
|
|||||||
};
|
};
|
||||||
|
|
||||||
m_sprite(
|
m_sprite(
|
||||||
m_set(path, texture_path),
|
m_set(texture, texture),
|
||||||
m_set(rect, bottom_center),
|
m_set(rect, bottom_center),
|
||||||
m_opt(texture_region, ((Rect) { bt, (float)texture_h - bt, (float)texture_w - bt2, bt })),
|
m_opt(texture_region, ((Rect) { bt, corners.y - bt, corners.x - bt2, bt })),
|
||||||
m_opt(color, color),
|
m_opt(color, color),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -153,9 +160,9 @@ void draw_9slice(const char *texture_path, int texture_w, int texture_h, int bor
|
|||||||
};
|
};
|
||||||
|
|
||||||
m_sprite(
|
m_sprite(
|
||||||
m_set(path, texture_path),
|
m_set(texture, texture),
|
||||||
m_set(rect, bottom_right),
|
m_set(rect, bottom_right),
|
||||||
m_opt(texture_region, ((Rect) { (float)texture_w - bt, (float)texture_h - bt, bt, bt })),
|
m_opt(texture_region, ((Rect) { corners.x - bt, corners.y - bt, bt, bt })),
|
||||||
m_opt(color, color),
|
m_opt(color, color),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -168,17 +175,40 @@ void draw_9slice(const char *texture_path, int texture_w, int texture_h, int bor
|
|||||||
};
|
};
|
||||||
|
|
||||||
m_sprite(
|
m_sprite(
|
||||||
m_set(path, texture_path),
|
m_set(texture, texture),
|
||||||
m_set(rect, center),
|
m_set(rect, center),
|
||||||
m_opt(texture_region, ((Rect) { bt, bt, (float)texture_w - bt2, (float)texture_h - bt2 })),
|
m_opt(texture_region, ((Rect) { bt, bt, corners.x - bt2, corners.y - bt2 })),
|
||||||
m_opt(color, color),
|
m_opt(color, color),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void render_2d(void) {
|
TWN_API void draw_quad(char const *texture,
|
||||||
use_2d_pipeline();
|
Vec3 v0, /* upper-left */
|
||||||
|
Vec3 v1, /* bottom-left */
|
||||||
|
Vec3 v2, /* bottom-right */
|
||||||
|
Vec3 v3, /* upper-right */
|
||||||
|
Rect texture_region,
|
||||||
|
Color color)
|
||||||
|
{
|
||||||
|
Vec2 const uv0 = { texture_region.x, texture_region.y };
|
||||||
|
Vec2 const uv1 = { texture_region.x, texture_region.y + texture_region.h };
|
||||||
|
Vec2 const uv2 = { texture_region.x + texture_region.w, texture_region.y + texture_region.h };
|
||||||
|
Vec2 const uv3 = { texture_region.x + texture_region.w, texture_region.y };
|
||||||
|
|
||||||
|
draw_triangle(texture,
|
||||||
|
v0, v1, v3,
|
||||||
|
uv0, uv1, uv3,
|
||||||
|
color, color, color);
|
||||||
|
|
||||||
|
draw_triangle(texture,
|
||||||
|
v3, v1, v2,
|
||||||
|
uv3, uv1, uv2,
|
||||||
|
color, color, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void render_2d(void) {
|
||||||
const size_t render_queue_len = arrlenu(ctx.render_queue_2d);
|
const size_t render_queue_len = arrlenu(ctx.render_queue_2d);
|
||||||
|
|
||||||
struct Render2DInvocation {
|
struct Render2DInvocation {
|
||||||
@ -255,6 +285,20 @@ static void render_2d(void) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* TODO: batching */
|
||||||
|
case PRIMITIVE_2D_LINE: {
|
||||||
|
struct Render2DInvocation const invocation = {
|
||||||
|
.primitive = current,
|
||||||
|
.layer = layer,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (current->line.color.a != 255)
|
||||||
|
arrput(ghostly_invocations, invocation);
|
||||||
|
else
|
||||||
|
arrput(opaque_invocations, invocation);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case PRIMITIVE_2D_TEXT: {
|
case PRIMITIVE_2D_TEXT: {
|
||||||
struct Render2DInvocation const invocation = {
|
struct Render2DInvocation const invocation = {
|
||||||
.primitive = current,
|
.primitive = current,
|
||||||
@ -291,6 +335,9 @@ static void render_2d(void) {
|
|||||||
case PRIMITIVE_2D_CIRCLE:
|
case PRIMITIVE_2D_CIRCLE:
|
||||||
render_circle(&invocation.primitive->circle);
|
render_circle(&invocation.primitive->circle);
|
||||||
break;
|
break;
|
||||||
|
case PRIMITIVE_2D_LINE:
|
||||||
|
render_line(&invocation.primitive->line);
|
||||||
|
break;
|
||||||
case PRIMITIVE_2D_TEXT:
|
case PRIMITIVE_2D_TEXT:
|
||||||
default:
|
default:
|
||||||
SDL_assert(false);
|
SDL_assert(false);
|
||||||
@ -320,6 +367,9 @@ static void render_2d(void) {
|
|||||||
case PRIMITIVE_2D_TEXT:
|
case PRIMITIVE_2D_TEXT:
|
||||||
render_text(&invocation.primitive->text);
|
render_text(&invocation.primitive->text);
|
||||||
break;
|
break;
|
||||||
|
case PRIMITIVE_2D_LINE:
|
||||||
|
render_line(&invocation.primitive->line);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
SDL_assert(false);
|
SDL_assert(false);
|
||||||
}
|
}
|
||||||
@ -333,18 +383,18 @@ static void render_2d(void) {
|
|||||||
static void render_space(void) {
|
static void render_space(void) {
|
||||||
/* nothing to do, abort */
|
/* nothing to do, abort */
|
||||||
/* as space pipeline isn't used we can have fewer changes and initialization costs */
|
/* as space pipeline isn't used we can have fewer changes and initialization costs */
|
||||||
if (hmlenu(ctx.uncolored_mesh_batches) == 0)
|
if (hmlenu(ctx.uncolored_mesh_batches) != 0 || hmlenu(ctx.billboard_batches) != 0) {
|
||||||
return;
|
|
||||||
|
|
||||||
use_space_pipeline();
|
|
||||||
apply_fog();
|
|
||||||
|
|
||||||
for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i) {
|
for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i) {
|
||||||
draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches[i].value,
|
finally_draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches[i].value,
|
||||||
ctx.uncolored_mesh_batches[i].key);
|
ctx.uncolored_mesh_batches[i].key);
|
||||||
}
|
}
|
||||||
|
|
||||||
pop_fog();
|
for (size_t i = 0; i < hmlenu(ctx.billboard_batches); ++i) {
|
||||||
|
finally_draw_billboard_batch(&ctx.billboard_batches[i].value, ctx.billboard_batches[i].key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render_skybox(); /* after everything else, as to use depth buffer for early z rejection */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -353,37 +403,143 @@ void render(void) {
|
|||||||
|
|
||||||
/* fit rendering context onto the resizable screen */
|
/* fit rendering context onto the resizable screen */
|
||||||
if (ctx.window_size_has_changed) {
|
if (ctx.window_size_has_changed) {
|
||||||
if ((float)ctx.window_dims.x / (float)ctx.window_dims.y > (float)ctx.base_render_width / (float)ctx.base_render_height) {
|
setup_viewport((int)ctx.viewport_rect.x, (int)ctx.viewport_rect.y, (int)ctx.viewport_rect.w, (int)ctx.viewport_rect.h);
|
||||||
float ratio = (float)ctx.window_dims.y / (float)ctx.base_render_height;
|
|
||||||
int w = (int)((float)ctx.base_render_width * ratio);
|
|
||||||
setup_viewport(
|
|
||||||
ctx.window_dims.x / 2 - w / 2,
|
|
||||||
0,
|
|
||||||
w,
|
|
||||||
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,
|
|
||||||
ctx.window_dims.y / 2 - h / 2,
|
|
||||||
ctx.window_dims.x,
|
|
||||||
h
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
start_render_frame(); {
|
start_render_frame(); {
|
||||||
render_space();
|
render_space();
|
||||||
render_skybox(); /* after space, as to use depth buffer for early z rejection */
|
|
||||||
render_2d();
|
render_2d();
|
||||||
} end_render_frame();
|
} end_render_frame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void draw_camera(const Camera *const camera) {
|
void draw_camera(Vec3 position, float fov, Vec3 up, Vec3 direction) {
|
||||||
/* TODO: skip recaulculating if it's the same? */
|
if (fabsf(0.0f - fov) < 0.00001f || fov >= M_PIf)
|
||||||
camera_projection_matrix = camera_perspective(camera);
|
log_warn("Invalid fov given (%f)", (double)fov);
|
||||||
camera_look_at_matrix = camera_look_at(camera);
|
|
||||||
|
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) {
|
||||||
|
if (fabsf(0.0f - fov) < 0.00001f || fov >= M_PIf)
|
||||||
|
log_warn("Invalid fov given (%f)", (double)fov);
|
||||||
|
|
||||||
|
(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 set_depth_range(double low, double high) {
|
||||||
|
depth_range_low = low;
|
||||||
|
depth_range_high = high;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void clear_draw_buffer(void) {
|
||||||
|
/* TODO: we can optimize a rectangle drawn over whole window to a clear color call*/
|
||||||
|
|
||||||
|
DeferredCommand command = {
|
||||||
|
.type = DEFERRED_COMMAND_TYPE_CLEAR,
|
||||||
|
.clear = (DeferredCommandClear) {
|
||||||
|
.clear_color = true,
|
||||||
|
.clear_depth = true,
|
||||||
|
.clear_stencil = true,
|
||||||
|
.color = (Color) { 230, 230, 230, 1 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
arrpush(deferred_commands, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void issue_deferred_draw_commands(void) {
|
||||||
|
for (size_t i = 0; i < arrlenu(deferred_commands); ++i) {
|
||||||
|
switch (deferred_commands[i].type) {
|
||||||
|
case DEFERRED_COMMAND_TYPE_CLEAR: {
|
||||||
|
finally_clear_draw_buffer(deferred_commands[i].clear);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case DEFERRED_COMMAND_TYPE_DRAW: {
|
||||||
|
finally_draw_command(deferred_commands[i].draw);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case DEFERRED_COMMAND_TYPE_DRAW_SKYBOX: {
|
||||||
|
finally_render_skybox(deferred_commands[i].draw_skybox);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
SDL_assert(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* TODO: Support thickness */
|
||||||
|
void draw_line(Vec2 start,
|
||||||
|
Vec2 finish,
|
||||||
|
float thickness,
|
||||||
|
Color color)
|
||||||
|
{
|
||||||
|
if (fabsf(1.0f - thickness) >= 0.00001f)
|
||||||
|
log_warn("Thickness isn't yet implemented for line drawing (got %f)", (double)thickness);
|
||||||
|
|
||||||
|
LinePrimitive line = {
|
||||||
|
.start = start,
|
||||||
|
.finish = finish,
|
||||||
|
.thickness = thickness,
|
||||||
|
.color = color,
|
||||||
|
};
|
||||||
|
|
||||||
|
Primitive2D primitive = {
|
||||||
|
.type = PRIMITIVE_2D_LINE,
|
||||||
|
.line = line,
|
||||||
|
};
|
||||||
|
|
||||||
|
arrput(ctx.render_queue_2d, primitive);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void draw_box(Rect rect,
|
||||||
|
float thickness,
|
||||||
|
Color color)
|
||||||
|
{
|
||||||
|
draw_line((Vec2){rect.x, rect.y}, (Vec2){rect.x + rect.w, rect.y}, thickness, color);
|
||||||
|
draw_line((Vec2){rect.x + rect.w, rect.y}, (Vec2){rect.x + rect.w, rect.y + rect.h}, thickness, color);
|
||||||
|
draw_line((Vec2){rect.x + rect.w, rect.y + rect.h}, (Vec2){rect.x, rect.y + rect.h}, thickness, color);
|
||||||
|
draw_line((Vec2){rect.x, rect.y + rect.h}, (Vec2){rect.x, rect.y}, thickness, color);
|
||||||
}
|
}
|
||||||
|
@ -1,36 +1,44 @@
|
|||||||
#ifndef TWN_DRAW_C_H
|
#ifndef TWN_DRAW_C_H
|
||||||
#define TWN_DRAW_C_H
|
#define TWN_DRAW_C_H
|
||||||
|
|
||||||
|
/* TODO: structure more categorically */
|
||||||
|
|
||||||
#include "twn_textures_c.h"
|
#include "twn_textures_c.h"
|
||||||
|
#include "twn_types_c.h"
|
||||||
#include "twn_text_c.h"
|
#include "twn_text_c.h"
|
||||||
#include "twn_util.h"
|
|
||||||
#include "twn_option.h"
|
#include "twn_option.h"
|
||||||
|
#include "twn_deferred_commands.h"
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <stb_truetype.h>
|
#include <stb_truetype.h>
|
||||||
|
|
||||||
#ifdef EMSCRIPTEN
|
|
||||||
#include <GLES2/gl2.h>
|
|
||||||
#else
|
|
||||||
#include <glad/glad.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
extern Matrix4 camera_projection_matrix;
|
extern Matrix4 camera_projection_matrix;
|
||||||
extern Matrix4 camera_look_at_matrix;
|
extern Matrix4 camera_look_at_matrix;
|
||||||
|
|
||||||
|
extern double depth_range_low, depth_range_high;
|
||||||
|
|
||||||
#define QUAD_ELEMENT_BUFFER_LENGTH (65536 / 6)
|
#define QUAD_ELEMENT_BUFFER_LENGTH (65536 / 6)
|
||||||
#define CIRCLE_VERTICES_MAX 2048
|
#define CIRCLE_VERTICES_MAX 2048
|
||||||
#define CIRCLE_ELEMENT_BUFFER_LENGTH (CIRCLE_VERTICES_MAX * 3)
|
|
||||||
|
/* TODO: limit to only most necessary */
|
||||||
|
enum {
|
||||||
|
TWN_FLOAT,
|
||||||
|
TWN_INT,
|
||||||
|
TWN_SHORT,
|
||||||
|
TWN_UNSIGNED_SHORT,
|
||||||
|
TWN_UNSIGNED_INT,
|
||||||
|
TWN_BYTE,
|
||||||
|
TWN_UNSIGNED_BYTE,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
typedef GLuint VertexBuffer;
|
typedef uint32_t VertexBuffer;
|
||||||
|
|
||||||
typedef struct VertexBufferBuilder {
|
typedef struct VertexBufferBuilder {
|
||||||
size_t bytes_left;
|
size_t size;
|
||||||
void *mapping;
|
void *base;
|
||||||
} VertexBufferBuilder;
|
} VertexBufferBuilder;
|
||||||
|
|
||||||
|
|
||||||
@ -39,45 +47,55 @@ typedef struct SpritePrimitive {
|
|||||||
Color color;
|
Color color;
|
||||||
float rotation;
|
float rotation;
|
||||||
TextureKey texture_key;
|
TextureKey texture_key;
|
||||||
bool flip_x;
|
|
||||||
bool flip_y;
|
|
||||||
bool repeat;
|
|
||||||
|
|
||||||
m_option_list(
|
m_option_list(
|
||||||
Rect, texture_region )
|
Rect, texture_region )
|
||||||
|
|
||||||
|
bool flip_x;
|
||||||
|
bool flip_y;
|
||||||
|
bool repeat;
|
||||||
} SpritePrimitive;
|
} SpritePrimitive;
|
||||||
|
|
||||||
|
typedef struct LinePrimitive {
|
||||||
|
Vec2 start;
|
||||||
|
Vec2 finish;
|
||||||
|
float thickness;
|
||||||
|
Color color;
|
||||||
|
} LinePrimitive;
|
||||||
|
|
||||||
typedef struct RectPrimitive {
|
typedef struct RectPrimitive {
|
||||||
Rect rect;
|
Rect rect;
|
||||||
Color color;
|
Color color;
|
||||||
} RectPrimitive;
|
} RectPrimitive;
|
||||||
|
|
||||||
typedef struct CirclePrimitive {
|
typedef struct CirclePrimitive {
|
||||||
|
Vec2 position;
|
||||||
float radius;
|
float radius;
|
||||||
Color color;
|
Color color;
|
||||||
Vec2 position;
|
|
||||||
} CirclePrimitive;
|
} CirclePrimitive;
|
||||||
|
|
||||||
typedef struct TextPrimitive {
|
typedef struct TextPrimitive {
|
||||||
Color color;
|
|
||||||
Vec2 position;
|
Vec2 position;
|
||||||
char *text;
|
char *text;
|
||||||
const char *font;
|
const char *font;
|
||||||
|
Color color;
|
||||||
int height_px;
|
int height_px;
|
||||||
} TextPrimitive;
|
} TextPrimitive;
|
||||||
|
|
||||||
typedef enum Primitive2DType {
|
typedef enum Primitive2DType {
|
||||||
PRIMITIVE_2D_SPRITE,
|
PRIMITIVE_2D_SPRITE,
|
||||||
|
PRIMITIVE_2D_LINE,
|
||||||
PRIMITIVE_2D_RECT,
|
PRIMITIVE_2D_RECT,
|
||||||
PRIMITIVE_2D_CIRCLE,
|
PRIMITIVE_2D_CIRCLE,
|
||||||
PRIMITIVE_2D_TEXT,
|
PRIMITIVE_2D_TEXT,
|
||||||
} Primitive2DType;
|
} Primitive2DType;
|
||||||
|
|
||||||
typedef struct Primitive2D {
|
typedef struct Primitive2D {
|
||||||
Primitive2DType type;
|
Primitive2DType type; /* TODO: separate to structure of arrays for more efficient memory usage? */
|
||||||
|
|
||||||
union {
|
union {
|
||||||
SpritePrimitive sprite;
|
SpritePrimitive sprite;
|
||||||
|
LinePrimitive line;
|
||||||
RectPrimitive rect;
|
RectPrimitive rect;
|
||||||
CirclePrimitive circle;
|
CirclePrimitive circle;
|
||||||
TextPrimitive text;
|
TextPrimitive text;
|
||||||
@ -85,34 +103,30 @@ typedef struct Primitive2D {
|
|||||||
} Primitive2D;
|
} Primitive2D;
|
||||||
|
|
||||||
/* union for in-place recalculation of texture coordinates */
|
/* union for in-place recalculation of texture coordinates */
|
||||||
union UncoloredSpaceTriangle {
|
/* needs to be later resolved in texture atlas */
|
||||||
/* pending for sending, uvs are not final as texture atlases could update */
|
typedef struct UncoloredSpaceTriangle {
|
||||||
struct UncoloredSpaceTrianglePrimitive {
|
|
||||||
Vec3 v0;
|
Vec3 v0;
|
||||||
Vec2 uv0; /* in pixels */
|
Vec2 uv0; /* in pixels */
|
||||||
Vec3 v1;
|
Vec3 v1;
|
||||||
Vec2 uv1; /* in pixels */
|
Vec2 uv1; /* in pixels */
|
||||||
Vec3 v2;
|
Vec3 v2;
|
||||||
Vec2 uv2; /* in pixels */
|
Vec2 uv2; /* in pixels */
|
||||||
} primitive;
|
} UncoloredSpaceTriangle;
|
||||||
|
|
||||||
/* TODO: have it packed? */
|
typedef struct SpaceBillboard {
|
||||||
/* structure that is passed in opengl vertex array */
|
Vec3 position;
|
||||||
struct UncoloredSpaceTrianglePayload {
|
Vec2 size;
|
||||||
Vec3 v0;
|
Color color;
|
||||||
Vec2 uv0;
|
// TextureKey texture; /* is assumed from other places */
|
||||||
Vec3 v1;
|
bool cylindrical;
|
||||||
Vec2 uv1;
|
} SpaceBillboard;
|
||||||
Vec3 v2;
|
|
||||||
Vec2 uv2;
|
|
||||||
} payload;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* batch of primitives with overlapping properties */
|
/* batch of primitives with overlapping properties */
|
||||||
typedef struct MeshBatch {
|
typedef struct MeshBatch {
|
||||||
uint8_t *primitives;
|
uint8_t *primitives; /* note: interpretation of it is arbitrary */
|
||||||
} MeshBatch;
|
} MeshBatch;
|
||||||
|
|
||||||
|
/* TODO: use atlas id instead */
|
||||||
typedef struct MeshBatchItem {
|
typedef struct MeshBatchItem {
|
||||||
TextureKey key;
|
TextureKey key;
|
||||||
struct MeshBatch value;
|
struct MeshBatch value;
|
||||||
@ -123,6 +137,95 @@ typedef struct TextCache {
|
|||||||
} TextCache;
|
} TextCache;
|
||||||
|
|
||||||
|
|
||||||
|
/* TODO: try using the fact we utilize edge coloring and step virtual color attributes to bogus points */
|
||||||
|
/* this is only doable is we take out color attribute to separate array or a portion of it */
|
||||||
|
/* interleaved vertex array data */
|
||||||
|
typedef struct ElementIndexedQuad {
|
||||||
|
/* upper-left */
|
||||||
|
Vec2 v0;
|
||||||
|
Vec2 uv0;
|
||||||
|
Color c0;
|
||||||
|
/* bottom-left */
|
||||||
|
Vec2 v1;
|
||||||
|
Vec2 uv1;
|
||||||
|
Color c1;
|
||||||
|
/* bottom-right */
|
||||||
|
Vec2 v2;
|
||||||
|
Vec2 uv2;
|
||||||
|
Color c2;
|
||||||
|
/* upper-right */
|
||||||
|
Vec2 v3;
|
||||||
|
Vec2 uv3;
|
||||||
|
Color c3;
|
||||||
|
} ElementIndexedQuad;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct ElementIndexedQuadWithoutColor {
|
||||||
|
/* upper-left */
|
||||||
|
Vec2 v0;
|
||||||
|
Vec2 uv0;
|
||||||
|
/* bottom-left */
|
||||||
|
Vec2 v1;
|
||||||
|
Vec2 uv1;
|
||||||
|
/* bottom-right */
|
||||||
|
Vec2 v2;
|
||||||
|
Vec2 uv2;
|
||||||
|
/* upper-right */
|
||||||
|
Vec2 v3;
|
||||||
|
Vec2 uv3;
|
||||||
|
} ElementIndexedQuadWithoutColor;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct ElementIndexedQuadWithoutTexture {
|
||||||
|
/* upper-left */
|
||||||
|
Vec2 v0;
|
||||||
|
Color c0;
|
||||||
|
/* bottom-left */
|
||||||
|
Vec2 v1;
|
||||||
|
Color c1;
|
||||||
|
/* bottom-right */
|
||||||
|
Vec2 v2;
|
||||||
|
Color c2;
|
||||||
|
/* upper-right */
|
||||||
|
Vec2 v3;
|
||||||
|
Color c3;
|
||||||
|
} ElementIndexedQuadWithoutTexture;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct ElementIndexedQuadWithoutColorWithoutTexture {
|
||||||
|
/* upper-left */
|
||||||
|
Vec2 v0;
|
||||||
|
/* bottom-left */
|
||||||
|
Vec2 v1;
|
||||||
|
/* bottom-right */
|
||||||
|
Vec2 v2;
|
||||||
|
/* upper-right */
|
||||||
|
Vec2 v3;
|
||||||
|
} ElementIndexedQuadWithoutColorWithoutTexture;
|
||||||
|
|
||||||
|
/* TODO: no color variant */
|
||||||
|
typedef struct ElementIndexedBillboard {
|
||||||
|
/* upper-left */
|
||||||
|
Vec3 v0;
|
||||||
|
Vec2 uv0;
|
||||||
|
Color c0;
|
||||||
|
/* bottom-left */
|
||||||
|
Vec3 v1;
|
||||||
|
Vec2 uv1;
|
||||||
|
Color c1;
|
||||||
|
/* bottom-right */
|
||||||
|
Vec3 v2;
|
||||||
|
Vec2 uv2;
|
||||||
|
Color c2;
|
||||||
|
/* upper-right */
|
||||||
|
Vec3 v3;
|
||||||
|
Vec2 uv3;
|
||||||
|
Color c3;
|
||||||
|
} ElementIndexedBillboard;
|
||||||
|
|
||||||
|
|
||||||
|
bool render_init(void);
|
||||||
|
|
||||||
/* renders the background, then the primitives in all render queues */
|
/* renders the background, then the primitives in all render queues */
|
||||||
void render(void);
|
void render(void);
|
||||||
|
|
||||||
@ -138,6 +241,7 @@ void create_circle_geometry(Vec2 position,
|
|||||||
|
|
||||||
struct QuadBatch {
|
struct QuadBatch {
|
||||||
size_t size; /* how many primitives are in current batch */
|
size_t size; /* how many primitives are in current batch */
|
||||||
|
TextureKey texture_key;
|
||||||
TextureMode mode; /* how color should be applied */
|
TextureMode mode; /* how color should be applied */
|
||||||
bool constant_colored; /* whether colored batch is uniformly colored */
|
bool constant_colored; /* whether colored batch is uniformly colored */
|
||||||
bool repeat; /* whether repeat is needed */
|
bool repeat; /* whether repeat is needed */
|
||||||
@ -149,9 +253,6 @@ struct QuadBatch collect_rect_batch(const Primitive2D primitives[], size_t len);
|
|||||||
void render_sprite_batch(const Primitive2D primitives[], struct QuadBatch batch);
|
void render_sprite_batch(const Primitive2D primitives[], struct QuadBatch batch);
|
||||||
void render_rect_batch(const Primitive2D primitives[], struct QuadBatch batch);
|
void render_rect_batch(const Primitive2D primitives[], struct QuadBatch batch);
|
||||||
|
|
||||||
void draw_uncolored_space_traingle_batch(MeshBatch *batch,
|
|
||||||
TextureKey texture_key);
|
|
||||||
|
|
||||||
/* text */
|
/* text */
|
||||||
|
|
||||||
void render_text(const TextPrimitive *text);
|
void render_text(const TextPrimitive *text);
|
||||||
@ -166,6 +267,8 @@ void text_cache_reset_arena(TextCache *cache);
|
|||||||
|
|
||||||
VertexBuffer create_vertex_buffer(void);
|
VertexBuffer create_vertex_buffer(void);
|
||||||
|
|
||||||
|
void restart_scratch_vertex_arrays(void);
|
||||||
|
|
||||||
VertexBuffer get_scratch_vertex_array(void);
|
VertexBuffer get_scratch_vertex_array(void);
|
||||||
|
|
||||||
void delete_vertex_buffer(VertexBuffer buffer);
|
void delete_vertex_buffer(VertexBuffer buffer);
|
||||||
@ -175,16 +278,14 @@ void specify_vertex_buffer(VertexBuffer buffer, void const *data, size_t bytes);
|
|||||||
/* uses present in 1.5 buffer mapping feature */
|
/* uses present in 1.5 buffer mapping feature */
|
||||||
VertexBufferBuilder build_vertex_buffer(VertexBuffer buffer, size_t bytes);
|
VertexBufferBuilder build_vertex_buffer(VertexBuffer buffer, size_t bytes);
|
||||||
|
|
||||||
/* collects bytes for sending to the gpu until all is pushed, which is when false is returned */
|
void finish_vertex_builder(VertexBufferBuilder *builder);
|
||||||
bool push_to_vertex_buffer_builder(VertexBufferBuilder *builder,
|
|
||||||
void const *bytes,
|
|
||||||
size_t size);
|
|
||||||
|
|
||||||
/* state */
|
/* state */
|
||||||
|
|
||||||
void setup_viewport(int x, int y, int width, int height);
|
void setup_viewport(int x, int y, int width, int height);
|
||||||
|
|
||||||
void clear_draw_buffer(void);
|
void clear_draw_buffer(void);
|
||||||
|
void finally_clear_draw_buffer(DeferredCommandClear command);
|
||||||
|
|
||||||
void swap_buffers(void);
|
void swap_buffers(void);
|
||||||
|
|
||||||
@ -196,35 +297,28 @@ VertexBuffer get_circle_element_buffer(void);
|
|||||||
|
|
||||||
void render_circle(const CirclePrimitive *circle);
|
void render_circle(const CirclePrimitive *circle);
|
||||||
|
|
||||||
|
void render_line(const LinePrimitive *line);
|
||||||
|
|
||||||
void render_rectangle(const RectPrimitive *rectangle);
|
void render_rectangle(const RectPrimitive *rectangle);
|
||||||
|
|
||||||
void use_space_pipeline(void);
|
|
||||||
|
|
||||||
void use_2d_pipeline(void);
|
|
||||||
|
|
||||||
void use_texture_mode(TextureMode mode);
|
|
||||||
|
|
||||||
void finally_render_quads(Primitive2D const primitives[],
|
void finally_render_quads(Primitive2D const primitives[],
|
||||||
struct QuadBatch batch,
|
struct QuadBatch batch,
|
||||||
VertexBuffer buffer);
|
VertexBuffer buffer);
|
||||||
|
|
||||||
size_t get_quad_payload_size(struct QuadBatch batch);
|
size_t get_quad_payload_size(struct QuadBatch batch);
|
||||||
|
|
||||||
bool push_quad_payload_to_vertex_buffer_builder(struct QuadBatch batch,
|
void push_quad_payload_to_vertex_buffer_builder(struct QuadBatch batch,
|
||||||
|
size_t index,
|
||||||
VertexBufferBuilder *builder,
|
VertexBufferBuilder *builder,
|
||||||
Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3,
|
Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3,
|
||||||
Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3,
|
Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3,
|
||||||
Color color);
|
Color color);
|
||||||
|
|
||||||
void finally_draw_uncolored_space_traingle_batch(MeshBatch const *batch,
|
void finally_draw_uncolored_space_traingle_batch(MeshBatch const *batch,
|
||||||
TextureKey texture_key,
|
TextureKey texture_key);
|
||||||
VertexBuffer buffer);
|
|
||||||
|
|
||||||
size_t get_text_payload_size(void);
|
void finally_draw_billboard_batch(MeshBatch const *batch,
|
||||||
|
TextureKey texture_key);
|
||||||
bool push_text_payload_to_vertex_buffer_builder(FontData const *font_data,
|
|
||||||
VertexBufferBuilder *builder,
|
|
||||||
stbtt_aligned_quad quad);
|
|
||||||
|
|
||||||
void finally_draw_text(FontData const *font_data,
|
void finally_draw_text(FontData const *font_data,
|
||||||
size_t len,
|
size_t len,
|
||||||
@ -232,19 +326,13 @@ void finally_draw_text(FontData const *font_data,
|
|||||||
VertexBuffer buffer);
|
VertexBuffer buffer);
|
||||||
|
|
||||||
void render_skybox(void);
|
void render_skybox(void);
|
||||||
|
void finally_render_skybox(DeferredCommandDrawSkybox);
|
||||||
void finally_render_skybox(char *paths_in_use);
|
|
||||||
|
|
||||||
void apply_fog(void);
|
|
||||||
|
|
||||||
void finally_apply_fog(float start, float end, float density, Color color);
|
|
||||||
|
|
||||||
void pop_fog(void);
|
|
||||||
|
|
||||||
void finally_pop_fog(void);
|
|
||||||
|
|
||||||
void start_render_frame(void);
|
void start_render_frame(void);
|
||||||
|
|
||||||
void end_render_frame(void);
|
void end_render_frame(void);
|
||||||
|
|
||||||
|
void finally_draw_command(DeferredCommandDraw command);
|
||||||
|
|
||||||
|
void issue_deferred_draw_commands(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
#include "twn_draw.h"
|
|
||||||
#include "twn_draw_c.h"
|
|
||||||
#include "twn_util.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
static float start_cache, end_cache, density_cache;
|
|
||||||
static Color color_cache;
|
|
||||||
static bool fog_used = false;
|
|
||||||
|
|
||||||
|
|
||||||
void draw_fog(float start, float end, float density, Color color) {
|
|
||||||
start_cache = start;
|
|
||||||
end_cache = end;
|
|
||||||
density_cache = density;
|
|
||||||
color_cache = color;
|
|
||||||
fog_used = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void apply_fog(void) {
|
|
||||||
if (!fog_used)
|
|
||||||
return;
|
|
||||||
|
|
||||||
finally_apply_fog(start_cache, end_cache, density_cache, color_cache);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void pop_fog(void) {
|
|
||||||
if (!fog_used)
|
|
||||||
return;
|
|
||||||
|
|
||||||
finally_pop_fog();
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
#include "twn_gpu_texture_c.h"
|
|
||||||
#include "twn_util_c.h"
|
|
||||||
|
|
||||||
|
|
||||||
GPUTexture create_gpu_texture(TextureFilter filter, bool generate_mipmaps) {
|
|
||||||
GLuint texture;
|
|
||||||
glGenTextures(1, &texture);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, texture);
|
|
||||||
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, (GLboolean)generate_mipmaps);
|
|
||||||
|
|
||||||
if (filter == TEXTURE_FILTER_NEAREAST) {
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
||||||
} else if (filter == TEXTURE_FILTER_LINEAR) {
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
||||||
}
|
|
||||||
|
|
||||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
||||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
||||||
|
|
||||||
#if !defined(EMSCRIPTEN)
|
|
||||||
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
return texture;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void delete_gpu_texture(GPUTexture texture) {
|
|
||||||
glDeleteTextures(1, &texture);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void upload_gpu_texture(GPUTexture texture, void *pixels, int channels, int width, int height) {
|
|
||||||
glBindTexture(GL_TEXTURE_2D, texture);
|
|
||||||
|
|
||||||
int format_internal, format;
|
|
||||||
if (channels == 4) {
|
|
||||||
format_internal = GL_RGBA8;
|
|
||||||
format = GL_RGBA;
|
|
||||||
} else if (channels == 3) {
|
|
||||||
format_internal = GL_RGBA8;
|
|
||||||
format = GL_RGB;
|
|
||||||
} else if (channels == 1) {
|
|
||||||
format_internal = GL_ALPHA;
|
|
||||||
format = GL_ALPHA;
|
|
||||||
} else {
|
|
||||||
CRY("upload_gpu_texture", "Unsupported channel count");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
glTexImage2D(GL_TEXTURE_2D,
|
|
||||||
0,
|
|
||||||
format_internal,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
0,
|
|
||||||
format,
|
|
||||||
GL_UNSIGNED_BYTE,
|
|
||||||
pixels);
|
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void bind_gpu_texture(GPUTexture texture) {
|
|
||||||
glBindTexture(GL_TEXTURE_2D, texture);
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,8 @@
|
|||||||
#include "twn_draw_c.h"
|
|
||||||
#include "twn_engine_context_c.h"
|
#include "twn_engine_context_c.h"
|
||||||
#include "twn_util_c.h"
|
#include "twn_util_c.h"
|
||||||
|
#include "twn_draw_c.h"
|
||||||
|
|
||||||
|
#include <stb_ds.h>
|
||||||
|
|
||||||
#ifdef EMSCRIPTEN
|
#ifdef EMSCRIPTEN
|
||||||
#include <GLES2/gl2.h>
|
#include <GLES2/gl2.h>
|
||||||
@ -42,16 +44,15 @@ VertexBuffer get_quad_element_buffer(void) {
|
|||||||
VertexBufferBuilder builder = build_vertex_buffer(buffer, sizeof (GLshort) * QUAD_ELEMENT_BUFFER_LENGTH * 6 );
|
VertexBufferBuilder builder = build_vertex_buffer(buffer, sizeof (GLshort) * QUAD_ELEMENT_BUFFER_LENGTH * 6 );
|
||||||
|
|
||||||
for (size_t i = 0; i < QUAD_ELEMENT_BUFFER_LENGTH; ++i) {
|
for (size_t i = 0; i < QUAD_ELEMENT_BUFFER_LENGTH; ++i) {
|
||||||
GLshort indices[6];
|
((GLshort *)builder.base)[i * 6 + 0] = (GLshort)(i * 4 + 0);
|
||||||
indices[0] = (GLshort)(i * 4 + 0);
|
((GLshort *)builder.base)[i * 6 + 1] = (GLshort)(i * 4 + 1);
|
||||||
indices[1] = (GLshort)(i * 4 + 1);
|
((GLshort *)builder.base)[i * 6 + 2] = (GLshort)(i * 4 + 2);
|
||||||
indices[2] = (GLshort)(i * 4 + 2);
|
((GLshort *)builder.base)[i * 6 + 3] = (GLshort)(i * 4 + 2);
|
||||||
indices[3] = (GLshort)(i * 4 + 2);
|
((GLshort *)builder.base)[i * 6 + 4] = (GLshort)(i * 4 + 3);
|
||||||
indices[4] = (GLshort)(i * 4 + 3);
|
((GLshort *)builder.base)[i * 6 + 5] = (GLshort)(i * 4 + 0);
|
||||||
indices[5] = (GLshort)(i * 4 + 0);
|
|
||||||
|
|
||||||
push_to_vertex_buffer_builder(&builder, indices, sizeof indices);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
finish_vertex_builder(&builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_assert_always(buffer);
|
SDL_assert_always(buffer);
|
||||||
@ -65,27 +66,135 @@ VertexBuffer get_circle_element_buffer(void) {
|
|||||||
|
|
||||||
if (buffer == 0) {
|
if (buffer == 0) {
|
||||||
buffer = create_vertex_buffer();
|
buffer = create_vertex_buffer();
|
||||||
VertexBufferBuilder builder = build_vertex_buffer(buffer, sizeof (GLshort) * CIRCLE_ELEMENT_BUFFER_LENGTH);
|
VertexBufferBuilder builder = build_vertex_buffer(buffer, sizeof (GLshort) * (CIRCLE_VERTICES_MAX - 2) * 3);
|
||||||
|
|
||||||
for (size_t i = 1; i < CIRCLE_VERTICES_MAX; ++i) {
|
for (size_t i = 1; i < CIRCLE_VERTICES_MAX - 1; ++i) {
|
||||||
/* first one is center point index, always zero */
|
/* first one is center point index, always zero */
|
||||||
GLshort indices[3];
|
((GLshort *)builder.base)[(i - 1) * 3 + 0] = 0;
|
||||||
|
|
||||||
indices[0] = 0;
|
|
||||||
|
|
||||||
/* generated point index */
|
/* generated point index */
|
||||||
indices[1] = (GLshort)i;
|
((GLshort *)builder.base)[(i - 1) * 3 + 1] = (GLshort)i;
|
||||||
|
((GLshort *)builder.base)[(i - 1) * 3 + 2] = (GLshort)i + 1;
|
||||||
size_t index = (i + 1) % (CIRCLE_VERTICES_MAX - 1);
|
|
||||||
if (index == 0) /* don't use center for outer ring */
|
|
||||||
index = (CIRCLE_VERTICES_MAX - 1);
|
|
||||||
indices[2] = (GLshort)index;
|
|
||||||
|
|
||||||
push_to_vertex_buffer_builder(&builder, indices, sizeof indices);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
finish_vertex_builder(&builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_assert_always(buffer);
|
SDL_assert_always(buffer);
|
||||||
|
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* potentially double buffered array of vertex array handles */
|
||||||
|
/* we assume they will be refilled fully each frame */
|
||||||
|
static size_t scratch_va_front_used, scratch_va_back_used;
|
||||||
|
static GLuint *front_scratch_vertex_arrays, *back_scratch_vertex_arrays;
|
||||||
|
static GLuint **current_scratch_vertex_array = &front_scratch_vertex_arrays;
|
||||||
|
|
||||||
|
void restart_scratch_vertex_arrays(void) {
|
||||||
|
scratch_va_front_used = 0;
|
||||||
|
scratch_va_back_used = 0;
|
||||||
|
|
||||||
|
if (ctx.render_double_buffered) {
|
||||||
|
current_scratch_vertex_array = current_scratch_vertex_array == &front_scratch_vertex_arrays ?
|
||||||
|
&back_scratch_vertex_arrays : &front_scratch_vertex_arrays;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GLuint get_scratch_vertex_array(void) {
|
||||||
|
size_t *used = current_scratch_vertex_array == &front_scratch_vertex_arrays ?
|
||||||
|
&scratch_va_front_used : &scratch_va_back_used;
|
||||||
|
|
||||||
|
if (arrlenu(*current_scratch_vertex_array) <= *used) {
|
||||||
|
GLuint handle;
|
||||||
|
glGenBuffers(1, &handle);
|
||||||
|
arrpush(*current_scratch_vertex_array, handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
(*used)++;
|
||||||
|
return (*current_scratch_vertex_array)[*used - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GPUTexture create_gpu_texture(TextureFilter filter, bool generate_mipmaps) {
|
||||||
|
GLuint texture;
|
||||||
|
glGenTextures(1, &texture);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
|
||||||
|
#if !defined(EMSCRIPTEN)
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, (GLboolean)generate_mipmaps);
|
||||||
|
#else
|
||||||
|
if (generate_mipmaps)
|
||||||
|
glGenerateMipmap(GL_TEXTURE_2D);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (filter == TEXTURE_FILTER_NEAREAST) {
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||||
|
} else if (filter == TEXTURE_FILTER_LINEAR) {
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||||
|
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||||
|
|
||||||
|
#if !defined(EMSCRIPTEN)
|
||||||
|
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void delete_gpu_texture(GPUTexture texture) {
|
||||||
|
glDeleteTextures(1, &texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void upload_gpu_texture(GPUTexture texture, void *pixels, int channels, int width, int height) {
|
||||||
|
glBindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
|
||||||
|
int format_internal, format;
|
||||||
|
if (channels == 4) {
|
||||||
|
#ifdef EMSCRIPTEN
|
||||||
|
format_internal = GL_RGBA;
|
||||||
|
#else
|
||||||
|
format_internal = GL_RGBA8;
|
||||||
|
#endif
|
||||||
|
format = GL_RGBA;
|
||||||
|
} else if (channels == 3) {
|
||||||
|
#ifdef EMSCRIPTEN
|
||||||
|
format_internal = GL_RGBA;
|
||||||
|
#else
|
||||||
|
format_internal = GL_RGBA8;
|
||||||
|
#endif
|
||||||
|
format = GL_RGB;
|
||||||
|
} else if (channels == 1) {
|
||||||
|
format_internal = GL_ALPHA;
|
||||||
|
format = GL_ALPHA;
|
||||||
|
} else {
|
||||||
|
CRY("upload_gpu_texture", "Unsupported channel count");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glTexImage2D(GL_TEXTURE_2D,
|
||||||
|
0,
|
||||||
|
format_internal,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
0,
|
||||||
|
format,
|
||||||
|
GL_UNSIGNED_BYTE,
|
||||||
|
pixels);
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void bind_gpu_texture(GPUTexture texture) {
|
||||||
|
glBindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
}
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
#define TWN_GPU_TEXTURE_C_H
|
#define TWN_GPU_TEXTURE_C_H
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
typedef GLuint GPUTexture;
|
typedef uint32_t GPUTexture;
|
||||||
|
|
||||||
typedef enum TextureFilter {
|
typedef enum TextureFilter {
|
||||||
TEXTURE_FILTER_NEAREAST,
|
TEXTURE_FILTER_NEAREAST,
|
||||||
|
@ -1,30 +1,173 @@
|
|||||||
#include "twn_draw_c.h"
|
#include "twn_draw_c.h"
|
||||||
|
|
||||||
|
#include <stb_ds.h>
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
|
|
||||||
struct QuadBatch collect_quad_batch(const Primitive2D primitives[], size_t len) {
|
void finally_render_quads(const Primitive2D primitives[],
|
||||||
if (primitives[0].type == PRIMITIVE_2D_SPRITE)
|
const struct QuadBatch batch,
|
||||||
return collect_sprite_batch(primitives, len);
|
const VertexBuffer buffer)
|
||||||
else if (primitives[0].type == PRIMITIVE_2D_RECT)
|
|
||||||
return collect_rect_batch(primitives, len);
|
|
||||||
else
|
|
||||||
SDL_assert(false);
|
|
||||||
|
|
||||||
return (struct QuadBatch){0};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* assumes that orthogonal matrix setup is done already */
|
|
||||||
void render_quad_batch(const Primitive2D primitives[],
|
|
||||||
const struct QuadBatch batch)
|
|
||||||
{
|
{
|
||||||
if (primitives[0].type == PRIMITIVE_2D_SPRITE)
|
DeferredCommandDraw command = {0};
|
||||||
render_sprite_batch(primitives, batch);
|
|
||||||
else if (primitives[0].type == PRIMITIVE_2D_RECT)
|
|
||||||
render_rect_batch(primitives, batch);
|
|
||||||
else
|
|
||||||
SDL_assert(false);
|
|
||||||
|
|
||||||
return (struct QuadBatch){0};
|
uint32_t off = 0, voff = 0, uvoff = 0, coff = 0;
|
||||||
|
|
||||||
|
if (!batch.constant_colored && batch.textured) {
|
||||||
|
off = offsetof(ElementIndexedQuad, v1);
|
||||||
|
voff = offsetof(ElementIndexedQuad, v0);
|
||||||
|
uvoff = offsetof(ElementIndexedQuad, uv0);
|
||||||
|
coff = offsetof(ElementIndexedQuad, c0);
|
||||||
|
} else if (batch.constant_colored && batch.textured) {
|
||||||
|
off = offsetof(ElementIndexedQuadWithoutColor, v1);
|
||||||
|
voff = offsetof(ElementIndexedQuadWithoutColor, v0);
|
||||||
|
uvoff = offsetof(ElementIndexedQuadWithoutColor, uv0);
|
||||||
|
} else if (!batch.constant_colored && !batch.textured) {
|
||||||
|
off = offsetof(ElementIndexedQuadWithoutTexture, v1);
|
||||||
|
voff = offsetof(ElementIndexedQuadWithoutTexture, v0);
|
||||||
|
coff = offsetof(ElementIndexedQuad, c0);
|
||||||
|
} else if (batch.constant_colored && !batch.textured) {
|
||||||
|
off = offsetof(ElementIndexedQuadWithoutColorWithoutTexture, v1);
|
||||||
|
voff = offsetof(ElementIndexedQuadWithoutColorWithoutTexture, v0);
|
||||||
|
}
|
||||||
|
|
||||||
|
command.vertices = (AttributeArrayPointer) {
|
||||||
|
.arity = 2,
|
||||||
|
.type = TWN_FLOAT,
|
||||||
|
.stride = off,
|
||||||
|
.offset = voff,
|
||||||
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
if (batch.textured)
|
||||||
|
command.texcoords = (AttributeArrayPointer) {
|
||||||
|
.arity = 2,
|
||||||
|
.type = TWN_FLOAT,
|
||||||
|
.stride = off,
|
||||||
|
.offset = uvoff,
|
||||||
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!batch.constant_colored) {
|
||||||
|
command.colors = (AttributeArrayPointer) {
|
||||||
|
.arity = 4,
|
||||||
|
.type = TWN_UNSIGNED_BYTE,
|
||||||
|
.stride = off,
|
||||||
|
.offset = coff,
|
||||||
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
command.constant_colored = true;
|
||||||
|
command.color = primitives[0].sprite.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (batch.textured) {
|
||||||
|
command.textured = true;
|
||||||
|
command.texture_key = batch.texture_key;
|
||||||
|
command.texture_repeat = batch.repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
command.element_buffer = get_quad_element_buffer();
|
||||||
|
command.element_count = 6 * (uint32_t)batch.size;
|
||||||
|
command.range_end = 6 * (uint32_t)batch.size;
|
||||||
|
|
||||||
|
command.texture_mode = batch.mode;
|
||||||
|
command.pipeline = PIPELINE_2D;
|
||||||
|
|
||||||
|
command.depth_range_high = depth_range_high;
|
||||||
|
command.depth_range_low = depth_range_low;
|
||||||
|
|
||||||
|
DeferredCommand final_command = {
|
||||||
|
.type = DEFERRED_COMMAND_TYPE_DRAW,
|
||||||
|
.draw = command
|
||||||
|
};
|
||||||
|
|
||||||
|
arrpush(deferred_commands, final_command);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
size_t get_quad_payload_size(struct QuadBatch batch) {
|
||||||
|
if (batch.constant_colored && batch.textured)
|
||||||
|
return sizeof (ElementIndexedQuadWithoutColor);
|
||||||
|
else if (!batch.constant_colored && batch.textured)
|
||||||
|
return sizeof (ElementIndexedQuad);
|
||||||
|
else if (batch.constant_colored && !batch.textured)
|
||||||
|
return sizeof (ElementIndexedQuadWithoutColorWithoutTexture);
|
||||||
|
else if (!batch.constant_colored && !batch.textured)
|
||||||
|
return sizeof (ElementIndexedQuadWithoutTexture);
|
||||||
|
|
||||||
|
SDL_assert(false);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void push_quad_payload_to_vertex_buffer_builder(struct QuadBatch batch,
|
||||||
|
size_t index,
|
||||||
|
VertexBufferBuilder *builder,
|
||||||
|
Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3,
|
||||||
|
Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3,
|
||||||
|
Color color)
|
||||||
|
{
|
||||||
|
if (!batch.constant_colored && batch.textured) {
|
||||||
|
ElementIndexedQuad const payload = {
|
||||||
|
.v0 = v0,
|
||||||
|
.v1 = v1,
|
||||||
|
.v2 = v2,
|
||||||
|
.v3 = v3,
|
||||||
|
|
||||||
|
.uv0 = uv0,
|
||||||
|
.uv1 = uv1,
|
||||||
|
.uv2 = uv2,
|
||||||
|
.uv3 = uv3,
|
||||||
|
|
||||||
|
/* equal for all (flat shaded) */
|
||||||
|
.c0 = color,
|
||||||
|
// .c1 = color,
|
||||||
|
.c2 = color,
|
||||||
|
// .c3 = color,
|
||||||
|
};
|
||||||
|
|
||||||
|
((ElementIndexedQuad *)builder->base)[index] = payload;
|
||||||
|
|
||||||
|
} else if (batch.constant_colored && batch.textured) {
|
||||||
|
ElementIndexedQuadWithoutColor const payload = {
|
||||||
|
.v0 = v0,
|
||||||
|
.v1 = v1,
|
||||||
|
.v2 = v2,
|
||||||
|
.v3 = v3,
|
||||||
|
|
||||||
|
.uv0 = uv0,
|
||||||
|
.uv1 = uv1,
|
||||||
|
.uv2 = uv2,
|
||||||
|
.uv3 = uv3,
|
||||||
|
};
|
||||||
|
|
||||||
|
((ElementIndexedQuadWithoutColor *)builder->base)[index] = payload;
|
||||||
|
|
||||||
|
} else if (!batch.constant_colored && !batch.textured) {
|
||||||
|
ElementIndexedQuadWithoutTexture const payload = {
|
||||||
|
.v0 = v0,
|
||||||
|
.v1 = v1,
|
||||||
|
.v2 = v2,
|
||||||
|
.v3 = v3,
|
||||||
|
|
||||||
|
/* equal for all (flat shaded) */
|
||||||
|
.c0 = color,
|
||||||
|
// .c1 = color,
|
||||||
|
.c2 = color,
|
||||||
|
// .c3 = color,
|
||||||
|
};
|
||||||
|
|
||||||
|
((ElementIndexedQuadWithoutTexture *)builder->base)[index] = payload;
|
||||||
|
|
||||||
|
} else if (batch.constant_colored && !batch.textured) {
|
||||||
|
ElementIndexedQuadWithoutColorWithoutTexture const payload = {
|
||||||
|
.v0 = v0,
|
||||||
|
.v1 = v1,
|
||||||
|
.v2 = v2,
|
||||||
|
.v3 = v3,
|
||||||
|
};
|
||||||
|
|
||||||
|
((ElementIndexedQuadWithoutColorWithoutTexture *)builder->base)[index] = payload;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
#include "twn_draw.h"
|
#include "twn_draw.h"
|
||||||
#include "twn_draw_c.h"
|
#include "twn_draw_c.h"
|
||||||
#include "twn_engine_context_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>
|
#include <stb_ds.h>
|
||||||
|
|
||||||
@ -36,7 +32,7 @@ struct QuadBatch collect_rect_batch(const Primitive2D primitives[], size_t len)
|
|||||||
.constant_colored = true,
|
.constant_colored = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const uint32_t uniform_color = *(const uint32_t *)&primitives[0].rect.color;
|
const uint32_t uniform_color = *(const uint32_t *)(void const *)&primitives[0].rect.color;
|
||||||
|
|
||||||
/* batch size is clamped so that reallocated short indices could be used */
|
/* batch size is clamped so that reallocated short indices could be used */
|
||||||
if (len >= QUAD_ELEMENT_BUFFER_LENGTH)
|
if (len >= QUAD_ELEMENT_BUFFER_LENGTH)
|
||||||
@ -54,7 +50,7 @@ struct QuadBatch collect_rect_batch(const Primitive2D primitives[], size_t len)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
/* if all are modulated the same we can skip sending the color data */
|
/* if all are modulated the same we can skip sending the color data */
|
||||||
if (*(const uint32_t *)¤t->rect.color != uniform_color)
|
if (*(const uint32_t *)(void const *)¤t->rect.color != uniform_color)
|
||||||
batch.constant_colored = false;
|
batch.constant_colored = false;
|
||||||
|
|
||||||
++batch.size;
|
++batch.size;
|
||||||
@ -74,8 +70,6 @@ void render_rect_batch(const Primitive2D primitives[],
|
|||||||
/* single vertex array is used for every batch with NULL glBufferData() trick at the end */
|
/* single vertex array is used for every batch with NULL glBufferData() trick at the end */
|
||||||
VertexBuffer const vertex_array = get_scratch_vertex_array();
|
VertexBuffer const vertex_array = get_scratch_vertex_array();
|
||||||
|
|
||||||
use_texture_mode(batch.mode);
|
|
||||||
|
|
||||||
/* vertex population over a vertex buffer builder interface */
|
/* vertex population over a vertex buffer builder interface */
|
||||||
{
|
{
|
||||||
VertexBufferBuilder payload = build_vertex_buffer(vertex_array, get_quad_payload_size(batch) * batch.size);
|
VertexBufferBuilder payload = build_vertex_buffer(vertex_array, get_quad_payload_size(batch) * batch.size);
|
||||||
@ -91,7 +85,7 @@ void render_rect_batch(const Primitive2D primitives[],
|
|||||||
Vec2 v3 = { rect.rect.x + rect.rect.w, rect.rect.y };
|
Vec2 v3 = { rect.rect.x + rect.rect.w, rect.rect.y };
|
||||||
|
|
||||||
push_quad_payload_to_vertex_buffer_builder(
|
push_quad_payload_to_vertex_buffer_builder(
|
||||||
batch, &payload,
|
batch, i, &payload,
|
||||||
v0, v1, v2, v3,
|
v0, v1, v2, v3,
|
||||||
(Vec2){0}, (Vec2){0}, (Vec2){0}, (Vec2){0},
|
(Vec2){0}, (Vec2){0}, (Vec2){0}, (Vec2){0},
|
||||||
rect.color);
|
rect.color);
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
#include "twn_draw_c.h"
|
#include "twn_draw_c.h"
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
#include <stb_ds.h>
|
||||||
|
|
||||||
static char *paths_in_use;
|
static char *paths_in_use;
|
||||||
|
|
||||||
@ -21,6 +22,14 @@ void render_skybox(void) {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
/* note: ownership of 'paths_in_use' goes there */
|
/* note: ownership of 'paths_in_use' goes there */
|
||||||
finally_render_skybox(paths_in_use);
|
DeferredCommand command = {
|
||||||
|
.type = DEFERRED_COMMAND_TYPE_DRAW_SKYBOX,
|
||||||
|
.draw_skybox = (DeferredCommandDrawSkybox){
|
||||||
|
.paths = paths_in_use
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
arrpush(deferred_commands, command);
|
||||||
|
|
||||||
paths_in_use = NULL;
|
paths_in_use = NULL;
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ void draw_sprite_args(const DrawSpriteArgs args) {
|
|||||||
bool const stretch = m_or(args, stretch, false);
|
bool const stretch = m_or(args, stretch, false);
|
||||||
Rect const *texture_region = m_is_set(args, texture_region) ? &args.texture_region_opt : NULL;
|
Rect const *texture_region = m_is_set(args, texture_region) ? &args.texture_region_opt : NULL;
|
||||||
|
|
||||||
draw_sprite(args.path, args.rect, texture_region, color, rotation, flip_x, flip_y, stretch);
|
draw_sprite(args.texture, args.rect, texture_region, color, rotation, flip_x, flip_y, stretch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -69,12 +69,13 @@ struct QuadBatch collect_sprite_batch(const Primitive2D primitives[], size_t len
|
|||||||
|
|
||||||
struct QuadBatch batch = {
|
struct QuadBatch batch = {
|
||||||
.mode = textures_get_mode(&ctx.texture_cache, primitives[0].sprite.texture_key),
|
.mode = textures_get_mode(&ctx.texture_cache, primitives[0].sprite.texture_key),
|
||||||
|
.texture_key = primitives[0].sprite.texture_key,
|
||||||
.constant_colored = true,
|
.constant_colored = true,
|
||||||
.repeat = primitives[0].sprite.repeat,
|
.repeat = primitives[0].sprite.repeat,
|
||||||
.textured = true,
|
.textured = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const uint32_t uniform_color = *(const uint32_t *)&primitives[0].sprite.color;
|
const uint32_t uniform_color = *(const uint32_t *)(void const*)&primitives[0].sprite.color;
|
||||||
|
|
||||||
/* batch size is clamped so that reallocated short indices could be used */
|
/* batch size is clamped so that reallocated short indices could be used */
|
||||||
if (len >= QUAD_ELEMENT_BUFFER_LENGTH)
|
if (len >= QUAD_ELEMENT_BUFFER_LENGTH)
|
||||||
@ -108,7 +109,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 all are modulated the same we can skip sending the color data */
|
||||||
if (*(const uint32_t *)¤t->sprite.color != uniform_color)
|
if (*(const uint32_t *)(void const *)¤t->sprite.color != uniform_color)
|
||||||
batch.constant_colored = false;
|
batch.constant_colored = false;
|
||||||
|
|
||||||
++batch.size;
|
++batch.size;
|
||||||
@ -131,7 +132,7 @@ void render_sprite_batch(const Primitive2D primitives[],
|
|||||||
textures_get_dims(&ctx.texture_cache, primitives->sprite.texture_key);
|
textures_get_dims(&ctx.texture_cache, primitives->sprite.texture_key);
|
||||||
|
|
||||||
/* cached srcrect */
|
/* cached srcrect */
|
||||||
Rect cached_srcrect;
|
Rect cached_srcrect = {0};
|
||||||
TextureKey cached_srcrect_key = TEXTURE_KEY_INVALID;
|
TextureKey cached_srcrect_key = TEXTURE_KEY_INVALID;
|
||||||
|
|
||||||
/* vertex population over a vertex buffer builder interface */
|
/* vertex population over a vertex buffer builder interface */
|
||||||
@ -215,7 +216,7 @@ void render_sprite_batch(const Primitive2D primitives[],
|
|||||||
|
|
||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
|
|
||||||
const Vec2 c = frect_center(sprite.rect);
|
const Vec2 c = rect_center(sprite.rect);
|
||||||
const Vec2 t = fast_cossine(sprite.rotation + (float)M_PI_4);
|
const Vec2 t = fast_cossine(sprite.rotation + (float)M_PI_4);
|
||||||
const Vec2 d = {
|
const Vec2 d = {
|
||||||
.x = t.x * sprite.rect.w * (float)M_SQRT1_2,
|
.x = t.x * sprite.rect.w * (float)M_SQRT1_2,
|
||||||
@ -230,7 +231,7 @@ void render_sprite_batch(const Primitive2D primitives[],
|
|||||||
} else {
|
} else {
|
||||||
/* rotated non-square case*/
|
/* rotated non-square case*/
|
||||||
|
|
||||||
const Vec2 c = frect_center(sprite.rect);
|
const Vec2 c = rect_center(sprite.rect);
|
||||||
const Vec2 t = fast_cossine(sprite.rotation);
|
const Vec2 t = fast_cossine(sprite.rotation);
|
||||||
|
|
||||||
const Vec2 h = { sprite.rect.w / 2, sprite.rect.h / 2 };
|
const Vec2 h = { sprite.rect.w / 2, sprite.rect.h / 2 };
|
||||||
@ -241,7 +242,7 @@ void render_sprite_batch(const Primitive2D primitives[],
|
|||||||
v3 = (Vec2){ c.x + t.x * +h.x - t.y * -h.y, c.y + t.y * +h.x + t.x * -h.y };
|
v3 = (Vec2){ c.x + t.x * +h.x - t.y * -h.y, c.y + t.y * +h.x + t.x * -h.y };
|
||||||
}
|
}
|
||||||
|
|
||||||
push_quad_payload_to_vertex_buffer_builder(batch, &payload, v0, v1, v2, v3, uv0, uv1, uv2, uv3, sprite.color);
|
push_quad_payload_to_vertex_buffer_builder(batch, i, &payload, v0, v1, v2, v3, uv0, uv1, uv2, uv3, sprite.color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,7 +175,7 @@ static void text_draw_with(FontData* font_data, char* text, Vec2 position, Color
|
|||||||
|
|
||||||
const size_t len = SDL_strlen(text);
|
const size_t len = SDL_strlen(text);
|
||||||
|
|
||||||
VertexBufferBuilder payload = build_vertex_buffer(vertex_array, get_text_payload_size() * len);
|
VertexBufferBuilder builder = build_vertex_buffer(vertex_array, sizeof (ElementIndexedQuadWithoutColor) * len);
|
||||||
|
|
||||||
for (size_t i = 0; i < len; ++i) {
|
for (size_t i = 0; i < len; ++i) {
|
||||||
const char c = text[i];
|
const char c = text[i];
|
||||||
@ -204,9 +204,22 @@ static void text_draw_with(FontData* font_data, char* text, Vec2 position, Color
|
|||||||
quad.y0 += (float)font_data->ascent;
|
quad.y0 += (float)font_data->ascent;
|
||||||
quad.y1 += (float)font_data->ascent;
|
quad.y1 += (float)font_data->ascent;
|
||||||
|
|
||||||
push_text_payload_to_vertex_buffer_builder(font_data, &payload, quad);
|
ElementIndexedQuadWithoutColor const payload = {
|
||||||
|
.v0 = (Vec2){ quad.x0, quad.y0 },
|
||||||
|
.v1 = (Vec2){ quad.x1, quad.y0 },
|
||||||
|
.v2 = (Vec2){ quad.x1, quad.y1 },
|
||||||
|
.v3 = (Vec2){ quad.x0, quad.y1 },
|
||||||
|
|
||||||
|
.uv0 = (Vec2){ quad.s0, quad.t0 },
|
||||||
|
.uv1 = (Vec2){ quad.s1, quad.t0 },
|
||||||
|
.uv2 = (Vec2){ quad.s1, quad.t1 },
|
||||||
|
.uv3 = (Vec2){ quad.s0, quad.t1 },
|
||||||
|
};
|
||||||
|
|
||||||
|
((ElementIndexedQuadWithoutColor *)builder.base)[i] = payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
finish_vertex_builder(&builder);
|
||||||
finally_draw_text(font_data, len, color, vertex_array);
|
finally_draw_text(font_data, len, color, vertex_array);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,8 +288,8 @@ void text_cache_reset_arena(TextCache *cache) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void draw_text(const char *string, Vec2 position, int height_px, Color color, const char *font_path) {
|
void draw_text(const char *string, Vec2 position, float height, Color color, const char *font) {
|
||||||
ensure_font_cache(font_path, height_px);
|
ensure_font_cache(font, (int)height);
|
||||||
|
|
||||||
/* the original string might not be around by the time it's used, so copy it */
|
/* the original string might not be around by the time it's used, so copy it */
|
||||||
size_t str_length = SDL_strlen(string) + 1;
|
size_t str_length = SDL_strlen(string) + 1;
|
||||||
@ -290,8 +303,8 @@ void draw_text(const char *string, Vec2 position, int height_px, Color color, co
|
|||||||
.color = color,
|
.color = color,
|
||||||
.position = position,
|
.position = position,
|
||||||
.text = dup_string,
|
.text = dup_string,
|
||||||
.font = font_path,
|
.font = font,
|
||||||
.height_px = height_px,
|
.height_px = (int)height,
|
||||||
};
|
};
|
||||||
|
|
||||||
Primitive2D primitive = {
|
Primitive2D primitive = {
|
||||||
@ -303,9 +316,9 @@ void draw_text(const char *string, Vec2 position, int height_px, Color color, co
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int draw_text_width(const char *string, int height_px, const char *font_path) {
|
float draw_text_width(const char *string, float height, const char *font) {
|
||||||
ensure_font_cache(font_path, height_px);
|
ensure_font_cache(font, (int)height);
|
||||||
FontData *font_data = get_font_data(font_path, height_px);
|
FontData *font_data = get_font_data(font, (int)height);
|
||||||
|
|
||||||
int length = 0;
|
int length = 0;
|
||||||
for (const char *p = string; *p != '\0'; ++p) {
|
for (const char *p = string; *p != '\0'; ++p) {
|
||||||
@ -316,5 +329,57 @@ int draw_text_width(const char *string, int height_px, const char *font_path) {
|
|||||||
length += advance_width;
|
length += advance_width;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (int)((float)length * font_data->scale_factor);
|
return (float)length * font_data->scale_factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void finally_draw_text(FontData const *font_data,
|
||||||
|
size_t len,
|
||||||
|
Color color,
|
||||||
|
VertexBuffer buffer)
|
||||||
|
{
|
||||||
|
DeferredCommandDraw command = {0};
|
||||||
|
|
||||||
|
command.vertices = (AttributeArrayPointer) {
|
||||||
|
.arity = 2,
|
||||||
|
.type = TWN_FLOAT,
|
||||||
|
.stride = offsetof(ElementIndexedQuadWithoutColor, v1),
|
||||||
|
.offset = offsetof(ElementIndexedQuadWithoutColor, v0),
|
||||||
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
command.texcoords = (AttributeArrayPointer) {
|
||||||
|
.arity = 2,
|
||||||
|
.type = TWN_FLOAT,
|
||||||
|
.stride = offsetof(ElementIndexedQuadWithoutColor, v1),
|
||||||
|
.offset = offsetof(ElementIndexedQuadWithoutColor, uv0),
|
||||||
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
command.constant_colored = true;
|
||||||
|
command.color = color;
|
||||||
|
|
||||||
|
command.gpu_texture = font_data->texture;
|
||||||
|
command.uses_gpu_key = true;
|
||||||
|
command.textured = true;
|
||||||
|
|
||||||
|
command.element_buffer = get_quad_element_buffer();
|
||||||
|
command.element_count = 6 * (uint32_t)len;
|
||||||
|
command.range_end = 6 * (uint32_t)len;
|
||||||
|
|
||||||
|
command.texture_mode = TEXTURE_MODE_GHOSTLY;
|
||||||
|
command.pipeline = PIPELINE_2D;
|
||||||
|
|
||||||
|
command.depth_range_high = depth_range_high;
|
||||||
|
command.depth_range_low = depth_range_low;
|
||||||
|
|
||||||
|
DeferredCommand final_command = {
|
||||||
|
.type = DEFERRED_COMMAND_TYPE_DRAW,
|
||||||
|
.draw = command
|
||||||
|
};
|
||||||
|
|
||||||
|
arrpush(deferred_commands, final_command);
|
||||||
|
|
||||||
|
/* TODO: why doesn't it get restored if not placed here? */
|
||||||
|
// glDepthMask(GL_TRUE);
|
||||||
}
|
}
|
||||||
|
@ -15,44 +15,51 @@ void draw_triangle(const char *path,
|
|||||||
Vec3 v2,
|
Vec3 v2,
|
||||||
Vec2 uv0,
|
Vec2 uv0,
|
||||||
Vec2 uv1,
|
Vec2 uv1,
|
||||||
Vec2 uv2)
|
Vec2 uv2,
|
||||||
|
Color c0,
|
||||||
|
Color c1,
|
||||||
|
Color c2)
|
||||||
{
|
{
|
||||||
|
// TODO: support color
|
||||||
|
(void)c0; (void)c1; (void)c2;
|
||||||
|
|
||||||
|
// TODO: order drawing by atlas id as well, so that texture rebinding is not as common
|
||||||
const TextureKey texture_key = textures_get_key(&ctx.texture_cache, path);
|
const TextureKey texture_key = textures_get_key(&ctx.texture_cache, path);
|
||||||
|
|
||||||
struct MeshBatchItem *batch_p = hmgetp_null(ctx.uncolored_mesh_batches, texture_key);
|
struct MeshBatchItem *batch_p = hmgetp_null(ctx.uncolored_mesh_batches, texture_key);
|
||||||
if (!batch_p) {
|
if (!batch_p) {
|
||||||
struct MeshBatch item = {0};
|
struct MeshBatch item = {0};
|
||||||
hmput(ctx.uncolored_mesh_batches, texture_key, item);
|
hmput(ctx.uncolored_mesh_batches, texture_key, item);
|
||||||
batch_p = &ctx.uncolored_mesh_batches[hmlenu(ctx.uncolored_mesh_batches) - 1]; /* TODO: can last index be used? */
|
batch_p = &ctx.uncolored_mesh_batches[hmlenu(ctx.uncolored_mesh_batches) - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
union UncoloredSpaceTriangle triangle = { .primitive = {
|
UncoloredSpaceTriangle const triangle = {
|
||||||
.v0 = v0,
|
.v0 = v0,
|
||||||
.v1 = v1,
|
.v1 = v1,
|
||||||
.v2 = v2,
|
.v2 = v2,
|
||||||
.uv1 = uv1,
|
.uv1 = uv1,
|
||||||
.uv0 = uv0,
|
.uv0 = uv0,
|
||||||
.uv2 = uv2,
|
.uv2 = uv2,
|
||||||
}};
|
};
|
||||||
|
|
||||||
union UncoloredSpaceTriangle *triangles = (union UncoloredSpaceTriangle *)(void *)batch_p->value.primitives;
|
UncoloredSpaceTriangle *triangles = (UncoloredSpaceTriangle *)(void *)batch_p->value.primitives;
|
||||||
|
|
||||||
arrpush(triangles, triangle);
|
arrpush(triangles, triangle);
|
||||||
batch_p->value.primitives = (uint8_t *)triangles;
|
batch_p->value.primitives = (uint8_t *)triangles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void draw_uncolored_space_traingle_batch(struct MeshBatch *batch,
|
void finally_draw_uncolored_space_traingle_batch(const MeshBatch *batch,
|
||||||
TextureKey texture_key)
|
const TextureKey texture_key)
|
||||||
{
|
{
|
||||||
VertexBuffer const vertex_array = get_scratch_vertex_array();
|
|
||||||
|
|
||||||
const size_t primitives_len = arrlenu(batch->primitives);
|
const size_t primitives_len = arrlenu(batch->primitives);
|
||||||
|
|
||||||
/* nothing to do */
|
/* nothing to do */
|
||||||
if (primitives_len == 0)
|
if (primitives_len == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
VertexBuffer const buffer = get_scratch_vertex_array();
|
||||||
|
|
||||||
const Rect srcrect = textures_get_srcrect(&ctx.texture_cache, texture_key);
|
const Rect srcrect = textures_get_srcrect(&ctx.texture_cache, texture_key);
|
||||||
const Rect dims = textures_get_dims(&ctx.texture_cache, texture_key);
|
const Rect dims = textures_get_dims(&ctx.texture_cache, texture_key);
|
||||||
|
|
||||||
@ -63,8 +70,8 @@ void draw_uncolored_space_traingle_batch(struct MeshBatch *batch,
|
|||||||
|
|
||||||
/* update pixel-based uvs to correspond with texture atlases */
|
/* update pixel-based uvs to correspond with texture atlases */
|
||||||
for (size_t i = 0; i < primitives_len; ++i) {
|
for (size_t i = 0; i < primitives_len; ++i) {
|
||||||
struct UncoloredSpaceTrianglePayload *payload =
|
UncoloredSpaceTriangle *payload =
|
||||||
&((union UncoloredSpaceTriangle *)(void *)batch->primitives)[i].payload;
|
&((UncoloredSpaceTriangle *)(void *)batch->primitives)[i];
|
||||||
|
|
||||||
payload->uv0.x = xr + ((float)payload->uv0.x / srcrect.w) * wr;
|
payload->uv0.x = xr + ((float)payload->uv0.x / srcrect.w) * wr;
|
||||||
payload->uv0.y = yr + ((float)payload->uv0.y / srcrect.h) * hr;
|
payload->uv0.y = yr + ((float)payload->uv0.y / srcrect.h) * hr;
|
||||||
@ -74,7 +81,46 @@ void draw_uncolored_space_traingle_batch(struct MeshBatch *batch,
|
|||||||
payload->uv2.y = yr + ((float)payload->uv2.y / srcrect.h) * hr;
|
payload->uv2.y = yr + ((float)payload->uv2.y / srcrect.h) * hr;
|
||||||
}
|
}
|
||||||
|
|
||||||
specify_vertex_buffer(vertex_array, batch->primitives, primitives_len * sizeof (struct UncoloredSpaceTrianglePayload));
|
specify_vertex_buffer(buffer, batch->primitives, primitives_len * sizeof (UncoloredSpaceTriangle));
|
||||||
|
|
||||||
finally_draw_uncolored_space_traingle_batch(batch, texture_key, vertex_array);
|
DeferredCommandDraw command = {0};
|
||||||
|
|
||||||
|
command.vertices = (AttributeArrayPointer) {
|
||||||
|
.arity = 3,
|
||||||
|
.type = TWN_FLOAT,
|
||||||
|
.stride = offsetof(UncoloredSpaceTriangle, v1),
|
||||||
|
.offset = offsetof(UncoloredSpaceTriangle, v0),
|
||||||
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
command.texcoords = (AttributeArrayPointer) {
|
||||||
|
.arity = 2,
|
||||||
|
.type = TWN_FLOAT,
|
||||||
|
.stride = offsetof(UncoloredSpaceTriangle, v1),
|
||||||
|
.offset = offsetof(UncoloredSpaceTriangle, uv0),
|
||||||
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
command.textured = true;
|
||||||
|
command.texture_key = texture_key;
|
||||||
|
|
||||||
|
command.primitive_count = (uint32_t)(3 * primitives_len);
|
||||||
|
|
||||||
|
/* TODO: support alpha blended case, with distance sort */
|
||||||
|
TextureMode mode = textures_get_mode(&ctx.texture_cache, texture_key);
|
||||||
|
if (mode == TEXTURE_MODE_GHOSTLY)
|
||||||
|
mode = TEXTURE_MODE_SEETHROUGH;
|
||||||
|
|
||||||
|
command.texture_mode = mode;
|
||||||
|
command.pipeline = PIPELINE_SPACE;
|
||||||
|
|
||||||
|
command.depth_range_high = depth_range_high;
|
||||||
|
command.depth_range_low = depth_range_low;
|
||||||
|
|
||||||
|
DeferredCommand final_command = {
|
||||||
|
.type = DEFERRED_COMMAND_TYPE_DRAW,
|
||||||
|
.draw = command
|
||||||
|
};
|
||||||
|
|
||||||
|
arrpush(deferred_commands, final_command);
|
||||||
}
|
}
|
||||||
|
@ -12,9 +12,10 @@
|
|||||||
|
|
||||||
#include "rendering/twn_circles.c"
|
#include "rendering/twn_circles.c"
|
||||||
#include "rendering/twn_draw.c"
|
#include "rendering/twn_draw.c"
|
||||||
#include "rendering/twn_fog.c"
|
|
||||||
#include "rendering/twn_skybox.c"
|
#include "rendering/twn_skybox.c"
|
||||||
#include "rendering/twn_sprites.c"
|
#include "rendering/twn_sprites.c"
|
||||||
#include "rendering/twn_rects.c"
|
#include "rendering/twn_rects.c"
|
||||||
#include "rendering/twn_text.c"
|
#include "rendering/twn_text.c"
|
||||||
|
#include "rendering/twn_quads.c"
|
||||||
#include "rendering/twn_triangles.c"
|
#include "rendering/twn_triangles.c"
|
||||||
|
#include "rendering/twn_billboards.c"
|
||||||
|
280
src/twn_audio.c
280
src/twn_audio.c
@ -2,10 +2,12 @@
|
|||||||
#include "twn_engine_context_c.h"
|
#include "twn_engine_context_c.h"
|
||||||
#include "twn_util.h"
|
#include "twn_util.h"
|
||||||
#include "twn_util_c.h"
|
#include "twn_util_c.h"
|
||||||
|
#include "twn_audio.h"
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <stb_ds.h>
|
#include <stb_ds.h>
|
||||||
#include <physfs.h>
|
#include <physfs.h>
|
||||||
|
#include <physfsrwops.h>
|
||||||
|
|
||||||
#define STB_VORBIS_NO_PUSHDATA_API
|
#define STB_VORBIS_NO_PUSHDATA_API
|
||||||
#define STB_VORBIS_HEADER_ONLY
|
#define STB_VORBIS_HEADER_ONLY
|
||||||
@ -16,9 +18,17 @@
|
|||||||
|
|
||||||
static const char *audio_exts[AUDIO_FILE_TYPE_COUNT] = {
|
static const char *audio_exts[AUDIO_FILE_TYPE_COUNT] = {
|
||||||
".ogg", /* AUDIO_FILE_TYPE_OGG */
|
".ogg", /* AUDIO_FILE_TYPE_OGG */
|
||||||
|
".wav", /* AUDIO_FILE_TYPE_WAV */
|
||||||
".xm", /* AUDIO_FILE_TYPE_XM */
|
".xm", /* AUDIO_FILE_TYPE_XM */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const uint8_t audio_exts_len[AUDIO_FILE_TYPE_COUNT] = {
|
||||||
|
sizeof ".ogg" - 1,
|
||||||
|
sizeof ".wav" - 1,
|
||||||
|
sizeof ".xm" - 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* TODO: allow for vectorization and packed vectors (alignment care and alike) */
|
||||||
|
|
||||||
/* TODO: count frames without use, free the memory when threshold is met */
|
/* TODO: count frames without use, free the memory when threshold is met */
|
||||||
/* TODO: count repeated usages for sound effect cases with rendering to ram? */
|
/* TODO: count repeated usages for sound effect cases with rendering to ram? */
|
||||||
@ -55,14 +65,10 @@ static int64_t get_audio_data(const char *path, unsigned char **data) {
|
|||||||
|
|
||||||
|
|
||||||
static AudioFileType infer_audio_file_type(const char *path) {
|
static AudioFileType infer_audio_file_type(const char *path) {
|
||||||
size_t path_len = SDL_strlen(path);
|
size_t const path_len = SDL_strlen(path);
|
||||||
|
|
||||||
for (int i = 0; i < AUDIO_FILE_TYPE_COUNT; ++i) {
|
for (int i = 0; i < AUDIO_FILE_TYPE_COUNT; ++i) {
|
||||||
size_t ext_length = SDL_strlen(audio_exts[i]);
|
if (SDL_strncmp(&path[path_len - audio_exts_len[i]], audio_exts[i], audio_exts_len[i]) == 0)
|
||||||
if (path_len <= ext_length)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (SDL_strcmp(&path[path_len - ext_length], audio_exts[i]) == 0)
|
|
||||||
return (AudioFileType)i;
|
return (AudioFileType)i;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,6 +78,7 @@ static AudioFileType infer_audio_file_type(const char *path) {
|
|||||||
|
|
||||||
/* TODO: error propagation and clearing of resources on partial success? */
|
/* TODO: error propagation and clearing of resources on partial success? */
|
||||||
/* or should we expect things to simply fail? */
|
/* or should we expect things to simply fail? */
|
||||||
|
/* TODO: reuse often used decoded/decompressed data */
|
||||||
static union AudioContext init_audio_context(const char *path, AudioFileType type) {
|
static union AudioContext init_audio_context(const char *path, AudioFileType type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case AUDIO_FILE_TYPE_OGG: {
|
case AUDIO_FILE_TYPE_OGG: {
|
||||||
@ -101,6 +108,54 @@ static union AudioContext init_audio_context(const char *path, AudioFileType typ
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* TODO: transform to destination format immediately? */
|
||||||
|
case AUDIO_FILE_TYPE_WAV: {
|
||||||
|
SDL_AudioSpec spec;
|
||||||
|
uint8_t *data;
|
||||||
|
uint32_t len;
|
||||||
|
if (!SDL_LoadWAV_RW(PHYSFSRWOPS_openRead(path), 1, &spec, &data, &len)) {
|
||||||
|
CRY_SDL("Cannot load .wav file:");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_AudioCVT cvt;
|
||||||
|
int conv = SDL_BuildAudioCVT(&cvt,
|
||||||
|
spec.format,
|
||||||
|
spec.channels,
|
||||||
|
spec.freq,
|
||||||
|
AUDIO_F32,
|
||||||
|
2,
|
||||||
|
AUDIO_FREQUENCY);
|
||||||
|
if (conv < 0) {
|
||||||
|
CRY_SDL("Cannot resample .wav:");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conv != 0) {
|
||||||
|
data = SDL_realloc(data, len * cvt.len_mult);
|
||||||
|
cvt.buf = data;
|
||||||
|
cvt.len = len;
|
||||||
|
if (SDL_ConvertAudio(&cvt) < 0) {
|
||||||
|
CRY_SDL("Error resampling .wav:");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
spec.channels = 2;
|
||||||
|
spec.freq = AUDIO_FREQUENCY;
|
||||||
|
/* TODO: test this */
|
||||||
|
spec.samples = (uint16_t)((size_t)(SDL_floor((double)len * cvt.len_ratio)) / sizeof (float) / 2);
|
||||||
|
} else {
|
||||||
|
spec.samples = (uint16_t)((size_t)(SDL_floor((double)len * cvt.len_ratio)) / sizeof (float) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (union AudioContext) {
|
||||||
|
.wav = {
|
||||||
|
.position = 0,
|
||||||
|
.samples = data,
|
||||||
|
.spec = spec,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
case AUDIO_FILE_TYPE_XM: {
|
case AUDIO_FILE_TYPE_XM: {
|
||||||
unsigned char *data;
|
unsigned char *data;
|
||||||
int64_t len = get_audio_data(path, &data);
|
int64_t len = get_audio_data(path, &data);
|
||||||
@ -137,6 +192,29 @@ static union AudioContext init_audio_context(const char *path, AudioFileType typ
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void free_audio_channel(AudioChannel channel) {
|
||||||
|
switch (channel.file_type) {
|
||||||
|
case AUDIO_FILE_TYPE_OGG: {
|
||||||
|
SDL_free(channel.context.vorbis.data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case AUDIO_FILE_TYPE_WAV: {
|
||||||
|
SDL_free(channel.context.wav.samples);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case AUDIO_FILE_TYPE_XM: {
|
||||||
|
xm_free_context(channel.context.xm.handle);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case AUDIO_FILE_TYPE_COUNT:
|
||||||
|
case AUDIO_FILE_TYPE_UNKNOWN:
|
||||||
|
default:
|
||||||
|
SDL_assert_always(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void repeat_audio(AudioChannel *channel) {
|
static void repeat_audio(AudioChannel *channel) {
|
||||||
switch (channel->file_type) {
|
switch (channel->file_type) {
|
||||||
case AUDIO_FILE_TYPE_OGG: {
|
case AUDIO_FILE_TYPE_OGG: {
|
||||||
@ -144,6 +222,11 @@ static void repeat_audio(AudioChannel *channel) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case AUDIO_FILE_TYPE_WAV: {
|
||||||
|
channel->context.wav.position = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case AUDIO_FILE_TYPE_XM: {
|
case AUDIO_FILE_TYPE_XM: {
|
||||||
xm_restart(channel->context.xm.handle);
|
xm_restart(channel->context.xm.handle);
|
||||||
break;
|
break;
|
||||||
@ -164,11 +247,41 @@ void audio_play(const char *path,
|
|||||||
float volume,
|
float volume,
|
||||||
float panning)
|
float panning)
|
||||||
{
|
{
|
||||||
|
if (!ctx.audio_initialized) {
|
||||||
|
profile_start("audio initialization");
|
||||||
|
|
||||||
|
SDL_AudioSpec request, got;
|
||||||
|
SDL_zero(request);
|
||||||
|
|
||||||
|
request.freq = AUDIO_FREQUENCY;
|
||||||
|
request.format = AUDIO_F32;
|
||||||
|
request.channels = 2;
|
||||||
|
#ifndef TWN_FEATURE_PUSH_AUDIO
|
||||||
|
request.callback = audio_callback;
|
||||||
|
#endif
|
||||||
|
/* TODO: check for errors */
|
||||||
|
ctx.audio_device = SDL_OpenAudioDevice(NULL, 0, &request, &got, 0);
|
||||||
|
ctx.audio_stream_format = got.format;
|
||||||
|
ctx.audio_stream_frequency = got.freq;
|
||||||
|
ctx.audio_stream_channel_count = got.channels;
|
||||||
|
/* TODO: relax this */
|
||||||
|
SDL_assert_always(got.freq == AUDIO_FREQUENCY);
|
||||||
|
SDL_assert_always(got.format == AUDIO_F32);
|
||||||
|
SDL_assert_always(got.channels == 2);
|
||||||
|
|
||||||
|
SDL_PauseAudioDevice(ctx.audio_device, 0);
|
||||||
|
|
||||||
|
profile_end("audio initialization");
|
||||||
|
|
||||||
|
ctx.audio_initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channel) {
|
||||||
AudioChannelItem *pair = shgetp_null(ctx.audio_channels, channel);
|
AudioChannelItem *pair = shgetp_null(ctx.audio_channels, channel);
|
||||||
|
|
||||||
/* create a channel if it doesn't exist */
|
/* create a channel if it doesn't exist */
|
||||||
if (!pair) {
|
if (!pair) {
|
||||||
AudioFileType file_type = infer_audio_file_type(path);
|
AudioFileType const file_type = infer_audio_file_type(path);
|
||||||
AudioChannel new_channel = {
|
AudioChannel new_channel = {
|
||||||
.file_type = file_type,
|
.file_type = file_type,
|
||||||
.context = init_audio_context(path, file_type),
|
.context = init_audio_context(path, file_type),
|
||||||
@ -187,48 +300,57 @@ void audio_play(const char *path,
|
|||||||
/* works for both restarts and new audio */
|
/* works for both restarts and new audio */
|
||||||
if (strcmp(pair->value.path, path) == 0)
|
if (strcmp(pair->value.path, path) == 0)
|
||||||
repeat_audio(&pair->value);
|
repeat_audio(&pair->value);
|
||||||
|
} else {
|
||||||
|
/* audio without channel plays without repeat and ability to change parameters over its course, nor stop it */
|
||||||
|
AudioFileType const file_type = infer_audio_file_type(path);
|
||||||
|
AudioChannel new_channel = {
|
||||||
|
.file_type = file_type,
|
||||||
|
.context = init_audio_context(path, file_type),
|
||||||
|
.path = path,
|
||||||
|
.name = NULL,
|
||||||
|
.repeat = false,
|
||||||
|
.volume = volume,
|
||||||
|
.panning = panning,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (repeat)
|
||||||
|
log_warn("Cannot repeat audio played on unnamed scratch channel (for %s)", path);
|
||||||
|
|
||||||
|
arrpush(ctx.unnamed_audio_channels, new_channel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
TWN_API void audio_set(const char *channel, AudioParam param, float value) {
|
TWN_API void audio_parameter(const char *channel, const char *param, float value) {
|
||||||
AudioChannelItem *pair = shgetp_null(ctx.audio_channels, channel);
|
AudioChannelItem *pair = shgetp_null(ctx.audio_channels, channel);
|
||||||
if (!pair) {
|
if (!pair) {
|
||||||
log_warn("No channel by the name of %s to set a parameter for", channel);
|
log_warn("No channel by the name of %s to set a parameter for", channel);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (param) {
|
if (SDL_strncmp(param, "repeat", sizeof "repeat" - 1) == 0) {
|
||||||
case AUDIO_PARAM_REPEAT:
|
|
||||||
pair->value.repeat = (bool)value;
|
pair->value.repeat = (bool)value;
|
||||||
break;
|
|
||||||
case AUDIO_PARAM_VOLUME:
|
} else if (SDL_strncmp(param, "volume", sizeof "volume" - 1) == 0) {
|
||||||
if (value > 1.0f) {
|
if (value > 1.0f || value < 0.0f) {
|
||||||
log_warn("Out of range volume for channel %s set", channel);
|
log_warn("Out of range volume for channel %s set", channel);
|
||||||
value = 1.0f;
|
value = clampf(value, 0.0f, 1.0f);
|
||||||
}
|
|
||||||
if (value < 0.0f) {
|
|
||||||
log_warn("Out of range volume for channel %s set", channel);
|
|
||||||
value = 0.0f;
|
|
||||||
}
|
}
|
||||||
pair->value.volume = value;
|
pair->value.volume = value;
|
||||||
break;
|
|
||||||
case AUDIO_PARAM_PANNING:
|
} else if (SDL_strncmp(param, "panning", sizeof "panning" - 1) == 0) {
|
||||||
if (value > 1.0f) {
|
if (value > 1.0f || value < -1.0f) {
|
||||||
log_warn("Out of range panning for channel %s set", channel);
|
log_warn("Out of range panning for channel %s set", channel);
|
||||||
value = 1.0f;
|
value = clampf(value, -1.0f, +1.0f);
|
||||||
}
|
|
||||||
if (value < -1.0f) {
|
|
||||||
log_warn("Out of range panning for channel %s set", channel);
|
|
||||||
value = -1.0f;
|
|
||||||
}
|
}
|
||||||
pair->value.panning = value;
|
pair->value.panning = value;
|
||||||
break;
|
|
||||||
default:
|
} else
|
||||||
CRY("Audio channel parameter setting failed", "Invalid parameter enum given");
|
CRY("Audio channel parameter setting failed", "Invalid parameter enum given");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* TODO: handle it more properly in regards to clipping and alike */
|
||||||
/* this assumes float based streams */
|
/* this assumes float based streams */
|
||||||
static void audio_mixin_streams(const AudioChannel *channel,
|
static void audio_mixin_streams(const AudioChannel *channel,
|
||||||
uint8_t *restrict a,
|
uint8_t *restrict a,
|
||||||
@ -241,37 +363,39 @@ static void audio_mixin_streams(const AudioChannel *channel,
|
|||||||
const float left_panning = fminf(fabsf(channel->panning - 1.0f), 1.0f);
|
const float left_panning = fminf(fabsf(channel->panning - 1.0f), 1.0f);
|
||||||
const float right_panning = fminf(fabsf(channel->panning + 1.0f), 1.0f);
|
const float right_panning = fminf(fabsf(channel->panning + 1.0f), 1.0f);
|
||||||
|
|
||||||
for (size_t s = 0; s < frames; s += 2) {
|
for (size_t s = 0; s < frames; ++s) {
|
||||||
/* left channel */
|
/* left channel */
|
||||||
sa[s] += (float)(sb[s] * channel->volume * left_panning);
|
sa[s * 2 + 0] += (float)(sb[s * 2 + 0] * channel->volume * left_panning);
|
||||||
|
sa[s * 2 + 0] *= 1 / (float)M_2_SQRTPI;
|
||||||
|
|
||||||
/* right channel */
|
/* right channel */
|
||||||
sa[s + 1] += (float)(sb[s + 1] * channel->volume * right_panning);
|
sa[s * 2 + 1] += (float)(sb[s * 2 + 1] * channel->volume * right_panning);
|
||||||
|
sa[s * 2 + 1] *= 1 / (float)M_2_SQRTPI;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* remember: sample is data for all channels where frame is a part of it */
|
/* remember: frame consists of sample * channel_count */
|
||||||
static void audio_sample_and_mixin_channel(const AudioChannel *channel,
|
static void audio_sample_and_mixin_channel(AudioChannel *channel,
|
||||||
uint8_t *stream,
|
uint8_t *stream,
|
||||||
int len)
|
int len)
|
||||||
{
|
{
|
||||||
static uint8_t buffer[16384];
|
static uint8_t buffer[16384]; /* TODO: better make it a growable scratch instead, which will simplify things */
|
||||||
const int float_buffer_frames = sizeof (buffer) / sizeof (float);
|
const size_t float_buffer_frames = sizeof (buffer) / sizeof (float) / 2;
|
||||||
const int stream_frames = len / (int)(sizeof (float));
|
const size_t stream_frames = len / sizeof (float) / 2;
|
||||||
|
|
||||||
switch (channel->file_type) {
|
switch (channel->file_type) {
|
||||||
case AUDIO_FILE_TYPE_OGG: {
|
case AUDIO_FILE_TYPE_OGG: {
|
||||||
/* feed stream for needed conversions */
|
/* feed stream for needed conversions */
|
||||||
for (int i = 0; i < stream_frames; ) {
|
for (size_t i = 0; i < stream_frames; ) {
|
||||||
const int n_frames = (stream_frames - i) > float_buffer_frames ?
|
const size_t n_frames = (stream_frames - i) > float_buffer_frames ?
|
||||||
float_buffer_frames : stream_frames - i;
|
float_buffer_frames : stream_frames - i;
|
||||||
|
|
||||||
const int samples_per_channel = stb_vorbis_get_samples_float_interleaved(
|
const size_t samples_per_channel = stb_vorbis_get_samples_float_interleaved(
|
||||||
channel->context.vorbis.handle,
|
channel->context.vorbis.handle,
|
||||||
channel->context.vorbis.channel_count,
|
2,
|
||||||
(float *)buffer,
|
(float *)buffer,
|
||||||
n_frames);
|
(int)n_frames * 2);
|
||||||
|
|
||||||
/* handle end of file */
|
/* handle end of file */
|
||||||
if (samples_per_channel == 0) {
|
if (samples_per_channel == 0) {
|
||||||
@ -279,30 +403,61 @@ static void audio_sample_and_mixin_channel(const AudioChannel *channel,
|
|||||||
/* seek to start and try sampling some more */
|
/* seek to start and try sampling some more */
|
||||||
stb_vorbis_seek_start(channel->context.vorbis.handle);
|
stb_vorbis_seek_start(channel->context.vorbis.handle);
|
||||||
continue;
|
continue;
|
||||||
} else
|
} else {
|
||||||
/* leave silence */
|
/* leave silence */
|
||||||
|
channel->finished = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* panning and mixing */
|
/* panning and mixing */
|
||||||
audio_mixin_streams(channel,
|
audio_mixin_streams(channel,
|
||||||
&stream[i * sizeof(float)], buffer,
|
&stream[i * sizeof(float) * 2], buffer,
|
||||||
samples_per_channel * 2);
|
samples_per_channel);
|
||||||
|
|
||||||
i += samples_per_channel * 2;
|
i += samples_per_channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case AUDIO_FILE_TYPE_WAV: {
|
||||||
|
/* feed stream for needed conversions */
|
||||||
|
for (size_t i = 0; i < stream_frames; ) {
|
||||||
|
const size_t limit = MIN(stream_frames - i, channel->context.wav.spec.samples - channel->context.wav.position);
|
||||||
|
|
||||||
|
/* same format, just feed it directly */
|
||||||
|
audio_mixin_streams(channel,
|
||||||
|
&stream[i * sizeof(float) * 2],
|
||||||
|
&((uint8_t *)channel->context.wav.samples)[channel->context.wav.position * sizeof (float) * 2],
|
||||||
|
limit);
|
||||||
|
|
||||||
|
channel->context.wav.position += limit;
|
||||||
|
|
||||||
|
if (channel->context.wav.position >= channel->context.wav.spec.samples) {
|
||||||
|
if (channel->repeat)
|
||||||
|
channel->context.wav.position = 0;
|
||||||
|
else {
|
||||||
|
/* leave silence */
|
||||||
|
channel->finished = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i += limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case AUDIO_FILE_TYPE_XM: {
|
case AUDIO_FILE_TYPE_XM: {
|
||||||
for (int i = 0; i < stream_frames; ) {
|
for (size_t i = 0; i < stream_frames; ) {
|
||||||
const int n_frames = (stream_frames - i) > float_buffer_frames ?
|
const size_t n_frames = (stream_frames - i) > float_buffer_frames ?
|
||||||
float_buffer_frames : stream_frames - i;
|
float_buffer_frames : stream_frames - i;
|
||||||
|
|
||||||
const int samples_per_channel = xm_generate_samples(channel->context.xm.handle,
|
const size_t samples_per_channel = xm_generate_samples(channel->context.xm.handle,
|
||||||
(float *)buffer,
|
(float *)buffer,
|
||||||
n_frames / 2);
|
n_frames);
|
||||||
|
|
||||||
/* handle end of file */
|
/* handle end of file */
|
||||||
if (samples_per_channel == 0) {
|
if (samples_per_channel == 0) {
|
||||||
@ -310,18 +465,20 @@ static void audio_sample_and_mixin_channel(const AudioChannel *channel,
|
|||||||
/* seek to start and try sampling some more */
|
/* seek to start and try sampling some more */
|
||||||
xm_restart(channel->context.xm.handle);
|
xm_restart(channel->context.xm.handle);
|
||||||
continue;
|
continue;
|
||||||
} else
|
} else {
|
||||||
|
channel->finished = true;
|
||||||
/* leave silence */
|
/* leave silence */
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* panning and mixing */
|
/* panning and mixing */
|
||||||
audio_mixin_streams(channel,
|
audio_mixin_streams(channel,
|
||||||
&stream[i * sizeof(float)],
|
&stream[i * sizeof(float) * 2],
|
||||||
buffer,
|
buffer,
|
||||||
samples_per_channel * 2);
|
samples_per_channel);
|
||||||
|
|
||||||
i += samples_per_channel * 2;
|
i += samples_per_channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -355,8 +512,23 @@ void audio_callback(void *userdata, uint8_t *stream, int len) {
|
|||||||
sanity_check_channel(&ctx.audio_channels[i].value);
|
sanity_check_channel(&ctx.audio_channels[i].value);
|
||||||
audio_sample_and_mixin_channel(&ctx.audio_channels[i].value, stream, len);
|
audio_sample_and_mixin_channel(&ctx.audio_channels[i].value, stream, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < arrlen(ctx.unnamed_audio_channels); ++i) {
|
||||||
|
sanity_check_channel(&ctx.unnamed_audio_channels[i]);
|
||||||
|
audio_sample_and_mixin_channel(&ctx.unnamed_audio_channels[i], stream, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ditch finished unnamed */
|
||||||
|
int i = 0;
|
||||||
|
while (i < arrlen(ctx.unnamed_audio_channels)) {
|
||||||
|
if (ctx.unnamed_audio_channels[i].finished) {
|
||||||
|
free_audio_channel(ctx.unnamed_audio_channels[i]);
|
||||||
|
arrdelswap(ctx.unnamed_audio_channels, i);
|
||||||
|
} else i++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
TWN_API void audio_play_args(PlayAudioArgs args) {
|
TWN_API void audio_play_args(PlayAudioArgs args) {
|
||||||
const char *channel = m_or(args, channel, NULL);
|
const char *channel = m_or(args, channel, NULL);
|
||||||
const bool repeat = m_or(args, repeat, false);
|
const bool repeat = m_or(args, repeat, false);
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
#ifndef TWN_AUDIO_C_H
|
#ifndef TWN_AUDIO_C_H
|
||||||
#define TWN_AUDIO_C_H
|
#define TWN_AUDIO_C_H
|
||||||
|
|
||||||
#include "twn_audio.h"
|
|
||||||
|
|
||||||
#include <SDL2/SDL_audio.h>
|
#include <SDL2/SDL_audio.h>
|
||||||
|
|
||||||
#define STB_VORBIS_HEADER_ONLY
|
#define STB_VORBIS_HEADER_ONLY
|
||||||
@ -15,9 +13,12 @@
|
|||||||
|
|
||||||
#define AUDIO_FREQUENCY 48000
|
#define AUDIO_FREQUENCY 48000
|
||||||
|
|
||||||
|
/* TODO: specify which PCM formats are usable with WAV */
|
||||||
|
/* TODO: specify limitations of libxm */
|
||||||
|
/* TODO: specify limitations of stb_vorbis */
|
||||||
typedef enum AudioFileType {
|
typedef enum AudioFileType {
|
||||||
AUDIO_FILE_TYPE_OGG,
|
AUDIO_FILE_TYPE_OGG,
|
||||||
|
AUDIO_FILE_TYPE_WAV,
|
||||||
AUDIO_FILE_TYPE_XM,
|
AUDIO_FILE_TYPE_XM,
|
||||||
AUDIO_FILE_TYPE_COUNT,
|
AUDIO_FILE_TYPE_COUNT,
|
||||||
AUDIO_FILE_TYPE_UNKNOWN,
|
AUDIO_FILE_TYPE_UNKNOWN,
|
||||||
@ -32,6 +33,12 @@ union AudioContext {
|
|||||||
uint8_t channel_count;
|
uint8_t channel_count;
|
||||||
} vorbis;
|
} vorbis;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
void *samples;
|
||||||
|
SDL_AudioSpec spec;
|
||||||
|
size_t position;
|
||||||
|
} wav;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
xm_context_t *handle;
|
xm_context_t *handle;
|
||||||
} xm;
|
} xm;
|
||||||
@ -46,6 +53,7 @@ typedef struct AudioChannel {
|
|||||||
bool repeat;
|
bool repeat;
|
||||||
float volume;
|
float volume;
|
||||||
float panning;
|
float panning;
|
||||||
|
bool finished;
|
||||||
} AudioChannel;
|
} AudioChannel;
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#include "twn_camera.h"
|
#include "twn_camera_c.h"
|
||||||
#include "twn_vec.h"
|
#include "twn_vec.h"
|
||||||
#include "twn_engine_context_c.h"
|
#include "twn_engine_context_c.h"
|
||||||
|
|
||||||
@ -28,9 +28,10 @@ Matrix4 camera_look_at(const Camera *const camera) {
|
|||||||
result.row[3].x = -m_vec_dot(r, camera->pos);
|
result.row[3].x = -m_vec_dot(r, camera->pos);
|
||||||
result.row[3].y = -m_vec_dot(u, camera->pos);
|
result.row[3].y = -m_vec_dot(u, camera->pos);
|
||||||
result.row[3].z = m_vec_dot(camera->target, camera->pos);
|
result.row[3].z = m_vec_dot(camera->target, camera->pos);
|
||||||
result.row[0].w = result.row[1].w = result.row[2].w = 0.0f;
|
|
||||||
result.row[3].w = 1.0f;
|
result.row[3].w = 1.0f;
|
||||||
|
|
||||||
|
result.row[0].w = result.row[1].w = result.row[2].w = 0.0f;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,7 +39,7 @@ Matrix4 camera_perspective(const Camera *const camera) {
|
|||||||
/* from cglm */
|
/* from cglm */
|
||||||
Matrix4 result = {0};
|
Matrix4 result = {0};
|
||||||
|
|
||||||
const float aspect = (float)(ctx.base_render_width / ctx.base_render_height);
|
const float aspect = ((float)ctx.base_render_width / (float)ctx.base_render_height);
|
||||||
|
|
||||||
const float f = 1.0f / tanf(camera->fov * 0.5f);
|
const float f = 1.0f / tanf(camera->fov * 0.5f);
|
||||||
const float fn = 1.0f / (CAMERA_NEAR_Z - CAMERA_FAR_Z);
|
const float fn = 1.0f / (CAMERA_NEAR_Z - CAMERA_FAR_Z);
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
#define TWN_CAMERA_H
|
#define TWN_CAMERA_H
|
||||||
|
|
||||||
#include "twn_types.h"
|
#include "twn_types.h"
|
||||||
#include "twn_engine_api.h"
|
#include "twn_types_c.h"
|
||||||
|
|
||||||
/* TODO: make it cached? */
|
/* TODO: make it cached? */
|
||||||
/* for example, perspective matrix only needs recaluclation on FOV change */
|
/* for example, perspective matrix only needs recaluclation on FOV change */
|
||||||
@ -15,8 +15,8 @@ typedef struct Camera {
|
|||||||
float fov; /* field of view, in radians */
|
float fov; /* field of view, in radians */
|
||||||
} Camera;
|
} Camera;
|
||||||
|
|
||||||
TWN_API Matrix4 camera_look_at(const Camera *camera);
|
Matrix4 camera_look_at(const Camera *camera);
|
||||||
|
|
||||||
TWN_API Matrix4 camera_perspective(const Camera *const camera);
|
Matrix4 camera_perspective(const Camera *const camera);
|
||||||
|
|
||||||
#endif
|
#endif
|
@ -28,8 +28,11 @@ typedef struct EngineContext {
|
|||||||
char **argv;
|
char **argv;
|
||||||
/* where the app was run from, used as the root for packs */
|
/* where the app was run from, used as the root for packs */
|
||||||
char *base_dir;
|
char *base_dir;
|
||||||
|
char *title;
|
||||||
|
|
||||||
Vec2i window_dims;
|
Vec2 window_dims;
|
||||||
|
Rect viewport_rect;
|
||||||
|
float viewport_scale;
|
||||||
|
|
||||||
/* configuration */
|
/* configuration */
|
||||||
toml_table_t *config_table;
|
toml_table_t *config_table;
|
||||||
@ -45,11 +48,13 @@ typedef struct EngineContext {
|
|||||||
/* rendering */
|
/* rendering */
|
||||||
Primitive2D *render_queue_2d;
|
Primitive2D *render_queue_2d;
|
||||||
MeshBatchItem *uncolored_mesh_batches;
|
MeshBatchItem *uncolored_mesh_batches;
|
||||||
|
MeshBatchItem *billboard_batches;
|
||||||
TextCache text_cache;
|
TextCache text_cache;
|
||||||
TextureCache texture_cache;
|
TextureCache texture_cache;
|
||||||
|
|
||||||
/* audio */
|
/* audio */
|
||||||
AudioChannelItem *audio_channels;
|
AudioChannelItem *audio_channels;
|
||||||
|
AudioChannel *unnamed_audio_channels;
|
||||||
SDL_AudioDeviceID audio_device;
|
SDL_AudioDeviceID audio_device;
|
||||||
int audio_stream_frequency;
|
int audio_stream_frequency;
|
||||||
SDL_AudioFormat audio_stream_format;
|
SDL_AudioFormat audio_stream_format;
|
||||||
@ -64,11 +69,6 @@ typedef struct EngineContext {
|
|||||||
int64_t delta_averager_residual;
|
int64_t delta_averager_residual;
|
||||||
int64_t time_averager[4];
|
int64_t time_averager[4];
|
||||||
|
|
||||||
/* 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_GLContext *gl_context;
|
||||||
SDL_Window *window;
|
SDL_Window *window;
|
||||||
uint32_t window_id;
|
uint32_t window_id;
|
||||||
@ -78,6 +78,9 @@ typedef struct EngineContext {
|
|||||||
bool resync_flag;
|
bool resync_flag;
|
||||||
bool was_successful;
|
bool was_successful;
|
||||||
bool render_double_buffered;
|
bool render_double_buffered;
|
||||||
|
/* signals mouse focus, used to disable mouse capture */
|
||||||
|
bool window_mouse_resident;
|
||||||
|
bool audio_initialized;
|
||||||
} EngineContext;
|
} EngineContext;
|
||||||
|
|
||||||
/* TODO: does it need to be marked with TWN_API? */
|
/* TODO: does it need to be marked with TWN_API? */
|
||||||
|
166
src/twn_input.c
166
src/twn_input.c
@ -1,13 +1,14 @@
|
|||||||
#include "twn_input_c.h"
|
#include "twn_input_c.h"
|
||||||
#include "twn_util.h"
|
#include "twn_util.h"
|
||||||
|
#include "twn_util_c.h"
|
||||||
#include "twn_control.h"
|
#include "twn_control.h"
|
||||||
#include "twn_engine_context_c.h"
|
#include "twn_engine_context_c.h"
|
||||||
|
#include "twn_input.h"
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <stb_ds.h>
|
#include <stb_ds.h>
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
|
|
||||||
static void update_action_pressed_state(InputState *input, Action *action) {
|
static void update_action_pressed_state(InputState *input, Action *action) {
|
||||||
@ -44,8 +45,8 @@ static void update_action_pressed_state(InputState *input, Action *action) {
|
|||||||
else {
|
else {
|
||||||
action->just_changed = !action->is_pressed;
|
action->just_changed = !action->is_pressed;
|
||||||
action->is_pressed = true;
|
action->is_pressed = true;
|
||||||
action->position.x = (float)input->mouse_window_position.x;
|
action->position.x = ((float)input->mouse_window_position.x - ctx.viewport_rect.x) / ctx.viewport_scale;
|
||||||
action->position.y = (float)input->mouse_window_position.y;
|
action->position.y = ((float)input->mouse_window_position.y - ctx.viewport_rect.y) / ctx.viewport_scale;
|
||||||
|
|
||||||
/* TODO: */
|
/* TODO: */
|
||||||
/*
|
/*
|
||||||
@ -69,20 +70,40 @@ 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,
|
static void input_bind_code_to_action(InputState *input,
|
||||||
char const *action_name,
|
char const *action_name,
|
||||||
ButtonSource source,
|
ButtonSource source,
|
||||||
union ButtonCode code)
|
union ButtonCode code)
|
||||||
{
|
{
|
||||||
ActionHashItem *action_item = shgetp_null(input->action_hash, action_name);
|
ActionHashItem *action_item = shgetp_null(input->action_hash, action_name);
|
||||||
if (action_item == NULL) {
|
if (!action_item)
|
||||||
log_warn("(%s) Action \"%s\" does not exist.", __func__, action_name);
|
action_item = input_add_action(action_name);
|
||||||
return;
|
|
||||||
}
|
|
||||||
Action *action = &action_item->value;
|
Action *action = &action_item->value;
|
||||||
|
|
||||||
/* check every binding to make sure this code isn't already bound */
|
/* check every binding to make sure this code isn't already bound */
|
||||||
for (size_t i = 0; i < (uint64_t)ctx.keybind_slots; ++i) {
|
for (size_t i = 0; i < action->num_bindings; ++i) {
|
||||||
Button *binding = &action->bindings[i];
|
Button *binding = &action->bindings[i];
|
||||||
|
|
||||||
if (binding->source != source)
|
if (binding->source != source)
|
||||||
@ -109,7 +130,8 @@ static void input_bind_code_to_action(InputState *input,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (is_already_bound) {
|
if (is_already_bound) {
|
||||||
log_warn("(%s) Code already bound to action \"%s\".", __func__, action_name);
|
/* keep it alive */
|
||||||
|
binding->in_use = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,13 +139,14 @@ static void input_bind_code_to_action(InputState *input,
|
|||||||
/* if we're at max bindings, forget the first element and shift the rest */
|
/* if we're at max bindings, forget the first element and shift the rest */
|
||||||
if (action->num_bindings == (uint64_t)ctx.keybind_slots) {
|
if (action->num_bindings == (uint64_t)ctx.keybind_slots) {
|
||||||
--action->num_bindings;
|
--action->num_bindings;
|
||||||
size_t shifted_size = (sizeof action->bindings) - (sizeof action->bindings[0]);
|
size_t shifted_size = sizeof action->bindings[0] * (ctx.keybind_slots - 1);
|
||||||
SDL_memmove(action->bindings, action->bindings + 1, shifted_size);
|
SDL_memmove(action->bindings, action->bindings + 1, shifted_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
action->bindings[action->num_bindings++] = (Button) {
|
action->bindings[action->num_bindings++] = (Button) {
|
||||||
.source = source,
|
.source = source,
|
||||||
.code = code,
|
.code = code,
|
||||||
|
.in_use = true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,16 +157,15 @@ static void input_unbind_code_from_action(InputState *input,
|
|||||||
union ButtonCode code)
|
union ButtonCode code)
|
||||||
{
|
{
|
||||||
ActionHashItem *action_item = shgetp_null(input->action_hash, action_name);
|
ActionHashItem *action_item = shgetp_null(input->action_hash, action_name);
|
||||||
if (action_item == NULL) {
|
if (!action_item)
|
||||||
log_warn("(%s) Action \"%s\" does not exist.", __func__, action_name);
|
action_item = input_add_action(action_name);
|
||||||
return;
|
|
||||||
}
|
|
||||||
Action *action = &action_item->value;
|
Action *action = &action_item->value;
|
||||||
|
|
||||||
/* check every binding to make sure this code is bound */
|
/* check every binding to make sure this code is bound */
|
||||||
size_t index = 0;
|
size_t index = 0;
|
||||||
bool is_bound = false;
|
bool is_bound = false;
|
||||||
for (index = 0; index < (uint64_t)ctx.keybind_slots; ++index) {
|
for (index = 0; index < action->num_bindings; ++index) {
|
||||||
Button *binding = &action->bindings[index];
|
Button *binding = &action->bindings[index];
|
||||||
|
|
||||||
if (binding->source != source)
|
if (binding->source != source)
|
||||||
@ -173,10 +195,8 @@ static void input_unbind_code_from_action(InputState *input,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_bound) {
|
if (!is_bound)
|
||||||
log_warn("(%s) Code is not bound to action \"%s\".", __func__, action_name);
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
/* remove the element to unbind and shift the rest so there isn't a gap */
|
/* remove the element to unbind and shift the rest so there isn't a gap */
|
||||||
size_t elements_after_index = action->num_bindings - index;
|
size_t elements_after_index = action->num_bindings - index;
|
||||||
@ -196,25 +216,54 @@ void input_state_deinit(InputState *input) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void input_state_update_postframe(InputState *input) {
|
||||||
|
/* TODO: don't spam it if it happens */
|
||||||
|
if (SDL_SetRelativeMouseMode(ctx.game_copy.mouse_capture && ctx.window_mouse_resident) != 0)
|
||||||
|
log_warn("(%s) Mouse capture isn't supported.", __func__);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void input_state_update(InputState *input) {
|
void input_state_update(InputState *input) {
|
||||||
|
int x, y;
|
||||||
|
|
||||||
input->keyboard_state = SDL_GetKeyboardState(NULL);
|
input->keyboard_state = SDL_GetKeyboardState(NULL);
|
||||||
input->mouse_state = SDL_GetMouseState(&input->mouse_window_position.x,
|
input->mouse_state = SDL_GetMouseState(&x, &y);
|
||||||
&input->mouse_window_position.y);
|
input->mouse_window_position = (Vec2){ (float)x, (float)y };
|
||||||
|
|
||||||
SDL_GetRelativeMouseState(&input->mouse_relative_position.x,
|
SDL_GetRelativeMouseState(&x, &y);
|
||||||
&input->mouse_relative_position.y);
|
input->mouse_relative_position = (Vec2){ (float)x, (float)y };
|
||||||
|
|
||||||
ctx.game.mouse_position = input->mouse_window_position;
|
ctx.game.mouse_position.x = ((float)input->mouse_window_position.x - ctx.viewport_rect.x) / ctx.viewport_scale;
|
||||||
|
ctx.game.mouse_position.y = ((float)input->mouse_window_position.y - ctx.viewport_rect.y) / ctx.viewport_scale;
|
||||||
|
|
||||||
|
if (ctx.window_mouse_resident)
|
||||||
ctx.game.mouse_movement = input->mouse_relative_position;
|
ctx.game.mouse_movement = input->mouse_relative_position;
|
||||||
|
else
|
||||||
|
ctx.game.mouse_movement = (Vec2){0};
|
||||||
|
|
||||||
for (size_t i = 0; i < shlenu(input->action_hash); ++i) {
|
for (size_t i = 0; i < shlenu(input->action_hash); ++i) {
|
||||||
Action *action = &input->action_hash[i].value;
|
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);
|
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_bind_action_control(char const *action_name,
|
void input_action(char const *action_name,
|
||||||
Control control)
|
Control control)
|
||||||
{
|
{
|
||||||
SDL_assert_always(action_name);
|
SDL_assert_always(action_name);
|
||||||
@ -236,57 +285,7 @@ void input_bind_action_control(char const *action_name,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void input_unbind_action_control(char const *action_name,
|
bool input_action_pressed(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);
|
SDL_assert_always(action_name);
|
||||||
|
|
||||||
ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
|
ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
|
||||||
@ -298,7 +297,7 @@ bool input_is_action_pressed(char const *action_name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool input_is_action_just_pressed(char const *action_name) {
|
bool input_action_just_pressed(char const *action_name) {
|
||||||
SDL_assert_always(action_name);
|
SDL_assert_always(action_name);
|
||||||
|
|
||||||
ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
|
ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
|
||||||
@ -310,7 +309,7 @@ bool input_is_action_just_pressed(char const *action_name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool input_is_action_just_released(char const *action_name) {
|
bool input_action_just_released(char const *action_name) {
|
||||||
SDL_assert_always(action_name);
|
SDL_assert_always(action_name);
|
||||||
|
|
||||||
ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
|
ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
|
||||||
@ -322,7 +321,7 @@ bool input_is_action_just_released(char const *action_name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Vec2 input_get_action_position(char const *action_name) {
|
Vec2 input_action_position(char const *action_name) {
|
||||||
SDL_assert_always(action_name);
|
SDL_assert_always(action_name);
|
||||||
|
|
||||||
ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
|
ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
|
||||||
@ -335,17 +334,6 @@ Vec2 input_get_action_position(char const *action_name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void input_reset_state(InputState *input) {
|
void input_reset_state(InputState *input) {
|
||||||
for (size_t i = 0; i < shlenu(input->action_hash); ++i) {
|
for (size_t i = 0; i < shlenu(input->action_hash); ++i) {
|
||||||
Action *action = &input->action_hash[i].value;
|
Action *action = &input->action_hash[i].value;
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
#ifndef TWN_INPUT_C_H
|
#ifndef TWN_INPUT_C_H
|
||||||
#define TWN_INPUT_C_H
|
#define TWN_INPUT_C_H
|
||||||
|
|
||||||
#include "twn_input.h"
|
#include "twn_types.h"
|
||||||
#include "twn_vec.h"
|
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
|
||||||
#define KEYBIND_SLOTS_DEFAULT 3
|
#define KEYBIND_SLOTS_DEFAULT 3
|
||||||
|
|
||||||
@ -32,6 +33,7 @@ typedef enum ButtonSource {
|
|||||||
typedef struct Button {
|
typedef struct Button {
|
||||||
enum ButtonSource source;
|
enum ButtonSource source;
|
||||||
union ButtonCode code;
|
union ButtonCode code;
|
||||||
|
bool in_use;
|
||||||
} Button;
|
} Button;
|
||||||
|
|
||||||
|
|
||||||
@ -59,8 +61,8 @@ typedef struct ActionHashItem {
|
|||||||
typedef struct InputState {
|
typedef struct InputState {
|
||||||
const uint8_t *keyboard_state; /* array of booleans indexed by scancode */
|
const uint8_t *keyboard_state; /* array of booleans indexed by scancode */
|
||||||
ActionHashItem *action_hash;
|
ActionHashItem *action_hash;
|
||||||
Vec2i mouse_window_position;
|
Vec2 mouse_window_position;
|
||||||
Vec2i mouse_relative_position;
|
Vec2 mouse_relative_position;
|
||||||
uint32_t mouse_state; /* SDL mouse button bitmask */
|
uint32_t mouse_state; /* SDL mouse button bitmask */
|
||||||
ButtonSource last_active_source;
|
ButtonSource last_active_source;
|
||||||
bool is_anything_just_pressed;
|
bool is_anything_just_pressed;
|
||||||
@ -74,6 +76,8 @@ void input_state_deinit(InputState *input);
|
|||||||
|
|
||||||
void input_state_update(InputState *input);
|
void input_state_update(InputState *input);
|
||||||
|
|
||||||
|
void input_state_update_postframe(InputState *input);
|
||||||
|
|
||||||
void input_reset_state(InputState *input);
|
void input_reset_state(InputState *input);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
352
src/twn_loop.c
352
src/twn_loop.c
@ -4,7 +4,6 @@
|
|||||||
#include "twn_util.h"
|
#include "twn_util.h"
|
||||||
#include "twn_util_c.h"
|
#include "twn_util_c.h"
|
||||||
#include "twn_game_object_c.h"
|
#include "twn_game_object_c.h"
|
||||||
#include "twn_audio_c.h"
|
|
||||||
#include "twn_textures_c.h"
|
#include "twn_textures_c.h"
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
@ -12,13 +11,6 @@
|
|||||||
#include <stb_ds.h>
|
#include <stb_ds.h>
|
||||||
#include <toml.h>
|
#include <toml.h>
|
||||||
|
|
||||||
/* TODO: should not be used here directly */
|
|
||||||
#ifdef EMSCRIPTEN
|
|
||||||
#include <GLES2/gl2.h>
|
|
||||||
#else
|
|
||||||
#include <glad/glad.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
|
||||||
@ -27,6 +19,9 @@
|
|||||||
#define PACKAGE_EXTENSION "btw"
|
#define PACKAGE_EXTENSION "btw"
|
||||||
|
|
||||||
|
|
||||||
|
static SDL_Thread *opengl_load_thread;
|
||||||
|
|
||||||
|
|
||||||
static int event_callback(void *userdata, SDL_Event *event) {
|
static int event_callback(void *userdata, SDL_Event *event) {
|
||||||
(void)userdata;
|
(void)userdata;
|
||||||
|
|
||||||
@ -37,11 +32,21 @@ static int event_callback(void *userdata, SDL_Event *event) {
|
|||||||
|
|
||||||
switch (event->window.event) {
|
switch (event->window.event) {
|
||||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||||
ctx.window_dims.x = event->window.data1;
|
ctx.window_dims.x = (float)event->window.data1;
|
||||||
ctx.window_dims.y = event->window.data2;
|
ctx.window_dims.y = (float)event->window.data2;
|
||||||
ctx.resync_flag = true;
|
ctx.resync_flag = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case SDL_WINDOWEVENT_FOCUS_LOST: {
|
||||||
|
ctx.window_mouse_resident = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SDL_WINDOWEVENT_FOCUS_GAINED: {
|
||||||
|
ctx.window_mouse_resident = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -90,33 +95,36 @@ static void poll_events(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifndef EMSCRIPTEN
|
|
||||||
|
|
||||||
static void APIENTRY opengl_log(GLenum source,
|
|
||||||
GLenum type,
|
|
||||||
GLuint id,
|
|
||||||
GLenum severity,
|
|
||||||
GLsizei length,
|
|
||||||
const GLchar* message,
|
|
||||||
const void* userParam)
|
|
||||||
{
|
|
||||||
(void)source;
|
|
||||||
(void)type;
|
|
||||||
(void)id;
|
|
||||||
(void)severity;
|
|
||||||
(void)userParam;
|
|
||||||
|
|
||||||
log_info("OpenGL: %.*s\n", length, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
static void preserve_persistent_ctx_fields(void) {
|
static void preserve_persistent_ctx_fields(void) {
|
||||||
ctx.game.udata = ctx.game_copy.udata;
|
ctx.game.udata = ctx.game_copy.udata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void update_viewport(void) {
|
||||||
|
Rect new_viewport;
|
||||||
|
float new_scale;
|
||||||
|
if ((float)ctx.window_dims.x / (float)ctx.window_dims.y > (float)ctx.base_render_width / (float)ctx.base_render_height) {
|
||||||
|
float ratio = (float)ctx.window_dims.y / (float)ctx.base_render_height;
|
||||||
|
float w = ((float)ctx.base_render_width * ratio);
|
||||||
|
new_viewport.x = ctx.window_dims.x / 2 - w / 2;
|
||||||
|
new_viewport.y = 0;
|
||||||
|
new_viewport.w = w;
|
||||||
|
new_viewport.h = ctx.window_dims.y;
|
||||||
|
new_scale = ratio;
|
||||||
|
} else {
|
||||||
|
float ratio = (float)ctx.window_dims.x / (float)ctx.base_render_width;
|
||||||
|
float h = ((float)ctx.base_render_height * ratio);
|
||||||
|
new_viewport.x = 0;
|
||||||
|
new_viewport.y = ctx.window_dims.y / 2 - h / 2;
|
||||||
|
new_viewport.w = ctx.window_dims.x;
|
||||||
|
new_viewport.h = h;
|
||||||
|
new_scale = ratio;
|
||||||
|
}
|
||||||
|
ctx.viewport_rect = new_viewport;
|
||||||
|
ctx.viewport_scale = new_scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void main_loop(void) {
|
static void main_loop(void) {
|
||||||
/*
|
/*
|
||||||
if (!ctx.is_running) {
|
if (!ctx.is_running) {
|
||||||
@ -198,21 +206,37 @@ static void main_loop(void) {
|
|||||||
|
|
||||||
/* finally, let's get to work */
|
/* finally, let's get to work */
|
||||||
int frames = 0;
|
int frames = 0;
|
||||||
while (ctx.frame_accumulator >= ctx.desired_frametime * ctx.update_multiplicity) {
|
while (ctx.frame_accumulator >= ctx.desired_frametime) {
|
||||||
frames += 1;
|
frames += 1;
|
||||||
for (size_t i = 0; i < ctx.update_multiplicity; ++i) {
|
|
||||||
/* TODO: disable rendering pushes on not-last ? */
|
/* TODO: disable rendering pushes on not-last ? */
|
||||||
render_queue_clear();
|
render_queue_clear();
|
||||||
poll_events();
|
poll_events();
|
||||||
|
if (ctx.window_size_has_changed)
|
||||||
|
update_viewport();
|
||||||
input_state_update(&ctx.input);
|
input_state_update(&ctx.input);
|
||||||
game_object_tick();
|
game_object_tick();
|
||||||
|
input_state_update_postframe(&ctx.input);
|
||||||
|
|
||||||
|
/* TODO: make it works when ctx.ticks_per_second != 60 */
|
||||||
|
#ifdef TWN_FEATURE_PUSH_AUDIO
|
||||||
|
static uint8_t audio_buffer[(AUDIO_FREQUENCY / 60) * sizeof (float) * 2];
|
||||||
|
if (ctx.audio_initialized) {
|
||||||
|
audio_callback(NULL, audio_buffer, sizeof audio_buffer);
|
||||||
|
if (SDL_QueueAudio(ctx.audio_device, audio_buffer, sizeof audio_buffer))
|
||||||
|
CRY_SDL("Error queueing audio: ");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* TODO: ctx.game_copy = ctx.game should be placed after it, but it messes with state used in render() */
|
||||||
preserve_persistent_ctx_fields();
|
preserve_persistent_ctx_fields();
|
||||||
|
|
||||||
ctx.frame_accumulator -= ctx.desired_frametime;
|
ctx.frame_accumulator -= ctx.desired_frametime;
|
||||||
ctx.game.frame_number = (ctx.game.frame_number % ULLONG_MAX) + 1;
|
ctx.game.frame_number++;
|
||||||
|
/* TODO: should we ask for reinitialization in such case? */
|
||||||
|
if (ctx.game.frame_number > 16777216)
|
||||||
|
ctx.game.frame_number = 0;
|
||||||
ctx.game.initialization_needed = false;
|
ctx.game.initialization_needed = false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO: in some cases machine might want to assume frames will be fed as much as possible */
|
/* TODO: in some cases machine might want to assume frames will be fed as much as possible */
|
||||||
/* which for now is broken as glBufferData with NULL is used all over right after render */
|
/* which for now is broken as glBufferData with NULL is used all over right after render */
|
||||||
@ -320,19 +344,25 @@ ERR_PACK_MANIFEST_PATH_ALLOC_FAIL:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool initialize(void) {
|
static int opengl_load_thread_fn(void *data) {
|
||||||
if (SDL_Init(SDL_INIT_EVERYTHING & ~SDL_INIT_HAPTIC) == -1) {
|
(void)data;
|
||||||
CRY_SDL("SDL initialization failed.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
SDL_GL_LoadLibrary(NULL);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool initialize(void) {
|
||||||
/* first things first, most things here will be loaded from the config file */
|
/* first things first, most things here will be loaded from the config file */
|
||||||
/* it's expected to be present in the data directory, no matter what */
|
/* it's expected to be present in the data directory, no matter what */
|
||||||
/* that is why PhysicsFS is initialized before anything else */
|
/* that is why PhysicsFS is initialized before anything else */
|
||||||
toml_set_memutil(SDL_malloc, SDL_free);
|
toml_set_memutil(SDL_malloc, SDL_free);
|
||||||
|
|
||||||
|
profile_start("pack dependency resolution");
|
||||||
/* time to orderly resolve any dependencies present */
|
/* time to orderly resolve any dependencies present */
|
||||||
resolve_pack_dependencies("data");
|
resolve_pack_dependencies("data");
|
||||||
|
profile_end("pack dependency resolution");
|
||||||
|
|
||||||
/* load the config file into an opaque table */
|
/* load the config file into an opaque table */
|
||||||
{
|
{
|
||||||
@ -396,11 +426,7 @@ static bool initialize(void) {
|
|||||||
/* debug mode defaults to being enabled */
|
/* debug mode defaults to being enabled */
|
||||||
/* pass --debug or --release to force a mode, ignoring configuration */
|
/* pass --debug or --release to force a mode, ignoring configuration */
|
||||||
toml_datum_t datum_debug = toml_bool_in(game, "debug");
|
toml_datum_t datum_debug = toml_bool_in(game, "debug");
|
||||||
if (!datum_debug.ok) {
|
ctx.game.debug = datum_debug.ok ? datum_debug.u.b : true;
|
||||||
ctx.game.debug = true;
|
|
||||||
} else {
|
|
||||||
ctx.game.debug = datum_debug.u.b;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef EMSCRIPTEN
|
#ifdef EMSCRIPTEN
|
||||||
/* emscripten interpretes those as GL ES version against WebGL */
|
/* emscripten interpretes those as GL ES version against WebGL */
|
||||||
@ -424,13 +450,12 @@ static bool initialize(void) {
|
|||||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||||
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
|
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
|
||||||
|
|
||||||
/* init got far enough to create a window */
|
|
||||||
{
|
|
||||||
toml_datum_t datum_title = toml_string_in(about, "title");
|
toml_datum_t datum_title = toml_string_in(about, "title");
|
||||||
if (!datum_title.ok) {
|
if (!datum_title.ok)
|
||||||
CRY("Initialization failed", "Valid about.title expected in configuration file");
|
datum_title.u.s = SDL_strdup("townengine project");
|
||||||
goto fail;
|
|
||||||
}
|
ctx.title = datum_title.u.s;
|
||||||
|
|
||||||
/* not yet used
|
/* not yet used
|
||||||
toml_datum_t datum_developer = toml_string_in(about, "developer");
|
toml_datum_t datum_developer = toml_string_in(about, "developer");
|
||||||
if (!datum_developer.ok) {
|
if (!datum_developer.ok) {
|
||||||
@ -438,121 +463,48 @@ static bool initialize(void) {
|
|||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
toml_datum_t datum_base_render_width = toml_int_in(game, "base_render_width");
|
|
||||||
|
toml_array_t *datum_resolution = toml_array_in(game, "resolution");
|
||||||
|
if (datum_resolution) {
|
||||||
|
toml_datum_t datum_base_render_width = toml_int_at(datum_resolution, 0);
|
||||||
if (!datum_base_render_width.ok) {
|
if (!datum_base_render_width.ok) {
|
||||||
CRY("Initialization failed", "Valid game.base_render_width expected in configuration file");
|
CRY("Initialization failed", "Valid game.resolution expected in configuration file");
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
ctx.base_render_width = datum_base_render_width.u.i;
|
|
||||||
ctx.game.resolution.x = (int)ctx.base_render_width;
|
|
||||||
|
|
||||||
toml_datum_t datum_base_render_height = toml_int_in(game, "base_render_height");
|
toml_datum_t datum_base_render_height = toml_int_at(datum_resolution, 1);
|
||||||
if (!datum_base_render_height.ok) {
|
if (!datum_base_render_height.ok) {
|
||||||
CRY("Initialization failed", "Valid game.base_render_height expected in configuration file");
|
CRY("Initialization failed", "Valid game.resolution expected in configuration file");
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.base_render_width = datum_base_render_width.u.i;
|
||||||
ctx.base_render_height = datum_base_render_height.u.i;
|
ctx.base_render_height = datum_base_render_height.u.i;
|
||||||
ctx.game.resolution.y = (int)ctx.base_render_height;
|
|
||||||
|
|
||||||
ctx.window = SDL_CreateWindow(datum_title.u.s,
|
} else {
|
||||||
SDL_WINDOWPOS_CENTERED,
|
ctx.base_render_width = 640;
|
||||||
SDL_WINDOWPOS_CENTERED,
|
ctx.base_render_height = 360;
|
||||||
(int)datum_base_render_width.u.i,
|
}
|
||||||
(int)datum_base_render_height.u.i,
|
|
||||||
//SDL_WINDOW_ALLOW_HIGHDPI |
|
ctx.game.resolution.x = (float)ctx.base_render_width;
|
||||||
SDL_WINDOW_RESIZABLE |
|
ctx.game.resolution.y = (float)ctx.base_render_height;
|
||||||
SDL_WINDOW_OPENGL);
|
|
||||||
|
|
||||||
SDL_free(datum_title.u.s);
|
|
||||||
//SDL_free(datum_developer.u.s);
|
//SDL_free(datum_developer.u.s);
|
||||||
}
|
|
||||||
|
|
||||||
if (ctx.window == NULL) {
|
|
||||||
CRY_SDL("Window creation failed.");
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.gl_context = SDL_GL_CreateContext(ctx.window);
|
|
||||||
if (!ctx.gl_context) {
|
|
||||||
CRY_SDL("GL context creation failed.");
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SDL_GL_MakeCurrent(ctx.window, ctx.gl_context)) {
|
|
||||||
CRY_SDL("GL context binding failed.");
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SDL_GL_SetSwapInterval(-1))
|
|
||||||
SDL_GL_SetSwapInterval(1);
|
|
||||||
|
|
||||||
#ifndef EMSCRIPTEN
|
|
||||||
if (gladLoadGL() == 0) {
|
|
||||||
CRY("Init", "GLAD failed");
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
log_info("OpenGL context: %s\n", glGetString(GL_VERSION));
|
|
||||||
|
|
||||||
#ifndef EMSCRIPTEN
|
|
||||||
glHint(GL_TEXTURE_COMPRESSION_HINT, GL_NICEST);
|
|
||||||
glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
|
|
||||||
glHint(GL_FOG_HINT, GL_FASTEST);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* might need this to have multiple windows */
|
|
||||||
ctx.window_id = SDL_GetWindowID(ctx.window);
|
|
||||||
|
|
||||||
glViewport(0, 0, (GLsizei)ctx.base_render_width, (GLsizei)ctx.base_render_height);
|
|
||||||
|
|
||||||
/* TODO: */
|
/* TODO: */
|
||||||
// SDL_GetRendererOutputSize(ctx.renderer, &ctx.window_w, &ctx.window_h);
|
// SDL_GetRendererOutputSize(ctx.renderer, &ctx.window_w, &ctx.window_h);
|
||||||
ctx.window_dims.x = (int)ctx.base_render_width;
|
ctx.window_dims.x = (float)ctx.base_render_width;
|
||||||
ctx.window_dims.y = (int)ctx.base_render_height;
|
ctx.window_dims.y = (float)ctx.base_render_height;
|
||||||
|
|
||||||
/* add a watcher for immediate updates on window size */
|
/* add a watcher for immediate updates on window size */
|
||||||
SDL_AddEventWatch(event_callback, NULL);
|
SDL_AddEventWatch(event_callback, NULL);
|
||||||
|
|
||||||
/* audio initialization */
|
|
||||||
{
|
|
||||||
|
|
||||||
SDL_AudioSpec request, got;
|
|
||||||
SDL_zero(request);
|
|
||||||
|
|
||||||
request.freq = AUDIO_FREQUENCY;
|
|
||||||
request.format = AUDIO_F32;
|
|
||||||
request.channels = 2;
|
|
||||||
request.callback = audio_callback;
|
|
||||||
/* TODO: check for errors */
|
|
||||||
ctx.audio_device = SDL_OpenAudioDevice(NULL, 0, &request, &got, 0);
|
|
||||||
ctx.audio_stream_format = got.format;
|
|
||||||
ctx.audio_stream_frequency = got.freq;
|
|
||||||
ctx.audio_stream_channel_count = got.channels;
|
|
||||||
SDL_assert_always(got.format == AUDIO_F32);
|
|
||||||
SDL_assert_always(got.channels == 2);
|
|
||||||
|
|
||||||
SDL_PauseAudioDevice(ctx.audio_device, 0);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* you could change this at runtime if you wanted */
|
|
||||||
ctx.update_multiplicity = 1;
|
|
||||||
|
|
||||||
#ifndef EMSCRIPTEN
|
|
||||||
/* hook up opengl debugging callback */
|
|
||||||
if (ctx.game.debug) {
|
|
||||||
glEnable(GL_DEBUG_OUTPUT);
|
|
||||||
glDebugMessageCallback(opengl_log, NULL);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* random seeding */
|
/* random seeding */
|
||||||
/* SDL_GetPerformanceCounter returns some platform-dependent number. */
|
/* SDL_GetPerformanceCounter returns some platform-dependent number. */
|
||||||
/* it should vary between game instances. i checked! random enough for me. */
|
/* it should vary between game instances. i checked! random enough for me. */
|
||||||
ctx.game.random_seed = SDL_GetPerformanceCounter();
|
ctx.game.random_seed = (float)(SDL_GetPerformanceCounter() % 16777216);
|
||||||
srand((unsigned int)ctx.game.random_seed);
|
srand((unsigned int)(SDL_GetPerformanceCounter()));
|
||||||
stbds_rand_seed(ctx.game.random_seed);
|
stbds_rand_seed((size_t)(SDL_GetPerformanceCounter()));
|
||||||
|
|
||||||
/* main loop machinery */
|
/* main loop machinery */
|
||||||
toml_datum_t datum_ticks_per_second = toml_int_in(engine, "ticks_per_second");
|
toml_datum_t datum_ticks_per_second = toml_int_in(engine, "ticks_per_second");
|
||||||
@ -620,9 +572,9 @@ static bool initialize(void) {
|
|||||||
if (!datum_font_filtering.ok) {
|
if (!datum_font_filtering.ok) {
|
||||||
ctx.font_filtering = TEXT_FONT_FILTERING_DEFAULT;
|
ctx.font_filtering = TEXT_FONT_FILTERING_DEFAULT;
|
||||||
} else {
|
} else {
|
||||||
if (SDL_strcmp(datum_font_filtering.u.s, "nearest") == 0) {
|
if (SDL_strncmp(datum_font_filtering.u.s, "nearest", sizeof "nearest" - 1) == 0) {
|
||||||
ctx.font_filtering = TEXTURE_FILTER_NEAREAST;
|
ctx.font_filtering = TEXTURE_FILTER_NEAREAST;
|
||||||
} else if (SDL_strcmp(datum_font_filtering.u.s, "linear") == 0) {
|
} else if (SDL_strncmp(datum_font_filtering.u.s, "linear", sizeof "linear" - 1) == 0) {
|
||||||
ctx.font_filtering = TEXTURE_FILTER_LINEAR;
|
ctx.font_filtering = TEXTURE_FILTER_LINEAR;
|
||||||
} else {
|
} else {
|
||||||
ctx.font_filtering = TEXT_FONT_FILTERING_DEFAULT;
|
ctx.font_filtering = TEXT_FONT_FILTERING_DEFAULT;
|
||||||
@ -635,9 +587,6 @@ static bool initialize(void) {
|
|||||||
ctx.render_queue_2d = NULL;
|
ctx.render_queue_2d = NULL;
|
||||||
ctx.uncolored_mesh_batches = NULL;
|
ctx.uncolored_mesh_batches = NULL;
|
||||||
|
|
||||||
textures_cache_init(&ctx.texture_cache, ctx.window);
|
|
||||||
text_cache_init(&ctx.text_cache);
|
|
||||||
|
|
||||||
/* input */
|
/* input */
|
||||||
toml_datum_t datum_keybind_slots = toml_int_in(engine, "keybind_slots");
|
toml_datum_t datum_keybind_slots = toml_int_in(engine, "keybind_slots");
|
||||||
if (!datum_keybind_slots.ok) {
|
if (!datum_keybind_slots.ok) {
|
||||||
@ -651,14 +600,67 @@ static bool initialize(void) {
|
|||||||
}
|
}
|
||||||
input_state_init(&ctx.input);
|
input_state_init(&ctx.input);
|
||||||
|
|
||||||
/* scripting */
|
ctx.render_double_buffered = true;
|
||||||
/*
|
ctx.window_mouse_resident = true;
|
||||||
if (!scripting_init(ctx)) {
|
|
||||||
|
ctx.game.fog_color = (Color){ 255, 255, 255, 255 }; /* TODO: pick some grey? */
|
||||||
|
|
||||||
|
update_viewport();
|
||||||
|
|
||||||
|
profile_start("game object load");
|
||||||
|
/* now we can actually start doing stuff */
|
||||||
|
game_object_load();
|
||||||
|
profile_end("game object load");
|
||||||
|
|
||||||
|
/* delayed as further as possible so that more work is done before we have to wait */
|
||||||
|
SDL_WaitThread(opengl_load_thread, NULL);
|
||||||
|
profile_end("opengl loading");
|
||||||
|
|
||||||
|
/* TODO: investigate viability of detached thread driver and window creation, as it's the worst load time offender */
|
||||||
|
profile_start("window creation");
|
||||||
|
ctx.window = SDL_CreateWindow(ctx.title,
|
||||||
|
SDL_WINDOWPOS_CENTERED,
|
||||||
|
SDL_WINDOWPOS_CENTERED,
|
||||||
|
(int)ctx.base_render_width,
|
||||||
|
(int)ctx.base_render_height,
|
||||||
|
//SDL_WINDOW_ALLOW_HIGHDPI |
|
||||||
|
SDL_WINDOW_RESIZABLE |
|
||||||
|
SDL_WINDOW_OPENGL);
|
||||||
|
profile_end("window creation");
|
||||||
|
|
||||||
|
if (ctx.window == NULL) {
|
||||||
|
CRY_SDL("Window creation failed.");
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
ctx.render_double_buffered = true;
|
profile_start("opengl context creation");
|
||||||
|
ctx.gl_context = SDL_GL_CreateContext(ctx.window);
|
||||||
|
if (!ctx.gl_context) {
|
||||||
|
CRY_SDL("GL context creation failed.");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SDL_GL_MakeCurrent(ctx.window, ctx.gl_context)) {
|
||||||
|
CRY_SDL("GL context binding failed.");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: figure out what we ultimately prefer */
|
||||||
|
SDL_GL_SetSwapInterval(0);
|
||||||
|
|
||||||
|
if (!render_init())
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
setup_viewport(0, 0, (int)ctx.base_render_width, (int)ctx.base_render_height);
|
||||||
|
profile_end("opengl context creation");
|
||||||
|
|
||||||
|
/* might need this to have multiple windows */
|
||||||
|
ctx.window_id = SDL_GetWindowID(ctx.window);
|
||||||
|
|
||||||
|
profile_start("texture and text cache initialization");
|
||||||
|
textures_cache_init(&ctx.texture_cache, ctx.window);
|
||||||
|
text_cache_init(&ctx.text_cache);
|
||||||
|
profile_end("texture and text cache initialization");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@ -670,10 +672,6 @@ fail:
|
|||||||
|
|
||||||
/* will not be called on an abnormal exit */
|
/* will not be called on an abnormal exit */
|
||||||
static void clean_up(void) {
|
static void clean_up(void) {
|
||||||
/*
|
|
||||||
scripting_deinit(ctx);
|
|
||||||
*/
|
|
||||||
|
|
||||||
input_state_deinit(&ctx.input);
|
input_state_deinit(&ctx.input);
|
||||||
text_cache_deinit(&ctx.text_cache);
|
text_cache_deinit(&ctx.text_cache);
|
||||||
textures_cache_deinit(&ctx.texture_cache);
|
textures_cache_deinit(&ctx.texture_cache);
|
||||||
@ -686,7 +684,9 @@ static void clean_up(void) {
|
|||||||
PHYSFS_deinit();
|
PHYSFS_deinit();
|
||||||
|
|
||||||
SDL_free(ctx.base_dir);
|
SDL_free(ctx.base_dir);
|
||||||
|
SDL_free(ctx.title);
|
||||||
SDL_GL_DeleteContext(ctx.gl_context);
|
SDL_GL_DeleteContext(ctx.gl_context);
|
||||||
|
SDL_GL_UnloadLibrary();
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -698,6 +698,22 @@ static void reset_state(void) {
|
|||||||
|
|
||||||
|
|
||||||
int enter_loop(int argc, char **argv) {
|
int enter_loop(int argc, char **argv) {
|
||||||
|
profile_start("startup");
|
||||||
|
|
||||||
|
profile_start("SDL initialization");
|
||||||
|
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_EVENTS) == -1) {
|
||||||
|
CRY_SDL("SDL initialization failed.");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
profile_end("SDL initialization");
|
||||||
|
|
||||||
|
profile_start("opengl loading");
|
||||||
|
opengl_load_thread = SDL_CreateThread(opengl_load_thread_fn, "opengl loader", NULL);
|
||||||
|
if (!opengl_load_thread) {
|
||||||
|
CRY_SDL("Cannot create opengl loading thread: ");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
ctx.argc = argc;
|
ctx.argc = argc;
|
||||||
ctx.argv = argv;
|
ctx.argv = argv;
|
||||||
ctx.base_dir = SDL_GetBasePath();
|
ctx.base_dir = SDL_GetBasePath();
|
||||||
@ -716,7 +732,7 @@ int enter_loop(int argc, char **argv) {
|
|||||||
|
|
||||||
for (int i = 1; i < argc; ++i) {
|
for (int i = 1; i < argc; ++i) {
|
||||||
/* override data directory */
|
/* override data directory */
|
||||||
if (SDL_strcmp(argv[i], "--data-dir") == 0) {
|
if (SDL_strncmp(argv[i], "--data-dir", sizeof "--data-dir" - 1) == 0) {
|
||||||
if (argv[i+1] == NULL || SDL_strncmp(argv[i+1], "--", 2) == 0) {
|
if (argv[i+1] == NULL || SDL_strncmp(argv[i+1], "--", 2) == 0) {
|
||||||
CRY("Data dir mount override failed.", "No arguments passed (expected 1).");
|
CRY("Data dir mount override failed.", "No arguments passed (expected 1).");
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
@ -733,14 +749,14 @@ int enter_loop(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* force debug mode */
|
/* force debug mode */
|
||||||
if (SDL_strcmp(argv[i], "--debug") == 0) {
|
if (SDL_strncmp(argv[i], "--debug", sizeof "--debug" - 1) == 0) {
|
||||||
force_debug = true;
|
force_debug = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* force release mode */
|
/* force release mode */
|
||||||
if (SDL_strcmp(argv[i], "--release") == 0) {
|
if (SDL_strncmp(argv[i], "--release", sizeof "--release" - 1) == 0) {
|
||||||
force_release = false;
|
force_release = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -773,12 +789,11 @@ int enter_loop(int argc, char **argv) {
|
|||||||
ctx.game.debug = false;
|
ctx.game.debug = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* now we can actually start doing stuff */
|
|
||||||
game_object_load();
|
|
||||||
|
|
||||||
ctx.was_successful = true;
|
ctx.was_successful = true;
|
||||||
ctx.game.initialization_needed = true;
|
ctx.game.initialization_needed = true;
|
||||||
|
|
||||||
|
profile_end("startup");
|
||||||
|
|
||||||
while (ctx.is_running) {
|
while (ctx.is_running) {
|
||||||
if (game_object_try_reloading()) {
|
if (game_object_try_reloading()) {
|
||||||
ctx.game.initialization_needed = true;
|
ctx.game.initialization_needed = true;
|
||||||
@ -788,6 +803,9 @@ int enter_loop(int argc, char **argv) {
|
|||||||
main_loop();
|
main_loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ctx.game.debug)
|
||||||
|
profile_list_stats();
|
||||||
|
|
||||||
/* loop is over */
|
/* loop is over */
|
||||||
game_object_unload();
|
game_object_unload();
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
#include "twn_loop.h"
|
#include "twn_loop.h"
|
||||||
|
|
||||||
|
#ifndef EMSCRIPTEN
|
||||||
#define SDL_MAIN_HANDLED
|
#define SDL_MAIN_HANDLED
|
||||||
|
#endif
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,6 +40,44 @@ static int load_eof_callback(void *user) {
|
|||||||
return context->position == context->size;
|
return context->position == context->size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static SDL_Surface *missing_texture_surface;
|
||||||
|
static uint16_t missing_texture_id;
|
||||||
|
|
||||||
|
static SDL_Surface *gen_missing_texture_surface(void) {
|
||||||
|
Uint32 rmask, gmask, bmask;
|
||||||
|
|
||||||
|
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
|
||||||
|
rmask = 0xff000000;
|
||||||
|
gmask = 0x00ff0000;
|
||||||
|
bmask = 0x0000ff00;
|
||||||
|
#else
|
||||||
|
rmask = 0x000000ff;
|
||||||
|
gmask = 0x0000ff00;
|
||||||
|
bmask = 0x00ff0000;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!missing_texture_surface) {
|
||||||
|
uint8_t *data = SDL_malloc(64 * 64 * 3);
|
||||||
|
for (int y = 0; y < 64; ++y) {
|
||||||
|
for (int x = 0; x < 64; ++x) {
|
||||||
|
/* diagonal stripes, chosen so that asked pixel uvs are corresponding to final output */
|
||||||
|
data[(y * 64 + x) * 3 + 0] = (x / 2 + y / 2) % 2 == 0 ? 175 : 0;
|
||||||
|
data[(y * 64 + x) * 3 + 1] = (x / 2 + y / 2) % 2 == 0 ? 0 : 0;
|
||||||
|
data[(y * 64 + x) * 3 + 2] = (x / 2 + y / 2) % 2 == 0 ? 175 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
missing_texture_surface = SDL_CreateRGBSurfaceFrom(data, 64, 64,
|
||||||
|
3 * 8,
|
||||||
|
64 * 3,
|
||||||
|
rmask, gmask, bmask, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return missing_texture_surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
SDL_Surface *textures_load_surface(const char *path) {
|
SDL_Surface *textures_load_surface(const char *path) {
|
||||||
SDL_RWops *handle = PHYSFSRWOPS_openRead(path);
|
SDL_RWops *handle = PHYSFSRWOPS_openRead(path);
|
||||||
if (handle == NULL)
|
if (handle == NULL)
|
||||||
@ -93,8 +131,9 @@ ERR_CANNOT_CREATE_SURFACE:
|
|||||||
|
|
||||||
ERR_CANNOT_READ_IMAGE:
|
ERR_CANNOT_READ_IMAGE:
|
||||||
ERR_CANNOT_OPEN_FILE:
|
ERR_CANNOT_OPEN_FILE:
|
||||||
CRY(path, "Failed to load image. Aborting...");
|
/* something didn't worked out, use a stub texture */
|
||||||
die_abruptly();
|
log_warn("Cannot open image: %s, using a stub", path);
|
||||||
|
return gen_missing_texture_surface();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -147,6 +186,8 @@ static void upload_texture_from_surface(GPUTexture texture, SDL_Surface *surface
|
|||||||
|
|
||||||
|
|
||||||
static void recreate_current_atlas_texture(TextureCache *cache) {
|
static void recreate_current_atlas_texture(TextureCache *cache) {
|
||||||
|
profile_start("atlas recreation");
|
||||||
|
|
||||||
/* TODO: should surfaces be freed after they cannot be referenced in atlas builing? */
|
/* TODO: should surfaces be freed after they cannot be referenced in atlas builing? */
|
||||||
/* for example, if full page of 64x64 tiles was already filled, there's no real reason to process them further */
|
/* for example, if full page of 64x64 tiles was already filled, there's no real reason to process them further */
|
||||||
SDL_Surface *atlas_surface = cache->atlas_surfaces[cache->atlas_index];
|
SDL_Surface *atlas_surface = cache->atlas_surfaces[cache->atlas_index];
|
||||||
@ -177,6 +218,8 @@ static void recreate_current_atlas_texture(TextureCache *cache) {
|
|||||||
|
|
||||||
/* texturize it! */
|
/* texturize it! */
|
||||||
upload_texture_from_surface(cache->atlas_textures[cache->atlas_index], atlas_surface);
|
upload_texture_from_surface(cache->atlas_textures[cache->atlas_index], atlas_surface);
|
||||||
|
|
||||||
|
profile_end("atlas recreation");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -268,10 +311,9 @@ void textures_cache_init(TextureCache *cache, SDL_Window *window) {
|
|||||||
cache->window = window;
|
cache->window = window;
|
||||||
sh_new_arena(cache->hash);
|
sh_new_arena(cache->hash);
|
||||||
|
|
||||||
cache->node_buffer = SDL_calloc(ctx.texture_atlas_size, sizeof *cache->node_buffer);
|
cache->node_buffer = SDL_malloc(ctx.texture_atlas_size * sizeof *cache->node_buffer);
|
||||||
|
|
||||||
add_new_atlas(cache);
|
add_new_atlas(cache);
|
||||||
recreate_current_atlas_texture(cache);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -290,7 +332,10 @@ void textures_cache_deinit(TextureCache *cache) {
|
|||||||
|
|
||||||
/* free cache hashes */
|
/* free cache hashes */
|
||||||
for (size_t i = 0; i < shlenu(cache->hash); ++i) {
|
for (size_t i = 0; i < shlenu(cache->hash); ++i) {
|
||||||
|
if (missing_texture_surface && cache->hash[i].value.data->pixels != missing_texture_surface->pixels)
|
||||||
stbi_image_free(cache->hash[i].value.data->pixels);
|
stbi_image_free(cache->hash[i].value.data->pixels);
|
||||||
|
else
|
||||||
|
SDL_free(cache->hash[i].value.data->pixels);
|
||||||
SDL_FreeSurface(cache->hash[i].value.data);
|
SDL_FreeSurface(cache->hash[i].value.data);
|
||||||
}
|
}
|
||||||
shfree(cache->hash);
|
shfree(cache->hash);
|
||||||
@ -334,6 +379,9 @@ static TextureKey textures_load(TextureCache *cache, const char *path) {
|
|||||||
return (TextureKey){ (uint16_t)i };
|
return (TextureKey){ (uint16_t)i };
|
||||||
|
|
||||||
SDL_Surface *surface = textures_load_surface(path);
|
SDL_Surface *surface = textures_load_surface(path);
|
||||||
|
if (surface == missing_texture_surface && missing_texture_id != 0)
|
||||||
|
return (TextureKey){ missing_texture_id };
|
||||||
|
|
||||||
Texture new_texture = {
|
Texture new_texture = {
|
||||||
.data = surface,
|
.data = surface,
|
||||||
.mode = infer_texture_mode(surface),
|
.mode = infer_texture_mode(surface),
|
||||||
@ -350,13 +398,22 @@ static TextureKey textures_load(TextureCache *cache, const char *path) {
|
|||||||
new_texture.loner_texture = create_gpu_texture(TEXTURE_FILTER_NEAREAST, true);
|
new_texture.loner_texture = create_gpu_texture(TEXTURE_FILTER_NEAREAST, true);
|
||||||
upload_texture_from_surface(new_texture.loner_texture, surface);
|
upload_texture_from_surface(new_texture.loner_texture, surface);
|
||||||
new_texture.srcrect = (Rect) { .w = (float)surface->w, .h = (float)surface->h };
|
new_texture.srcrect = (Rect) { .w = (float)surface->w, .h = (float)surface->h };
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
/* will be fully populated as the atlas updates */
|
/* will be fully populated as the atlas updates */
|
||||||
new_texture.atlas_index = cache->atlas_index;
|
new_texture.atlas_index = cache->atlas_index;
|
||||||
cache->is_dirty = true;
|
cache->is_dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
shput(cache->hash, path, new_texture);
|
shput(cache->hash, path, new_texture);
|
||||||
return (TextureKey){ (uint16_t)shgeti(cache->hash, path) };
|
|
||||||
|
uint16_t const id = (uint16_t)shlenu(cache->hash) - 1;
|
||||||
|
|
||||||
|
/* reuse this id for every later missing texture */
|
||||||
|
if (surface == missing_texture_surface)
|
||||||
|
missing_texture_id = id;
|
||||||
|
|
||||||
|
return (TextureKey){ id };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -439,6 +496,8 @@ TextureKey textures_get_key(TextureCache *cache, const char *path) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* TODO: this will be bad when dynamic strings are involved */
|
||||||
|
/* to mitigate that we could free ptr_to_texture each frame */
|
||||||
/* try loading */
|
/* try loading */
|
||||||
last_texture = textures_load(cache, path);
|
last_texture = textures_load(cache, path);
|
||||||
hmput(ptr_to_texture, path, last_texture);
|
hmput(ptr_to_texture, path, last_texture);
|
||||||
@ -533,7 +592,7 @@ void textures_bind_repeating(const TextureCache *cache, TextureKey key) {
|
|||||||
|
|
||||||
upload_gpu_texture(repeating_texture,
|
upload_gpu_texture(repeating_texture,
|
||||||
texture.data->pixels,
|
texture.data->pixels,
|
||||||
4,
|
texture.data->format->BytesPerPixel,
|
||||||
texture.data->w,
|
texture.data->w,
|
||||||
texture.data->h);
|
texture.data->h);
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#ifndef TWN_TEXTURES_C_H
|
#ifndef TWN_TEXTURES_C_H
|
||||||
#define TWN_TEXTURES_C_H
|
#define TWN_TEXTURES_C_H
|
||||||
|
|
||||||
#include "twn_util.h"
|
#include "twn_types.h"
|
||||||
#include "twn_texture_modes.h"
|
#include "twn_texture_modes.h"
|
||||||
#include "rendering/twn_gpu_texture_c.h"
|
#include "rendering/twn_gpu_texture_c.h"
|
||||||
|
|
||||||
|
19
src/twn_types_c.h
Normal file
19
src/twn_types_c.h
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#ifndef TWN_TYPES_C_H
|
||||||
|
#define TWN_TYPES_C_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef struct Vec4 {
|
||||||
|
float x;
|
||||||
|
float y;
|
||||||
|
float z;
|
||||||
|
float w;
|
||||||
|
} Vec4;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct Matrix4 {
|
||||||
|
Vec4 row[4];
|
||||||
|
} Matrix4;
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
189
src/twn_util.c
189
src/twn_util.c
@ -9,6 +9,17 @@
|
|||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
|
||||||
|
static struct ProfileItem {
|
||||||
|
char const *key;
|
||||||
|
struct Profile {
|
||||||
|
uint64_t tick_start;
|
||||||
|
uint64_t tick_accum;
|
||||||
|
uint64_t sample_count;
|
||||||
|
uint64_t worst_tick;
|
||||||
|
} value;
|
||||||
|
} *profiles;
|
||||||
|
|
||||||
|
|
||||||
void cry_impl(const char *file, const int line, const char *title, const char *text) {
|
void cry_impl(const char *file, const int line, const char *title, const char *text) {
|
||||||
SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"TEARS AT %s:%d: %s ... %s", file, line, title, text);
|
"TEARS AT %s:%d: %s ... %s", file, line, title, text);
|
||||||
@ -197,59 +208,27 @@ bool strends(const char *str, const char *suffix) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool overlap_rect(const Recti *a, const Recti *b, Recti *result) {
|
/* TODO: have our own */
|
||||||
SDL_Rect a_sdl = { a->x, a->y, a->w, a->h };
|
Rect rect_overlap(const Rect a, const Rect b) {
|
||||||
SDL_Rect b_sdl = { b->x, b->y, b->w, b->h };
|
SDL_FRect a_sdl = { a.x, a.y, a.w, a.h };
|
||||||
SDL_Rect result_sdl = { 0 };
|
SDL_FRect b_sdl = { b.x, b.y, b.w, b.h };
|
||||||
|
|
||||||
bool intersection = SDL_IntersectRect(&a_sdl, &b_sdl, &result_sdl);
|
|
||||||
|
|
||||||
if (result != NULL)
|
|
||||||
*result = (Recti){ result_sdl.x, result_sdl.y, result_sdl.w, result_sdl.h };
|
|
||||||
|
|
||||||
return intersection;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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 };
|
SDL_FRect result_sdl = { 0 };
|
||||||
|
|
||||||
bool intersection = SDL_IntersectFRect(&a_sdl, &b_sdl, &result_sdl);
|
(void)SDL_IntersectFRect(&a_sdl, &b_sdl, &result_sdl);
|
||||||
|
|
||||||
if (result != NULL)
|
return (Rect){ result_sdl.x, result_sdl.y, result_sdl.w, result_sdl.h };
|
||||||
*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) {
|
/* TODO: have our own */
|
||||||
SDL_Rect a_sdl = { a->x, a->y, a->w, a->h };
|
bool rect_intersects(const Rect a, const Rect b) {
|
||||||
SDL_Rect b_sdl = { b->x, b->y, b->w, b->h };
|
SDL_FRect a_sdl = { a.x, a.y, a.w, a.h };
|
||||||
return SDL_HasIntersection(&a_sdl, &b_sdl);
|
SDL_FRect b_sdl = { b.x, b.y, b.w, b.h };
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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);
|
return SDL_HasIntersectionF(&a_sdl, &b_sdl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Rect to_frect(Recti rect) {
|
Vec2 rect_center(Rect 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){
|
return (Vec2){
|
||||||
.x = rect.x + rect.w / 2,
|
.x = rect.x + rect.w / 2,
|
||||||
.y = rect.y + rect.h / 2,
|
.y = rect.y + rect.h / 2,
|
||||||
@ -257,25 +236,52 @@ Vec2 frect_center(Rect rect) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void tick_timer(int *value) {
|
int32_t timer_tick_frames(int32_t frames_left) {
|
||||||
*value = MAX(*value - 1, 0);
|
SDL_assert(frames_left >= 0);
|
||||||
|
return MAX(frames_left - 1, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void tick_ftimer(float *value) {
|
float timer_tick_seconds(float seconds_left) {
|
||||||
*value = MAX(*value - ((float)ctx.delta_time / (float)ctx.clocks_per_second), 0.0f);
|
SDL_assert(seconds_left >= 0);
|
||||||
|
return MAX(seconds_left - ((float)ctx.delta_time / (float)ctx.clocks_per_second), 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool repeat_ftimer(float *value, float at) {
|
TimerElapseFramesResult timer_elapse_frames(int32_t frames_left, int32_t interval) {
|
||||||
*value -= (float)ctx.delta_time / (float)ctx.clocks_per_second;
|
SDL_assert(frames_left >= 0);
|
||||||
if (*value < 0.0f) {
|
SDL_assert(interval > 0);
|
||||||
*value += at;
|
|
||||||
return true;
|
frames_left -= 1;
|
||||||
|
bool elapsed = false;
|
||||||
|
if (frames_left <= 0) {
|
||||||
|
elapsed = true;
|
||||||
|
frames_left += interval;
|
||||||
}
|
}
|
||||||
return false;
|
return (TimerElapseFramesResult) {
|
||||||
|
.elapsed = elapsed,
|
||||||
|
.frames_left = frames_left
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 */
|
/* TODO: handle utf8 */
|
||||||
char *expand_asterisk(const char *mask, const char *to) {
|
char *expand_asterisk(const char *mask, const char *to) {
|
||||||
const char *offset = SDL_strchr(mask, '*');
|
const char *offset = SDL_strchr(mask, '*');
|
||||||
@ -292,3 +298,80 @@ char *expand_asterisk(const char *mask, const char *to) {
|
|||||||
SDL_memcpy(str + (offset - mask) + to_len, offset + 1, mask_len - (offset - mask));
|
SDL_memcpy(str + (offset - mask) + to_len, offset + 1, mask_len - (offset - mask));
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void profile_start(char profile[const static 1]) {
|
||||||
|
uint64_t tick_accum = 0, sample_count = 0, worst_tick = 0;
|
||||||
|
|
||||||
|
struct ProfileItem const *p = shgetp_null(profiles, profile);
|
||||||
|
if (p) {
|
||||||
|
tick_accum = p->value.tick_accum;
|
||||||
|
sample_count = p->value.sample_count;
|
||||||
|
worst_tick = p->value.worst_tick;
|
||||||
|
}
|
||||||
|
|
||||||
|
shput(profiles, profile, ((struct Profile) {
|
||||||
|
.tick_start = SDL_GetPerformanceCounter(),
|
||||||
|
.tick_accum = tick_accum,
|
||||||
|
.sample_count = sample_count,
|
||||||
|
.worst_tick = worst_tick,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t took = SDL_GetPerformanceCounter() - p->value.tick_start;
|
||||||
|
|
||||||
|
p->value.tick_accum += took;
|
||||||
|
p->value.sample_count++;
|
||||||
|
|
||||||
|
if (p->value.worst_tick < took)
|
||||||
|
p->value.worst_tick = took;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void profile_list_stats(void) {
|
||||||
|
for (size_t i = 0; i < shlenu(profiles); ++i) {
|
||||||
|
if (profiles[i].value.sample_count == 0) {
|
||||||
|
log_warn("Profile %s was started, but not once finished.", profiles[i].key);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (profiles[i].value.sample_count == 1) {
|
||||||
|
log_info("Profile '%s' took: %fs",
|
||||||
|
profiles[i].key,
|
||||||
|
(double)profiles[i].value.tick_accum / (double)(SDL_GetPerformanceFrequency()));
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (profiles[i].value.sample_count > 1) {
|
||||||
|
log_info("Profile '%s' on average took: %fs, worst case: %fs, sample count: %llu",
|
||||||
|
profiles[i].key,
|
||||||
|
(double)profiles[i].value.tick_accum /
|
||||||
|
(double)profiles[i].value.sample_count /
|
||||||
|
(double)(SDL_GetPerformanceFrequency()),
|
||||||
|
(double)profiles[i].value.worst_tick / (double)(SDL_GetPerformanceFrequency()),
|
||||||
|
profiles[i].value.sample_count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void log_vec2(Vec2 vector, char const *message) {
|
||||||
|
if (!message) message = "Vec2";
|
||||||
|
log_info("%s = (%f, %f)", message, (double)vector.x, (double)vector.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void log_vec3(Vec3 vector, char const *message) {
|
||||||
|
if (!message) message = "Vec3";
|
||||||
|
log_info("%s = (%f, %f, %f)", message, (double)vector.x, (double)vector.y, (double)vector.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
void log_rect(Rect rect, char const *message) {
|
||||||
|
if (!message) message = "Rect";
|
||||||
|
log_info("%s = (%f, %f, %f, %f)", message, (double)rect.x, (double)rect.y, (double)rect.w, (double)rect.h);
|
||||||
|
}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
#define MAX SDL_max
|
#define MAX SDL_max
|
||||||
#define MIN SDL_min
|
#define MIN SDL_min
|
||||||
|
2
third-party/libxm/CMakeLists.txt
vendored
2
third-party/libxm/CMakeLists.txt
vendored
@ -1,4 +1,4 @@
|
|||||||
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
|
CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
|
||||||
PROJECT(libxm C)
|
PROJECT(libxm C)
|
||||||
|
|
||||||
FUNCTION(OPTION_AND_DEFINE name description default_value)
|
FUNCTION(OPTION_AND_DEFINE name description default_value)
|
||||||
|
2
third-party/physfs/CMakeLists.txt
vendored
2
third-party/physfs/CMakeLists.txt
vendored
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
set(PHYSFS_VERSION 3.2.0)
|
set(PHYSFS_VERSION 3.2.0)
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.0)
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
|
||||||
project(PhysicsFS VERSION ${PHYSFS_VERSION} LANGUAGES C )
|
project(PhysicsFS VERSION ${PHYSFS_VERSION} LANGUAGES C )
|
||||||
|
|
||||||
|
2
third-party/stb/stb_ds.h
vendored
2
third-party/stb/stb_ds.h
vendored
@ -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_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_arraddnoff stbds_arraddnindex
|
||||||
#define stbds_arrlast(a) ((a)[stbds_header(a)->length-1])
|
#define stbds_arrlast(a) ((a)[stbds_header(a)->length-1])
|
||||||
#define stbds_arrfree(a) ((void) ((a) ? STBDS_FREE(NULL,stbds_header(a)) : (void)0), (a)=NULL)
|
#define stbds_arrfree(a) ((void) ((a) ? stbds_arrfreef(a) : (void)0), (a)=NULL)
|
||||||
#define stbds_arrdel(a,i) stbds_arrdeln(a,i,1)
|
#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_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)
|
#define stbds_arrdelswap(a,i) ((a)[i] = stbds_arrlast(a), stbds_header(a)->length -= 1)
|
||||||
|
Reference in New Issue
Block a user