Compare commits
	
		
			127 Commits
		
	
	
		
			double
			...
			e281ba593c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					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
 | 
			
		||||
*.xm 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/
 | 
			
		||||
.idea/
 | 
			
		||||
.cache/
 | 
			
		||||
.build/
 | 
			
		||||
.build-web/
 | 
			
		||||
build/
 | 
			
		||||
build-web/
 | 
			
		||||
build/
 | 
			
		||||
out/
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,9 @@ cmake_minimum_required(VERSION 3.21)
 | 
			
		||||
 | 
			
		||||
project(townengine LANGUAGES C)
 | 
			
		||||
 | 
			
		||||
set(CMAKE_MESSAGE_LOG_LEVEL "WARNING")
 | 
			
		||||
set(CMAKE_INSTALL_MESSAGE NEVER)
 | 
			
		||||
 | 
			
		||||
# SDL dependencies
 | 
			
		||||
# for whatever reason Emscripten has SDL2 config, but not actual SDL2 port by default
 | 
			
		||||
if(NOT EMSCRIPTEN)
 | 
			
		||||
@@ -24,6 +27,7 @@ set(TWN_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "")
 | 
			
		||||
 | 
			
		||||
# feature configuration, set them with -DFEATURE=ON/OFF in cli
 | 
			
		||||
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)
 | 
			
		||||
 | 
			
		||||
# todo: figure out how to compile for dynamic linking instead
 | 
			
		||||
@@ -81,8 +85,7 @@ endif()
 | 
			
		||||
if(TWN_RENDERING_API MATCHES OPENGL_15)
 | 
			
		||||
        set(SYSTEM_SOURCE_FILES ${SYSTEM_SOURCE_FILES}
 | 
			
		||||
                src/rendering/twn_gl_any_rendering.c
 | 
			
		||||
                src/rendering/twn_gl_15_rendering.c
 | 
			
		||||
                src/rendering/twn_gl_15_gpu_texture.c)
 | 
			
		||||
                src/rendering/twn_gl_15_rendering.c)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
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_util.c include/twn_util.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/rendering/twn_draw.c src/rendering/twn_draw_c.h
 | 
			
		||||
        src/rendering/twn_quads.c
 | 
			
		||||
        src/rendering/twn_sprites.c
 | 
			
		||||
        src/rendering/twn_rects.c
 | 
			
		||||
        src/rendering/twn_text.c
 | 
			
		||||
        src/rendering/twn_triangles.c
 | 
			
		||||
        src/rendering/twn_billboards.c
 | 
			
		||||
        src/rendering/twn_circles.c
 | 
			
		||||
        src/rendering/twn_skybox.c
 | 
			
		||||
        src/rendering/twn_fog.c)
 | 
			
		||||
        src/rendering/twn_skybox.c)
 | 
			
		||||
 | 
			
		||||
set(TWN_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_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
 | 
			
		||||
target_precompile_headers(${TWN_TARGET} PRIVATE
 | 
			
		||||
        $<$<NOT:$<BOOL:${EMSCRIPTEN}>>:third-party/glad/include/glad/glad.h>
 | 
			
		||||
@@ -156,14 +162,13 @@ function(give_options_without_warnings target)
 | 
			
		||||
 | 
			
		||||
        set(BUILD_FLAGS_RELEASE
 | 
			
		||||
                -O3
 | 
			
		||||
                -flto=auto
 | 
			
		||||
                -flto=$<IF:$<STREQUAL:${CMAKE_C_COMPILER_ID},Clang>,thin,auto>
 | 
			
		||||
                -mavx -mavx2
 | 
			
		||||
                -Wl,--gc-sections
 | 
			
		||||
                -fdata-sections
 | 
			
		||||
                -ffunction-sections
 | 
			
		||||
                -funroll-loops
 | 
			
		||||
                -fomit-frame-pointer
 | 
			
		||||
                -s)
 | 
			
		||||
                $<$<STREQUAL:${CMAKE_C_COMPILER_ID},Gnu>:-s>)
 | 
			
		||||
 | 
			
		||||
        set(BUILD_FLAGS_DEBUG
 | 
			
		||||
                -O0
 | 
			
		||||
@@ -173,6 +178,19 @@ function(give_options_without_warnings target)
 | 
			
		||||
		$<$<BOOL:${TWN_SANITIZE}>:-fstack-protector-all -fsanitize=undefined -fsanitize=address>
 | 
			
		||||
                $<$<BOOL:${EMSCRIPTEN}>:-gsource-map>)
 | 
			
		||||
 | 
			
		||||
        if (CMAKE_C_COMPILER_LINKER_ID MATCHES GNU OR CMAKE_C_COMPILER_LINKER_ID MATCHES GNUgold)
 | 
			
		||||
                set(THINLTO_USAGE "-plugin-opt,")
 | 
			
		||||
        endif()
 | 
			
		||||
        if (CMAKE_C_COMPILER_LINKER_ID MATCHES LLD)
 | 
			
		||||
                set(THINLTO_USAGE "--thinlto-")
 | 
			
		||||
        endif()
 | 
			
		||||
 | 
			
		||||
        if (THINLTO_USAGE)
 | 
			
		||||
                set(BUILD_SHARED_LIBRARY_FLAGS_RELEASE
 | 
			
		||||
                        $<$<STREQUAL:${CMAKE_C_COMPILER_ID},Clang>:-Wl,${THINLTO_USAGE}cache-dir=${CMAKE_CURRENT_BINARY_DIR}/linker-cache/>
 | 
			
		||||
                        $<$<STREQUAL:${CMAKE_C_COMPILER_ID},Clang>:-Wl,${THINLTO_USAGE}cache-policy=prune_after=30m>)
 | 
			
		||||
        endif()
 | 
			
		||||
 | 
			
		||||
        target_compile_options(${target} PUBLIC
 | 
			
		||||
                                         ${BUILD_FLAGS}
 | 
			
		||||
                                         $<$<CONFIG:Release>:${BUILD_FLAGS_RELEASE}>
 | 
			
		||||
@@ -186,6 +204,15 @@ function(give_options_without_warnings target)
 | 
			
		||||
                                      -Bsymbolic-functions
 | 
			
		||||
                                      $<$<BOOL:${LINUX}>:-Wl,--hash-style=gnu>)
 | 
			
		||||
 | 
			
		||||
        get_target_property(target_type ${target} TYPE)
 | 
			
		||||
        if (target_type MATCHES SHARED_LIBRARY)
 | 
			
		||||
                target_compile_options(${target} PUBLIC
 | 
			
		||||
                                                 $<$<CONFIG:Release>:${BUILD_SHARED_LIBRARY_FLAGS_RELEASE}>)
 | 
			
		||||
 | 
			
		||||
                target_link_options(${target} PUBLIC
 | 
			
		||||
                                              $<$<CONFIG:Release>:${BUILD_SHARED_LIBRARY_FLAGS_RELEASE}>)
 | 
			
		||||
        endif()
 | 
			
		||||
 | 
			
		||||
        target_compile_definitions(${target} PRIVATE
 | 
			
		||||
                                             $<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:TWN_FEATURE_DYNLIB_GAME>
 | 
			
		||||
                                             $<$<BOOL:${LINUX}>:_GNU_SOURCE>)
 | 
			
		||||
@@ -316,8 +343,3 @@ include_deps(${TWN_TARGET})
 | 
			
		||||
link_deps(${TWN_TARGET})
 | 
			
		||||
target_link_libraries(${TWN_TARGET} PUBLIC twn_third_parties)
 | 
			
		||||
target_include_directories(${TWN_TARGET} PRIVATE ${TWN_ROOT_DIR}/src)
 | 
			
		||||
 | 
			
		||||
# move compie_commands.json into root directory so that it plays nicer with code editors without any configuration
 | 
			
		||||
add_custom_target(copy-compile-commands ALL
 | 
			
		||||
                 ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_BINARY_DIR}/compile_commands.json
 | 
			
		||||
                                                       ${TWN_ROOT_DIR})
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ if(NOT CMAKE_BUILD_TYPE)
 | 
			
		||||
        set(CMAKE_BUILD_TYPE Debug)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNBUILDDIR})
 | 
			
		||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build)
 | 
			
		||||
 | 
			
		||||
set(SOURCE_FILES
 | 
			
		||||
        game.c
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,6 @@ app_id = "bunnymark"
 | 
			
		||||
dev_id = "morshy"
 | 
			
		||||
 | 
			
		||||
[game]
 | 
			
		||||
base_render_width = 640
 | 
			
		||||
base_render_height = 480
 | 
			
		||||
resolution = [ 640, 480 ]
 | 
			
		||||
 | 
			
		||||
[engine]
 | 
			
		||||
 
 | 
			
		||||
@@ -20,13 +20,13 @@ static void handle_input(void)
 | 
			
		||||
    if (ctx.mouse_position.y <= 60)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    if (input_is_action_pressed("add_a_bit"))
 | 
			
		||||
    if (input_action_pressed("add_a_bit"))
 | 
			
		||||
    { // Left click
 | 
			
		||||
        for (int i = 0; i < LEFT_CLICK_ADD; i++)
 | 
			
		||||
        {
 | 
			
		||||
            if (state->bunniesCount < MAX_BUNNIES)
 | 
			
		||||
            {
 | 
			
		||||
                state->bunnies[state->bunniesCount].position = input_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.y = (float)(rand() % 500 - 250) / 60.0f;
 | 
			
		||||
                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
 | 
			
		||||
        for (int i = 0; i < RIGHT_CLICK_ADD; i++)
 | 
			
		||||
        {
 | 
			
		||||
            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.y = (float)(rand() % 500 - 250) / 60.0f;
 | 
			
		||||
                state->bunnies[state->bunniesCount].color =
 | 
			
		||||
@@ -67,14 +67,11 @@ void game_tick(void)
 | 
			
		||||
        // Allocating State struct to store data there
 | 
			
		||||
        if (!ctx.udata)
 | 
			
		||||
            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;
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i < state->bunniesCount; i++)
 | 
			
		||||
@@ -97,7 +94,7 @@ void game_tick(void)
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i < state->bunniesCount; i++)
 | 
			
		||||
    { // Draw each bunny based on their position and color, also scale accordingly
 | 
			
		||||
        m_sprite(m_set(path, "wabbit_alpha.png"),
 | 
			
		||||
        m_sprite(m_set(texture, "wabbit_alpha.png"),
 | 
			
		||||
                 m_set(rect, ((Rect){.x = state->bunnies[i].position.x,
 | 
			
		||||
                                     .y = state->bunnies[i].position.y,
 | 
			
		||||
                                     .w = BUNNY_W * SPRITE_SCALE,
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ if(NOT CMAKE_BUILD_TYPE)
 | 
			
		||||
        set(CMAKE_BUILD_TYPE Debug)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNBUILDDIR})
 | 
			
		||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build)
 | 
			
		||||
 | 
			
		||||
set(SOURCE_FILES
 | 
			
		||||
        game.c
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,6 @@ app_id = "platformer-demo"
 | 
			
		||||
dev_id = "townengine-team"
 | 
			
		||||
 | 
			
		||||
[game]
 | 
			
		||||
base_render_width = 640
 | 
			
		||||
base_render_height = 360
 | 
			
		||||
resolution = [ 640, 360 ]
 | 
			
		||||
 | 
			
		||||
[engine]
 | 
			
		||||
 
 | 
			
		||||
@@ -18,45 +18,18 @@ void game_tick(void) {
 | 
			
		||||
            state->ctx = &ctx;
 | 
			
		||||
            state->scene = title_scene(state);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        input_add_action("debug_toggle");
 | 
			
		||||
        input_bind_action_control("debug_toggle", CONTROL_BACKSPACE);
 | 
			
		||||
 | 
			
		||||
        input_add_action("debug_dump_atlases");
 | 
			
		||||
        input_bind_action_control("debug_dump_atlases", CONTROL_HOME);
 | 
			
		||||
 | 
			
		||||
        input_add_action("player_left");
 | 
			
		||||
        input_bind_action_control("player_left", CONTROL_A);
 | 
			
		||||
 | 
			
		||||
        input_add_action("player_right");
 | 
			
		||||
        input_bind_action_control("player_right", CONTROL_D);
 | 
			
		||||
 | 
			
		||||
        input_add_action("player_forward");
 | 
			
		||||
        input_bind_action_control("player_forward", CONTROL_W);
 | 
			
		||||
 | 
			
		||||
        input_add_action("player_backward");
 | 
			
		||||
        input_bind_action_control("player_backward", CONTROL_S);
 | 
			
		||||
 | 
			
		||||
        input_add_action("player_jump");
 | 
			
		||||
        input_bind_action_control("player_jump", CONTROL_SPACE);
 | 
			
		||||
 | 
			
		||||
        input_add_action("player_run");
 | 
			
		||||
        input_bind_action_control("player_run", CONTROL_LSHIFT);
 | 
			
		||||
 | 
			
		||||
        input_add_action("ui_accept");
 | 
			
		||||
        input_bind_action_control("ui_accept", CONTROL_RETURN);
 | 
			
		||||
 | 
			
		||||
        input_add_action("mouse_capture_toggle");
 | 
			
		||||
        input_bind_action_control("mouse_capture_toggle", CONTROL_ESCAPE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    State *state = ctx.udata;
 | 
			
		||||
 | 
			
		||||
    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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (input_is_action_just_pressed("debug_dump_atlases")) {
 | 
			
		||||
    if (input_action_just_pressed("debug_dump_atlases")) {
 | 
			
		||||
        textures_dump_atlases();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,28 +11,28 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static void update_timers(Player *player) {
 | 
			
		||||
     tick_timer(&player->jump_air_timer);
 | 
			
		||||
     tick_timer(&player->jump_coyote_timer);
 | 
			
		||||
     tick_timer(&player->jump_buffer_timer);
 | 
			
		||||
    player->jump_air_timer = timer_tick_frames(player->jump_air_timer);
 | 
			
		||||
    player->jump_coyote_timer = timer_tick_frames(player->jump_coyote_timer);
 | 
			
		||||
    player->jump_buffer_timer = timer_tick_frames(player->jump_buffer_timer);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static void input_move(Player *player) {
 | 
			
		||||
    /* apply horizontal damping when the player stops moving */
 | 
			
		||||
    /* in other words, make it decelerate to a standstill */
 | 
			
		||||
    if (!input_is_action_pressed("player_left") &&
 | 
			
		||||
        !input_is_action_pressed("player_right"))
 | 
			
		||||
    if (!input_action_pressed("player_left") &&
 | 
			
		||||
        !input_action_pressed("player_right"))
 | 
			
		||||
    {
 | 
			
		||||
        player->dx *= player->horizontal_damping;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int input_dir = 0;
 | 
			
		||||
    if (input_is_action_pressed("player_left"))
 | 
			
		||||
    if (input_action_pressed("player_left"))
 | 
			
		||||
        input_dir = -1;
 | 
			
		||||
    if (input_is_action_pressed("player_right"))
 | 
			
		||||
    if (input_action_pressed("player_right"))
 | 
			
		||||
        input_dir = 1;
 | 
			
		||||
    if (input_is_action_pressed("player_left") &&
 | 
			
		||||
        input_is_action_pressed("player_right"))
 | 
			
		||||
    if (input_action_pressed("player_left") &&
 | 
			
		||||
        input_action_pressed("player_right"))
 | 
			
		||||
        input_dir = 0;
 | 
			
		||||
 | 
			
		||||
    player->dx += (float)input_dir * player->run_horizontal_speed;
 | 
			
		||||
@@ -56,7 +56,7 @@ static void jump(Player *player) {
 | 
			
		||||
static void input_jump(Player *player) {
 | 
			
		||||
    player->current_gravity_multiplier = player->jump_default_multiplier;
 | 
			
		||||
 | 
			
		||||
    if (input_is_action_just_pressed("player_jump")) {
 | 
			
		||||
    if (input_action_just_pressed("player_jump")) {
 | 
			
		||||
        player->jump_air_timer = 0;
 | 
			
		||||
        player->jump_buffer_timer = player->jump_buffer_ticks;
 | 
			
		||||
 | 
			
		||||
@@ -65,7 +65,7 @@ static void input_jump(Player *player) {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (input_is_action_pressed("player_jump")) {
 | 
			
		||||
    if (input_action_pressed("player_jump")) {
 | 
			
		||||
        if (player->action != PLAYER_ACTION_GROUND && player->jump_air_timer > 0) {
 | 
			
		||||
            player->current_gravity_multiplier = player->jump_boosted_multiplier;
 | 
			
		||||
            player->dy += player->jump_force_increase;
 | 
			
		||||
@@ -147,7 +147,7 @@ static bool corner_correct(Player *player, Rect collision) {
 | 
			
		||||
 | 
			
		||||
static void calc_collisions_x(Player *player) {
 | 
			
		||||
    Rect collision;
 | 
			
		||||
    bool is_colliding = world_find_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;
 | 
			
		||||
 | 
			
		||||
    float player_center_x = player->collider_x.x + (player->collider_x.w / 2);
 | 
			
		||||
@@ -164,7 +164,7 @@ static void calc_collisions_x(Player *player) {
 | 
			
		||||
 | 
			
		||||
static void calc_collisions_y(Player *player) {
 | 
			
		||||
    Rect collision;
 | 
			
		||||
    bool is_colliding = world_find_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;
 | 
			
		||||
 | 
			
		||||
    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 },
 | 
			
		||||
                24,
 | 
			
		||||
                (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) {
 | 
			
		||||
    SceneIngame *scn = (SceneIngame *)state->scene;
 | 
			
		||||
 | 
			
		||||
    input_action("player_left", CONTROL_A);
 | 
			
		||||
    input_action("player_right", CONTROL_D);
 | 
			
		||||
    input_action("player_forward", CONTROL_W);
 | 
			
		||||
    input_action("player_backward", CONTROL_S);
 | 
			
		||||
    input_action("player_jump", CONTROL_SPACE);
 | 
			
		||||
    input_action("player_run", CONTROL_LSHIFT);
 | 
			
		||||
 | 
			
		||||
    world_drawdef(scn->world);
 | 
			
		||||
    player_calc(scn->player);
 | 
			
		||||
 | 
			
		||||
    const Vec3 right = m_vec_norm(m_vec_cross(scn->cam.target, scn->cam.up));
 | 
			
		||||
    const float speed = 0.04f; /* TODO: put this in a better place */
 | 
			
		||||
    if (input_is_action_pressed("player_left"))
 | 
			
		||||
        scn->cam.pos = vec3_sub(scn->cam.pos, m_vec_scale(right, speed));
 | 
			
		||||
 | 
			
		||||
    if (input_is_action_pressed("player_right"))
 | 
			
		||||
        scn->cam.pos = vec3_add(scn->cam.pos, m_vec_scale(right, speed));
 | 
			
		||||
 | 
			
		||||
    if (input_is_action_pressed("player_forward"))
 | 
			
		||||
        scn->cam.pos = vec3_add(scn->cam.pos, m_vec_scale(scn->cam.target, speed));
 | 
			
		||||
 | 
			
		||||
    if (input_is_action_pressed("player_backward"))
 | 
			
		||||
        scn->cam.pos = vec3_sub(scn->cam.pos, m_vec_scale(scn->cam.target, speed));
 | 
			
		||||
 | 
			
		||||
    if (input_is_action_pressed("player_jump"))
 | 
			
		||||
        scn->cam.pos.y += speed;
 | 
			
		||||
 | 
			
		||||
    if (input_is_action_pressed("player_run"))
 | 
			
		||||
        scn->cam.pos.y -= speed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -54,7 +41,5 @@ Scene *ingame_scene(State *state) {
 | 
			
		||||
    new_scene->world = world_create();
 | 
			
		||||
    new_scene->player = player_create(new_scene->world);
 | 
			
		||||
 | 
			
		||||
    new_scene->cam = (Camera){ .pos = { 32, 0, 1 }, .up = { 0, 1, 0 }, .fov = (float)M_PI_2 };
 | 
			
		||||
 | 
			
		||||
    return (Scene *)new_scene;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,8 +15,6 @@ typedef struct SceneIngame {
 | 
			
		||||
    World *world;
 | 
			
		||||
    Player *player;
 | 
			
		||||
 | 
			
		||||
    Camera cam;
 | 
			
		||||
 | 
			
		||||
    /* TODO: put this in a better place */
 | 
			
		||||
    float yaw;
 | 
			
		||||
    float pitch;
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,9 @@ static void title_tick(State *state) {
 | 
			
		||||
    SceneTitle *scn = (SceneTitle *)state->scene;
 | 
			
		||||
    (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);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
@@ -23,13 +25,13 @@ static void title_tick(State *state) {
 | 
			
		||||
            ((float)ctx.resolution.x / 2) - ((float)320 / 2), 64, 320, 128 }));
 | 
			
		||||
    
 | 
			
		||||
    /* draw the tick count as an example of dynamic text */
 | 
			
		||||
    size_t text_str_len = snprintf(NULL, 0, "%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);
 | 
			
		||||
    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";
 | 
			
		||||
    int text_h = 32;
 | 
			
		||||
    int text_w = draw_text_width(text_str, text_h, font);
 | 
			
		||||
    float text_h = 32;
 | 
			
		||||
    float text_w = draw_text_width(text_str, text_h, font);
 | 
			
		||||
 | 
			
		||||
    draw_rectangle(
 | 
			
		||||
        (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 col = 0; col < world->tilemap_width; ++col) {
 | 
			
		||||
            world->tiles[(row * world->tilemap_width) + col] = (struct Tile) {
 | 
			
		||||
                .rect = (Recti) {
 | 
			
		||||
                    .x = (int)col * world->tile_size,
 | 
			
		||||
                    .y = (int)row * world->tile_size,
 | 
			
		||||
                    .w = world->tile_size,
 | 
			
		||||
                    .h = world->tile_size,
 | 
			
		||||
                .rect = (Rect) {
 | 
			
		||||
                    .x = (float)(col * world->tile_size),
 | 
			
		||||
                    .y = (float)(row * world->tile_size),
 | 
			
		||||
                    .w = (float)world->tile_size,
 | 
			
		||||
                    .h = (float)world->tile_size,
 | 
			
		||||
                },
 | 
			
		||||
                .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) {
 | 
			
		||||
    return (Vec2i) {
 | 
			
		||||
        .x = (int)floor(x / (float)world->tile_size),
 | 
			
		||||
        .y = (int)floor(y / (float)world->tile_size),
 | 
			
		||||
static Vec2 to_grid_location(struct World *world, float x, float y) {
 | 
			
		||||
    return (Vec2) {
 | 
			
		||||
        .x = floor(x / (float)world->tile_size),
 | 
			
		||||
        .y = floor(y / (float)world->tile_size),
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -39,8 +39,7 @@ static void drawdef_debug(struct World *world) {
 | 
			
		||||
    for (size_t i = 0; i < world->tilemap_height * world->tilemap_width; ++i) {
 | 
			
		||||
        if (world->tiles[i].type == TILE_TYPE_VOID) continue;
 | 
			
		||||
 | 
			
		||||
        draw_rectangle(to_frect(world->tiles[i].rect),
 | 
			
		||||
                        (Color) { 255, 0, 255, 128 });
 | 
			
		||||
        draw_rectangle(world->tiles[i].rect, (Color) { 255, 0, 255, 128 });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -106,14 +105,14 @@ void world_drawdef(struct World *world) {
 | 
			
		||||
        if (world->tiles[i].type == TILE_TYPE_VOID)
 | 
			
		||||
            continue;
 | 
			
		||||
 | 
			
		||||
        m_sprite("/assets/white.png", to_frect(world->tiles[i].rect));
 | 
			
		||||
        m_sprite("/assets/white.png", world->tiles[i].rect);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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;
 | 
			
		||||
 | 
			
		||||
    const size_t tile_count = world->tilemap_height * world->tilemap_width;
 | 
			
		||||
@@ -121,19 +120,12 @@ bool world_find_intersect_frect(struct World *world, Rect rect, Rect *intersecti
 | 
			
		||||
        if (world->tiles[i].type == TILE_TYPE_VOID)
 | 
			
		||||
            continue;
 | 
			
		||||
 | 
			
		||||
        Rect tile_frect = {
 | 
			
		||||
            .x = (float)(world->tiles[i].rect.x),
 | 
			
		||||
            .y = (float)(world->tiles[i].rect.y),
 | 
			
		||||
            .w = (float)(world->tiles[i].rect.w),
 | 
			
		||||
            .h = (float)(world->tiles[i].rect.h),
 | 
			
		||||
        };
 | 
			
		||||
        Rect const tile_frect = world->tiles[i].rect;
 | 
			
		||||
 | 
			
		||||
        if (intersection == NULL) {
 | 
			
		||||
            Rect temp;
 | 
			
		||||
            is_intersecting = overlap_frect(&rect, &tile_frect, &temp);
 | 
			
		||||
        } else {
 | 
			
		||||
            is_intersecting = overlap_frect(&rect, &tile_frect, intersection);
 | 
			
		||||
        }
 | 
			
		||||
        is_intersecting = rect_intersects(rect, tile_frect);
 | 
			
		||||
 | 
			
		||||
        if (intersection)
 | 
			
		||||
            *intersection = rect_overlap(rect, tile_frect);
 | 
			
		||||
 | 
			
		||||
        if (is_intersecting)
 | 
			
		||||
            break; 
 | 
			
		||||
@@ -143,46 +135,21 @@ bool world_find_intersect_frect(struct World *world, Rect rect, Rect *intersecti
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
bool world_find_intersect_rect(struct World *world, Recti rect, Recti *intersection) {
 | 
			
		||||
    bool is_intersecting = false;
 | 
			
		||||
 | 
			
		||||
    const size_t tile_count = world->tilemap_height * world->tilemap_width;
 | 
			
		||||
    for (size_t i = 0; i < tile_count; ++i) {
 | 
			
		||||
        if (world->tiles[i].type == TILE_TYPE_VOID)
 | 
			
		||||
            continue;
 | 
			
		||||
 | 
			
		||||
        Recti *tile_rect = &world->tiles[i].rect;
 | 
			
		||||
 | 
			
		||||
        if (intersection == NULL) {
 | 
			
		||||
            Recti temp;
 | 
			
		||||
            is_intersecting = overlap_rect(&rect, tile_rect, &temp);
 | 
			
		||||
        } else {
 | 
			
		||||
            is_intersecting = overlap_rect(&rect, tile_rect, intersection);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (is_intersecting)
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return is_intersecting;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
bool world_is_tile_at(struct World *world, float x, float y) {
 | 
			
		||||
    Vec2i position_in_grid = to_grid_location(world, x, y);
 | 
			
		||||
    return world->tilemap[position_in_grid.y][position_in_grid.x] != TILE_TYPE_VOID;
 | 
			
		||||
    Vec2 position_in_grid = to_grid_location(world, x, y);
 | 
			
		||||
    return world->tilemap[(int32_t)position_in_grid.y][(int32_t)position_in_grid.x] != TILE_TYPE_VOID;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void world_place_tile(struct World *world, float x, float y) {
 | 
			
		||||
    Vec2i position_in_grid = to_grid_location(world, x, y);
 | 
			
		||||
    world->tilemap[position_in_grid.y][position_in_grid.x] = TILE_TYPE_SOLID;
 | 
			
		||||
    Vec2 position_in_grid = to_grid_location(world, x, y);
 | 
			
		||||
    world->tilemap[(int32_t)position_in_grid.y][(int32_t)position_in_grid.x] = TILE_TYPE_SOLID;
 | 
			
		||||
    update_tiles(world);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void world_remove_tile(struct World *world, float x, float y) {
 | 
			
		||||
    Vec2i position_in_grid = to_grid_location(world, x, y);
 | 
			
		||||
    world->tilemap[position_in_grid.y][position_in_grid.x] = TILE_TYPE_VOID;
 | 
			
		||||
    Vec2 position_in_grid = to_grid_location(world, x, y);
 | 
			
		||||
    world->tilemap[(int32_t)position_in_grid.y][(int32_t)position_in_grid.x] = TILE_TYPE_VOID;
 | 
			
		||||
    update_tiles(world);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ typedef enum TileType {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
typedef struct Tile {
 | 
			
		||||
    Recti rect;
 | 
			
		||||
    Rect rect;
 | 
			
		||||
    TileType type;
 | 
			
		||||
} Tile;
 | 
			
		||||
 | 
			
		||||
@@ -34,8 +34,7 @@ typedef struct World {
 | 
			
		||||
World *world_create(void);
 | 
			
		||||
void world_destroy(World *world);
 | 
			
		||||
void world_drawdef(World *world);
 | 
			
		||||
bool world_find_intersect_frect(World *world, Rect rect, Rect *intersection);
 | 
			
		||||
bool world_find_intersect_rect(World *world, Recti rect, Recti *intersection);
 | 
			
		||||
bool world_find_rect_intersects(World *world, Rect rect, Rect *intersection);
 | 
			
		||||
bool world_is_tile_at(World *world, float x, float y);
 | 
			
		||||
void world_place_tile(World *world, float x, float y);
 | 
			
		||||
void world_remove_tile(World *world, float x, float y);
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ if(NOT CMAKE_BUILD_TYPE)
 | 
			
		||||
        set(CMAKE_BUILD_TYPE Debug)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNBUILDDIR})
 | 
			
		||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build)
 | 
			
		||||
 | 
			
		||||
set(SOURCE_FILES
 | 
			
		||||
        game.c
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,10 @@
 | 
			
		||||
[about]
 | 
			
		||||
title = "Serene Scenery"
 | 
			
		||||
developer = "Townengine Team"
 | 
			
		||||
app_id = "platformer-demo"
 | 
			
		||||
app_id = "scenery-demo"
 | 
			
		||||
dev_id = "townengine-team"
 | 
			
		||||
 | 
			
		||||
[game]
 | 
			
		||||
base_render_width = 640
 | 
			
		||||
base_render_height = 360
 | 
			
		||||
resolution = [ 640, 360 ]
 | 
			
		||||
 | 
			
		||||
[engine]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
#include "state.h"
 | 
			
		||||
#include "scenes/scene.h"
 | 
			
		||||
#include "scenes/title.h"
 | 
			
		||||
#include "scenes/ingame.h"
 | 
			
		||||
 | 
			
		||||
#include "twn_game_api.h"
 | 
			
		||||
 | 
			
		||||
@@ -17,47 +18,20 @@ void game_tick(void) {
 | 
			
		||||
 | 
			
		||||
            State *state = ctx.udata;
 | 
			
		||||
            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;
 | 
			
		||||
 | 
			
		||||
    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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (input_is_action_just_pressed("debug_dump_atlases")) {
 | 
			
		||||
    if (input_action_just_pressed("debug_dump_atlases")) {
 | 
			
		||||
        textures_dump_atlases();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,63 +12,147 @@
 | 
			
		||||
#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;
 | 
			
		||||
 | 
			
		||||
    if (input_is_mouse_captured()) {
 | 
			
		||||
        const float sensitivity = 0.6f; /* TODO: put this in a better place */
 | 
			
		||||
        scn->yaw += (float)ctx.mouse_movement.x * sensitivity;
 | 
			
		||||
        scn->pitch -= (float)ctx.mouse_movement.y * sensitivity;
 | 
			
		||||
        scn->pitch = clampf(scn->pitch, -89.0f, 89.0f);
 | 
			
		||||
    DrawCameraFromPrincipalAxesResult dir_and_up =
 | 
			
		||||
        draw_camera_from_principal_axes(scn->pos, (float)M_PI_2 * 0.8f, scn->roll, scn->pitch, scn->yaw);
 | 
			
		||||
 | 
			
		||||
        const float yaw_rad = scn->yaw * (float)DEG2RAD;
 | 
			
		||||
        const float pitch_rad = scn->pitch * (float)DEG2RAD;
 | 
			
		||||
 | 
			
		||||
        scn->cam.target = m_vec_norm(((Vec3){
 | 
			
		||||
            cosf(yaw_rad) * cosf(pitch_rad),
 | 
			
		||||
            sinf(pitch_rad),
 | 
			
		||||
            sinf(yaw_rad) * cosf(pitch_rad)
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const Vec3 right = m_vec_norm(m_vec_cross(scn->cam.target, scn->cam.up));
 | 
			
		||||
    const Vec3 right = m_vec_norm(m_vec_cross(dir_and_up.direction, dir_and_up.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_action_pressed("player_left"))
 | 
			
		||||
        scn->pos = vec3_sub(scn->pos, m_vec_scale(right, speed));
 | 
			
		||||
 | 
			
		||||
    if (input_is_action_pressed("player_right"))
 | 
			
		||||
        scn->cam.pos = vec3_add(scn->cam.pos, m_vec_scale(right, speed));
 | 
			
		||||
    if (input_action_pressed("player_right"))
 | 
			
		||||
        scn->pos = vec3_add(scn->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_action_pressed("player_forward"))
 | 
			
		||||
        scn->pos = vec3_add(scn->pos, m_vec_scale(dir_and_up.direction, speed));
 | 
			
		||||
 | 
			
		||||
    if (input_is_action_pressed("player_backward"))
 | 
			
		||||
        scn->cam.pos = vec3_sub(scn->cam.pos, m_vec_scale(scn->cam.target, speed));
 | 
			
		||||
    if (input_action_pressed("player_backward"))
 | 
			
		||||
        scn->pos = vec3_sub(scn->pos, m_vec_scale(dir_and_up.direction, speed));
 | 
			
		||||
 | 
			
		||||
    if (input_is_action_pressed("player_jump"))
 | 
			
		||||
        scn->cam.pos.y += speed;
 | 
			
		||||
    if (input_action_pressed("player_jump"))
 | 
			
		||||
        scn->pos.y += speed;
 | 
			
		||||
 | 
			
		||||
    if (input_is_action_pressed("player_run"))
 | 
			
		||||
        scn->cam.pos.y -= speed;
 | 
			
		||||
    if (input_action_pressed("player_run"))
 | 
			
		||||
        scn->pos.y -= speed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    /* toggle mouse capture with end key */
 | 
			
		||||
    if (input_is_action_just_pressed("mouse_capture_toggle")) {
 | 
			
		||||
        input_set_mouse_captured(!input_is_mouse_captured());
 | 
			
		||||
 | 
			
		||||
static float height_at(SceneIngame *scn, Vec2 position) {
 | 
			
		||||
    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--;) {
 | 
			
		||||
        for (int lx = 64; lx--;) {
 | 
			
		||||
            float x = SDL_truncf(scn->cam.pos.x + 32 - (float)lx);
 | 
			
		||||
            float y = SDL_truncf(scn->cam.pos.z + 32 - (float)ly);
 | 
			
		||||
static void process_ground_mode(State *state) {
 | 
			
		||||
    SceneIngame *scn = (SceneIngame *)state->scene;
 | 
			
		||||
 | 
			
		||||
            float d0 = stb_perlin_noise3((float)x       * TERRAIN_FREQUENCY, (float)y       * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6;
 | 
			
		||||
            float d1 = stb_perlin_noise3((float)(x + 1) * TERRAIN_FREQUENCY, (float)y       * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6;
 | 
			
		||||
            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;
 | 
			
		||||
    DrawCameraFromPrincipalAxesResult dir_and_up =
 | 
			
		||||
        draw_camera_from_principal_axes(scn->pos, (float)M_PI_2 * 0.8f, scn->roll, scn->pitch, scn->yaw);
 | 
			
		||||
 | 
			
		||||
    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",
 | 
			
		||||
                            (Vec3){ (float)x,     d0, (float)y },
 | 
			
		||||
@@ -76,7 +160,10 @@ static void ingame_tick(State *state) {
 | 
			
		||||
                            (Vec3){ (float)x,     d3, (float)y - 1 },
 | 
			
		||||
                            (Vec2){ 128, 128 },
 | 
			
		||||
                            (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",
 | 
			
		||||
                            (Vec3){ (float)x + 1, d1, (float)y },
 | 
			
		||||
@@ -84,12 +171,61 @@ static void ingame_tick(State *state) {
 | 
			
		||||
                            (Vec3){ (float)x,     d3, (float)y - 1 },
 | 
			
		||||
                            (Vec2){ 128, 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_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.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_opt(channel, "soundtrack"),
 | 
			
		||||
            m_opt(repeat, true));
 | 
			
		||||
 | 
			
		||||
    input_set_mouse_captured(true);
 | 
			
		||||
    new_scene->pos = (Vec3){ 0.1f, 0.0, 0.1f };
 | 
			
		||||
 | 
			
		||||
    return (Scene *)new_scene;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,16 +6,19 @@
 | 
			
		||||
#include "../state.h"
 | 
			
		||||
#include "scene.h"
 | 
			
		||||
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
typedef struct SceneIngame {
 | 
			
		||||
    Scene base;
 | 
			
		||||
 | 
			
		||||
    Camera cam;
 | 
			
		||||
 | 
			
		||||
    /* TODO: put this in a better place */
 | 
			
		||||
    Vec3 pos;
 | 
			
		||||
    float yaw;
 | 
			
		||||
    float pitch;
 | 
			
		||||
    float roll;
 | 
			
		||||
 | 
			
		||||
    bool mouse_captured;
 | 
			
		||||
    bool flying_camera;
 | 
			
		||||
} SceneIngame;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,9 @@ static void title_tick(State *state) {
 | 
			
		||||
    SceneTitle *scn = (SceneTitle *)state->scene;
 | 
			
		||||
    (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);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
@@ -20,20 +22,20 @@ static void title_tick(State *state) {
 | 
			
		||||
            ((float)ctx.resolution.x / 2) - ((float)320 / 2), 64, 320, 128 }));
 | 
			
		||||
 | 
			
		||||
    /* draw the tick count as an example of dynamic text */
 | 
			
		||||
    size_t text_str_len = snprintf(NULL, 0, "%llu", 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);
 | 
			
		||||
    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";
 | 
			
		||||
    int text_h = 32;
 | 
			
		||||
    int text_w = draw_text_width(text_str, text_h, font);
 | 
			
		||||
    float text_h = 32;
 | 
			
		||||
    float text_w = draw_text_width(text_str, text_h, font);
 | 
			
		||||
 | 
			
		||||
    draw_rectangle(
 | 
			
		||||
        (Rect) {
 | 
			
		||||
            .x = 0,
 | 
			
		||||
            .y = 0,
 | 
			
		||||
            .w = (float)text_w,
 | 
			
		||||
            .h = (float)text_h,
 | 
			
		||||
            .w = text_w,
 | 
			
		||||
            .h = text_h,
 | 
			
		||||
        },
 | 
			
		||||
        (Color) { 0, 0, 0, 255 }
 | 
			
		||||
    );
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ if(NOT CMAKE_BUILD_TYPE)
 | 
			
		||||
        set(CMAKE_BUILD_TYPE Debug)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNBUILDDIR})
 | 
			
		||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build)
 | 
			
		||||
 | 
			
		||||
set(SOURCE_FILES
 | 
			
		||||
        game.c
 | 
			
		||||
 
 | 
			
		||||
@@ -13,8 +13,7 @@ dev_id = "you"
 | 
			
		||||
 | 
			
		||||
# Game runtime details
 | 
			
		||||
[game]
 | 
			
		||||
base_render_width = 640
 | 
			
		||||
base_render_height = 480
 | 
			
		||||
resolution = [ 640, 480 ]
 | 
			
		||||
#debug = true
 | 
			
		||||
 | 
			
		||||
# 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)
 | 
			
		||||
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
 | 
			
		||||
        game.c
 | 
			
		||||
        state.h
 | 
			
		||||
 | 
			
		||||
        ${CMAKE_CURRENT_SOURCE_DIR}/luabind.c
 | 
			
		||||
 | 
			
		||||
        lua/src/lapi.c
 | 
			
		||||
        lua/src/lapi.h
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
function game_tick()
 | 
			
		||||
    rectangle {
 | 
			
		||||
    input_action {
 | 
			
		||||
        name = "press",
 | 
			
		||||
        control = "A"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    draw_rectangle {
 | 
			
		||||
        rect = { x = 0, y = 0, w = 640, h = 360 },
 | 
			
		||||
        color = { r = 127, g = 0, b = 127, a = 255 },
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sprite {
 | 
			
		||||
        path = "/assets/title.png",
 | 
			
		||||
    draw_sprite {
 | 
			
		||||
        texture = "/assets/title.png",
 | 
			
		||||
        rect = {
 | 
			
		||||
            x = 320 - (320 / 2),
 | 
			
		||||
            y = 180 - (128 / 2),
 | 
			
		||||
@@ -19,11 +24,13 @@ function game_tick()
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    text {
 | 
			
		||||
        string = "IT KEEPS HAPPENING",
 | 
			
		||||
        position = offset,
 | 
			
		||||
        font = "/fonts/kenney-pixel.ttf",
 | 
			
		||||
    }
 | 
			
		||||
    if input_action_pressed { name = "press" } then
 | 
			
		||||
        draw_text {
 | 
			
		||||
            string = "it never happened",
 | 
			
		||||
            position = offset,
 | 
			
		||||
            font = "/fonts/kenney-pixel.ttf",
 | 
			
		||||
        }
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    offset.x = ORIGIN.x + (math.cos(angle) * RADIUS)
 | 
			
		||||
    offset.y = ORIGIN.y + (math.sin(angle) * RADIUS)
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,6 @@ app_id = "twnlua"
 | 
			
		||||
dev_id = "somebody"
 | 
			
		||||
 | 
			
		||||
[game]
 | 
			
		||||
base_render_width = 640
 | 
			
		||||
base_render_height = 360
 | 
			
		||||
resolution = [ 640, 360 ]
 | 
			
		||||
 | 
			
		||||
[engine]
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,13 @@
 | 
			
		||||
#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 */
 | 
			
		||||
static int physfs_loader(lua_State *L) {
 | 
			
		||||
    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) {
 | 
			
		||||
    /* types are checked here to help prevent unexpected results */
 | 
			
		||||
    Rect rect;
 | 
			
		||||
    int is_num;
 | 
			
		||||
/* WARN! experimental and will probably be removed */
 | 
			
		||||
/* it is an attempt to ease memory usage problems posed by using lua, partially successful */
 | 
			
		||||
static void *custom_alloc(void *ud, void *ptr, size_t osize, size_t nsize) {
 | 
			
		||||
    (void)ud;
 | 
			
		||||
 | 
			
		||||
    lua_getfield(L, idx, "x");
 | 
			
		||||
    rect.x = (float)lua_tonumberx(L, -1, &is_num);
 | 
			
		||||
    if (!is_num)
 | 
			
		||||
        luaL_error(L, "bad field 'x' in '%s' (number expected, got %s)", name, luaL_typename(L, -1));
 | 
			
		||||
    lua_pop(L, 1);
 | 
			
		||||
    /* small allocations are placed in slots, as there's a big chance they will not need to be resized */
 | 
			
		||||
    static char slots[1024][128];
 | 
			
		||||
    static int16_t free_slots[1024] = { [0] = -1 };
 | 
			
		||||
    static size_t free_slot_count = 1024;
 | 
			
		||||
 | 
			
		||||
    lua_getfield(L, idx, "y");
 | 
			
		||||
    rect.y = (float)lua_tonumberx(L, -1, &is_num);
 | 
			
		||||
    if (!is_num)
 | 
			
		||||
        luaL_error(L, "bad field 'y' in '%s' (number expected, got %s)", name, luaL_typename(L, -1));
 | 
			
		||||
    lua_pop(L, 1);
 | 
			
		||||
    if (free_slots[0] == -1)
 | 
			
		||||
        for (int i = 0; i < 1024; i++)
 | 
			
		||||
            free_slots[i] = (int16_t)i;
 | 
			
		||||
 | 
			
		||||
    lua_getfield(L, idx, "w");
 | 
			
		||||
    rect.w = (float)lua_tonumberx(L, -1, &is_num);
 | 
			
		||||
    if (!is_num)
 | 
			
		||||
        luaL_error(L, "bad field 'w' in '%s' (number expected, got %s)", name, luaL_typename(L, -1));
 | 
			
		||||
    lua_pop(L, 1);
 | 
			
		||||
    if (nsize == 0) {
 | 
			
		||||
        if (ptr && (char *)ptr >= &slots[0][0] && (char *)ptr <= &slots[1024-1][128-1])
 | 
			
		||||
            free_slots[free_slot_count++] = (int16_t)(((uintptr_t)ptr - (uintptr_t)slots) / 128);
 | 
			
		||||
        else if (osize)
 | 
			
		||||
            SDL_free(ptr);
 | 
			
		||||
        return NULL;
 | 
			
		||||
    } else {
 | 
			
		||||
        if (!ptr && nsize <= 128 && free_slot_count > 0) {
 | 
			
		||||
            /* use a slot */
 | 
			
		||||
            return slots[free_slots[--free_slot_count]];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    lua_getfield(L, idx, "h");
 | 
			
		||||
    rect.h = (float)lua_tonumberx(L, -1, &is_num);
 | 
			
		||||
    if (!is_num)
 | 
			
		||||
        luaL_error(L, "bad field 'h' in '%s' (number expected, got %s)", name, luaL_typename(L, -1));
 | 
			
		||||
    lua_pop(L, 1);
 | 
			
		||||
        if ((char *)ptr >= &slots[0][0] && (char *)ptr <= &slots[1024-1][128-1]) {
 | 
			
		||||
            /* still fits */
 | 
			
		||||
            if (nsize <= 128)
 | 
			
		||||
                return ptr;
 | 
			
		||||
 | 
			
		||||
    return rect;
 | 
			
		||||
}
 | 
			
		||||
            /* 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;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
        return SDL_realloc(ptr, nsize);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    lua_getfield(L, 1, "color");
 | 
			
		||||
    if (lua_istable(L, -1)) {
 | 
			
		||||
        args.color_opt = table_to_color(L, -1);
 | 
			
		||||
        args.color_opt_set = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    lua_getfield(L, 1, "rotation");
 | 
			
		||||
    if (lua_isnumber(L, -1)) {
 | 
			
		||||
        args.rotation_opt = (float)lua_tonumber(L, -1);
 | 
			
		||||
        args.rotation_opt_set = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    lua_getfield(L, 1, "flip_x");
 | 
			
		||||
    if (lua_isboolean(L, -1)) {
 | 
			
		||||
        args.flip_x_opt = lua_toboolean(L, -1);
 | 
			
		||||
        args.flip_x_opt_set = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    lua_getfield(L, 1, "flip_y");
 | 
			
		||||
    if (lua_isboolean(L, -1)) {
 | 
			
		||||
        args.flip_y_opt = lua_toboolean(L, -1);
 | 
			
		||||
        args.flip_y_opt_set = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    lua_getfield(L, 1, "stretch");
 | 
			
		||||
    if (lua_isboolean(L, -1)) {
 | 
			
		||||
        args.stretch_opt = lua_toboolean(L, -1);
 | 
			
		||||
        args.stretch_opt_set = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    draw_sprite_args(args);
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* rectangle(data [table]) */
 | 
			
		||||
/* data should contain the following fields: */
 | 
			
		||||
/*
 | 
			
		||||
 * rect [table]
 | 
			
		||||
 * color [table], optional, defaults to white
 | 
			
		||||
 */
 | 
			
		||||
static int b_rectangle(lua_State *L) {
 | 
			
		||||
    luaL_checktype(L, 1, LUA_TTABLE);
 | 
			
		||||
 | 
			
		||||
    lua_getfield(L, 1, "rect");
 | 
			
		||||
    if (!lua_istable(L, -1))
 | 
			
		||||
        luaL_error(L, "bad field 'rect' in 'data' (table expected, got %s)", luaL_typename(L, -1));
 | 
			
		||||
    Rect rect = table_to_rect(L, -1, "data.rect");
 | 
			
		||||
 | 
			
		||||
    Color color = { 255, 255, 255, 255 };
 | 
			
		||||
    lua_getfield(L, 1, "color");
 | 
			
		||||
    if (lua_istable(L, -1))
 | 
			
		||||
        color = table_to_color(L, -1);
 | 
			
		||||
 | 
			
		||||
    draw_rectangle(rect, color);
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* text(data [table]) */
 | 
			
		||||
/* data should contain the following fields: */
 | 
			
		||||
/*
 | 
			
		||||
 * string [string]
 | 
			
		||||
 * position [table]
 | 
			
		||||
 * height_px [number], optional, defaults to 22
 | 
			
		||||
 * color [table], optional, defaults to black
 | 
			
		||||
 * font [string]
 | 
			
		||||
 */
 | 
			
		||||
static int b_text(lua_State *L) {
 | 
			
		||||
    luaL_checktype(L, 1, LUA_TTABLE);
 | 
			
		||||
 | 
			
		||||
    lua_getfield(L, 1, "string");
 | 
			
		||||
    const char *string = lua_tostring(L, -1);
 | 
			
		||||
    if (string == NULL)
 | 
			
		||||
        luaL_error(L, "bad field 'string' in 'data' (string expected, got %s)", luaL_typename(L, -1));
 | 
			
		||||
 | 
			
		||||
    lua_getfield(L, 1, "position");
 | 
			
		||||
    if (!lua_istable(L, -1))
 | 
			
		||||
        luaL_error(L, "bad field 'position' in 'data' (table expected, got %s)", luaL_typename(L, -1));
 | 
			
		||||
    lua_getfield(L, -1, "x");
 | 
			
		||||
    float x = (float)lua_tonumber(L, -1);
 | 
			
		||||
    lua_getfield(L, -2, "y");
 | 
			
		||||
    float y = (float)lua_tonumber(L, -1);
 | 
			
		||||
 | 
			
		||||
    lua_getfield(L, 1, "height_px");
 | 
			
		||||
    int is_num;
 | 
			
		||||
    int height_px = (int)lua_tointegerx(L, -1, &is_num);
 | 
			
		||||
    if (!is_num)
 | 
			
		||||
        height_px = 22;
 | 
			
		||||
 | 
			
		||||
    lua_getfield(L, 1, "color");
 | 
			
		||||
    Color color = { 0, 0, 0, 255 };
 | 
			
		||||
    if (lua_istable(L, -1))
 | 
			
		||||
        color = table_to_color(L, -1);
 | 
			
		||||
 | 
			
		||||
    lua_getfield(L, 1, "font");
 | 
			
		||||
    const char *font = lua_tostring(L, -1);
 | 
			
		||||
    if (font == NULL)
 | 
			
		||||
        luaL_error(L, "bad field 'font' in 'data' (string expected, got %s)", luaL_typename(L, -1));
 | 
			
		||||
 | 
			
		||||
    draw_text(string, (Vec2) { x, y }, height_px, color, font);
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -246,14 +101,15 @@ void game_tick(void) {
 | 
			
		||||
        }
 | 
			
		||||
        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[] = {
 | 
			
		||||
               { LUA_GNAME, luaopen_base },
 | 
			
		||||
               { LUA_LOADLIBNAME, luaopen_package },
 | 
			
		||||
               { LUA_COLIBNAME, luaopen_coroutine },
 | 
			
		||||
               { LUA_TABLIBNAME, luaopen_table },
 | 
			
		||||
               { LUA_OSLIBNAME, luaopen_os },
 | 
			
		||||
               { LUA_STRLIBNAME, luaopen_string },
 | 
			
		||||
               { LUA_MATHLIBNAME, luaopen_math },
 | 
			
		||||
               { LUA_UTF8LIBNAME, luaopen_utf8 },
 | 
			
		||||
@@ -269,7 +125,7 @@ void game_tick(void) {
 | 
			
		||||
 | 
			
		||||
        /* package.searchers = { physfs_loader } */
 | 
			
		||||
        lua_getglobal(state->L, "package");
 | 
			
		||||
        lua_newtable(state->L);
 | 
			
		||||
        lua_createtable(state->L, 0, 1);
 | 
			
		||||
        lua_setfield(state->L, -2, "searchers");
 | 
			
		||||
 | 
			
		||||
        lua_getfield(state->L, -1, "searchers");
 | 
			
		||||
@@ -280,9 +136,11 @@ void game_tick(void) {
 | 
			
		||||
        lua_pop(state->L, 2);
 | 
			
		||||
 | 
			
		||||
        /* binding */
 | 
			
		||||
        lua_register(state->L, "sprite", b_sprite);
 | 
			
		||||
        lua_register(state->L, "rectangle", b_rectangle);
 | 
			
		||||
        lua_register(state->L, "text", b_text);
 | 
			
		||||
        // lua_register(state->L, "sprite", b_sprite);
 | 
			
		||||
        // lua_register(state->L, "rectangle", b_rectangle);
 | 
			
		||||
        // lua_register(state->L, "text", b_text);
 | 
			
		||||
 | 
			
		||||
        bindgen_load_twn(state->L);
 | 
			
		||||
 | 
			
		||||
        /* now finally get to running the code */
 | 
			
		||||
        unsigned char *game_buf = NULL;
 | 
			
		||||
@@ -299,16 +157,29 @@ void game_tick(void) {
 | 
			
		||||
 | 
			
		||||
    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");
 | 
			
		||||
    if (lua_pcall(state->L, 0, 0, 0) != LUA_OK) {
 | 
			
		||||
        log_critical("%s", lua_tostring(state->L, -1));
 | 
			
		||||
        lua_pop(state->L, 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    lua_getglobal(state->L, "ctx");
 | 
			
		||||
    bindgen_upload_context(state->L);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void game_end(void) {
 | 
			
		||||
    State *state = ctx.udata;
 | 
			
		||||
    bindgen_unload_twn(state->L);
 | 
			
		||||
    lua_close(state->L);
 | 
			
		||||
    free(state);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,6 @@
 | 
			
		||||
#ifndef STATE_H
 | 
			
		||||
#define STATE_H
 | 
			
		||||
 | 
			
		||||
#include "twn_game_api.h"
 | 
			
		||||
 | 
			
		||||
#include <lua.h>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										15
									
								
								bin/build.sh
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								bin/build.sh
									
									
									
									
									
								
							@@ -1,12 +1,19 @@
 | 
			
		||||
#!/bin/env sh
 | 
			
		||||
 | 
			
		||||
set +e
 | 
			
		||||
 | 
			
		||||
# check whether ninja is around (you better start running)
 | 
			
		||||
if [ -x "$(command -v ninja)" ]; then
 | 
			
		||||
    generator="-G Ninja"
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if [ "$1" = "web" ]; then
 | 
			
		||||
    emcmake cmake $generator -B .build-web "${@:2}" && cmake --build .build-web --parallel
 | 
			
		||||
else
 | 
			
		||||
    cmake $generator -B .build "$@" && cmake --build .build --parallel
 | 
			
		||||
# check whether clang is around (it's just better)
 | 
			
		||||
if [ -x "$(command -v clang)" ]; then
 | 
			
		||||
    cc="-DCMAKE_C_COMPILER=clang"
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if [ "$1" = "web" ]; then
 | 
			
		||||
    emcmake cmake $generator $cc -B build-web "${@:2}" && cmake --build build-web --parallel
 | 
			
		||||
else
 | 
			
		||||
    cmake $generator $cc -B build "$@" && cmake --build build --parallel
 | 
			
		||||
fi
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,6 @@
 | 
			
		||||
#!/bin/env sh
 | 
			
		||||
# 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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								bin/twn
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								bin/twn
									
									
									
									
									
								
							@@ -6,7 +6,6 @@ set +e
 | 
			
		||||
exe="$(basename $PWD)"
 | 
			
		||||
toolpath="$(dirname -- "${BASH_SOURCE[0]}")"
 | 
			
		||||
export TWNROOT=$(realpath "$toolpath"/../)
 | 
			
		||||
export TWNBUILDDIR=$(realpath "$toolpath"/../.build)
 | 
			
		||||
 | 
			
		||||
case "$1" in
 | 
			
		||||
    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
 | 
			
		||||
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 */
 | 
			
		||||
/* path path must contain valid file extension to infer which file format it is */
 | 
			
		||||
/* supported formats: .ogg, .xm */
 | 
			
		||||
TWN_API void audio_play(const char *path,
 | 
			
		||||
TWN_API void audio_play(const char *audio,
 | 
			
		||||
                        const char *channel,    /* optional */
 | 
			
		||||
                        bool  repeat,           /* default: false */
 | 
			
		||||
                        float volume,           /* default: 1.0f, range: 0.0f to 1.0f */
 | 
			
		||||
                        float panning);         /* default: 0.0f, range: -1.0 to 1.0f */
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    AUDIO_PARAM_REPEAT,
 | 
			
		||||
    AUDIO_PARAM_VOLUME,
 | 
			
		||||
    AUDIO_PARAM_PANNING,
 | 
			
		||||
} AudioParam;
 | 
			
		||||
 | 
			
		||||
TWN_API void audio_set(const char *channel, AudioParam param, float value);
 | 
			
		||||
/* possible parameter options: "volume", "panning", "repeat" */
 | 
			
		||||
TWN_API void audio_parameter(const char *channel, const char *parameter, float value);
 | 
			
		||||
 | 
			
		||||
/* TODO */
 | 
			
		||||
// TWN_API bool audio_ended(const char *channel);
 | 
			
		||||
 
 | 
			
		||||
@@ -16,21 +16,25 @@ typedef struct Context {
 | 
			
		||||
    void *udata;
 | 
			
		||||
 | 
			
		||||
    /* which frame is it, starting from 0 at startup */
 | 
			
		||||
    uint64_t frame_number;
 | 
			
		||||
    float frame_number;
 | 
			
		||||
 | 
			
		||||
    /* 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 */
 | 
			
		||||
    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 */
 | 
			
		||||
    /* even if scaling is done, game logic should never change over that */
 | 
			
		||||
    Vec2i resolution;
 | 
			
		||||
    Vec2i mouse_position;
 | 
			
		||||
    Vec2i mouse_movement;
 | 
			
		||||
    Vec2 resolution;
 | 
			
		||||
    Vec2 mouse_position;
 | 
			
		||||
    Vec2 mouse_movement;
 | 
			
		||||
 | 
			
		||||
    /* 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 */
 | 
			
		||||
    bool debug;
 | 
			
		||||
@@ -38,6 +42,7 @@ typedef struct Context {
 | 
			
		||||
    /* is set to true when state is invalidated and needs to be rebuilt */
 | 
			
		||||
    /* watch for it and handle properly! */
 | 
			
		||||
    bool initialization_needed;
 | 
			
		||||
    bool mouse_capture;
 | 
			
		||||
} Context;
 | 
			
		||||
 | 
			
		||||
/* when included after twn_engine_context there's an 'ctx' defined already */
 | 
			
		||||
 
 | 
			
		||||
@@ -3,20 +3,19 @@
 | 
			
		||||
 | 
			
		||||
#include "twn_types.h"
 | 
			
		||||
#include "twn_option.h"
 | 
			
		||||
#include "twn_camera.h"
 | 
			
		||||
#include "twn_engine_api.h"
 | 
			
		||||
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
 | 
			
		||||
/* pushes a sprite onto the sprite render queue */
 | 
			
		||||
TWN_API void draw_sprite(char const *path,
 | 
			
		||||
TWN_API void draw_sprite(char const *texture,
 | 
			
		||||
                         Rect        rect,
 | 
			
		||||
                         Rect const *texture_region,    /* optional, default: NULL */
 | 
			
		||||
                         Color       color,             /* optional, default: all 255 */
 | 
			
		||||
                         float       rotation,          /* optional, default: 0 */
 | 
			
		||||
                         bool        flip_x,            /* optional, default: false */
 | 
			
		||||
                         bool        flip_y,            /* optional, default: false */
 | 
			
		||||
                         bool        stretch);          /* optional, default: false */
 | 
			
		||||
                         bool        stretch);          /* optional, default: true */
 | 
			
		||||
 | 
			
		||||
/* pushes a filled rectangle onto the rectangle render queue */
 | 
			
		||||
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 */
 | 
			
		||||
TWN_API void draw_text(char const *string,
 | 
			
		||||
                       Vec2        position,
 | 
			
		||||
                       int         height_px,       /* optional, default: 22 */
 | 
			
		||||
                       Color       color,           /* optional, default: all 0 */
 | 
			
		||||
                       char const *font);
 | 
			
		||||
                       float       height,              /* optional, default: 22 */
 | 
			
		||||
                       Color       color,               /* optional, default: all 0 */
 | 
			
		||||
                       char const *font);               /* optional, default: NULL */
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
TWN_API int draw_text_width(char const *string,
 | 
			
		||||
                            int         height_px,  /* TODO: make optional */
 | 
			
		||||
                            char const *font);
 | 
			
		||||
TWN_API float draw_text_width(char const *string,
 | 
			
		||||
                              float       height,       /* optional, default: 22 */
 | 
			
		||||
                              char const *font);        /* optional, default: NULL */
 | 
			
		||||
 | 
			
		||||
TWN_API void draw_9slice(char const *texture_path,
 | 
			
		||||
                         int         texture_w,
 | 
			
		||||
                         int         texture_h,
 | 
			
		||||
                         int         border_thickness,
 | 
			
		||||
                         Rect        rect,
 | 
			
		||||
                         Color       color);        /* TODO: make optional */
 | 
			
		||||
TWN_API void draw_nine_slice(char const *texture,
 | 
			
		||||
                             Vec2        corners,
 | 
			
		||||
                             Rect        rect,
 | 
			
		||||
                             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 */
 | 
			
		||||
/* vertices are in absolute coordinates, relative to world origin */
 | 
			
		||||
/* texture coordinates are in pixels */
 | 
			
		||||
TWN_API void draw_triangle(char const *path,
 | 
			
		||||
TWN_API void draw_triangle(char const *texture,
 | 
			
		||||
                           Vec3        v0,
 | 
			
		||||
                           Vec3        v1,
 | 
			
		||||
                           Vec3        v2,
 | 
			
		||||
                           Vec2        uv0,
 | 
			
		||||
                           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?
 | 
			
		||||
//       intended usage for it is baked lighting, i would think.
 | 
			
		||||
/* pushes a colored textured 3d triangle onto the render queue */
 | 
			
		||||
// void unfurl_colored_triangle(const char *path,
 | 
			
		||||
//                              Vec3 v0,
 | 
			
		||||
//                              Vec3 v1,
 | 
			
		||||
//                              Vec3 v2,
 | 
			
		||||
//                              Vec2sh uv0,
 | 
			
		||||
//                              Vec2sh uv1,
 | 
			
		||||
//                              Vec2sh uv2,
 | 
			
		||||
//                              Color c0,
 | 
			
		||||
//                              Color c1,
 | 
			
		||||
//                              Color c2);
 | 
			
		||||
TWN_API void draw_quad(char const *texture,
 | 
			
		||||
                       Vec3        v0,          /* upper-left */
 | 
			
		||||
                       Vec3        v1,          /* bottom-left */
 | 
			
		||||
                       Vec3        v2,          /* bottom-right */
 | 
			
		||||
                       Vec3        v3,          /* upper-right */
 | 
			
		||||
                       Rect        texture_region,
 | 
			
		||||
                       Color       color);      /* optional, default: all 255 */
 | 
			
		||||
 | 
			
		||||
// TODO:
 | 
			
		||||
// http://www.lighthouse3d.com/opengl/billboarding/index.php?billCheat2
 | 
			
		||||
// void unfurl_billboard(const char *path,
 | 
			
		||||
//                       Vec2 position,
 | 
			
		||||
//                       Vec2 scaling,
 | 
			
		||||
//                       Rect uvs);
 | 
			
		||||
TWN_API void draw_billboard(const char *texture,
 | 
			
		||||
                            Vec3 position,
 | 
			
		||||
                            Vec2 size,
 | 
			
		||||
                            Color color,        /* optional, default: all 255 */
 | 
			
		||||
                            bool cylindrical);  /* optional, default: false */
 | 
			
		||||
 | 
			
		||||
/* pushes a camera state to be used for all future unfurl_* commands */
 | 
			
		||||
TWN_API void draw_camera(const Camera *camera);
 | 
			
		||||
/* sets a perspective 3d camera to be used for all 3d commands */
 | 
			
		||||
TWN_API void draw_camera(Vec3 position, float fov, Vec3 up, Vec3 direction);
 | 
			
		||||
 | 
			
		||||
/* same as draw_camera(), but with 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' */
 | 
			
		||||
TWN_API void draw_skybox(const char *paths);
 | 
			
		||||
 | 
			
		||||
TWN_API void draw_fog(float start, float end, float density, Color color);
 | 
			
		||||
TWN_API void draw_skybox(const char *textures);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#ifndef TWN_NOT_C
 | 
			
		||||
 | 
			
		||||
typedef struct DrawSpriteArgs {
 | 
			
		||||
    char const *path;
 | 
			
		||||
    char const *texture;
 | 
			
		||||
    Rect rect;
 | 
			
		||||
 | 
			
		||||
    m_option_list(
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,9 @@
 | 
			
		||||
#ifndef 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)
 | 
			
		||||
#else
 | 
			
		||||
	#define TWN_API __attribute__((visibility("default")))
 | 
			
		||||
 
 | 
			
		||||
@@ -3,11 +3,11 @@
 | 
			
		||||
#define TWN_GAME_API_H
 | 
			
		||||
 | 
			
		||||
#include "twn_input.h"
 | 
			
		||||
#include "twn_context.h"
 | 
			
		||||
#include "twn_draw.h"
 | 
			
		||||
#include "twn_audio.h"
 | 
			
		||||
#include "twn_engine_api.h"
 | 
			
		||||
#include "twn_util.h"
 | 
			
		||||
#include "twn_context.h"
 | 
			
		||||
 | 
			
		||||
#ifndef TWN_NOT_C
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,19 +10,10 @@
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
TWN_API void input_bind_action_control(const char *action_name, Control control);
 | 
			
		||||
TWN_API void input_unbind_action_control(const char *action_name, Control control);
 | 
			
		||||
 | 
			
		||||
TWN_API void input_add_action(const char *action_name);
 | 
			
		||||
TWN_API void input_delete_action(const char *action_name);
 | 
			
		||||
 | 
			
		||||
TWN_API bool input_is_action_pressed(const char *action_name);
 | 
			
		||||
TWN_API bool input_is_action_just_pressed(const char *action_name);
 | 
			
		||||
TWN_API bool input_is_action_just_released(const char *action_name);
 | 
			
		||||
 | 
			
		||||
TWN_API Vec2 input_get_action_position(const char *action_name);
 | 
			
		||||
 | 
			
		||||
TWN_API void input_set_mouse_captured(bool enabled);
 | 
			
		||||
TWN_API bool input_is_mouse_captured(void);
 | 
			
		||||
TWN_API void input_action(const char *name, Control control);
 | 
			
		||||
TWN_API bool input_action_pressed(const char *name);
 | 
			
		||||
TWN_API bool input_action_just_pressed(const char *name);
 | 
			
		||||
TWN_API bool input_action_just_released(const char *name);
 | 
			
		||||
TWN_API Vec2 input_action_position(const char *name);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,8 @@ typedef enum TextureMode {
 | 
			
		||||
    TEXTURE_MODE_OPAQUE,        /* all pixels are solid */
 | 
			
		||||
    TEXTURE_MODE_SEETHROUGH,  	/* some pixels are alpha zero */
 | 
			
		||||
    TEXTURE_MODE_GHOSTLY,       /* arbitrary alpha values */
 | 
			
		||||
    TEXTURE_MODE_COUNT,
 | 
			
		||||
    TEXTURE_MODE_UNKNOWN = -1,  /* a sentinel */
 | 
			
		||||
} TextureMode;
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -6,13 +6,6 @@
 | 
			
		||||
/* 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) */
 | 
			
		||||
typedef struct Vec2 {
 | 
			
		||||
    float x;
 | 
			
		||||
@@ -29,16 +22,6 @@ typedef struct 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 */
 | 
			
		||||
typedef struct Color {
 | 
			
		||||
    uint8_t r;
 | 
			
		||||
@@ -48,15 +31,6 @@ typedef struct 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) */
 | 
			
		||||
typedef struct Rect {
 | 
			
		||||
    float x;
 | 
			
		||||
@@ -66,9 +40,4 @@ typedef struct Rect {
 | 
			
		||||
} Rect;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
typedef struct Matrix4 {
 | 
			
		||||
    Vec4 row[4];
 | 
			
		||||
} Matrix4;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -24,74 +24,64 @@
 | 
			
		||||
    TWN_API void *crealloc(void *ptr, size_t size);
 | 
			
		||||
    TWN_API void *ccalloc(size_t num, size_t size);
 | 
			
		||||
 | 
			
		||||
    TWN_API void log_info(const char *restrict format, ...);
 | 
			
		||||
    TWN_API void log_critical(const char *restrict format, ...);
 | 
			
		||||
    TWN_API void log_warn(const char *restrict format, ...);
 | 
			
		||||
 | 
			
		||||
    /* saves all texture atlases as BMP files in the write directory */
 | 
			
		||||
    TWN_API void textures_dump_atlases(void);
 | 
			
		||||
 | 
			
		||||
    /* returns true if str ends with suffix */
 | 
			
		||||
    TWN_API bool strends(const char *str, const char *suffix);
 | 
			
		||||
 | 
			
		||||
    /* TODO: this is why generics were invented. sorry, i'm tired today */
 | 
			
		||||
    TWN_API double clamp(double d, double min, double max);
 | 
			
		||||
    TWN_API float clampf(float f, float min, float max);
 | 
			
		||||
    TWN_API int clampi(int i, int min, int max);
 | 
			
		||||
 | 
			
		||||
    /* sets buf_out to a pointer to a byte buffer which must be freed. */
 | 
			
		||||
    /* returns the size of this buffer. */
 | 
			
		||||
    TWN_API int64_t file_to_bytes(const char *path, unsigned char **buf_out);
 | 
			
		||||
 | 
			
		||||
    /* returns a pointer to a string which must be freed */
 | 
			
		||||
    TWN_API char *file_to_str(const char *path);
 | 
			
		||||
 | 
			
		||||
    /* returns true if the file exists in the filesystem */
 | 
			
		||||
    TWN_API bool file_exists(const char *path);
 | 
			
		||||
 | 
			
		||||
#endif /* TWN_NOT_C */
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
TWN_API void log_info(const char *restrict format, ...);
 | 
			
		||||
TWN_API void log_critical(const char *restrict format, ...);
 | 
			
		||||
TWN_API void log_warn(const char *restrict format, ...);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* TODO: this is why generics were invented. sorry, i'm tired today */
 | 
			
		||||
TWN_API double clamp(double d, double min, double max);
 | 
			
		||||
TWN_API float clampf(float f, float min, float max);
 | 
			
		||||
TWN_API int clampi(int i, int min, int max);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* sets buf_out to a pointer to a byte buffer which must be freed. */
 | 
			
		||||
/* returns the size of this buffer. */
 | 
			
		||||
TWN_API int64_t file_to_bytes(const char *path, unsigned char **buf_out);
 | 
			
		||||
 | 
			
		||||
/* returns a pointer to a string which must be freed */
 | 
			
		||||
TWN_API char *file_to_str(const char *path);
 | 
			
		||||
 | 
			
		||||
/* returns true if the file exists in the filesystem */
 | 
			
		||||
TWN_API bool file_exists(const char *path);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* saves all texture atlases as BMP files in the write directory */
 | 
			
		||||
TWN_API void textures_dump_atlases(void);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* returns true if str ends with suffix */
 | 
			
		||||
TWN_API TWN_API bool strends(const char *str, const char *suffix);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*                      */
 | 
			
		||||
/* GAME LOGIC UTILITIES */
 | 
			
		||||
/*                      */
 | 
			
		||||
 | 
			
		||||
/* calculates the overlap of two rectangles and places it in result. */
 | 
			
		||||
/* result may be NULL. if this is the case, it will simply be ignored. */
 | 
			
		||||
/* returns true if the rectangles are indeed intersecting. */
 | 
			
		||||
TWN_API bool overlap_rect(const Recti *a, const Recti *b, Recti *result);
 | 
			
		||||
TWN_API bool overlap_frect(const Rect *a, const Rect *b, Rect *result);
 | 
			
		||||
 | 
			
		||||
/* calculates the overlap of two rectangles */
 | 
			
		||||
TWN_API Rect rect_overlap(Rect a, Rect b);
 | 
			
		||||
/* returns true if two rectangles are intersecting */
 | 
			
		||||
TWN_API bool intersect_rect(const Recti *a, const Recti *b);
 | 
			
		||||
TWN_API bool intersect_frect(const Rect *a, const Rect *b);
 | 
			
		||||
TWN_API bool rect_intersects(Rect a, Rect b);
 | 
			
		||||
TWN_API Vec2 rect_center(Rect rect);
 | 
			
		||||
 | 
			
		||||
/* TODO: generics and specials (see m_vec2_from() for an example)*/
 | 
			
		||||
TWN_API Rect to_frect(Recti rect);
 | 
			
		||||
 | 
			
		||||
TWN_API Vec2 frect_center(Rect rect);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* decrements an lvalue (which should be an int), stopping at 0 */
 | 
			
		||||
/* decrements an integer value, stopping at 0 */
 | 
			
		||||
/* meant for tick-based timers in game logic */
 | 
			
		||||
/*
 | 
			
		||||
 * example:
 | 
			
		||||
 * 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 */
 | 
			
		||||
/* 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 */
 | 
			
		||||
/* returns true if value was cycled */
 | 
			
		||||
TWN_API bool repeat_ftimer(float *value, float at);
 | 
			
		||||
typedef struct TimerElapseFramesResult {
 | 
			
		||||
    bool elapsed; int32_t frames_left;
 | 
			
		||||
} TimerElapseFramesResult;
 | 
			
		||||
TWN_API TimerElapseFramesResult timer_elapse_frames(int32_t frames_left, int32_t interval);
 | 
			
		||||
 | 
			
		||||
typedef struct TimerElapseSecondsResult {
 | 
			
		||||
    bool elapsed; float seconds_left;
 | 
			
		||||
} TimerElapseSecondsResult;
 | 
			
		||||
TWN_API TimerElapseSecondsResult timer_elapse_seconds(float seconds_left, float interval);
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -9,13 +9,28 @@
 | 
			
		||||
#include <math.h>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* aren't macros to prevent double evaluation with side effects */
 | 
			
		||||
/* maybe could be inlined? i hope LTO will resolve this */
 | 
			
		||||
static inline Vec2 vec2_from_vec2i(Vec2i vec) {
 | 
			
		||||
    return (Vec2) {
 | 
			
		||||
        .x = (float)vec.x,
 | 
			
		||||
        .y = (float)vec.y,
 | 
			
		||||
    };
 | 
			
		||||
static inline Vec2 vec2_add(Vec2 a, Vec2 b) {
 | 
			
		||||
    return (Vec2) { a.x + b.x, a.y + b.y };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline Vec2 vec2_sub(Vec2 a, Vec2 b) {
 | 
			
		||||
    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) {
 | 
			
		||||
@@ -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 };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline Vec2 vec2_div(Vec2 a, Vec2 b) {
 | 
			
		||||
    return (Vec2) { a.x / b.x, a.y / b.y };
 | 
			
		||||
static inline Vec3 vec3_div(Vec3 a, Vec3 b) {
 | 
			
		||||
    return (Vec3) { a.x / b.x, a.y / b.y, a.z / b.z };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline Vec2 vec2_scale(Vec2 a, float s) {
 | 
			
		||||
    return (Vec2) { a.x * s, a.y * s };
 | 
			
		||||
static inline Vec3 vec3_mul(Vec3 a, Vec3 b) {
 | 
			
		||||
    return (Vec3) { a.x * b.x, a.y * b.y, a.z * b.z };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
    return (Vec3) {
 | 
			
		||||
        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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define m_vec2_from(p_any_vec2) (_Generic((p_any_vec2),       \
 | 
			
		||||
                                    Vec2i:  vec2_from_vec2i,  \
 | 
			
		||||
                                )(p_any_vec2))
 | 
			
		||||
 | 
			
		||||
/* TODO: remove. */
 | 
			
		||||
#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),   \
 | 
			
		||||
                                    Vec2:   vec2_sub,               \
 | 
			
		||||
                                    Vec3:   vec3_sub                \
 | 
			
		||||
                                )(p_any_vec0, p_any_vec1))
 | 
			
		||||
 | 
			
		||||
#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))
 | 
			
		||||
 | 
			
		||||
#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_engine_context_c.h"
 | 
			
		||||
#include "twn_util_c.h"
 | 
			
		||||
#include "twn_util.h"
 | 
			
		||||
 | 
			
		||||
#include <x-watcher.h>
 | 
			
		||||
#include <SDL2/SDL.h>
 | 
			
		||||
@@ -18,7 +19,7 @@ static void (*game_end_callback)(void);
 | 
			
		||||
static x_watcher *watcher;
 | 
			
		||||
static void *handle = NULL;
 | 
			
		||||
 | 
			
		||||
static uint64_t last_tick_modified;
 | 
			
		||||
static float last_tick_modified;
 | 
			
		||||
static bool loaded_after_modification = true;
 | 
			
		||||
static SDL_mutex *lock;
 | 
			
		||||
 | 
			
		||||
@@ -58,7 +59,7 @@ static void load_game_object(void) {
 | 
			
		||||
 | 
			
		||||
    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");
 | 
			
		||||
 | 
			
		||||
    return;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
#include "twn_game_object_c.h"
 | 
			
		||||
#include "twn_engine_context_c.h"
 | 
			
		||||
#include "twn_util_c.h"
 | 
			
		||||
#include "twn_util.h"
 | 
			
		||||
 | 
			
		||||
#include <errhandlingapi.h>
 | 
			
		||||
#include <libloaderapi.h>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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_draw_c.h"
 | 
			
		||||
#include "twn_draw.h"
 | 
			
		||||
#include "twn_util_c.h"
 | 
			
		||||
 | 
			
		||||
#include <SDL2/SDL.h>
 | 
			
		||||
#include <stb_ds.h>
 | 
			
		||||
@@ -27,8 +28,10 @@ void create_circle_geometry(Vec2 position,
 | 
			
		||||
                            size_t num_vertices,
 | 
			
		||||
                            Vec2 vertices[])
 | 
			
		||||
{
 | 
			
		||||
    SDL_assert(num_vertices <= CIRCLE_VERTICES_MAX);
 | 
			
		||||
 | 
			
		||||
    /* the angle (in radians) to rotate by on each iteration */
 | 
			
		||||
    float seg_rotation_angle = (360.0f / (float)num_vertices) * ((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].y = (float)position.y;
 | 
			
		||||
@@ -37,7 +40,7 @@ void create_circle_geometry(Vec2 position,
 | 
			
		||||
    float start_x = 0.0f - radius;
 | 
			
		||||
    float start_y = 0.0f;
 | 
			
		||||
 | 
			
		||||
    for (size_t i = 1; i < num_vertices + 1; ++i) {
 | 
			
		||||
    for (size_t i = 1; i < num_vertices - 1; ++i) {
 | 
			
		||||
        float final_seg_rotation_angle = (float)i * seg_rotation_angle;
 | 
			
		||||
 | 
			
		||||
        float c, s;
 | 
			
		||||
@@ -49,4 +52,76 @@ void create_circle_geometry(Vec2 position,
 | 
			
		||||
        vertices[i].x += position.x;
 | 
			
		||||
        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.h"
 | 
			
		||||
#include "twn_engine_context_c.h"
 | 
			
		||||
#include "twn_camera.h"
 | 
			
		||||
#include "twn_camera_c.h"
 | 
			
		||||
#include "twn_types.h"
 | 
			
		||||
#include "twn_util.h"
 | 
			
		||||
#include "twn_vec.h"
 | 
			
		||||
#include "twn_deferred_commands.h"
 | 
			
		||||
 | 
			
		||||
#include <SDL2/SDL.h>
 | 
			
		||||
#include <stb_ds.h>
 | 
			
		||||
 | 
			
		||||
#ifdef EMSCRIPTEN
 | 
			
		||||
#include <GLES2/gl2.h>
 | 
			
		||||
#else
 | 
			
		||||
#include <glad/glad.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
#include <math.h>
 | 
			
		||||
#include <tgmath.h>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
DeferredCommand *deferred_commands;
 | 
			
		||||
 | 
			
		||||
/* 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_look_at_matrix;
 | 
			
		||||
 | 
			
		||||
double depth_range_low, depth_range_high;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void render_queue_clear(void) {
 | 
			
		||||
    text_cache_reset_arena(&ctx.text_cache);
 | 
			
		||||
@@ -30,13 +33,17 @@ void render_queue_clear(void) {
 | 
			
		||||
    /* and start overwriting the existing data */
 | 
			
		||||
    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)
 | 
			
		||||
        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) {
 | 
			
		||||
    const float bt = (float)border_thickness; /* i know! */
 | 
			
		||||
void draw_nine_slice(const char *texture, Vec2 corners, Rect rect, float border_thickness, Color color) {
 | 
			
		||||
    const float bt = border_thickness;
 | 
			
		||||
    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_set(path, texture_path),
 | 
			
		||||
        m_set(texture, texture),
 | 
			
		||||
        m_set(rect, top_left),
 | 
			
		||||
        m_opt(texture_region, ((Rect) { 0, 0, bt, bt })),
 | 
			
		||||
        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_set(path, texture_path),
 | 
			
		||||
        m_set(texture, texture),
 | 
			
		||||
        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),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@@ -78,9 +85,9 @@ void draw_9slice(const char *texture_path, int texture_w, int texture_h, int bor
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    m_sprite(
 | 
			
		||||
        m_set(path, texture_path),
 | 
			
		||||
        m_set(texture, texture),
 | 
			
		||||
        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),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@@ -93,9 +100,9 @@ void draw_9slice(const char *texture_path, int texture_w, int texture_h, int bor
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    m_sprite(
 | 
			
		||||
        m_set(path, texture_path),
 | 
			
		||||
        m_set(texture, texture),
 | 
			
		||||
        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),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@@ -108,9 +115,9 @@ void draw_9slice(const char *texture_path, int texture_w, int texture_h, int bor
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    m_sprite(
 | 
			
		||||
        m_set(path, texture_path),
 | 
			
		||||
        m_set(texture, texture),
 | 
			
		||||
        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),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@@ -123,9 +130,9 @@ void draw_9slice(const char *texture_path, int texture_w, int texture_h, int bor
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    m_sprite(
 | 
			
		||||
        m_set(path, texture_path),
 | 
			
		||||
        m_set(texture, texture),
 | 
			
		||||
        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),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@@ -138,9 +145,9 @@ void draw_9slice(const char *texture_path, int texture_w, int texture_h, int bor
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    m_sprite(
 | 
			
		||||
        m_set(path, texture_path),
 | 
			
		||||
        m_set(texture, texture),
 | 
			
		||||
        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),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@@ -153,9 +160,9 @@ void draw_9slice(const char *texture_path, int texture_w, int texture_h, int bor
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    m_sprite(
 | 
			
		||||
        m_set(path, texture_path),
 | 
			
		||||
        m_set(texture, texture),
 | 
			
		||||
        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),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@@ -168,17 +175,40 @@ void draw_9slice(const char *texture_path, int texture_w, int texture_h, int bor
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    m_sprite(
 | 
			
		||||
        m_set(path, texture_path),
 | 
			
		||||
        m_set(texture, texture),
 | 
			
		||||
        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),
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static void render_2d(void) {
 | 
			
		||||
    use_2d_pipeline();
 | 
			
		||||
TWN_API void draw_quad(char const *texture,
 | 
			
		||||
                       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);
 | 
			
		||||
 | 
			
		||||
    struct Render2DInvocation {
 | 
			
		||||
@@ -255,6 +285,20 @@ static void render_2d(void) {
 | 
			
		||||
                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: {
 | 
			
		||||
                struct Render2DInvocation const invocation = {
 | 
			
		||||
                    .primitive = current,
 | 
			
		||||
@@ -291,6 +335,9 @@ static void render_2d(void) {
 | 
			
		||||
            case PRIMITIVE_2D_CIRCLE:
 | 
			
		||||
                render_circle(&invocation.primitive->circle);
 | 
			
		||||
                break;
 | 
			
		||||
            case PRIMITIVE_2D_LINE:
 | 
			
		||||
                render_line(&invocation.primitive->line);
 | 
			
		||||
                break;
 | 
			
		||||
            case PRIMITIVE_2D_TEXT:
 | 
			
		||||
            default:
 | 
			
		||||
                SDL_assert(false);
 | 
			
		||||
@@ -320,6 +367,9 @@ static void render_2d(void) {
 | 
			
		||||
            case PRIMITIVE_2D_TEXT:
 | 
			
		||||
                render_text(&invocation.primitive->text);
 | 
			
		||||
                break;
 | 
			
		||||
            case PRIMITIVE_2D_LINE:
 | 
			
		||||
                render_line(&invocation.primitive->line);
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                SDL_assert(false);
 | 
			
		||||
        }
 | 
			
		||||
@@ -333,18 +383,18 @@ static void render_2d(void) {
 | 
			
		||||
static void render_space(void) {
 | 
			
		||||
    /* nothing to do, abort */
 | 
			
		||||
    /* as space pipeline isn't used we can have fewer changes and initialization costs */
 | 
			
		||||
    if (hmlenu(ctx.uncolored_mesh_batches) == 0)
 | 
			
		||||
        return;
 | 
			
		||||
    if (hmlenu(ctx.uncolored_mesh_batches) != 0 || hmlenu(ctx.billboard_batches) != 0) {
 | 
			
		||||
        for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i) {
 | 
			
		||||
            finally_draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches[i].value,
 | 
			
		||||
                                                        ctx.uncolored_mesh_batches[i].key);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    use_space_pipeline();
 | 
			
		||||
    apply_fog();
 | 
			
		||||
 | 
			
		||||
    for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i) {
 | 
			
		||||
        draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches[i].value,
 | 
			
		||||
                                            ctx.uncolored_mesh_batches[i].key);
 | 
			
		||||
        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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pop_fog();
 | 
			
		||||
    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 */
 | 
			
		||||
    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) {
 | 
			
		||||
            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
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        setup_viewport((int)ctx.viewport_rect.x, (int)ctx.viewport_rect.y, (int)ctx.viewport_rect.w, (int)ctx.viewport_rect.h);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    start_render_frame(); {
 | 
			
		||||
        render_space();
 | 
			
		||||
        render_skybox(); /* after space, as to use depth buffer for early z rejection */
 | 
			
		||||
        render_2d();
 | 
			
		||||
    } end_render_frame();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void draw_camera(const Camera *const camera) {
 | 
			
		||||
    /* TODO: skip recaulculating if it's the same? */
 | 
			
		||||
    camera_projection_matrix = camera_perspective(camera);
 | 
			
		||||
    camera_look_at_matrix    = camera_look_at(camera);
 | 
			
		||||
void draw_camera(Vec3 position, float fov, Vec3 up, Vec3 direction) {
 | 
			
		||||
    if (fabsf(0.0f - fov) < 0.00001f || fov > (M_PIf / 2))
 | 
			
		||||
        log_warn("Invalid fov given (%f)", (double)fov);
 | 
			
		||||
 | 
			
		||||
    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 / 2))
 | 
			
		||||
        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
 | 
			
		||||
#define TWN_DRAW_C_H
 | 
			
		||||
 | 
			
		||||
/* TODO: structure more categorically */
 | 
			
		||||
 | 
			
		||||
#include "twn_textures_c.h"
 | 
			
		||||
#include "twn_types_c.h"
 | 
			
		||||
#include "twn_text_c.h"
 | 
			
		||||
#include "twn_util.h"
 | 
			
		||||
#include "twn_option.h"
 | 
			
		||||
#include "twn_deferred_commands.h"
 | 
			
		||||
 | 
			
		||||
#include <SDL2/SDL.h>
 | 
			
		||||
#include <stb_truetype.h>
 | 
			
		||||
 | 
			
		||||
#ifdef EMSCRIPTEN
 | 
			
		||||
#include <GLES2/gl2.h>
 | 
			
		||||
#else
 | 
			
		||||
#include <glad/glad.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
 | 
			
		||||
extern Matrix4 camera_projection_matrix;
 | 
			
		||||
extern Matrix4 camera_look_at_matrix;
 | 
			
		||||
 | 
			
		||||
extern double depth_range_low, depth_range_high;
 | 
			
		||||
 | 
			
		||||
#define QUAD_ELEMENT_BUFFER_LENGTH (65536 / 6)
 | 
			
		||||
#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 {
 | 
			
		||||
    size_t bytes_left;
 | 
			
		||||
    void *mapping;
 | 
			
		||||
    size_t size;
 | 
			
		||||
    void *base;
 | 
			
		||||
} VertexBufferBuilder;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -39,45 +47,55 @@ typedef struct SpritePrimitive {
 | 
			
		||||
    Color color;
 | 
			
		||||
    float rotation;
 | 
			
		||||
    TextureKey texture_key;
 | 
			
		||||
    bool flip_x;
 | 
			
		||||
    bool flip_y;
 | 
			
		||||
    bool repeat;
 | 
			
		||||
 | 
			
		||||
    m_option_list(
 | 
			
		||||
        Rect, texture_region )
 | 
			
		||||
 | 
			
		||||
    bool flip_x;
 | 
			
		||||
    bool flip_y;
 | 
			
		||||
    bool repeat;
 | 
			
		||||
} SpritePrimitive;
 | 
			
		||||
 | 
			
		||||
typedef struct LinePrimitive {
 | 
			
		||||
    Vec2 start;
 | 
			
		||||
    Vec2 finish;
 | 
			
		||||
    float thickness;
 | 
			
		||||
    Color color;
 | 
			
		||||
} LinePrimitive;
 | 
			
		||||
 | 
			
		||||
typedef struct RectPrimitive {
 | 
			
		||||
    Rect rect;
 | 
			
		||||
    Color color;
 | 
			
		||||
} RectPrimitive;
 | 
			
		||||
 | 
			
		||||
typedef struct CirclePrimitive {
 | 
			
		||||
    Vec2 position;
 | 
			
		||||
    float radius;
 | 
			
		||||
    Color color;
 | 
			
		||||
    Vec2 position;
 | 
			
		||||
} CirclePrimitive;
 | 
			
		||||
 | 
			
		||||
typedef struct TextPrimitive {
 | 
			
		||||
    Color color;
 | 
			
		||||
    Vec2 position;
 | 
			
		||||
    char *text;
 | 
			
		||||
    const char *font;
 | 
			
		||||
    Color color;
 | 
			
		||||
    int height_px;
 | 
			
		||||
} TextPrimitive;
 | 
			
		||||
 | 
			
		||||
typedef enum Primitive2DType {
 | 
			
		||||
    PRIMITIVE_2D_SPRITE,
 | 
			
		||||
    PRIMITIVE_2D_LINE,
 | 
			
		||||
    PRIMITIVE_2D_RECT,
 | 
			
		||||
    PRIMITIVE_2D_CIRCLE,
 | 
			
		||||
    PRIMITIVE_2D_TEXT,
 | 
			
		||||
} Primitive2DType;
 | 
			
		||||
 | 
			
		||||
typedef struct Primitive2D {
 | 
			
		||||
    Primitive2DType type;
 | 
			
		||||
    Primitive2DType type; /* TODO: separate to structure of arrays for more efficient memory usage? */
 | 
			
		||||
 | 
			
		||||
    union {
 | 
			
		||||
        SpritePrimitive sprite;
 | 
			
		||||
        LinePrimitive line;
 | 
			
		||||
        RectPrimitive rect;
 | 
			
		||||
        CirclePrimitive circle;
 | 
			
		||||
        TextPrimitive text;
 | 
			
		||||
@@ -85,34 +103,30 @@ typedef struct Primitive2D {
 | 
			
		||||
} Primitive2D;
 | 
			
		||||
 | 
			
		||||
/* union for in-place recalculation of texture coordinates */
 | 
			
		||||
union UncoloredSpaceTriangle {
 | 
			
		||||
    /* pending for sending, uvs are not final as texture atlases could update */
 | 
			
		||||
    struct UncoloredSpaceTrianglePrimitive {
 | 
			
		||||
        Vec3 v0;
 | 
			
		||||
        Vec2 uv0; /* in pixels */
 | 
			
		||||
        Vec3 v1;
 | 
			
		||||
        Vec2 uv1; /* in pixels */
 | 
			
		||||
        Vec3 v2;
 | 
			
		||||
        Vec2 uv2; /* in pixels */
 | 
			
		||||
    } primitive;
 | 
			
		||||
/* needs to be later resolved in texture atlas */
 | 
			
		||||
typedef struct UncoloredSpaceTriangle {
 | 
			
		||||
    Vec3 v0;
 | 
			
		||||
    Vec2 uv0; /* in pixels */
 | 
			
		||||
    Vec3 v1;
 | 
			
		||||
    Vec2 uv1; /* in pixels */
 | 
			
		||||
    Vec3 v2;
 | 
			
		||||
    Vec2 uv2; /* in pixels */
 | 
			
		||||
} UncoloredSpaceTriangle;
 | 
			
		||||
 | 
			
		||||
    /* TODO: have it packed? */
 | 
			
		||||
    /* structure that is passed in opengl vertex array */
 | 
			
		||||
    struct UncoloredSpaceTrianglePayload {
 | 
			
		||||
        Vec3 v0;
 | 
			
		||||
        Vec2 uv0;
 | 
			
		||||
        Vec3 v1;
 | 
			
		||||
        Vec2 uv1;
 | 
			
		||||
        Vec3 v2;
 | 
			
		||||
        Vec2 uv2;
 | 
			
		||||
    } payload;
 | 
			
		||||
};
 | 
			
		||||
typedef struct SpaceBillboard {
 | 
			
		||||
    Vec3 position;
 | 
			
		||||
    Vec2 size;
 | 
			
		||||
    Color color;
 | 
			
		||||
    // TextureKey texture; /* is assumed from other places */
 | 
			
		||||
    bool cylindrical;
 | 
			
		||||
} SpaceBillboard;
 | 
			
		||||
 | 
			
		||||
/* batch of primitives with overlapping properties */
 | 
			
		||||
typedef struct MeshBatch {
 | 
			
		||||
    uint8_t *primitives;
 | 
			
		||||
    uint8_t *primitives; /* note: interpretation of it is arbitrary */
 | 
			
		||||
} MeshBatch;
 | 
			
		||||
 | 
			
		||||
/* TODO: use atlas id instead */
 | 
			
		||||
typedef struct MeshBatchItem {
 | 
			
		||||
    TextureKey key;
 | 
			
		||||
    struct MeshBatch value;
 | 
			
		||||
@@ -123,6 +137,95 @@ typedef struct 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 */
 | 
			
		||||
void render(void);
 | 
			
		||||
 | 
			
		||||
@@ -138,6 +241,7 @@ void create_circle_geometry(Vec2 position,
 | 
			
		||||
 | 
			
		||||
struct QuadBatch {
 | 
			
		||||
    size_t size;             /* how many primitives are in current batch */
 | 
			
		||||
    TextureKey texture_key;
 | 
			
		||||
    TextureMode mode;        /* how color should be applied */
 | 
			
		||||
    bool constant_colored;   /* whether colored batch is uniformly colored */
 | 
			
		||||
    bool repeat;             /* whether repeat is needed */
 | 
			
		||||
@@ -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_rect_batch(const Primitive2D primitives[], struct QuadBatch batch);
 | 
			
		||||
 | 
			
		||||
void draw_uncolored_space_traingle_batch(MeshBatch *batch,
 | 
			
		||||
                                         TextureKey texture_key);
 | 
			
		||||
 | 
			
		||||
/* text */
 | 
			
		||||
 | 
			
		||||
void render_text(const TextPrimitive *text);
 | 
			
		||||
@@ -166,6 +267,8 @@ void text_cache_reset_arena(TextCache *cache);
 | 
			
		||||
 | 
			
		||||
VertexBuffer create_vertex_buffer(void);
 | 
			
		||||
 | 
			
		||||
void restart_scratch_vertex_arrays(void);
 | 
			
		||||
 | 
			
		||||
VertexBuffer get_scratch_vertex_array(void);
 | 
			
		||||
 | 
			
		||||
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 */
 | 
			
		||||
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 */
 | 
			
		||||
bool push_to_vertex_buffer_builder(VertexBufferBuilder *builder,
 | 
			
		||||
                                   void const *bytes,
 | 
			
		||||
                                   size_t size);
 | 
			
		||||
void finish_vertex_builder(VertexBufferBuilder *builder);
 | 
			
		||||
 | 
			
		||||
/* state */
 | 
			
		||||
 | 
			
		||||
void setup_viewport(int x, int y, int width, int height);
 | 
			
		||||
 | 
			
		||||
void clear_draw_buffer(void);
 | 
			
		||||
void finally_clear_draw_buffer(DeferredCommandClear command);
 | 
			
		||||
 | 
			
		||||
void swap_buffers(void);
 | 
			
		||||
 | 
			
		||||
@@ -196,35 +297,28 @@ VertexBuffer get_circle_element_buffer(void);
 | 
			
		||||
 | 
			
		||||
void render_circle(const CirclePrimitive *circle);
 | 
			
		||||
 | 
			
		||||
void render_line(const LinePrimitive *line);
 | 
			
		||||
 | 
			
		||||
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[],
 | 
			
		||||
                          struct QuadBatch batch,
 | 
			
		||||
                          VertexBuffer buffer);
 | 
			
		||||
 | 
			
		||||
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,
 | 
			
		||||
                                                Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3,
 | 
			
		||||
                                                Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3,
 | 
			
		||||
                                                Color color);
 | 
			
		||||
 | 
			
		||||
void finally_draw_uncolored_space_traingle_batch(MeshBatch const *batch,
 | 
			
		||||
                                                 TextureKey texture_key,
 | 
			
		||||
                                                 VertexBuffer buffer);
 | 
			
		||||
                                                 TextureKey texture_key);
 | 
			
		||||
 | 
			
		||||
size_t get_text_payload_size(void);
 | 
			
		||||
 | 
			
		||||
bool push_text_payload_to_vertex_buffer_builder(FontData const *font_data,
 | 
			
		||||
                                                VertexBufferBuilder *builder,
 | 
			
		||||
                                                stbtt_aligned_quad quad);
 | 
			
		||||
void finally_draw_billboard_batch(MeshBatch const *batch,
 | 
			
		||||
                                  TextureKey texture_key);
 | 
			
		||||
 | 
			
		||||
void finally_draw_text(FontData const *font_data,
 | 
			
		||||
                       size_t len,
 | 
			
		||||
@@ -232,19 +326,13 @@ void finally_draw_text(FontData const *font_data,
 | 
			
		||||
                       VertexBuffer buffer);
 | 
			
		||||
 | 
			
		||||
void render_skybox(void);
 | 
			
		||||
 | 
			
		||||
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 finally_render_skybox(DeferredCommandDrawSkybox);
 | 
			
		||||
 | 
			
		||||
void start_render_frame(void);
 | 
			
		||||
 | 
			
		||||
void end_render_frame(void);
 | 
			
		||||
 | 
			
		||||
void finally_draw_command(DeferredCommandDraw command);
 | 
			
		||||
 | 
			
		||||
void issue_deferred_draw_commands(void);
 | 
			
		||||
 | 
			
		||||
#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_util_c.h"
 | 
			
		||||
#include "twn_draw_c.h"
 | 
			
		||||
 | 
			
		||||
#include <stb_ds.h>
 | 
			
		||||
 | 
			
		||||
#ifdef EMSCRIPTEN
 | 
			
		||||
#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 );
 | 
			
		||||
 | 
			
		||||
        for (size_t i = 0; i < QUAD_ELEMENT_BUFFER_LENGTH; ++i) {
 | 
			
		||||
            GLshort indices[6];
 | 
			
		||||
            indices[0] = (GLshort)(i * 4 + 0);
 | 
			
		||||
            indices[1] = (GLshort)(i * 4 + 1);
 | 
			
		||||
            indices[2] = (GLshort)(i * 4 + 2);
 | 
			
		||||
            indices[3] = (GLshort)(i * 4 + 2);
 | 
			
		||||
            indices[4] = (GLshort)(i * 4 + 3);
 | 
			
		||||
            indices[5] = (GLshort)(i * 4 + 0);
 | 
			
		||||
 | 
			
		||||
            push_to_vertex_buffer_builder(&builder, indices, sizeof indices);
 | 
			
		||||
            ((GLshort *)builder.base)[i * 6 + 0] = (GLshort)(i * 4 + 0);
 | 
			
		||||
            ((GLshort *)builder.base)[i * 6 + 1] = (GLshort)(i * 4 + 1);
 | 
			
		||||
            ((GLshort *)builder.base)[i * 6 + 2] = (GLshort)(i * 4 + 2);
 | 
			
		||||
            ((GLshort *)builder.base)[i * 6 + 3] = (GLshort)(i * 4 + 2);
 | 
			
		||||
            ((GLshort *)builder.base)[i * 6 + 4] = (GLshort)(i * 4 + 3);
 | 
			
		||||
            ((GLshort *)builder.base)[i * 6 + 5] = (GLshort)(i * 4 + 0);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        finish_vertex_builder(&builder);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    SDL_assert_always(buffer);
 | 
			
		||||
@@ -65,27 +66,135 @@ VertexBuffer get_circle_element_buffer(void) {
 | 
			
		||||
 | 
			
		||||
    if (buffer == 0) {
 | 
			
		||||
        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 */
 | 
			
		||||
            GLshort indices[3];
 | 
			
		||||
 | 
			
		||||
            indices[0] = 0;
 | 
			
		||||
            ((GLshort *)builder.base)[(i - 1) * 3 + 0] = 0;
 | 
			
		||||
 | 
			
		||||
            /* generated point index */
 | 
			
		||||
            indices[1] = (GLshort)i;
 | 
			
		||||
 | 
			
		||||
            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);
 | 
			
		||||
            ((GLshort *)builder.base)[(i - 1) * 3 + 1] = (GLshort)i;
 | 
			
		||||
            ((GLshort *)builder.base)[(i - 1) * 3 + 2] = (GLshort)i + 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        finish_vertex_builder(&builder);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    SDL_assert_always(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
 | 
			
		||||
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
typedef GLuint GPUTexture;
 | 
			
		||||
typedef uint32_t GPUTexture;
 | 
			
		||||
 | 
			
		||||
typedef enum TextureFilter {
 | 
			
		||||
    TEXTURE_FILTER_NEAREAST,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,30 +1,173 @@
 | 
			
		||||
#include "twn_draw_c.h"
 | 
			
		||||
 | 
			
		||||
#include <stb_ds.h>
 | 
			
		||||
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
struct QuadBatch collect_quad_batch(const Primitive2D primitives[], size_t len) {
 | 
			
		||||
    if (primitives[0].type == PRIMITIVE_2D_SPRITE)
 | 
			
		||||
        return collect_sprite_batch(primitives, len);
 | 
			
		||||
    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)
 | 
			
		||||
void finally_render_quads(const Primitive2D primitives[],
 | 
			
		||||
                          const struct QuadBatch batch,
 | 
			
		||||
                          const VertexBuffer buffer)
 | 
			
		||||
{
 | 
			
		||||
    if (primitives[0].type == PRIMITIVE_2D_SPRITE)
 | 
			
		||||
        render_sprite_batch(primitives, batch);
 | 
			
		||||
    else if (primitives[0].type == PRIMITIVE_2D_RECT)
 | 
			
		||||
        render_rect_batch(primitives, batch);
 | 
			
		||||
    else
 | 
			
		||||
        SDL_assert(false);
 | 
			
		||||
    DeferredCommandDraw command = {0};
 | 
			
		||||
 | 
			
		||||
    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_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>
 | 
			
		||||
 | 
			
		||||
@@ -36,7 +32,7 @@ struct QuadBatch collect_rect_batch(const Primitive2D primitives[], size_t len)
 | 
			
		||||
        .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 */
 | 
			
		||||
    if (len >= QUAD_ELEMENT_BUFFER_LENGTH)
 | 
			
		||||
@@ -54,7 +50,7 @@ struct QuadBatch collect_rect_batch(const Primitive2D primitives[], size_t len)
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        /* if all are modulated the same we can skip sending the color data */
 | 
			
		||||
        if (*(const uint32_t *)¤t->rect.color != uniform_color)
 | 
			
		||||
        if (*(const uint32_t *)(void const *)¤t->rect.color != uniform_color)
 | 
			
		||||
            batch.constant_colored = false;
 | 
			
		||||
 | 
			
		||||
        ++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 */
 | 
			
		||||
    VertexBuffer const vertex_array = get_scratch_vertex_array();
 | 
			
		||||
 | 
			
		||||
    use_texture_mode(batch.mode);
 | 
			
		||||
 | 
			
		||||
    /* vertex population over a vertex buffer builder interface */
 | 
			
		||||
    {
 | 
			
		||||
        VertexBufferBuilder payload = build_vertex_buffer(vertex_array, get_quad_payload_size(batch) * batch.size);
 | 
			
		||||
@@ -85,13 +79,13 @@ void render_rect_batch(const Primitive2D primitives[],
 | 
			
		||||
            const size_t cur = batch.mode == TEXTURE_MODE_GHOSTLY ? i : batch.size - i - 1;
 | 
			
		||||
            const RectPrimitive rect = primitives[cur].rect;
 | 
			
		||||
 | 
			
		||||
            Vec2 v0 = { rect.rect.x,          rect.rect.y };
 | 
			
		||||
            Vec2 v1 = { rect.rect.x,          rect.rect.y + rect.rect.h };
 | 
			
		||||
            Vec2 v0 = { rect.rect.x,               rect.rect.y };
 | 
			
		||||
            Vec2 v1 = { rect.rect.x,               rect.rect.y + rect.rect.h };
 | 
			
		||||
            Vec2 v2 = { rect.rect.x + rect.rect.w, rect.rect.y + rect.rect.h };
 | 
			
		||||
            Vec2 v3 = { rect.rect.x + rect.rect.w, rect.rect.y };
 | 
			
		||||
 | 
			
		||||
            push_quad_payload_to_vertex_buffer_builder(
 | 
			
		||||
                batch, &payload,
 | 
			
		||||
                batch, i, &payload,
 | 
			
		||||
                v0, v1, v2, v3,
 | 
			
		||||
                (Vec2){0}, (Vec2){0}, (Vec2){0}, (Vec2){0},
 | 
			
		||||
                rect.color);
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
#include "twn_draw_c.h"
 | 
			
		||||
 | 
			
		||||
#include <SDL2/SDL.h>
 | 
			
		||||
#include <stb_ds.h>
 | 
			
		||||
 | 
			
		||||
static char *paths_in_use;
 | 
			
		||||
 | 
			
		||||
@@ -21,6 +22,14 @@ void render_skybox(void) {
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    /* 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;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -56,7 +56,7 @@ void draw_sprite_args(const DrawSpriteArgs args) {
 | 
			
		||||
    bool const stretch = m_or(args, stretch, false);
 | 
			
		||||
    Rect const *texture_region = m_is_set(args, texture_region) ? &args.texture_region_opt : NULL;
 | 
			
		||||
 | 
			
		||||
    draw_sprite(args.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 = {
 | 
			
		||||
        .mode = textures_get_mode(&ctx.texture_cache, primitives[0].sprite.texture_key),
 | 
			
		||||
        .texture_key = primitives[0].sprite.texture_key,
 | 
			
		||||
        .constant_colored = true,
 | 
			
		||||
        .repeat = primitives[0].sprite.repeat,
 | 
			
		||||
        .textured = true,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const uint32_t uniform_color = *(const uint32_t *)&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 */
 | 
			
		||||
    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 (*(const uint32_t *)¤t->sprite.color != uniform_color)
 | 
			
		||||
        if (*(const uint32_t *)(void const *)¤t->sprite.color != uniform_color)
 | 
			
		||||
            batch.constant_colored = false;
 | 
			
		||||
 | 
			
		||||
        ++batch.size;
 | 
			
		||||
@@ -131,7 +132,7 @@ void render_sprite_batch(const Primitive2D primitives[],
 | 
			
		||||
        textures_get_dims(&ctx.texture_cache, primitives->sprite.texture_key);
 | 
			
		||||
 | 
			
		||||
    /* cached srcrect */
 | 
			
		||||
    Rect cached_srcrect;
 | 
			
		||||
    Rect cached_srcrect = {0};
 | 
			
		||||
    TextureKey cached_srcrect_key = TEXTURE_KEY_INVALID;
 | 
			
		||||
 | 
			
		||||
    /* vertex population over a vertex buffer builder interface */
 | 
			
		||||
@@ -215,7 +216,7 @@ void render_sprite_batch(const Primitive2D primitives[],
 | 
			
		||||
 | 
			
		||||
#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 d = {
 | 
			
		||||
                    .x = t.x * sprite.rect.w * (float)M_SQRT1_2,
 | 
			
		||||
@@ -230,7 +231,7 @@ void render_sprite_batch(const Primitive2D primitives[],
 | 
			
		||||
            } else {
 | 
			
		||||
                /* 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 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 };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            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);
 | 
			
		||||
 | 
			
		||||
    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) {
 | 
			
		||||
        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.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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -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) {
 | 
			
		||||
    ensure_font_cache(font_path, height_px);
 | 
			
		||||
void draw_text(const char *string, Vec2 position, float height, Color color, const char *font) {
 | 
			
		||||
    ensure_font_cache(font, (int)height);
 | 
			
		||||
 | 
			
		||||
    /* the original string might not be around by the time it's used, so copy it */
 | 
			
		||||
    size_t str_length = SDL_strlen(string) + 1;
 | 
			
		||||
@@ -290,8 +303,8 @@ void draw_text(const char *string, Vec2 position, int height_px, Color color, co
 | 
			
		||||
        .color = color,
 | 
			
		||||
        .position = position,
 | 
			
		||||
        .text = dup_string,
 | 
			
		||||
        .font = font_path,
 | 
			
		||||
        .height_px = height_px,
 | 
			
		||||
        .font = font,
 | 
			
		||||
        .height_px = (int)height,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    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) {
 | 
			
		||||
    ensure_font_cache(font_path, height_px);
 | 
			
		||||
    FontData *font_data = get_font_data(font_path, height_px);
 | 
			
		||||
float draw_text_width(const char *string, float height, const char *font) {
 | 
			
		||||
    ensure_font_cache(font, (int)height);
 | 
			
		||||
    FontData *font_data = get_font_data(font, (int)height);
 | 
			
		||||
 | 
			
		||||
    int length = 0;
 | 
			
		||||
    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;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    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,
 | 
			
		||||
                   Vec2 uv0,
 | 
			
		||||
                   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);
 | 
			
		||||
 | 
			
		||||
    struct MeshBatchItem *batch_p = hmgetp_null(ctx.uncolored_mesh_batches, texture_key);
 | 
			
		||||
    if (!batch_p) {
 | 
			
		||||
        struct MeshBatch item = {0};
 | 
			
		||||
        hmput(ctx.uncolored_mesh_batches, texture_key, item);
 | 
			
		||||
        batch_p = &ctx.uncolored_mesh_batches[hmlenu(ctx.uncolored_mesh_batches) - 1]; /* 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,
 | 
			
		||||
        .v1 = v1,
 | 
			
		||||
        .v2 = v2,
 | 
			
		||||
        .uv1 = uv1,
 | 
			
		||||
        .uv0 = uv0,
 | 
			
		||||
        .uv2 = uv2,
 | 
			
		||||
    }};
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    union UncoloredSpaceTriangle *triangles = (union UncoloredSpaceTriangle *)(void *)batch_p->value.primitives;
 | 
			
		||||
    UncoloredSpaceTriangle *triangles = (UncoloredSpaceTriangle *)(void *)batch_p->value.primitives;
 | 
			
		||||
 | 
			
		||||
    arrpush(triangles, triangle);
 | 
			
		||||
    batch_p->value.primitives = (uint8_t *)triangles;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void draw_uncolored_space_traingle_batch(struct MeshBatch *batch,
 | 
			
		||||
                                         TextureKey texture_key)
 | 
			
		||||
void finally_draw_uncolored_space_traingle_batch(const MeshBatch *batch,
 | 
			
		||||
                                                 const TextureKey texture_key)
 | 
			
		||||
{
 | 
			
		||||
    VertexBuffer const vertex_array = get_scratch_vertex_array();
 | 
			
		||||
 | 
			
		||||
    const size_t primitives_len = arrlenu(batch->primitives);
 | 
			
		||||
 | 
			
		||||
    /* nothing to do */
 | 
			
		||||
    if (primitives_len == 0)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    VertexBuffer const buffer = get_scratch_vertex_array();
 | 
			
		||||
 | 
			
		||||
    const Rect srcrect = textures_get_srcrect(&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 */
 | 
			
		||||
    for (size_t i = 0; i < primitives_len; ++i) {
 | 
			
		||||
        struct UncoloredSpaceTrianglePayload *payload =
 | 
			
		||||
            &((union UncoloredSpaceTriangle *)(void *)batch->primitives)[i].payload;
 | 
			
		||||
        UncoloredSpaceTriangle *payload =
 | 
			
		||||
            &((UncoloredSpaceTriangle *)(void *)batch->primitives)[i];
 | 
			
		||||
 | 
			
		||||
        payload->uv0.x = xr + ((float)payload->uv0.x / srcrect.w) * wr;
 | 
			
		||||
        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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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_draw.c"
 | 
			
		||||
#include "rendering/twn_fog.c"
 | 
			
		||||
#include "rendering/twn_skybox.c"
 | 
			
		||||
#include "rendering/twn_sprites.c"
 | 
			
		||||
#include "rendering/twn_rects.c"
 | 
			
		||||
#include "rendering/twn_text.c"
 | 
			
		||||
#include "rendering/twn_quads.c"
 | 
			
		||||
#include "rendering/twn_triangles.c"
 | 
			
		||||
#include "rendering/twn_billboards.c"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										308
									
								
								src/twn_audio.c
									
									
									
									
									
								
							
							
						
						
									
										308
									
								
								src/twn_audio.c
									
									
									
									
									
								
							@@ -2,10 +2,12 @@
 | 
			
		||||
#include "twn_engine_context_c.h"
 | 
			
		||||
#include "twn_util.h"
 | 
			
		||||
#include "twn_util_c.h"
 | 
			
		||||
#include "twn_audio.h"
 | 
			
		||||
 | 
			
		||||
#include <SDL2/SDL.h>
 | 
			
		||||
#include <stb_ds.h>
 | 
			
		||||
#include <physfs.h>
 | 
			
		||||
#include <physfsrwops.h>
 | 
			
		||||
 | 
			
		||||
#define STB_VORBIS_NO_PUSHDATA_API
 | 
			
		||||
#define STB_VORBIS_HEADER_ONLY
 | 
			
		||||
@@ -16,9 +18,17 @@
 | 
			
		||||
 | 
			
		||||
static const char *audio_exts[AUDIO_FILE_TYPE_COUNT] = {
 | 
			
		||||
    ".ogg", /* AUDIO_FILE_TYPE_OGG */
 | 
			
		||||
    ".wav", /* AUDIO_FILE_TYPE_WAV */
 | 
			
		||||
    ".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 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) {
 | 
			
		||||
    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) {
 | 
			
		||||
        size_t ext_length = SDL_strlen(audio_exts[i]);
 | 
			
		||||
        if (path_len <= ext_length)
 | 
			
		||||
            continue;
 | 
			
		||||
 | 
			
		||||
        if (SDL_strcmp(&path[path_len - ext_length], audio_exts[i]) == 0)
 | 
			
		||||
        if (SDL_strncmp(&path[path_len - audio_exts_len[i]], audio_exts[i], audio_exts_len[i]) == 0)
 | 
			
		||||
            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? */
 | 
			
		||||
/*       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) {
 | 
			
		||||
    switch (type) {
 | 
			
		||||
    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: {
 | 
			
		||||
        unsigned char *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) {
 | 
			
		||||
    switch (channel->file_type) {
 | 
			
		||||
    case AUDIO_FILE_TYPE_OGG: {
 | 
			
		||||
@@ -144,6 +222,11 @@ static void repeat_audio(AudioChannel *channel) {
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case AUDIO_FILE_TYPE_WAV: {
 | 
			
		||||
        channel->context.wav.position = 0;
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case AUDIO_FILE_TYPE_XM: {
 | 
			
		||||
        xm_restart(channel->context.xm.handle);
 | 
			
		||||
        break;
 | 
			
		||||
@@ -164,71 +247,110 @@ void audio_play(const char *path,
 | 
			
		||||
                float volume,
 | 
			
		||||
                float panning)
 | 
			
		||||
{
 | 
			
		||||
    AudioChannelItem *pair = shgetp_null(ctx.audio_channels, channel);
 | 
			
		||||
    if (!ctx.audio_initialized) {
 | 
			
		||||
        profile_start("audio initialization");
 | 
			
		||||
 | 
			
		||||
    /* create a channel if it doesn't exist */
 | 
			
		||||
    if (!pair) {
 | 
			
		||||
        AudioFileType file_type = infer_audio_file_type(path);
 | 
			
		||||
        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);
 | 
			
		||||
 | 
			
		||||
        /* create a channel if it doesn't exist */
 | 
			
		||||
        if (!pair) {
 | 
			
		||||
            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 = channel,
 | 
			
		||||
                .repeat = repeat,
 | 
			
		||||
                .volume = volume,
 | 
			
		||||
                .panning = panning,
 | 
			
		||||
            };
 | 
			
		||||
            shput(ctx.audio_channels, channel, new_channel);
 | 
			
		||||
            pair = shgetp_null(ctx.audio_channels, channel);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /* TODO: destroy and create new context when channel is reused for different file */
 | 
			
		||||
 | 
			
		||||
        /* works for both restarts and new audio */
 | 
			
		||||
        if (strcmp(pair->value.path, path) == 0)
 | 
			
		||||
            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 = channel,
 | 
			
		||||
            .repeat = repeat,
 | 
			
		||||
            .name = NULL,
 | 
			
		||||
            .repeat = false,
 | 
			
		||||
            .volume = volume,
 | 
			
		||||
            .panning = panning,
 | 
			
		||||
        };
 | 
			
		||||
        shput(ctx.audio_channels, channel, new_channel);
 | 
			
		||||
        pair = shgetp_null(ctx.audio_channels, channel);
 | 
			
		||||
 | 
			
		||||
        if (repeat)
 | 
			
		||||
            log_warn("Cannot repeat audio played on unnamed scratch channel (for %s)", path);
 | 
			
		||||
 | 
			
		||||
        arrpush(ctx.unnamed_audio_channels, new_channel);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* TODO: destroy and create new context when channel is reused for different file */
 | 
			
		||||
 | 
			
		||||
    /* works for both restarts and new audio */
 | 
			
		||||
    if (strcmp(pair->value.path, path) == 0)
 | 
			
		||||
        repeat_audio(&pair->value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
    if (!pair) {
 | 
			
		||||
        log_warn("No channel by the name of %s to set a parameter for", channel);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    switch (param) {
 | 
			
		||||
    case AUDIO_PARAM_REPEAT:
 | 
			
		||||
    if (SDL_strncmp(param, "repeat", sizeof "repeat" - 1) == 0) {
 | 
			
		||||
        pair->value.repeat = (bool)value;
 | 
			
		||||
        break;
 | 
			
		||||
    case AUDIO_PARAM_VOLUME:
 | 
			
		||||
        if (value > 1.0f) {
 | 
			
		||||
 | 
			
		||||
    } else if (SDL_strncmp(param, "volume", sizeof "volume" - 1) == 0) {
 | 
			
		||||
        if (value > 1.0f || value < 0.0f) {
 | 
			
		||||
            log_warn("Out of range volume for channel %s set", channel);
 | 
			
		||||
            value = 1.0f;
 | 
			
		||||
        }
 | 
			
		||||
        if (value < 0.0f) {
 | 
			
		||||
            log_warn("Out of range volume for channel %s set", channel);
 | 
			
		||||
            value = 0.0f;
 | 
			
		||||
            value = clampf(value, 0.0f, 1.0f);
 | 
			
		||||
        }
 | 
			
		||||
        pair->value.volume = value;
 | 
			
		||||
        break;
 | 
			
		||||
    case AUDIO_PARAM_PANNING:
 | 
			
		||||
        if (value > 1.0f) {
 | 
			
		||||
 | 
			
		||||
    } else if (SDL_strncmp(param, "panning", sizeof "panning" - 1) == 0) {
 | 
			
		||||
        if (value > 1.0f || value < -1.0f) {
 | 
			
		||||
            log_warn("Out of range panning for channel %s set", channel);
 | 
			
		||||
            value = 1.0f;
 | 
			
		||||
        }
 | 
			
		||||
        if (value < -1.0f) {
 | 
			
		||||
            log_warn("Out of range panning for channel %s set", channel);
 | 
			
		||||
            value = -1.0f;
 | 
			
		||||
            value = clampf(value, -1.0f, +1.0f);
 | 
			
		||||
        }
 | 
			
		||||
        pair->value.panning = value;
 | 
			
		||||
        break;
 | 
			
		||||
    default:
 | 
			
		||||
 | 
			
		||||
    } else
 | 
			
		||||
        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 */
 | 
			
		||||
static void audio_mixin_streams(const AudioChannel *channel,
 | 
			
		||||
                                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 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 */
 | 
			
		||||
        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 */
 | 
			
		||||
        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 */
 | 
			
		||||
static void audio_sample_and_mixin_channel(const AudioChannel *channel,
 | 
			
		||||
/* remember: frame consists of sample * channel_count */
 | 
			
		||||
static void audio_sample_and_mixin_channel(AudioChannel *channel,
 | 
			
		||||
                                           uint8_t *stream,
 | 
			
		||||
                                           int len)
 | 
			
		||||
{
 | 
			
		||||
    static uint8_t buffer[16384];
 | 
			
		||||
    const int float_buffer_frames = sizeof (buffer) / sizeof (float);
 | 
			
		||||
    const int stream_frames = len / (int)(sizeof (float));
 | 
			
		||||
    static uint8_t buffer[16384]; /* TODO: better make it a growable scratch instead, which will simplify things */
 | 
			
		||||
    const size_t float_buffer_frames = sizeof (buffer) / sizeof (float) / 2;
 | 
			
		||||
    const size_t stream_frames = len / sizeof (float) / 2;
 | 
			
		||||
 | 
			
		||||
    switch (channel->file_type) {
 | 
			
		||||
    case AUDIO_FILE_TYPE_OGG: {
 | 
			
		||||
        /* feed stream for needed conversions */
 | 
			
		||||
        for (int i = 0; i < stream_frames; ) {
 | 
			
		||||
            const int n_frames = (stream_frames - i) > float_buffer_frames ?
 | 
			
		||||
        for (size_t i = 0; i < stream_frames; ) {
 | 
			
		||||
            const size_t n_frames = (stream_frames - i) > float_buffer_frames ?
 | 
			
		||||
                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.channel_count,
 | 
			
		||||
                2,
 | 
			
		||||
                (float *)buffer,
 | 
			
		||||
                n_frames);
 | 
			
		||||
                (int)n_frames * 2);
 | 
			
		||||
 | 
			
		||||
            /* handle end of file */
 | 
			
		||||
            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 */
 | 
			
		||||
                    stb_vorbis_seek_start(channel->context.vorbis.handle);
 | 
			
		||||
                    continue;
 | 
			
		||||
                } else
 | 
			
		||||
                } else {
 | 
			
		||||
                    /* leave silence */
 | 
			
		||||
                    channel->finished = true;
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /* panning and mixing */
 | 
			
		||||
            audio_mixin_streams(channel,
 | 
			
		||||
                                &stream[i * sizeof(float)], buffer,
 | 
			
		||||
                                samples_per_channel * 2);
 | 
			
		||||
                                &stream[i * sizeof(float) * 2], buffer,
 | 
			
		||||
                                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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case AUDIO_FILE_TYPE_XM: {
 | 
			
		||||
        for (int i = 0; i < stream_frames; ) {
 | 
			
		||||
            const int n_frames = (stream_frames - i) > float_buffer_frames ?
 | 
			
		||||
        for (size_t i = 0; i < stream_frames; ) {
 | 
			
		||||
            const size_t n_frames = (stream_frames - i) > float_buffer_frames ?
 | 
			
		||||
                float_buffer_frames : stream_frames - i;
 | 
			
		||||
 | 
			
		||||
            const int samples_per_channel = xm_generate_samples(channel->context.xm.handle,
 | 
			
		||||
                                                                (float *)buffer,
 | 
			
		||||
                                                                n_frames / 2);
 | 
			
		||||
            const size_t samples_per_channel = xm_generate_samples(channel->context.xm.handle,
 | 
			
		||||
                                                                   (float *)buffer,
 | 
			
		||||
                                                                   n_frames);
 | 
			
		||||
 | 
			
		||||
            /* handle end of file */
 | 
			
		||||
            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 */
 | 
			
		||||
                    xm_restart(channel->context.xm.handle);
 | 
			
		||||
                    continue;
 | 
			
		||||
                } else
 | 
			
		||||
                } else {
 | 
			
		||||
                    channel->finished = true;
 | 
			
		||||
                    /* leave silence */
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /* panning and mixing */
 | 
			
		||||
            audio_mixin_streams(channel,
 | 
			
		||||
                                &stream[i * sizeof(float)],
 | 
			
		||||
                                &stream[i * sizeof(float) * 2],
 | 
			
		||||
                                buffer,
 | 
			
		||||
                                samples_per_channel * 2);
 | 
			
		||||
                                samples_per_channel);
 | 
			
		||||
 | 
			
		||||
            i += samples_per_channel * 2;
 | 
			
		||||
            i += samples_per_channel;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        break;
 | 
			
		||||
@@ -355,8 +512,23 @@ void audio_callback(void *userdata, uint8_t *stream, int len) {
 | 
			
		||||
        sanity_check_channel(&ctx.audio_channels[i].value);
 | 
			
		||||
        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) {
 | 
			
		||||
    const char *channel = m_or(args, channel, NULL);
 | 
			
		||||
    const bool repeat = m_or(args, repeat, false);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,6 @@
 | 
			
		||||
#ifndef TWN_AUDIO_C_H
 | 
			
		||||
#define TWN_AUDIO_C_H
 | 
			
		||||
 | 
			
		||||
#include "twn_audio.h"
 | 
			
		||||
 | 
			
		||||
#include <SDL2/SDL_audio.h>
 | 
			
		||||
 | 
			
		||||
#define STB_VORBIS_HEADER_ONLY
 | 
			
		||||
@@ -15,9 +13,12 @@
 | 
			
		||||
 | 
			
		||||
#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 {
 | 
			
		||||
    AUDIO_FILE_TYPE_OGG,
 | 
			
		||||
    AUDIO_FILE_TYPE_WAV,
 | 
			
		||||
    AUDIO_FILE_TYPE_XM,
 | 
			
		||||
    AUDIO_FILE_TYPE_COUNT,
 | 
			
		||||
    AUDIO_FILE_TYPE_UNKNOWN,
 | 
			
		||||
@@ -32,6 +33,12 @@ union AudioContext {
 | 
			
		||||
        uint8_t channel_count;
 | 
			
		||||
    } vorbis;
 | 
			
		||||
 | 
			
		||||
    struct {
 | 
			
		||||
        void *samples;
 | 
			
		||||
        SDL_AudioSpec spec;
 | 
			
		||||
        size_t position;
 | 
			
		||||
    } wav;
 | 
			
		||||
 | 
			
		||||
    struct {
 | 
			
		||||
        xm_context_t *handle;
 | 
			
		||||
    } xm;
 | 
			
		||||
@@ -46,6 +53,7 @@ typedef struct AudioChannel {
 | 
			
		||||
    bool repeat;
 | 
			
		||||
    float volume;
 | 
			
		||||
    float panning;
 | 
			
		||||
    bool finished;
 | 
			
		||||
} AudioChannel;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
#include "twn_camera.h"
 | 
			
		||||
#include "twn_camera_c.h"
 | 
			
		||||
#include "twn_vec.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].y = -m_vec_dot(u, 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[0].w =  result.row[1].w = result.row[2].w = 0.0f;
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -38,7 +39,7 @@ Matrix4 camera_perspective(const Camera *const camera) {
 | 
			
		||||
    /* from cglm */
 | 
			
		||||
    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 fn = 1.0f / (CAMERA_NEAR_Z - CAMERA_FAR_Z);
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
#define TWN_CAMERA_H
 | 
			
		||||
 | 
			
		||||
#include "twn_types.h"
 | 
			
		||||
#include "twn_engine_api.h"
 | 
			
		||||
#include "twn_types_c.h"
 | 
			
		||||
 | 
			
		||||
/* TODO: make it cached? */
 | 
			
		||||
/*       for example, perspective matrix only needs recaluclation on FOV change */
 | 
			
		||||
@@ -12,11 +12,11 @@ typedef struct Camera {
 | 
			
		||||
    Vec3 pos;        /* eye position */
 | 
			
		||||
    Vec3 target;     /* normalized target vector */
 | 
			
		||||
    Vec3 up;         /* normalized up vector */
 | 
			
		||||
    float fov;          /* field of view, in radians */
 | 
			
		||||
    float fov;       /* field of view, in radians */
 | 
			
		||||
} 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
 | 
			
		||||
@@ -28,8 +28,11 @@ typedef struct EngineContext {
 | 
			
		||||
    char **argv;
 | 
			
		||||
    /* where the app was run from, used as the root for packs */
 | 
			
		||||
    char *base_dir;
 | 
			
		||||
    char *title;
 | 
			
		||||
 | 
			
		||||
    Vec2i window_dims;
 | 
			
		||||
    Vec2 window_dims;
 | 
			
		||||
    Rect viewport_rect;
 | 
			
		||||
    float viewport_scale;
 | 
			
		||||
 | 
			
		||||
    /* configuration */
 | 
			
		||||
    toml_table_t *config_table;
 | 
			
		||||
@@ -45,11 +48,13 @@ typedef struct EngineContext {
 | 
			
		||||
    /* rendering */
 | 
			
		||||
    Primitive2D *render_queue_2d;
 | 
			
		||||
    MeshBatchItem *uncolored_mesh_batches;
 | 
			
		||||
    MeshBatchItem *billboard_batches;
 | 
			
		||||
    TextCache text_cache;
 | 
			
		||||
    TextureCache texture_cache;
 | 
			
		||||
 | 
			
		||||
    /* audio */
 | 
			
		||||
    AudioChannelItem *audio_channels;
 | 
			
		||||
    AudioChannel *unnamed_audio_channels;
 | 
			
		||||
    SDL_AudioDeviceID audio_device;
 | 
			
		||||
    int audio_stream_frequency;
 | 
			
		||||
    SDL_AudioFormat audio_stream_format;
 | 
			
		||||
@@ -64,11 +69,6 @@ typedef struct EngineContext {
 | 
			
		||||
    int64_t delta_averager_residual;
 | 
			
		||||
    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_Window *window;
 | 
			
		||||
    uint32_t window_id;
 | 
			
		||||
@@ -78,6 +78,9 @@ typedef struct EngineContext {
 | 
			
		||||
    bool resync_flag;
 | 
			
		||||
    bool was_successful;
 | 
			
		||||
    bool render_double_buffered;
 | 
			
		||||
    /* signals mouse focus, used to disable mouse capture */
 | 
			
		||||
    bool window_mouse_resident;
 | 
			
		||||
    bool audio_initialized;
 | 
			
		||||
} EngineContext;
 | 
			
		||||
 | 
			
		||||
/* TODO: does it need to be marked with TWN_API? */
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										169
									
								
								src/twn_input.c
									
									
									
									
									
								
							
							
						
						
									
										169
									
								
								src/twn_input.c
									
									
									
									
									
								
							@@ -1,13 +1,14 @@
 | 
			
		||||
#include "twn_input_c.h"
 | 
			
		||||
#include "twn_util.h"
 | 
			
		||||
#include "twn_util_c.h"
 | 
			
		||||
#include "twn_control.h"
 | 
			
		||||
#include "twn_engine_context_c.h"
 | 
			
		||||
#include "twn_input.h"
 | 
			
		||||
 | 
			
		||||
#include <SDL2/SDL.h>
 | 
			
		||||
#include <stb_ds.h>
 | 
			
		||||
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static void update_action_pressed_state(InputState *input, Action *action) {
 | 
			
		||||
@@ -44,8 +45,8 @@ static void update_action_pressed_state(InputState *input, Action *action) {
 | 
			
		||||
            else {
 | 
			
		||||
                action->just_changed = !action->is_pressed;
 | 
			
		||||
                action->is_pressed = true;
 | 
			
		||||
                action->position.x = (float)input->mouse_window_position.x;
 | 
			
		||||
                action->position.y = (float)input->mouse_window_position.y;
 | 
			
		||||
                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 - ctx.viewport_rect.y) / ctx.viewport_scale;
 | 
			
		||||
 | 
			
		||||
                /* 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,
 | 
			
		||||
                                      char const *action_name,
 | 
			
		||||
                                      ButtonSource source,
 | 
			
		||||
                                      union ButtonCode code)
 | 
			
		||||
{
 | 
			
		||||
    ActionHashItem *action_item = shgetp_null(input->action_hash, action_name);
 | 
			
		||||
    if (action_item == NULL) {
 | 
			
		||||
        log_warn("(%s) Action \"%s\" does not exist.", __func__, action_name);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    if (!action_item)
 | 
			
		||||
        action_item = input_add_action(action_name);
 | 
			
		||||
 | 
			
		||||
    Action *action = &action_item->value;
 | 
			
		||||
 | 
			
		||||
    /* 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];
 | 
			
		||||
 | 
			
		||||
        if (binding->source != source)
 | 
			
		||||
@@ -109,7 +130,8 @@ static void input_bind_code_to_action(InputState *input,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (is_already_bound) {
 | 
			
		||||
            log_warn("(%s) Code already bound to action \"%s\".", __func__, action_name);
 | 
			
		||||
            /* keep it alive */
 | 
			
		||||
            binding->in_use = true;
 | 
			
		||||
            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 (action->num_bindings == (uint64_t)ctx.keybind_slots) {
 | 
			
		||||
        --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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    action->bindings[action->num_bindings++] = (Button) {
 | 
			
		||||
        .source = source,
 | 
			
		||||
        .code = code,
 | 
			
		||||
        .in_use = true,
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -134,16 +157,15 @@ static void input_unbind_code_from_action(InputState *input,
 | 
			
		||||
                                          union ButtonCode code)
 | 
			
		||||
{
 | 
			
		||||
    ActionHashItem *action_item = shgetp_null(input->action_hash, action_name);
 | 
			
		||||
    if (action_item == NULL) {
 | 
			
		||||
        log_warn("(%s) Action \"%s\" does not exist.", __func__, action_name);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    if (!action_item)
 | 
			
		||||
        action_item = input_add_action(action_name);
 | 
			
		||||
 | 
			
		||||
    Action *action = &action_item->value;
 | 
			
		||||
 | 
			
		||||
    /* check every binding to make sure this code is bound */
 | 
			
		||||
    size_t index = 0;
 | 
			
		||||
    bool is_bound = false;
 | 
			
		||||
    for (index = 0; index < (uint64_t)ctx.keybind_slots; ++index) {
 | 
			
		||||
    for (index = 0; index < action->num_bindings; ++index) {
 | 
			
		||||
        Button *binding = &action->bindings[index];
 | 
			
		||||
 | 
			
		||||
        if (binding->source != source)
 | 
			
		||||
@@ -173,10 +195,8 @@ static void input_unbind_code_from_action(InputState *input,
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!is_bound) {
 | 
			
		||||
        log_warn("(%s) Code is not bound to action \"%s\".", __func__, action_name);
 | 
			
		||||
    if (!is_bound)
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* remove the element to unbind and shift the rest so there isn't a gap */
 | 
			
		||||
    size_t elements_after_index = action->num_bindings - index;
 | 
			
		||||
@@ -196,25 +216,53 @@ void input_state_deinit(InputState *input) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void input_state_update(InputState *input) {
 | 
			
		||||
    input->keyboard_state = SDL_GetKeyboardState(NULL);
 | 
			
		||||
    input->mouse_state = SDL_GetMouseState(&input->mouse_window_position.x,
 | 
			
		||||
                                           &input->mouse_window_position.y);
 | 
			
		||||
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__);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    SDL_GetRelativeMouseState(&input->mouse_relative_position.x,
 | 
			
		||||
                              &input->mouse_relative_position.y);
 | 
			
		||||
 | 
			
		||||
void input_state_update(InputState *input) {
 | 
			
		||||
    int x, y;
 | 
			
		||||
 | 
			
		||||
    input->keyboard_state = SDL_GetKeyboardState(NULL);
 | 
			
		||||
    input->mouse_state = SDL_GetMouseState(&x, &y);
 | 
			
		||||
    input->mouse_window_position = (Vec2){ (float)x, (float)y };
 | 
			
		||||
 | 
			
		||||
    SDL_GetRelativeMouseState(&x, &y);
 | 
			
		||||
    input->mouse_relative_position = (Vec2){ (float)x, (float)y };
 | 
			
		||||
 | 
			
		||||
    ctx.game.mouse_position = input->mouse_window_position;
 | 
			
		||||
    ctx.game.mouse_movement = input->mouse_relative_position;
 | 
			
		||||
 | 
			
		||||
    if (ctx.window_mouse_resident)
 | 
			
		||||
        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) {
 | 
			
		||||
        Action *action = &input->action_hash[i].value;
 | 
			
		||||
        /* collect unused */
 | 
			
		||||
        for (size_t u = 0; u < action->num_bindings; ++u) {
 | 
			
		||||
            Button *button = &action->bindings[u];
 | 
			
		||||
            if (!button->in_use)
 | 
			
		||||
                input_unbind_code_from_action(input, input->action_hash[i].key, button->source, button->code);
 | 
			
		||||
            else 
 | 
			
		||||
                button->in_use = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        update_action_pressed_state(input, action);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    size_t removed = 0;
 | 
			
		||||
    for (size_t i = 0; i < shlenu(input->action_hash); ++i) {
 | 
			
		||||
        if (input->action_hash[i - removed].value.num_bindings == 0)
 | 
			
		||||
            input_delete_action(input->action_hash[i - removed].key);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void input_bind_action_control(char const *action_name,
 | 
			
		||||
void input_action(char const *action_name,
 | 
			
		||||
                               Control control)
 | 
			
		||||
{
 | 
			
		||||
    SDL_assert_always(action_name);
 | 
			
		||||
@@ -236,57 +284,7 @@ void input_bind_action_control(char const *action_name,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void input_unbind_action_control(char const *action_name,
 | 
			
		||||
                                 Control control)
 | 
			
		||||
{
 | 
			
		||||
    SDL_assert_always(action_name);
 | 
			
		||||
 | 
			
		||||
    if (CONTROL_SCANCODE_START <= control && control < CONTROL_SCANCODE_LIMIT)
 | 
			
		||||
        input_unbind_code_from_action(&ctx.input,
 | 
			
		||||
                                      action_name,
 | 
			
		||||
                                      BUTTON_SOURCE_KEYBOARD_PHYSICAL,
 | 
			
		||||
                                      (union ButtonCode) { .scancode = (SDL_Scancode)control });
 | 
			
		||||
 | 
			
		||||
    else if (CONTROL_MOUSECODE_START <= control && control < CONTROL_MOUSECODE_LIMIT) {
 | 
			
		||||
        uint8_t const mouse_button = (uint8_t)(control - CONTROL_MOUSECODE_START);
 | 
			
		||||
        input_unbind_code_from_action(&ctx.input,
 | 
			
		||||
                                      action_name,
 | 
			
		||||
                                      BUTTON_SOURCE_MOUSE,
 | 
			
		||||
                                      (union ButtonCode) { .mouse_button = (uint8_t)SDL_BUTTON(mouse_button)});
 | 
			
		||||
    } else
 | 
			
		||||
        log_warn("(%s) Invalid control value given: %i.", __func__, control);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void input_add_action(char const *action_name) {
 | 
			
		||||
    SDL_assert_always(action_name);
 | 
			
		||||
 | 
			
		||||
    if (shgeti(ctx.input.action_hash, action_name) >= 0) {
 | 
			
		||||
        log_warn("(%s) Action \"%s\" is already registered.", __func__, action_name);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Action new_action = { 0 };
 | 
			
		||||
    new_action.bindings = ccalloc(ctx.keybind_slots, sizeof *new_action.bindings);
 | 
			
		||||
    shput(ctx.input.action_hash, action_name, new_action);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void input_delete_action(char const *action_name) {
 | 
			
		||||
    SDL_assert_always(action_name);
 | 
			
		||||
 | 
			
		||||
    ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
 | 
			
		||||
    if (action == NULL) {
 | 
			
		||||
        log_warn("(%s) Action \"%s\" is not registered.", __func__, action_name);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    SDL_free(action->value.bindings);
 | 
			
		||||
    shdel(ctx.input.action_hash, action_name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
bool input_is_action_pressed(char const *action_name) {
 | 
			
		||||
bool input_action_pressed(char const *action_name) {
 | 
			
		||||
    SDL_assert_always(action_name);
 | 
			
		||||
 | 
			
		||||
    ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
 | 
			
		||||
@@ -298,7 +296,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);
 | 
			
		||||
 | 
			
		||||
    ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
 | 
			
		||||
@@ -310,7 +308,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);
 | 
			
		||||
 | 
			
		||||
    ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
 | 
			
		||||
@@ -322,7 +320,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);
 | 
			
		||||
 | 
			
		||||
    ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
 | 
			
		||||
@@ -335,17 +333,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) {
 | 
			
		||||
    for (size_t i = 0; i < shlenu(input->action_hash); ++i) {
 | 
			
		||||
        Action *action = &input->action_hash[i].value;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,12 @@
 | 
			
		||||
#ifndef TWN_INPUT_C_H
 | 
			
		||||
#define TWN_INPUT_C_H
 | 
			
		||||
 | 
			
		||||
#include "twn_input.h"
 | 
			
		||||
#include "twn_vec.h"
 | 
			
		||||
#include "twn_types.h"
 | 
			
		||||
 | 
			
		||||
#include <SDL2/SDL.h>
 | 
			
		||||
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#define KEYBIND_SLOTS_DEFAULT 3
 | 
			
		||||
 | 
			
		||||
@@ -32,6 +33,7 @@ typedef enum ButtonSource {
 | 
			
		||||
typedef struct Button {
 | 
			
		||||
    enum ButtonSource source;
 | 
			
		||||
    union ButtonCode code;
 | 
			
		||||
    bool in_use;
 | 
			
		||||
} Button;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -59,8 +61,8 @@ typedef struct ActionHashItem {
 | 
			
		||||
typedef struct InputState {
 | 
			
		||||
    const uint8_t *keyboard_state; /* array of booleans indexed by scancode */
 | 
			
		||||
    ActionHashItem *action_hash;
 | 
			
		||||
    Vec2i mouse_window_position;
 | 
			
		||||
    Vec2i mouse_relative_position;
 | 
			
		||||
    Vec2 mouse_window_position;
 | 
			
		||||
    Vec2 mouse_relative_position;
 | 
			
		||||
    uint32_t mouse_state;          /* SDL mouse button bitmask */
 | 
			
		||||
    ButtonSource last_active_source;
 | 
			
		||||
    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_postframe(InputState *input);
 | 
			
		||||
 | 
			
		||||
void input_reset_state(InputState *input);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										380
									
								
								src/twn_loop.c
									
									
									
									
									
								
							
							
						
						
									
										380
									
								
								src/twn_loop.c
									
									
									
									
									
								
							@@ -4,7 +4,6 @@
 | 
			
		||||
#include "twn_util.h"
 | 
			
		||||
#include "twn_util_c.h"
 | 
			
		||||
#include "twn_game_object_c.h"
 | 
			
		||||
#include "twn_audio_c.h"
 | 
			
		||||
#include "twn_textures_c.h"
 | 
			
		||||
 | 
			
		||||
#include <SDL2/SDL.h>
 | 
			
		||||
@@ -12,13 +11,6 @@
 | 
			
		||||
#include <stb_ds.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 <limits.h>
 | 
			
		||||
 | 
			
		||||
@@ -27,6 +19,9 @@
 | 
			
		||||
#define PACKAGE_EXTENSION "btw"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static SDL_Thread *opengl_load_thread;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static int event_callback(void *userdata, SDL_Event *event) {
 | 
			
		||||
    (void)userdata;
 | 
			
		||||
 | 
			
		||||
@@ -37,11 +32,21 @@ static int event_callback(void *userdata, SDL_Event *event) {
 | 
			
		||||
 | 
			
		||||
        switch (event->window.event) {
 | 
			
		||||
        case SDL_WINDOWEVENT_SIZE_CHANGED:
 | 
			
		||||
            ctx.window_dims.x = event->window.data1;
 | 
			
		||||
            ctx.window_dims.y = event->window.data2;
 | 
			
		||||
            ctx.window_dims.x = (float)event->window.data1;
 | 
			
		||||
            ctx.window_dims.y = (float)event->window.data2;
 | 
			
		||||
            ctx.resync_flag = true;
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case SDL_WINDOWEVENT_FOCUS_LOST: {
 | 
			
		||||
            ctx.window_mouse_resident = false;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case SDL_WINDOWEVENT_FOCUS_GAINED: {
 | 
			
		||||
            ctx.window_mouse_resident = true;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        default:
 | 
			
		||||
            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) {
 | 
			
		||||
    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) {
 | 
			
		||||
    /*
 | 
			
		||||
    if (!ctx.is_running) {
 | 
			
		||||
@@ -198,20 +206,36 @@ static void main_loop(void) {
 | 
			
		||||
 | 
			
		||||
    /* finally, let's get to work */
 | 
			
		||||
    int frames = 0;
 | 
			
		||||
    while (ctx.frame_accumulator >= ctx.desired_frametime * ctx.update_multiplicity) {
 | 
			
		||||
    while (ctx.frame_accumulator >= ctx.desired_frametime) {
 | 
			
		||||
        frames += 1;
 | 
			
		||||
        for (size_t i = 0; i < ctx.update_multiplicity; ++i) {
 | 
			
		||||
            /* TODO: disable rendering pushes on not-last ? */
 | 
			
		||||
            render_queue_clear();
 | 
			
		||||
            poll_events();
 | 
			
		||||
            input_state_update(&ctx.input);
 | 
			
		||||
            game_object_tick();
 | 
			
		||||
            preserve_persistent_ctx_fields();
 | 
			
		||||
        /* TODO: disable rendering pushes on not-last ? */
 | 
			
		||||
        render_queue_clear();
 | 
			
		||||
        poll_events();
 | 
			
		||||
        if (ctx.window_size_has_changed)
 | 
			
		||||
            update_viewport();
 | 
			
		||||
        input_state_update(&ctx.input);
 | 
			
		||||
        game_object_tick();
 | 
			
		||||
        input_state_update_postframe(&ctx.input);
 | 
			
		||||
 | 
			
		||||
            ctx.frame_accumulator -= ctx.desired_frametime;
 | 
			
		||||
            ctx.game.frame_number = (ctx.game.frame_number % ULLONG_MAX) + 1;
 | 
			
		||||
            ctx.game.initialization_needed = false;
 | 
			
		||||
        /* 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();
 | 
			
		||||
 | 
			
		||||
        ctx.frame_accumulator -= ctx.desired_frametime;
 | 
			
		||||
        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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* TODO: in some cases machine might want to assume frames will be fed as much as possible */
 | 
			
		||||
@@ -320,19 +344,25 @@ ERR_PACK_MANIFEST_PATH_ALLOC_FAIL:
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static bool initialize(void) {
 | 
			
		||||
    if (SDL_Init(SDL_INIT_EVERYTHING & ~SDL_INIT_HAPTIC) == -1) {
 | 
			
		||||
        CRY_SDL("SDL initialization failed.");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
static int opengl_load_thread_fn(void *data) {
 | 
			
		||||
    (void)data;
 | 
			
		||||
 | 
			
		||||
    SDL_GL_LoadLibrary(NULL);
 | 
			
		||||
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static bool initialize(void) {
 | 
			
		||||
    /* 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 */
 | 
			
		||||
    /* that is why PhysicsFS is initialized before anything else */
 | 
			
		||||
    toml_set_memutil(SDL_malloc, SDL_free);
 | 
			
		||||
 | 
			
		||||
    profile_start("pack dependency resolution");
 | 
			
		||||
    /* time to orderly resolve any dependencies present */
 | 
			
		||||
    resolve_pack_dependencies("data");
 | 
			
		||||
    profile_end("pack dependency resolution");
 | 
			
		||||
 | 
			
		||||
    /* load the config file into an opaque table */
 | 
			
		||||
    {
 | 
			
		||||
@@ -396,11 +426,7 @@ static bool initialize(void) {
 | 
			
		||||
    /* debug mode defaults to being enabled */
 | 
			
		||||
    /* pass --debug or --release to force a mode, ignoring configuration */
 | 
			
		||||
    toml_datum_t datum_debug = toml_bool_in(game, "debug");
 | 
			
		||||
    if (!datum_debug.ok) {
 | 
			
		||||
        ctx.game.debug = true;
 | 
			
		||||
    } else {
 | 
			
		||||
        ctx.game.debug = datum_debug.u.b;
 | 
			
		||||
    }
 | 
			
		||||
    ctx.game.debug = datum_debug.ok ? datum_debug.u.b : true;
 | 
			
		||||
 | 
			
		||||
#ifdef EMSCRIPTEN
 | 
			
		||||
    /* emscripten interpretes those as GL ES version against WebGL */
 | 
			
		||||
@@ -424,135 +450,61 @@ static bool initialize(void) {
 | 
			
		||||
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 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");
 | 
			
		||||
        if (!datum_title.ok) {
 | 
			
		||||
            CRY("Initialization failed", "Valid about.title expected in configuration file");
 | 
			
		||||
            goto fail;
 | 
			
		||||
        }
 | 
			
		||||
        /* not yet used
 | 
			
		||||
        toml_datum_t datum_developer = toml_string_in(about, "developer");
 | 
			
		||||
        if (!datum_developer.ok) {
 | 
			
		||||
            CRY("Initialization failed", "Valid about.developer expected in configuration file");
 | 
			
		||||
            goto fail;
 | 
			
		||||
        }
 | 
			
		||||
        */
 | 
			
		||||
        toml_datum_t datum_base_render_width = toml_int_in(game, "base_render_width");
 | 
			
		||||
    toml_datum_t datum_title = toml_string_in(about, "title");
 | 
			
		||||
    if (!datum_title.ok)
 | 
			
		||||
        datum_title.u.s = SDL_strdup("townengine project");
 | 
			
		||||
 | 
			
		||||
    ctx.title = datum_title.u.s;
 | 
			
		||||
 | 
			
		||||
    /* not yet used
 | 
			
		||||
    toml_datum_t datum_developer = toml_string_in(about, "developer");
 | 
			
		||||
    if (!datum_developer.ok) {
 | 
			
		||||
        CRY("Initialization failed", "Valid about.developer expected in configuration file");
 | 
			
		||||
        goto fail;
 | 
			
		||||
    }
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    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) {
 | 
			
		||||
            CRY("Initialization failed", "Valid game.base_render_width expected in configuration file");
 | 
			
		||||
            CRY("Initialization failed", "Valid game.resolution expected in configuration file");
 | 
			
		||||
            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) {
 | 
			
		||||
            CRY("Initialization failed", "Valid game.base_render_height expected in configuration file");
 | 
			
		||||
            CRY("Initialization failed", "Valid game.resolution expected in configuration file");
 | 
			
		||||
            goto fail;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ctx.base_render_width = datum_base_render_width.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,
 | 
			
		||||
                                      SDL_WINDOWPOS_CENTERED,
 | 
			
		||||
                                      SDL_WINDOWPOS_CENTERED,
 | 
			
		||||
                                      (int)datum_base_render_width.u.i,
 | 
			
		||||
                                      (int)datum_base_render_height.u.i,
 | 
			
		||||
                                      //SDL_WINDOW_ALLOW_HIGHDPI |
 | 
			
		||||
                                          SDL_WINDOW_RESIZABLE |
 | 
			
		||||
                                          SDL_WINDOW_OPENGL);
 | 
			
		||||
 | 
			
		||||
        SDL_free(datum_title.u.s);
 | 
			
		||||
        //SDL_free(datum_developer.u.s);
 | 
			
		||||
    } else {
 | 
			
		||||
        ctx.base_render_width = 640;
 | 
			
		||||
        ctx.base_render_height = 360;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (ctx.window == NULL) {
 | 
			
		||||
        CRY_SDL("Window creation failed.");
 | 
			
		||||
        goto fail;
 | 
			
		||||
    }
 | 
			
		||||
    ctx.game.resolution.x = (float)ctx.base_render_width;
 | 
			
		||||
    ctx.game.resolution.y = (float)ctx.base_render_height;
 | 
			
		||||
 | 
			
		||||
    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);
 | 
			
		||||
    //SDL_free(datum_developer.u.s);
 | 
			
		||||
 | 
			
		||||
    /* TODO: */
 | 
			
		||||
    // SDL_GetRendererOutputSize(ctx.renderer, &ctx.window_w, &ctx.window_h);
 | 
			
		||||
    ctx.window_dims.x = (int)ctx.base_render_width;
 | 
			
		||||
    ctx.window_dims.y = (int)ctx.base_render_height;
 | 
			
		||||
    ctx.window_dims.x = (float)ctx.base_render_width;
 | 
			
		||||
    ctx.window_dims.y = (float)ctx.base_render_height;
 | 
			
		||||
 | 
			
		||||
    /* add a watcher for immediate updates on window size */
 | 
			
		||||
    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 */
 | 
			
		||||
    /* SDL_GetPerformanceCounter returns some platform-dependent number. */
 | 
			
		||||
    /* it should vary between game instances. i checked! random enough for me. */
 | 
			
		||||
    ctx.game.random_seed = SDL_GetPerformanceCounter();
 | 
			
		||||
    srand((unsigned int)ctx.game.random_seed);
 | 
			
		||||
    stbds_rand_seed(ctx.game.random_seed);
 | 
			
		||||
    ctx.game.random_seed = (float)(SDL_GetPerformanceCounter() % 16777216);
 | 
			
		||||
    srand((unsigned int)(SDL_GetPerformanceCounter()));
 | 
			
		||||
    stbds_rand_seed((size_t)(SDL_GetPerformanceCounter()));
 | 
			
		||||
 | 
			
		||||
    /* main loop machinery */
 | 
			
		||||
    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) {
 | 
			
		||||
            ctx.font_filtering = TEXT_FONT_FILTERING_DEFAULT;
 | 
			
		||||
        } 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;
 | 
			
		||||
            } 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;
 | 
			
		||||
            } else {
 | 
			
		||||
                ctx.font_filtering = TEXT_FONT_FILTERING_DEFAULT;
 | 
			
		||||
@@ -635,9 +587,6 @@ static bool initialize(void) {
 | 
			
		||||
    ctx.render_queue_2d = NULL;
 | 
			
		||||
    ctx.uncolored_mesh_batches = NULL;
 | 
			
		||||
 | 
			
		||||
    textures_cache_init(&ctx.texture_cache, ctx.window);
 | 
			
		||||
    text_cache_init(&ctx.text_cache);
 | 
			
		||||
 | 
			
		||||
    /* input */
 | 
			
		||||
    toml_datum_t datum_keybind_slots = toml_int_in(engine, "keybind_slots");
 | 
			
		||||
    if (!datum_keybind_slots.ok) {
 | 
			
		||||
@@ -651,14 +600,67 @@ static bool initialize(void) {
 | 
			
		||||
    }
 | 
			
		||||
    input_state_init(&ctx.input);
 | 
			
		||||
 | 
			
		||||
    /* scripting */
 | 
			
		||||
    /*
 | 
			
		||||
    if (!scripting_init(ctx)) {
 | 
			
		||||
    ctx.render_double_buffered = true;
 | 
			
		||||
    ctx.window_mouse_resident = true;
 | 
			
		||||
 | 
			
		||||
    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;
 | 
			
		||||
    }
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    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;
 | 
			
		||||
 | 
			
		||||
@@ -670,10 +672,6 @@ fail:
 | 
			
		||||
 | 
			
		||||
/* will not be called on an abnormal exit */
 | 
			
		||||
static void clean_up(void) {
 | 
			
		||||
    /*
 | 
			
		||||
    scripting_deinit(ctx);
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    input_state_deinit(&ctx.input);
 | 
			
		||||
    text_cache_deinit(&ctx.text_cache);
 | 
			
		||||
    textures_cache_deinit(&ctx.texture_cache);
 | 
			
		||||
@@ -686,7 +684,9 @@ static void clean_up(void) {
 | 
			
		||||
    PHYSFS_deinit();
 | 
			
		||||
 | 
			
		||||
    SDL_free(ctx.base_dir);
 | 
			
		||||
    SDL_free(ctx.title);
 | 
			
		||||
    SDL_GL_DeleteContext(ctx.gl_context);
 | 
			
		||||
    SDL_GL_UnloadLibrary();
 | 
			
		||||
    SDL_Quit();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -698,6 +698,22 @@ static void reset_state(void) {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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.argv = argv;
 | 
			
		||||
    ctx.base_dir = SDL_GetBasePath();
 | 
			
		||||
@@ -716,7 +732,7 @@ int enter_loop(int argc, char **argv) {
 | 
			
		||||
 | 
			
		||||
    for (int i = 1; i < argc; ++i) {
 | 
			
		||||
        /* 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) {
 | 
			
		||||
                CRY("Data dir mount override failed.", "No arguments passed (expected 1).");
 | 
			
		||||
                return EXIT_FAILURE;
 | 
			
		||||
@@ -733,14 +749,14 @@ int enter_loop(int argc, char **argv) {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /* force debug mode */
 | 
			
		||||
        if (SDL_strcmp(argv[i], "--debug") == 0) {
 | 
			
		||||
        if (SDL_strncmp(argv[i], "--debug", sizeof "--debug" - 1) == 0) {
 | 
			
		||||
            force_debug = true;
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /* force release mode */
 | 
			
		||||
        if (SDL_strcmp(argv[i], "--release") == 0) {
 | 
			
		||||
            force_release = false;
 | 
			
		||||
        if (SDL_strncmp(argv[i], "--release", sizeof "--release" - 1) == 0) {
 | 
			
		||||
            force_release = true;
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -773,12 +789,11 @@ int enter_loop(int argc, char **argv) {
 | 
			
		||||
        ctx.game.debug = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* now we can actually start doing stuff */
 | 
			
		||||
    game_object_load();
 | 
			
		||||
 | 
			
		||||
    ctx.was_successful = true;
 | 
			
		||||
    ctx.game.initialization_needed = true;
 | 
			
		||||
 | 
			
		||||
    profile_end("startup");
 | 
			
		||||
 | 
			
		||||
    while (ctx.is_running) {
 | 
			
		||||
        if (game_object_try_reloading()) {
 | 
			
		||||
            ctx.game.initialization_needed = true;
 | 
			
		||||
@@ -788,6 +803,9 @@ int enter_loop(int argc, char **argv) {
 | 
			
		||||
        main_loop();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (ctx.game.debug)
 | 
			
		||||
        profile_list_stats();
 | 
			
		||||
 | 
			
		||||
    /* loop is over */
 | 
			
		||||
    game_object_unload();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
#include "twn_loop.h"
 | 
			
		||||
 | 
			
		||||
#ifndef EMSCRIPTEN
 | 
			
		||||
#define SDL_MAIN_HANDLED
 | 
			
		||||
#endif
 | 
			
		||||
#include <SDL2/SDL.h>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -40,6 +40,44 @@ static int load_eof_callback(void *user) {
 | 
			
		||||
    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_RWops *handle = PHYSFSRWOPS_openRead(path);
 | 
			
		||||
    if (handle == NULL)
 | 
			
		||||
@@ -93,8 +131,9 @@ ERR_CANNOT_CREATE_SURFACE:
 | 
			
		||||
 | 
			
		||||
ERR_CANNOT_READ_IMAGE:
 | 
			
		||||
ERR_CANNOT_OPEN_FILE:
 | 
			
		||||
    CRY(path, "Failed to load image. Aborting...");
 | 
			
		||||
    die_abruptly();
 | 
			
		||||
    /* something didn't worked out, use a stub texture */
 | 
			
		||||
    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) {
 | 
			
		||||
    profile_start("atlas recreation");
 | 
			
		||||
 | 
			
		||||
    /* 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 */
 | 
			
		||||
    SDL_Surface *atlas_surface = cache->atlas_surfaces[cache->atlas_index];
 | 
			
		||||
@@ -177,6 +218,8 @@ static void recreate_current_atlas_texture(TextureCache *cache) {
 | 
			
		||||
 | 
			
		||||
    /* texturize it! */
 | 
			
		||||
    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;
 | 
			
		||||
    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);
 | 
			
		||||
    recreate_current_atlas_texture(cache);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -290,7 +332,10 @@ void textures_cache_deinit(TextureCache *cache) {
 | 
			
		||||
 | 
			
		||||
    /* free cache hashes */
 | 
			
		||||
    for (size_t i = 0; i < shlenu(cache->hash); ++i) {
 | 
			
		||||
        stbi_image_free(cache->hash[i].value.data->pixels);
 | 
			
		||||
        if (missing_texture_surface && cache->hash[i].value.data->pixels != missing_texture_surface->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);
 | 
			
		||||
    }
 | 
			
		||||
    shfree(cache->hash);
 | 
			
		||||
@@ -334,6 +379,9 @@ static TextureKey textures_load(TextureCache *cache, const char *path) {
 | 
			
		||||
        return (TextureKey){ (uint16_t)i };
 | 
			
		||||
 | 
			
		||||
    SDL_Surface *surface = textures_load_surface(path);
 | 
			
		||||
    if (surface == missing_texture_surface && missing_texture_id != 0)
 | 
			
		||||
        return (TextureKey){ missing_texture_id };
 | 
			
		||||
 | 
			
		||||
    Texture new_texture = {
 | 
			
		||||
        .data = 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);
 | 
			
		||||
        upload_texture_from_surface(new_texture.loner_texture, surface); 
 | 
			
		||||
        new_texture.srcrect = (Rect) { .w = (float)surface->w, .h = (float)surface->h };
 | 
			
		||||
 | 
			
		||||
    } else {
 | 
			
		||||
        /* will be fully populated as the atlas updates */
 | 
			
		||||
        new_texture.atlas_index = cache->atlas_index;
 | 
			
		||||
        cache->is_dirty = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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 */
 | 
			
		||||
    last_texture = textures_load(cache, path);
 | 
			
		||||
    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,
 | 
			
		||||
                               texture.data->pixels,
 | 
			
		||||
                               4,
 | 
			
		||||
                               texture.data->format->BytesPerPixel,
 | 
			
		||||
                               texture.data->w,
 | 
			
		||||
                               texture.data->h);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#ifndef TWN_TEXTURES_C_H
 | 
			
		||||
#define TWN_TEXTURES_C_H
 | 
			
		||||
 | 
			
		||||
#include "twn_util.h"
 | 
			
		||||
#include "twn_types.h"
 | 
			
		||||
#include "twn_texture_modes.h"
 | 
			
		||||
#include "rendering/twn_gpu_texture_c.h"
 | 
			
		||||
 | 
			
		||||
@@ -17,8 +17,8 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
typedef struct Texture {
 | 
			
		||||
    Rect srcrect;            /* position in atlas */
 | 
			
		||||
    SDL_Surface *data;          /* original image data */
 | 
			
		||||
    Rect srcrect;                   /* position in atlas */
 | 
			
		||||
    SDL_Surface *data;              /* original image data */
 | 
			
		||||
    int atlas_index;
 | 
			
		||||
    GPUTexture loner_texture;       /* stored directly for loners, == 0 means atlas_index should be used */
 | 
			
		||||
    GPUTexture repeating_texture;   /* separately allocated Texture, for loners == loner_texture */
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
							
								
								
									
										187
									
								
								src/twn_util.c
									
									
									
									
									
								
							
							
						
						
									
										187
									
								
								src/twn_util.c
									
									
									
									
									
								
							@@ -9,6 +9,17 @@
 | 
			
		||||
#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) {
 | 
			
		||||
    SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION,
 | 
			
		||||
                    "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) {
 | 
			
		||||
    SDL_Rect a_sdl = { a->x, a->y, a->w, a->h };
 | 
			
		||||
    SDL_Rect b_sdl = { b->x, b->y, b->w, b->h };
 | 
			
		||||
    SDL_Rect result_sdl = { 0 };
 | 
			
		||||
 | 
			
		||||
    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 };
 | 
			
		||||
/* TODO: have our own */
 | 
			
		||||
Rect rect_overlap(const Rect a, const Rect b) {
 | 
			
		||||
    SDL_FRect a_sdl = { a.x, a.y, a.w, a.h };
 | 
			
		||||
    SDL_FRect b_sdl = { b.x, b.y, b.w, b.h };
 | 
			
		||||
    SDL_FRect result_sdl = { 0 };
 | 
			
		||||
 | 
			
		||||
    bool intersection = SDL_IntersectFRect(&a_sdl, &b_sdl, &result_sdl);
 | 
			
		||||
    (void)SDL_IntersectFRect(&a_sdl, &b_sdl, &result_sdl);
 | 
			
		||||
 | 
			
		||||
    if (result != NULL)
 | 
			
		||||
        *result = (Rect){ result_sdl.x, result_sdl.y, result_sdl.w, result_sdl.h };
 | 
			
		||||
 | 
			
		||||
    return intersection;
 | 
			
		||||
    return (Rect){ result_sdl.x, result_sdl.y, result_sdl.w, result_sdl.h };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
bool intersect_rect(const Recti *a, const Recti *b) {
 | 
			
		||||
    SDL_Rect a_sdl = { a->x, a->y, a->w, a->h };
 | 
			
		||||
    SDL_Rect b_sdl = { b->x, b->y, b->w, b->h };
 | 
			
		||||
    return SDL_HasIntersection(&a_sdl, &b_sdl);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
bool intersect_frect(const Rect *a, const Rect *b) {
 | 
			
		||||
    SDL_FRect a_sdl = { a->x, a->y, a->w, a->h };
 | 
			
		||||
    SDL_FRect b_sdl = { b->x, b->y, b->w, b->h };
 | 
			
		||||
/* TODO: have our own */
 | 
			
		||||
bool rect_intersects(const Rect a, const Rect b) {
 | 
			
		||||
    SDL_FRect a_sdl = { a.x, a.y, a.w, a.h };
 | 
			
		||||
    SDL_FRect b_sdl = { b.x, b.y, b.w, b.h };
 | 
			
		||||
    return SDL_HasIntersectionF(&a_sdl, &b_sdl);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Rect to_frect(Recti rect) {
 | 
			
		||||
    return (Rect) {
 | 
			
		||||
        .h = (float)rect.h,
 | 
			
		||||
        .w = (float)rect.w,
 | 
			
		||||
        .x = (float)rect.x,
 | 
			
		||||
        .y = (float)rect.y,
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Vec2 frect_center(Rect rect) {
 | 
			
		||||
Vec2 rect_center(Rect rect) {
 | 
			
		||||
    return (Vec2){
 | 
			
		||||
        .x = rect.x + rect.w / 2,
 | 
			
		||||
        .y = rect.y + rect.h / 2,
 | 
			
		||||
@@ -257,25 +236,52 @@ Vec2 frect_center(Rect rect) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void tick_timer(int *value) {
 | 
			
		||||
    *value = MAX(*value - 1, 0);
 | 
			
		||||
int32_t timer_tick_frames(int32_t frames_left) {
 | 
			
		||||
    SDL_assert(frames_left >= 0);
 | 
			
		||||
    return MAX(frames_left - 1, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void tick_ftimer(float *value) {
 | 
			
		||||
    *value = MAX(*value - ((float)ctx.delta_time / (float)ctx.clocks_per_second), 0.0f);
 | 
			
		||||
float timer_tick_seconds(float seconds_left) {
 | 
			
		||||
    SDL_assert(seconds_left >= 0);
 | 
			
		||||
    return MAX(seconds_left - ((float)ctx.delta_time / (float)ctx.clocks_per_second), 0.0f);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
bool repeat_ftimer(float *value, float at) {
 | 
			
		||||
    *value -= (float)ctx.delta_time / (float)ctx.clocks_per_second;
 | 
			
		||||
    if (*value < 0.0f) {
 | 
			
		||||
        *value += at;
 | 
			
		||||
        return true;
 | 
			
		||||
TimerElapseFramesResult timer_elapse_frames(int32_t frames_left, int32_t interval) {
 | 
			
		||||
    SDL_assert(frames_left >= 0);
 | 
			
		||||
    SDL_assert(interval > 0);
 | 
			
		||||
 | 
			
		||||
    frames_left -= 1;
 | 
			
		||||
    bool elapsed = false;
 | 
			
		||||
    if (frames_left <= 0) {
 | 
			
		||||
        elapsed = true;
 | 
			
		||||
        frames_left += interval;
 | 
			
		||||
    }
 | 
			
		||||
    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 */
 | 
			
		||||
char *expand_asterisk(const char *mask, const char *to) {
 | 
			
		||||
    const char *offset = SDL_strchr(mask, '*');
 | 
			
		||||
@@ -292,3 +298,78 @@ char *expand_asterisk(const char *mask, const char *to) {
 | 
			
		||||
    SDL_memcpy(str + (offset - mask) + to_len, offset + 1, mask_len - (offset - mask));
 | 
			
		||||
    return str;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void profile_start(char profile[const static 1]) {
 | 
			
		||||
    uint64_t tick_accum = 0, sample_count = 0;
 | 
			
		||||
 | 
			
		||||
    struct ProfileItem const *p = shgetp_null(profiles, profile);
 | 
			
		||||
    if (p) {
 | 
			
		||||
        tick_accum = p->value.tick_accum;
 | 
			
		||||
        sample_count = p->value.sample_count;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    shput(profiles, profile, ((struct Profile) {
 | 
			
		||||
            .tick_start = SDL_GetPerformanceCounter(),
 | 
			
		||||
            .tick_accum = tick_accum,
 | 
			
		||||
            .sample_count = sample_count,
 | 
			
		||||
    }));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void profile_end(char profile[const static 1]) {
 | 
			
		||||
    struct ProfileItem *p = shgetp_null(profiles, profile);
 | 
			
		||||
    if (!p) {
 | 
			
		||||
        log_warn("Profile %s wasn't started!", profile);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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: %f seconds",
 | 
			
		||||
                     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: %f seconds, worst case: %f, 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 <stdbool.h>
 | 
			
		||||
 | 
			
		||||
#include <math.h>
 | 
			
		||||
 | 
			
		||||
#define MAX SDL_max
 | 
			
		||||
#define MIN SDL_min
 | 
			
		||||
@@ -23,6 +23,10 @@ _Noreturn void die_abruptly(void);
 | 
			
		||||
/* note: you must free the returned string */
 | 
			
		||||
char *expand_asterisk(const char *mask, const char *to);
 | 
			
		||||
 | 
			
		||||
void profile_start(char profile[const static 1]);
 | 
			
		||||
void profile_end(char profile[const static 1]);
 | 
			
		||||
void profile_list_stats(void);
 | 
			
		||||
 | 
			
		||||
/* http://www.azillionmonkeys.com/qed/sqroot.html */
 | 
			
		||||
static inline float fast_sqrt(float x)
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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)
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
 | 
			
		||||
cmake_minimum_required(VERSION 3.0)
 | 
			
		||||
cmake_minimum_required(VERSION 3.5)
 | 
			
		||||
 | 
			
		||||
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_arraddnoff       stbds_arraddnindex
 | 
			
		||||
#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_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)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user