Compare commits
	
		
			165 Commits
		
	
	
		
			double
			...
			916e433753
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					916e433753 | ||
| 
						 | 
					458b44d0b0 | ||
| 
						 | 
					8de4a1f09b | ||
| 
						 | 
					ac93d114c9 | ||
| 
						 | 
					37b0f4e3a6 | ||
| 
						 | 
					fad11041bc | ||
| 
						 | 
					bbd654a569 | ||
| 
						 | 
					7c33107585 | ||
| 
						 | 
					f625dde8d1 | ||
| 
						 | 
					166ae43981 | ||
| 
						 | 
					6ef3cf1a3a | ||
| 
						 | 
					d84e5f8610 | ||
| 
						 | 
					6a87119c70 | ||
| 
						 | 
					791ab628ca | ||
| 
						 | 
					20394eed6c | ||
| 
						 | 
					a54657e7be | ||
| 
						 | 
					3bdee30e7b | ||
| 
						 | 
					0e4abaae3c | ||
| 
						 | 
					cf8227d7d3 | ||
| 
						 | 
					f365cff590 | ||
| 
						 | 
					77ff5c7f25 | ||
| 
						 | 
					bf3eb50b55 | ||
| 
						 | 
					6a029b7e79 | ||
| 
						 | 
					fad46137a0 | ||
| 
						 | 
					7a403c766e | ||
| 
						 | 
					b0e718876a | ||
| 
						 | 
					785b507330 | ||
| 
						 | 
					d884a9026f | ||
| 
						 | 
					9ddc5c4a66 | ||
| 
						 | 
					16bd49b42e | ||
| 
						 | 
					dd158dee01 | ||
| 
						 | 
					ab3c032313 | ||
| 
						 | 
					34a3de73c6 | ||
| 
						 | 
					597168c282 | ||
| 
						 | 
					37cd8cf2cf | ||
| 
						 | 
					cb5f207761 | ||
| 
						 | 
					6e421543c4 | ||
| 
						 | 
					c97d9b2568 | ||
| 
						 | 
					e281ba593c | ||
| 
						 | 
					a20be2c523 | ||
| 
						 | 
					b20e7202fe | ||
| 
						 | 
					045d2764fa | ||
| 
						 | 
					53917b05b7 | ||
| 
						 | 
					0dc0a18019 | ||
| 
						 | 
					2df5616410 | ||
| 
						 | 
					3f9906a918 | ||
| 
						 | 
					8a5d639f95 | ||
| 
						 | 
					40aef0a1f9 | ||
| 
						 | 
					2de536210b | ||
| 
						 | 
					449d4d3c32 | ||
| 
						 | 
					8233f31269 | ||
| 
						 | 
					45b8b21ec3 | ||
| 
						 | 
					4659cf2aef | ||
| 
						 | 
					db530ca3a0 | ||
| 
						 | 
					f0dfd5627a | ||
| 
						 | 
					0da1e413aa | ||
| 
						 | 
					8c165974c7 | ||
| 
						 | 
					1ba33bdc26 | ||
| 
						 | 
					760515c551 | ||
| 
						 | 
					9d0a2cab81 | ||
| 
						 | 
					d5b42fa242 | ||
| 
						 | 
					851ab80292 | ||
| 
						 | 
					688d71953a | ||
| 
						 | 
					63abf3d374 | ||
| 
						 | 
					80c77424e2 | ||
| 
						 | 
					82d4f21a4b | ||
| 
						 | 
					3990f78a74 | ||
| 
						 | 
					f0ad9b9a8a | ||
| 
						 | 
					ea0af5159f | ||
| 
						 | 
					5059802d09 | ||
| 
						 | 
					b7cb37c06a | ||
| 
						 | 
					664f123a85 | ||
| 
						 | 
					2351d4114c | ||
| 
						 | 
					86bf16b680 | ||
| 
						 | 
					dbe6217e24 | ||
| 
						 | 
					b037d7a0b9 | ||
| 
						 | 
					e984e95fa8 | ||
| 
						 | 
					4ed3764c1d | ||
| 
						 | 
					6d19d2d819 | ||
| 
						 | 
					5bce3e5238 | ||
| 
						 | 
					6298394957 | ||
| 
						 | 
					eefd53a630 | ||
| 
						 | 
					87ae1a7312 | ||
| 
						 | 
					3052bb693a | ||
| 
						 | 
					5f6c8dd8e6 | ||
| 
						 | 
					c694dfff82 | ||
| 
						 | 
					b6ca9bedb4 | ||
| 
						 | 
					8d67e44009 | ||
| 
						 | 
					192907a0db | ||
| 
						 | 
					e8b02570a2 | ||
| 
						 | 
					46e077ba63 | ||
| 
						 | 
					41d0e24780 | ||
| 
						 | 
					777a06a002 | ||
| 
						 | 
					313108092b | ||
| 
						 | 
					83e2dc5468 | ||
| 
						 | 
					951d9c76c8 | ||
| 
						 | 
					f3848d2d52 | ||
| 
						 | 
					8c401eda75 | ||
| 
						 | 
					5c89c55b3e | ||
| 
						 | 
					5b05386bb0 | ||
| 
						 | 
					b0549612a9 | ||
| 
						 | 
					6463ac3dd7 | ||
| 
						 | 
					e914cad0dd | ||
| 
						 | 
					a9d9936cb7 | ||
| 
						 | 
					4dd028aeae | ||
| 
						 | 
					d7a119a592 | ||
| 
						 | 
					cb6c1df0be | ||
| 
						 | 
					3bfa86066e | ||
| 
						 | 
					7c0bf39f12 | ||
| 
						 | 
					4f2b8ccd01 | ||
| 
						 | 
					472a0657f3 | ||
| 
						 | 
					0f368e2700 | ||
| 
						 | 
					33471b4c46 | ||
| 
						 | 
					edcb7fc39c | ||
| 
						 | 
					f9a8448782 | ||
| 
						 | 
					6d5732cc2b | ||
| 
						 | 
					62d738cbbe | ||
| 
						 | 
					f4a3298906 | ||
| 
						 | 
					8ec5a96333 | ||
| 
						 | 
					4277852fc5 | ||
| 
						 | 
					dc2535358e | ||
| 
						 | 
					190eb1f107 | ||
| 
						 | 
					e06c879869 | ||
| 
						 | 
					e7ed72dfc0 | ||
| 
						 | 
					c4c097f050 | ||
| 
						 | 
					1d34c91106 | ||
| 
						 | 
					0d81236331 | ||
| 
						 | 
					f9bb6412b7 | ||
| 
						 | 
					b18f6f1d87 | ||
| 
						 | 
					2f94e17852 | ||
| 
						 | 
					26a2bf293f | ||
| 
						 | 
					4b0d584b7e | ||
| 
						 | 
					19215d5795 | ||
| 
						 | 
					1c97053675 | ||
| 
						 | 
					ee7fc42fbc | ||
| 
						 | 
					cd9c65212d | ||
| 
						 | 
					ccaef34d61 | ||
| 
						 | 
					833f7dbc53 | ||
| 
						 | 
					a7feb7b61b | ||
| 
						 | 
					4be27816c2 | ||
| 
						 | 
					d794ca862f | ||
| 
						 | 
					26c75ffd7c | ||
| 
						 | 
					e4da4a8b7f | ||
| 
						 | 
					963d549eed | ||
| 
						 | 
					9121da0675 | ||
| 
						 | 
					6464d14b3e | ||
| 
						 | 
					eff9fe6918 | ||
| 
						 | 
					d11143ac86 | ||
| 
						 | 
					1d35a3859b | ||
| 
						 | 
					9da26638c8 | ||
| 
						 | 
					a22bcfd97e | ||
| 
						 | 
					a527036436 | ||
| 
						 | 
					b390e9db23 | ||
| 
						 | 
					5a08c01208 | ||
| 
						 | 
					eff2d9c5e1 | ||
| 
						 | 
					8aecc2bd06 | ||
| 
						 | 
					1296d41ad7 | ||
| 
						 | 
					48f63fc9df | ||
| c49789f1f4 | |||
| a7b09b9f39 | |||
| 399b199266 | |||
| 73b6ab047d | |||
| 024f17de91 | |||
| 92de2c00c0 | |||
| b683594013 | 
							
								
								
									
										15
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					root = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[*]
 | 
				
			||||||
 | 
					end_of_line = lf
 | 
				
			||||||
 | 
					insert_final_newline = true
 | 
				
			||||||
 | 
					charset = utf-8
 | 
				
			||||||
 | 
					trim_trailing_whitespace = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[*.{c,h,py}]
 | 
				
			||||||
 | 
					indent_style = space
 | 
				
			||||||
 | 
					indent_size = 4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[CMakeListst.txt]
 | 
				
			||||||
 | 
					indent_style = space
 | 
				
			||||||
 | 
					indent_size = 8
 | 
				
			||||||
							
								
								
									
										2
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							@@ -2,3 +2,5 @@
 | 
				
			|||||||
*.ogg filter=lfs diff=lfs merge=lfs -text
 | 
					*.ogg filter=lfs diff=lfs merge=lfs -text
 | 
				
			||||||
*.xm filter=lfs diff=lfs merge=lfs -text
 | 
					*.xm filter=lfs diff=lfs merge=lfs -text
 | 
				
			||||||
*.tga filter=lfs diff=lfs merge=lfs -text
 | 
					*.tga filter=lfs diff=lfs merge=lfs -text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					text=auto eol=lf
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -22,8 +22,8 @@
 | 
				
			|||||||
.vscode/
 | 
					.vscode/
 | 
				
			||||||
.idea/
 | 
					.idea/
 | 
				
			||||||
.cache/
 | 
					.cache/
 | 
				
			||||||
.build/
 | 
					build/
 | 
				
			||||||
.build-web/
 | 
					build-web/
 | 
				
			||||||
build/
 | 
					build/
 | 
				
			||||||
out/
 | 
					out/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,9 @@ cmake_minimum_required(VERSION 3.21)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
project(townengine LANGUAGES C)
 | 
					project(townengine LANGUAGES C)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set(CMAKE_MESSAGE_LOG_LEVEL "WARNING")
 | 
				
			||||||
 | 
					set(CMAKE_INSTALL_MESSAGE NEVER)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# SDL dependencies
 | 
					# SDL dependencies
 | 
				
			||||||
# for whatever reason Emscripten has SDL2 config, but not actual SDL2 port by default
 | 
					# for whatever reason Emscripten has SDL2 config, but not actual SDL2 port by default
 | 
				
			||||||
if(NOT EMSCRIPTEN)
 | 
					if(NOT EMSCRIPTEN)
 | 
				
			||||||
@@ -24,6 +27,7 @@ set(TWN_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "")
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# feature configuration, set them with -DFEATURE=ON/OFF in cli
 | 
					# feature configuration, set them with -DFEATURE=ON/OFF in cli
 | 
				
			||||||
option(TWN_FEATURE_DYNLIB_GAME  "Enable dynamic library loading support" ON)
 | 
					option(TWN_FEATURE_DYNLIB_GAME  "Enable dynamic library loading support" ON)
 | 
				
			||||||
 | 
					option(TWN_FEATURE_PUSH_AUDIO  "Enable frame based audio push for easy realtime audio" ON)
 | 
				
			||||||
option(TWN_USE_AMALGAM  "Enable use of twn_amalgam.c as a single compilation unit" ON)
 | 
					option(TWN_USE_AMALGAM  "Enable use of twn_amalgam.c as a single compilation unit" ON)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# todo: figure out how to compile for dynamic linking instead
 | 
					# todo: figure out how to compile for dynamic linking instead
 | 
				
			||||||
@@ -64,6 +68,7 @@ add_subdirectory(third-party/libxm ${CMAKE_CURRENT_BINARY_DIR}/third-party/libxm
 | 
				
			|||||||
if(LINUX)
 | 
					if(LINUX)
 | 
				
			||||||
        set(SYSTEM_SOURCE_FILES
 | 
					        set(SYSTEM_SOURCE_FILES
 | 
				
			||||||
                src/system/linux/twn_elf.c
 | 
					                src/system/linux/twn_elf.c
 | 
				
			||||||
 | 
					                src/system/linux/twn_timer.c
 | 
				
			||||||
                $<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:src/game_object/twn_linux_game_object.c>)
 | 
					                $<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:src/game_object/twn_linux_game_object.c>)
 | 
				
			||||||
elseif(WIN32)
 | 
					elseif(WIN32)
 | 
				
			||||||
        set(SYSTEM_SOURCE_FILES
 | 
					        set(SYSTEM_SOURCE_FILES
 | 
				
			||||||
@@ -81,8 +86,7 @@ endif()
 | 
				
			|||||||
if(TWN_RENDERING_API MATCHES OPENGL_15)
 | 
					if(TWN_RENDERING_API MATCHES OPENGL_15)
 | 
				
			||||||
        set(SYSTEM_SOURCE_FILES ${SYSTEM_SOURCE_FILES}
 | 
					        set(SYSTEM_SOURCE_FILES ${SYSTEM_SOURCE_FILES}
 | 
				
			||||||
                src/rendering/twn_gl_any_rendering.c
 | 
					                src/rendering/twn_gl_any_rendering.c
 | 
				
			||||||
                src/rendering/twn_gl_15_rendering.c
 | 
					                src/rendering/twn_gl_15_rendering.c)
 | 
				
			||||||
                src/rendering/twn_gl_15_gpu_texture.c)
 | 
					 | 
				
			||||||
endif()
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set(TWN_THIRD_PARTY_SOURCE_FILES
 | 
					set(TWN_THIRD_PARTY_SOURCE_FILES
 | 
				
			||||||
@@ -99,17 +103,18 @@ set(TWN_NONOPT_SOURCE_FILES
 | 
				
			|||||||
        src/twn_audio.c include/twn_audio.h
 | 
					        src/twn_audio.c include/twn_audio.h
 | 
				
			||||||
        src/twn_util.c include/twn_util.h
 | 
					        src/twn_util.c include/twn_util.h
 | 
				
			||||||
        src/twn_input.c include/twn_input.h
 | 
					        src/twn_input.c include/twn_input.h
 | 
				
			||||||
        src/twn_camera.c include/twn_camera.h
 | 
					        src/twn_camera.c src/twn_camera_c.h
 | 
				
			||||||
        src/twn_textures.c src/twn_textures_c.h
 | 
					        src/twn_textures.c src/twn_textures_c.h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        src/rendering/twn_draw.c src/rendering/twn_draw_c.h
 | 
					        src/rendering/twn_draw.c src/rendering/twn_draw_c.h
 | 
				
			||||||
 | 
					        src/rendering/twn_quads.c
 | 
				
			||||||
        src/rendering/twn_sprites.c
 | 
					        src/rendering/twn_sprites.c
 | 
				
			||||||
        src/rendering/twn_rects.c
 | 
					        src/rendering/twn_rects.c
 | 
				
			||||||
        src/rendering/twn_text.c
 | 
					        src/rendering/twn_text.c
 | 
				
			||||||
        src/rendering/twn_triangles.c
 | 
					        src/rendering/twn_triangles.c
 | 
				
			||||||
 | 
					        src/rendering/twn_billboards.c
 | 
				
			||||||
        src/rendering/twn_circles.c
 | 
					        src/rendering/twn_circles.c
 | 
				
			||||||
        src/rendering/twn_skybox.c
 | 
					        src/rendering/twn_skybox.c)
 | 
				
			||||||
        src/rendering/twn_fog.c)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
set(TWN_SOURCE_FILES
 | 
					set(TWN_SOURCE_FILES
 | 
				
			||||||
        $<IF:$<BOOL:${TWN_USE_AMALGAM}>,src/twn_amalgam.c src/twn_stb.c,${TWN_NONOPT_SOURCE_FILES}>
 | 
					        $<IF:$<BOOL:${TWN_USE_AMALGAM}>,src/twn_amalgam.c src/twn_stb.c,${TWN_NONOPT_SOURCE_FILES}>
 | 
				
			||||||
@@ -137,6 +142,8 @@ set_target_properties(${TWN_TARGET} PROPERTIES
 | 
				
			|||||||
                                    C_STANDARD_REQUIRED ON
 | 
					                                    C_STANDARD_REQUIRED ON
 | 
				
			||||||
                                    C_EXTENSIONS ON) # extensions are required by stb_ds.h
 | 
					                                    C_EXTENSIONS ON) # extensions are required by stb_ds.h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					target_compile_definitions(${TWN_TARGET} PRIVATE $<$<BOOL:${TWN_FEATURE_PUSH_AUDIO}>:TWN_FEATURE_PUSH_AUDIO>)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# precompile commonly used not-so-small headers
 | 
					# precompile commonly used not-so-small headers
 | 
				
			||||||
target_precompile_headers(${TWN_TARGET} PRIVATE
 | 
					target_precompile_headers(${TWN_TARGET} PRIVATE
 | 
				
			||||||
        $<$<NOT:$<BOOL:${EMSCRIPTEN}>>:third-party/glad/include/glad/glad.h>
 | 
					        $<$<NOT:$<BOOL:${EMSCRIPTEN}>>:third-party/glad/include/glad/glad.h>
 | 
				
			||||||
@@ -156,14 +163,13 @@ function(give_options_without_warnings target)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        set(BUILD_FLAGS_RELEASE
 | 
					        set(BUILD_FLAGS_RELEASE
 | 
				
			||||||
                -O3
 | 
					                -O3
 | 
				
			||||||
                -flto=auto
 | 
					                -flto=$<IF:$<STREQUAL:${CMAKE_C_COMPILER_ID},Clang>,thin,auto>
 | 
				
			||||||
                -mavx -mavx2
 | 
					                -mavx -mavx2
 | 
				
			||||||
                -Wl,--gc-sections
 | 
					 | 
				
			||||||
                -fdata-sections
 | 
					                -fdata-sections
 | 
				
			||||||
                -ffunction-sections
 | 
					                -ffunction-sections
 | 
				
			||||||
                -funroll-loops
 | 
					                -funroll-loops
 | 
				
			||||||
                -fomit-frame-pointer
 | 
					                -fomit-frame-pointer
 | 
				
			||||||
                -s)
 | 
					                $<$<STREQUAL:${CMAKE_C_COMPILER_ID},Gnu>:-s>)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        set(BUILD_FLAGS_DEBUG
 | 
					        set(BUILD_FLAGS_DEBUG
 | 
				
			||||||
                -O0
 | 
					                -O0
 | 
				
			||||||
@@ -173,6 +179,19 @@ function(give_options_without_warnings target)
 | 
				
			|||||||
		$<$<BOOL:${TWN_SANITIZE}>:-fstack-protector-all -fsanitize=undefined -fsanitize=address>
 | 
							$<$<BOOL:${TWN_SANITIZE}>:-fstack-protector-all -fsanitize=undefined -fsanitize=address>
 | 
				
			||||||
                $<$<BOOL:${EMSCRIPTEN}>:-gsource-map>)
 | 
					                $<$<BOOL:${EMSCRIPTEN}>:-gsource-map>)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (CMAKE_C_COMPILER_LINKER_ID MATCHES GNU OR CMAKE_C_COMPILER_LINKER_ID MATCHES GNUgold)
 | 
				
			||||||
 | 
					                set(THINLTO_USAGE "-plugin-opt,")
 | 
				
			||||||
 | 
					        endif()
 | 
				
			||||||
 | 
					        if (CMAKE_C_COMPILER_LINKER_ID MATCHES LLD)
 | 
				
			||||||
 | 
					                set(THINLTO_USAGE "--thinlto-")
 | 
				
			||||||
 | 
					        endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (THINLTO_USAGE)
 | 
				
			||||||
 | 
					                set(BUILD_SHARED_LIBRARY_FLAGS_RELEASE
 | 
				
			||||||
 | 
					                        $<$<STREQUAL:${CMAKE_C_COMPILER_ID},Clang>:-Wl,${THINLTO_USAGE}cache-dir=${CMAKE_CURRENT_BINARY_DIR}/linker-cache/>
 | 
				
			||||||
 | 
					                        $<$<STREQUAL:${CMAKE_C_COMPILER_ID},Clang>:-Wl,${THINLTO_USAGE}cache-policy=prune_after=30m>)
 | 
				
			||||||
 | 
					        endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        target_compile_options(${target} PUBLIC
 | 
					        target_compile_options(${target} PUBLIC
 | 
				
			||||||
                                         ${BUILD_FLAGS}
 | 
					                                         ${BUILD_FLAGS}
 | 
				
			||||||
                                         $<$<CONFIG:Release>:${BUILD_FLAGS_RELEASE}>
 | 
					                                         $<$<CONFIG:Release>:${BUILD_FLAGS_RELEASE}>
 | 
				
			||||||
@@ -186,6 +205,15 @@ function(give_options_without_warnings target)
 | 
				
			|||||||
                                      -Bsymbolic-functions
 | 
					                                      -Bsymbolic-functions
 | 
				
			||||||
                                      $<$<BOOL:${LINUX}>:-Wl,--hash-style=gnu>)
 | 
					                                      $<$<BOOL:${LINUX}>:-Wl,--hash-style=gnu>)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        get_target_property(target_type ${target} TYPE)
 | 
				
			||||||
 | 
					        if (target_type MATCHES SHARED_LIBRARY)
 | 
				
			||||||
 | 
					                target_compile_options(${target} PUBLIC
 | 
				
			||||||
 | 
					                                                 $<$<CONFIG:Release>:${BUILD_SHARED_LIBRARY_FLAGS_RELEASE}>)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                target_link_options(${target} PUBLIC
 | 
				
			||||||
 | 
					                                              $<$<CONFIG:Release>:${BUILD_SHARED_LIBRARY_FLAGS_RELEASE}>)
 | 
				
			||||||
 | 
					        endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        target_compile_definitions(${target} PRIVATE
 | 
					        target_compile_definitions(${target} PRIVATE
 | 
				
			||||||
                                             $<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:TWN_FEATURE_DYNLIB_GAME>
 | 
					                                             $<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:TWN_FEATURE_DYNLIB_GAME>
 | 
				
			||||||
                                             $<$<BOOL:${LINUX}>:_GNU_SOURCE>)
 | 
					                                             $<$<BOOL:${LINUX}>:_GNU_SOURCE>)
 | 
				
			||||||
@@ -201,7 +229,8 @@ function(give_options target)
 | 
				
			|||||||
                -Wno-declaration-after-statement
 | 
					                -Wno-declaration-after-statement
 | 
				
			||||||
                -Wno-unsafe-buffer-usage
 | 
					                -Wno-unsafe-buffer-usage
 | 
				
			||||||
                -Wno-unused-command-line-argument
 | 
					                -Wno-unused-command-line-argument
 | 
				
			||||||
                -Wno-covered-switch-default)
 | 
					                -Wno-covered-switch-default
 | 
				
			||||||
 | 
					                -Wno-disabled-macro-expansion)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        set(WARNING_FLAGS
 | 
					        set(WARNING_FLAGS
 | 
				
			||||||
                -Wall
 | 
					                -Wall
 | 
				
			||||||
@@ -316,8 +345,3 @@ include_deps(${TWN_TARGET})
 | 
				
			|||||||
link_deps(${TWN_TARGET})
 | 
					link_deps(${TWN_TARGET})
 | 
				
			||||||
target_link_libraries(${TWN_TARGET} PUBLIC twn_third_parties)
 | 
					target_link_libraries(${TWN_TARGET} PUBLIC twn_third_parties)
 | 
				
			||||||
target_include_directories(${TWN_TARGET} PRIVATE ${TWN_ROOT_DIR}/src)
 | 
					target_include_directories(${TWN_TARGET} PRIVATE ${TWN_ROOT_DIR}/src)
 | 
				
			||||||
 | 
					 | 
				
			||||||
# move compie_commands.json into root directory so that it plays nicer with code editors without any configuration
 | 
					 | 
				
			||||||
add_custom_target(copy-compile-commands ALL
 | 
					 | 
				
			||||||
                 ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_BINARY_DIR}/compile_commands.json
 | 
					 | 
				
			||||||
                                                       ${TWN_ROOT_DIR})
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,7 @@ if(NOT CMAKE_BUILD_TYPE)
 | 
				
			|||||||
        set(CMAKE_BUILD_TYPE Debug)
 | 
					        set(CMAKE_BUILD_TYPE Debug)
 | 
				
			||||||
endif()
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNBUILDDIR})
 | 
					add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set(SOURCE_FILES
 | 
					set(SOURCE_FILES
 | 
				
			||||||
        game.c
 | 
					        game.c
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,6 @@ app_id = "bunnymark"
 | 
				
			|||||||
dev_id = "morshy"
 | 
					dev_id = "morshy"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[game]
 | 
					[game]
 | 
				
			||||||
base_render_width = 640
 | 
					resolution = [ 640, 480 ]
 | 
				
			||||||
base_render_height = 480
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
[engine]
 | 
					[engine]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,13 +20,13 @@ static void handle_input(void)
 | 
				
			|||||||
    if (ctx.mouse_position.y <= 60)
 | 
					    if (ctx.mouse_position.y <= 60)
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (input_is_action_pressed("add_a_bit"))
 | 
					    if (input_action_pressed("add_a_bit"))
 | 
				
			||||||
    { // Left click
 | 
					    { // Left click
 | 
				
			||||||
        for (int i = 0; i < LEFT_CLICK_ADD; i++)
 | 
					        for (int i = 0; i < LEFT_CLICK_ADD; i++)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (state->bunniesCount < MAX_BUNNIES)
 | 
					            if (state->bunniesCount < MAX_BUNNIES)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                state->bunnies[state->bunniesCount].position = input_get_action_position("add_a_bit");
 | 
					                state->bunnies[state->bunniesCount].position = input_action_position("add_a_bit");
 | 
				
			||||||
                state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0f;
 | 
					                state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0f;
 | 
				
			||||||
                state->bunnies[state->bunniesCount].speed.y = (float)(rand() % 500 - 250) / 60.0f;
 | 
					                state->bunnies[state->bunniesCount].speed.y = (float)(rand() % 500 - 250) / 60.0f;
 | 
				
			||||||
                state->bunnies[state->bunniesCount].color =
 | 
					                state->bunnies[state->bunniesCount].color =
 | 
				
			||||||
@@ -38,13 +38,13 @@ static void handle_input(void)
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (input_is_action_pressed("add_a_lot"))
 | 
					    if (input_action_pressed("add_a_lot"))
 | 
				
			||||||
    { // Right click
 | 
					    { // Right click
 | 
				
			||||||
        for (int i = 0; i < RIGHT_CLICK_ADD; i++)
 | 
					        for (int i = 0; i < RIGHT_CLICK_ADD; i++)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (state->bunniesCount < MAX_BUNNIES)
 | 
					            if (state->bunniesCount < MAX_BUNNIES)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                state->bunnies[state->bunniesCount].position = input_get_action_position("add_a_lot");
 | 
					                state->bunnies[state->bunniesCount].position = input_action_position("add_a_lot");
 | 
				
			||||||
                state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0f;
 | 
					                state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0f;
 | 
				
			||||||
                state->bunnies[state->bunniesCount].speed.y = (float)(rand() % 500 - 250) / 60.0f;
 | 
					                state->bunnies[state->bunniesCount].speed.y = (float)(rand() % 500 - 250) / 60.0f;
 | 
				
			||||||
                state->bunnies[state->bunniesCount].color =
 | 
					                state->bunnies[state->bunniesCount].color =
 | 
				
			||||||
@@ -67,14 +67,11 @@ void game_tick(void)
 | 
				
			|||||||
        // Allocating State struct to store data there
 | 
					        // Allocating State struct to store data there
 | 
				
			||||||
        if (!ctx.udata)
 | 
					        if (!ctx.udata)
 | 
				
			||||||
            ctx.udata = ccalloc(1, sizeof(State));
 | 
					            ctx.udata = ccalloc(1, sizeof(State));
 | 
				
			||||||
 | 
					 | 
				
			||||||
        input_add_action("add_a_bit");
 | 
					 | 
				
			||||||
        input_bind_action_control("add_a_bit", CONTROL_LEFT_MOUSE);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        input_add_action("add_a_lot");
 | 
					 | 
				
			||||||
        input_bind_action_control("add_a_lot", CONTROL_RIGHT_MOUSE);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    input_action("add_a_bit", CONTROL_LEFT_MOUSE);
 | 
				
			||||||
 | 
					    input_action("add_a_lot", CONTROL_RIGHT_MOUSE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    State *state = ctx.udata;
 | 
					    State *state = ctx.udata;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (int i = 0; i < state->bunniesCount; i++)
 | 
					    for (int i = 0; i < state->bunniesCount; i++)
 | 
				
			||||||
@@ -97,7 +94,7 @@ void game_tick(void)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    for (int i = 0; i < state->bunniesCount; i++)
 | 
					    for (int i = 0; i < state->bunniesCount; i++)
 | 
				
			||||||
    { // Draw each bunny based on their position and color, also scale accordingly
 | 
					    { // Draw each bunny based on their position and color, also scale accordingly
 | 
				
			||||||
        m_sprite(m_set(path, "wabbit_alpha.png"),
 | 
					        m_sprite(m_set(texture, "wabbit_alpha.png"),
 | 
				
			||||||
                 m_set(rect, ((Rect){.x = state->bunnies[i].position.x,
 | 
					                 m_set(rect, ((Rect){.x = state->bunnies[i].position.x,
 | 
				
			||||||
                                     .y = state->bunnies[i].position.y,
 | 
					                                     .y = state->bunnies[i].position.y,
 | 
				
			||||||
                                     .w = BUNNY_W * SPRITE_SCALE,
 | 
					                                     .w = BUNNY_W * SPRITE_SCALE,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,7 @@ if(NOT CMAKE_BUILD_TYPE)
 | 
				
			|||||||
        set(CMAKE_BUILD_TYPE Debug)
 | 
					        set(CMAKE_BUILD_TYPE Debug)
 | 
				
			||||||
endif()
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNBUILDDIR})
 | 
					add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set(SOURCE_FILES
 | 
					set(SOURCE_FILES
 | 
				
			||||||
        game.c
 | 
					        game.c
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,6 @@ app_id = "platformer-demo"
 | 
				
			|||||||
dev_id = "townengine-team"
 | 
					dev_id = "townengine-team"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[game]
 | 
					[game]
 | 
				
			||||||
base_render_width = 640
 | 
					resolution = [ 640, 360 ]
 | 
				
			||||||
base_render_height = 360
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
[engine]
 | 
					[engine]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,45 +18,18 @@ void game_tick(void) {
 | 
				
			|||||||
            state->ctx = &ctx;
 | 
					            state->ctx = &ctx;
 | 
				
			||||||
            state->scene = title_scene(state);
 | 
					            state->scene = title_scene(state);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        input_add_action("debug_toggle");
 | 
					 | 
				
			||||||
        input_bind_action_control("debug_toggle", CONTROL_BACKSPACE);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        input_add_action("debug_dump_atlases");
 | 
					 | 
				
			||||||
        input_bind_action_control("debug_dump_atlases", CONTROL_HOME);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        input_add_action("player_left");
 | 
					 | 
				
			||||||
        input_bind_action_control("player_left", CONTROL_A);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        input_add_action("player_right");
 | 
					 | 
				
			||||||
        input_bind_action_control("player_right", CONTROL_D);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        input_add_action("player_forward");
 | 
					 | 
				
			||||||
        input_bind_action_control("player_forward", CONTROL_W);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        input_add_action("player_backward");
 | 
					 | 
				
			||||||
        input_bind_action_control("player_backward", CONTROL_S);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        input_add_action("player_jump");
 | 
					 | 
				
			||||||
        input_bind_action_control("player_jump", CONTROL_SPACE);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        input_add_action("player_run");
 | 
					 | 
				
			||||||
        input_bind_action_control("player_run", CONTROL_LSHIFT);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        input_add_action("ui_accept");
 | 
					 | 
				
			||||||
        input_bind_action_control("ui_accept", CONTROL_RETURN);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        input_add_action("mouse_capture_toggle");
 | 
					 | 
				
			||||||
        input_bind_action_control("mouse_capture_toggle", CONTROL_ESCAPE);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    State *state = ctx.udata;
 | 
					    State *state = ctx.udata;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (input_is_action_just_pressed("debug_toggle")) {
 | 
					    input_action("debug_toggle", CONTROL_BACKSPACE);
 | 
				
			||||||
 | 
					    input_action("debug_dump_atlases", CONTROL_HOME);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (input_action_just_pressed("debug_toggle")) {
 | 
				
			||||||
        ctx.debug = !ctx.debug;
 | 
					        ctx.debug = !ctx.debug;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (input_is_action_just_pressed("debug_dump_atlases")) {
 | 
					    if (input_action_just_pressed("debug_dump_atlases")) {
 | 
				
			||||||
        textures_dump_atlases();
 | 
					        textures_dump_atlases();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,28 +11,28 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void update_timers(Player *player) {
 | 
					static void update_timers(Player *player) {
 | 
				
			||||||
     tick_timer(&player->jump_air_timer);
 | 
					    player->jump_air_timer = timer_tick_frames(player->jump_air_timer);
 | 
				
			||||||
     tick_timer(&player->jump_coyote_timer);
 | 
					    player->jump_coyote_timer = timer_tick_frames(player->jump_coyote_timer);
 | 
				
			||||||
     tick_timer(&player->jump_buffer_timer);
 | 
					    player->jump_buffer_timer = timer_tick_frames(player->jump_buffer_timer);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void input_move(Player *player) {
 | 
					static void input_move(Player *player) {
 | 
				
			||||||
    /* apply horizontal damping when the player stops moving */
 | 
					    /* apply horizontal damping when the player stops moving */
 | 
				
			||||||
    /* in other words, make it decelerate to a standstill */
 | 
					    /* in other words, make it decelerate to a standstill */
 | 
				
			||||||
    if (!input_is_action_pressed("player_left") &&
 | 
					    if (!input_action_pressed("player_left") &&
 | 
				
			||||||
        !input_is_action_pressed("player_right"))
 | 
					        !input_action_pressed("player_right"))
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        player->dx *= player->horizontal_damping;
 | 
					        player->dx *= player->horizontal_damping;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    int input_dir = 0;
 | 
					    int input_dir = 0;
 | 
				
			||||||
    if (input_is_action_pressed("player_left"))
 | 
					    if (input_action_pressed("player_left"))
 | 
				
			||||||
        input_dir = -1;
 | 
					        input_dir = -1;
 | 
				
			||||||
    if (input_is_action_pressed("player_right"))
 | 
					    if (input_action_pressed("player_right"))
 | 
				
			||||||
        input_dir = 1;
 | 
					        input_dir = 1;
 | 
				
			||||||
    if (input_is_action_pressed("player_left") &&
 | 
					    if (input_action_pressed("player_left") &&
 | 
				
			||||||
        input_is_action_pressed("player_right"))
 | 
					        input_action_pressed("player_right"))
 | 
				
			||||||
        input_dir = 0;
 | 
					        input_dir = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    player->dx += (float)input_dir * player->run_horizontal_speed;
 | 
					    player->dx += (float)input_dir * player->run_horizontal_speed;
 | 
				
			||||||
@@ -56,7 +56,7 @@ static void jump(Player *player) {
 | 
				
			|||||||
static void input_jump(Player *player) {
 | 
					static void input_jump(Player *player) {
 | 
				
			||||||
    player->current_gravity_multiplier = player->jump_default_multiplier;
 | 
					    player->current_gravity_multiplier = player->jump_default_multiplier;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (input_is_action_just_pressed("player_jump")) {
 | 
					    if (input_action_just_pressed("player_jump")) {
 | 
				
			||||||
        player->jump_air_timer = 0;
 | 
					        player->jump_air_timer = 0;
 | 
				
			||||||
        player->jump_buffer_timer = player->jump_buffer_ticks;
 | 
					        player->jump_buffer_timer = player->jump_buffer_ticks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -65,7 +65,7 @@ static void input_jump(Player *player) {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (input_is_action_pressed("player_jump")) {
 | 
					    if (input_action_pressed("player_jump")) {
 | 
				
			||||||
        if (player->action != PLAYER_ACTION_GROUND && player->jump_air_timer > 0) {
 | 
					        if (player->action != PLAYER_ACTION_GROUND && player->jump_air_timer > 0) {
 | 
				
			||||||
            player->current_gravity_multiplier = player->jump_boosted_multiplier;
 | 
					            player->current_gravity_multiplier = player->jump_boosted_multiplier;
 | 
				
			||||||
            player->dy += player->jump_force_increase;
 | 
					            player->dy += player->jump_force_increase;
 | 
				
			||||||
@@ -147,7 +147,7 @@ static bool corner_correct(Player *player, Rect collision) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
static void calc_collisions_x(Player *player) {
 | 
					static void calc_collisions_x(Player *player) {
 | 
				
			||||||
    Rect collision;
 | 
					    Rect collision;
 | 
				
			||||||
    bool is_colliding = world_find_intersect_frect(player->world, player->collider_x, &collision);
 | 
					    bool is_colliding = world_find_rect_intersects(player->world, player->collider_x, &collision);
 | 
				
			||||||
    if (!is_colliding) return;
 | 
					    if (!is_colliding) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    float player_center_x = player->collider_x.x + (player->collider_x.w / 2);
 | 
					    float player_center_x = player->collider_x.x + (player->collider_x.w / 2);
 | 
				
			||||||
@@ -164,7 +164,7 @@ static void calc_collisions_x(Player *player) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
static void calc_collisions_y(Player *player) {
 | 
					static void calc_collisions_y(Player *player) {
 | 
				
			||||||
    Rect collision;
 | 
					    Rect collision;
 | 
				
			||||||
    bool is_colliding = world_find_intersect_frect(player->world, player->collider_y, &collision);
 | 
					    bool is_colliding = world_find_rect_intersects(player->world, player->collider_y, &collision);
 | 
				
			||||||
    if (!is_colliding) return;
 | 
					    if (!is_colliding) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    float player_center_y = player->collider_y.y + (player->collider_y.h / 2);
 | 
					    float player_center_y = player->collider_y.y + (player->collider_y.h / 2);
 | 
				
			||||||
@@ -255,6 +255,10 @@ static void drawdef(Player *player) {
 | 
				
			|||||||
    draw_circle((Vec2) { 256, 128 },
 | 
					    draw_circle((Vec2) { 256, 128 },
 | 
				
			||||||
                24,
 | 
					                24,
 | 
				
			||||||
                (Color) { 255, 0, 0, 255 });
 | 
					                (Color) { 255, 0, 0, 255 });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    draw_circle((Vec2) { 304, 128 },
 | 
				
			||||||
 | 
					                24,
 | 
				
			||||||
 | 
					                (Color) { 255, 0, 0, 255 });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,28 +11,15 @@
 | 
				
			|||||||
static void ingame_tick(State *state) {
 | 
					static void ingame_tick(State *state) {
 | 
				
			||||||
    SceneIngame *scn = (SceneIngame *)state->scene;
 | 
					    SceneIngame *scn = (SceneIngame *)state->scene;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    input_action("player_left", CONTROL_A);
 | 
				
			||||||
 | 
					    input_action("player_right", CONTROL_D);
 | 
				
			||||||
 | 
					    input_action("player_forward", CONTROL_W);
 | 
				
			||||||
 | 
					    input_action("player_backward", CONTROL_S);
 | 
				
			||||||
 | 
					    input_action("player_jump", CONTROL_SPACE);
 | 
				
			||||||
 | 
					    input_action("player_run", CONTROL_LSHIFT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    world_drawdef(scn->world);
 | 
					    world_drawdef(scn->world);
 | 
				
			||||||
    player_calc(scn->player);
 | 
					    player_calc(scn->player);
 | 
				
			||||||
 | 
					 | 
				
			||||||
    const Vec3 right = m_vec_norm(m_vec_cross(scn->cam.target, scn->cam.up));
 | 
					 | 
				
			||||||
    const float speed = 0.04f; /* TODO: put this in a better place */
 | 
					 | 
				
			||||||
    if (input_is_action_pressed("player_left"))
 | 
					 | 
				
			||||||
        scn->cam.pos = vec3_sub(scn->cam.pos, m_vec_scale(right, speed));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (input_is_action_pressed("player_right"))
 | 
					 | 
				
			||||||
        scn->cam.pos = vec3_add(scn->cam.pos, m_vec_scale(right, speed));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (input_is_action_pressed("player_forward"))
 | 
					 | 
				
			||||||
        scn->cam.pos = vec3_add(scn->cam.pos, m_vec_scale(scn->cam.target, speed));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (input_is_action_pressed("player_backward"))
 | 
					 | 
				
			||||||
        scn->cam.pos = vec3_sub(scn->cam.pos, m_vec_scale(scn->cam.target, speed));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (input_is_action_pressed("player_jump"))
 | 
					 | 
				
			||||||
        scn->cam.pos.y += speed;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (input_is_action_pressed("player_run"))
 | 
					 | 
				
			||||||
        scn->cam.pos.y -= speed;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -54,7 +41,5 @@ Scene *ingame_scene(State *state) {
 | 
				
			|||||||
    new_scene->world = world_create();
 | 
					    new_scene->world = world_create();
 | 
				
			||||||
    new_scene->player = player_create(new_scene->world);
 | 
					    new_scene->player = player_create(new_scene->world);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    new_scene->cam = (Camera){ .pos = { 32, 0, 1 }, .up = { 0, 1, 0 }, .fov = (float)M_PI_2 };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return (Scene *)new_scene;
 | 
					    return (Scene *)new_scene;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,8 +15,6 @@ typedef struct SceneIngame {
 | 
				
			|||||||
    World *world;
 | 
					    World *world;
 | 
				
			||||||
    Player *player;
 | 
					    Player *player;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Camera cam;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /* TODO: put this in a better place */
 | 
					    /* TODO: put this in a better place */
 | 
				
			||||||
    float yaw;
 | 
					    float yaw;
 | 
				
			||||||
    float pitch;
 | 
					    float pitch;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,7 +14,9 @@ static void title_tick(State *state) {
 | 
				
			|||||||
    SceneTitle *scn = (SceneTitle *)state->scene;
 | 
					    SceneTitle *scn = (SceneTitle *)state->scene;
 | 
				
			||||||
    (void)scn;
 | 
					    (void)scn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (input_is_action_just_pressed("ui_accept")) {
 | 
					    input_action("ui_accept", CONTROL_RETURN);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (input_action_just_pressed("ui_accept")) {
 | 
				
			||||||
        switch_to(state, ingame_scene);
 | 
					        switch_to(state, ingame_scene);
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -23,13 +25,13 @@ static void title_tick(State *state) {
 | 
				
			|||||||
            ((float)ctx.resolution.x / 2) - ((float)320 / 2), 64, 320, 128 }));
 | 
					            ((float)ctx.resolution.x / 2) - ((float)320 / 2), 64, 320, 128 }));
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /* draw the tick count as an example of dynamic text */
 | 
					    /* draw the tick count as an example of dynamic text */
 | 
				
			||||||
    size_t text_str_len = snprintf(NULL, 0, "%lu", state->ctx->frame_number) + 1;
 | 
					    size_t text_str_len = snprintf(NULL, 0, "%llu", (unsigned long long)state->ctx->frame_number) + 1;
 | 
				
			||||||
    char *text_str = cmalloc(text_str_len);
 | 
					    char *text_str = cmalloc(text_str_len);
 | 
				
			||||||
    snprintf(text_str, text_str_len, "%lu", state->ctx->frame_number);
 | 
					    snprintf(text_str, text_str_len, "%llu", (unsigned long long)state->ctx->frame_number);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const char *font = "fonts/kenney-pixel.ttf";
 | 
					    const char *font = "fonts/kenney-pixel.ttf";
 | 
				
			||||||
    int text_h = 32;
 | 
					    float text_h = 32;
 | 
				
			||||||
    int text_w = draw_text_width(text_str, text_h, font);
 | 
					    float text_w = draw_text_width(text_str, text_h, font);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    draw_rectangle(
 | 
					    draw_rectangle(
 | 
				
			||||||
        (Rect) {
 | 
					        (Rect) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,11 +12,11 @@ static void update_tiles(struct World *world) {
 | 
				
			|||||||
    for (size_t row = 0; row < world->tilemap_height; ++row) {
 | 
					    for (size_t row = 0; row < world->tilemap_height; ++row) {
 | 
				
			||||||
        for (size_t col = 0; col < world->tilemap_width; ++col) {
 | 
					        for (size_t col = 0; col < world->tilemap_width; ++col) {
 | 
				
			||||||
            world->tiles[(row * world->tilemap_width) + col] = (struct Tile) {
 | 
					            world->tiles[(row * world->tilemap_width) + col] = (struct Tile) {
 | 
				
			||||||
                .rect = (Recti) {
 | 
					                .rect = (Rect) {
 | 
				
			||||||
                    .x = (int)col * world->tile_size,
 | 
					                    .x = (float)(col * world->tile_size),
 | 
				
			||||||
                    .y = (int)row * world->tile_size,
 | 
					                    .y = (float)(row * world->tile_size),
 | 
				
			||||||
                    .w = world->tile_size,
 | 
					                    .w = (float)world->tile_size,
 | 
				
			||||||
                    .h = world->tile_size,
 | 
					                    .h = (float)world->tile_size,
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                .type = world->tilemap[row][col],
 | 
					                .type = world->tilemap[row][col],
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
@@ -25,10 +25,10 @@ static void update_tiles(struct World *world) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static Vec2i to_grid_location(struct World *world, float x, float y) {
 | 
					static Vec2 to_grid_location(struct World *world, float x, float y) {
 | 
				
			||||||
    return (Vec2i) {
 | 
					    return (Vec2) {
 | 
				
			||||||
        .x = (int)floor(x / (float)world->tile_size),
 | 
					        .x = floor(x / (float)world->tile_size),
 | 
				
			||||||
        .y = (int)floor(y / (float)world->tile_size),
 | 
					        .y = floor(y / (float)world->tile_size),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -39,8 +39,7 @@ static void drawdef_debug(struct World *world) {
 | 
				
			|||||||
    for (size_t i = 0; i < world->tilemap_height * world->tilemap_width; ++i) {
 | 
					    for (size_t i = 0; i < world->tilemap_height * world->tilemap_width; ++i) {
 | 
				
			||||||
        if (world->tiles[i].type == TILE_TYPE_VOID) continue;
 | 
					        if (world->tiles[i].type == TILE_TYPE_VOID) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        draw_rectangle(to_frect(world->tiles[i].rect),
 | 
					        draw_rectangle(world->tiles[i].rect, (Color) { 255, 0, 255, 128 });
 | 
				
			||||||
                        (Color) { 255, 0, 255, 128 });
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -106,14 +105,14 @@ void world_drawdef(struct World *world) {
 | 
				
			|||||||
        if (world->tiles[i].type == TILE_TYPE_VOID)
 | 
					        if (world->tiles[i].type == TILE_TYPE_VOID)
 | 
				
			||||||
            continue;
 | 
					            continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        m_sprite("/assets/white.png", to_frect(world->tiles[i].rect));
 | 
					        m_sprite("/assets/white.png", world->tiles[i].rect);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    drawdef_debug(world);
 | 
					    drawdef_debug(world);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool world_find_intersect_frect(struct World *world, Rect rect, Rect *intersection) {
 | 
					bool world_find_rect_intersects(struct World *world, Rect rect, Rect *intersection) {
 | 
				
			||||||
    bool is_intersecting = false;
 | 
					    bool is_intersecting = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const size_t tile_count = world->tilemap_height * world->tilemap_width;
 | 
					    const size_t tile_count = world->tilemap_height * world->tilemap_width;
 | 
				
			||||||
@@ -121,44 +120,12 @@ bool world_find_intersect_frect(struct World *world, Rect rect, Rect *intersecti
 | 
				
			|||||||
        if (world->tiles[i].type == TILE_TYPE_VOID)
 | 
					        if (world->tiles[i].type == TILE_TYPE_VOID)
 | 
				
			||||||
            continue;
 | 
					            continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Rect tile_frect = {
 | 
					        Rect const tile_frect = world->tiles[i].rect;
 | 
				
			||||||
            .x = (float)(world->tiles[i].rect.x),
 | 
					 | 
				
			||||||
            .y = (float)(world->tiles[i].rect.y),
 | 
					 | 
				
			||||||
            .w = (float)(world->tiles[i].rect.w),
 | 
					 | 
				
			||||||
            .h = (float)(world->tiles[i].rect.h),
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (intersection == NULL) {
 | 
					        is_intersecting = rect_intersects(rect, tile_frect);
 | 
				
			||||||
            Rect temp;
 | 
					 | 
				
			||||||
            is_intersecting = overlap_frect(&rect, &tile_frect, &temp);
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            is_intersecting = overlap_frect(&rect, &tile_frect, intersection);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (is_intersecting)
 | 
					        if (intersection)
 | 
				
			||||||
            break; 
 | 
					            *intersection = rect_overlap(rect, tile_frect);
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return is_intersecting;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool world_find_intersect_rect(struct World *world, Recti rect, Recti *intersection) {
 | 
					 | 
				
			||||||
    bool is_intersecting = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const size_t tile_count = world->tilemap_height * world->tilemap_width;
 | 
					 | 
				
			||||||
    for (size_t i = 0; i < tile_count; ++i) {
 | 
					 | 
				
			||||||
        if (world->tiles[i].type == TILE_TYPE_VOID)
 | 
					 | 
				
			||||||
            continue;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Recti *tile_rect = &world->tiles[i].rect;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (intersection == NULL) {
 | 
					 | 
				
			||||||
            Recti temp;
 | 
					 | 
				
			||||||
            is_intersecting = overlap_rect(&rect, tile_rect, &temp);
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            is_intersecting = overlap_rect(&rect, tile_rect, intersection);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (is_intersecting)
 | 
					        if (is_intersecting)
 | 
				
			||||||
            break; 
 | 
					            break; 
 | 
				
			||||||
@@ -169,20 +136,20 @@ bool world_find_intersect_rect(struct World *world, Recti rect, Recti *intersect
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool world_is_tile_at(struct World *world, float x, float y) {
 | 
					bool world_is_tile_at(struct World *world, float x, float y) {
 | 
				
			||||||
    Vec2i position_in_grid = to_grid_location(world, x, y);
 | 
					    Vec2 position_in_grid = to_grid_location(world, x, y);
 | 
				
			||||||
    return world->tilemap[position_in_grid.y][position_in_grid.x] != TILE_TYPE_VOID;
 | 
					    return world->tilemap[(int32_t)position_in_grid.y][(int32_t)position_in_grid.x] != TILE_TYPE_VOID;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void world_place_tile(struct World *world, float x, float y) {
 | 
					void world_place_tile(struct World *world, float x, float y) {
 | 
				
			||||||
    Vec2i position_in_grid = to_grid_location(world, x, y);
 | 
					    Vec2 position_in_grid = to_grid_location(world, x, y);
 | 
				
			||||||
    world->tilemap[position_in_grid.y][position_in_grid.x] = TILE_TYPE_SOLID;
 | 
					    world->tilemap[(int32_t)position_in_grid.y][(int32_t)position_in_grid.x] = TILE_TYPE_SOLID;
 | 
				
			||||||
    update_tiles(world);
 | 
					    update_tiles(world);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void world_remove_tile(struct World *world, float x, float y) {
 | 
					void world_remove_tile(struct World *world, float x, float y) {
 | 
				
			||||||
    Vec2i position_in_grid = to_grid_location(world, x, y);
 | 
					    Vec2 position_in_grid = to_grid_location(world, x, y);
 | 
				
			||||||
    world->tilemap[position_in_grid.y][position_in_grid.x] = TILE_TYPE_VOID;
 | 
					    world->tilemap[(int32_t)position_in_grid.y][(int32_t)position_in_grid.x] = TILE_TYPE_VOID;
 | 
				
			||||||
    update_tiles(world);
 | 
					    update_tiles(world);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,7 +14,7 @@ typedef enum TileType {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct Tile {
 | 
					typedef struct Tile {
 | 
				
			||||||
    Recti rect;
 | 
					    Rect rect;
 | 
				
			||||||
    TileType type;
 | 
					    TileType type;
 | 
				
			||||||
} Tile;
 | 
					} Tile;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -34,8 +34,7 @@ typedef struct World {
 | 
				
			|||||||
World *world_create(void);
 | 
					World *world_create(void);
 | 
				
			||||||
void world_destroy(World *world);
 | 
					void world_destroy(World *world);
 | 
				
			||||||
void world_drawdef(World *world);
 | 
					void world_drawdef(World *world);
 | 
				
			||||||
bool world_find_intersect_frect(World *world, Rect rect, Rect *intersection);
 | 
					bool world_find_rect_intersects(World *world, Rect rect, Rect *intersection);
 | 
				
			||||||
bool world_find_intersect_rect(World *world, Recti rect, Recti *intersection);
 | 
					 | 
				
			||||||
bool world_is_tile_at(World *world, float x, float y);
 | 
					bool world_is_tile_at(World *world, float x, float y);
 | 
				
			||||||
void world_place_tile(World *world, float x, float y);
 | 
					void world_place_tile(World *world, float x, float y);
 | 
				
			||||||
void world_remove_tile(World *world, float x, float y);
 | 
					void world_remove_tile(World *world, float x, float y);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,7 @@ if(NOT CMAKE_BUILD_TYPE)
 | 
				
			|||||||
        set(CMAKE_BUILD_TYPE Debug)
 | 
					        set(CMAKE_BUILD_TYPE Debug)
 | 
				
			||||||
endif()
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNBUILDDIR})
 | 
					add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set(SOURCE_FILES
 | 
					set(SOURCE_FILES
 | 
				
			||||||
        game.c
 | 
					        game.c
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,10 @@
 | 
				
			|||||||
[about]
 | 
					[about]
 | 
				
			||||||
title = "Serene Scenery"
 | 
					title = "Serene Scenery"
 | 
				
			||||||
developer = "Townengine Team"
 | 
					developer = "Townengine Team"
 | 
				
			||||||
app_id = "platformer-demo"
 | 
					app_id = "scenery-demo"
 | 
				
			||||||
dev_id = "townengine-team"
 | 
					dev_id = "townengine-team"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[game]
 | 
					[game]
 | 
				
			||||||
base_render_width = 640
 | 
					resolution = [ 640, 360 ]
 | 
				
			||||||
base_render_height = 360
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
[engine]
 | 
					[engine]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
#include "state.h"
 | 
					#include "state.h"
 | 
				
			||||||
#include "scenes/scene.h"
 | 
					#include "scenes/scene.h"
 | 
				
			||||||
#include "scenes/title.h"
 | 
					#include "scenes/title.h"
 | 
				
			||||||
 | 
					#include "scenes/ingame.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "twn_game_api.h"
 | 
					#include "twn_game_api.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -17,47 +18,20 @@ void game_tick(void) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            State *state = ctx.udata;
 | 
					            State *state = ctx.udata;
 | 
				
			||||||
            state->ctx = &ctx;
 | 
					            state->ctx = &ctx;
 | 
				
			||||||
            state->scene = title_scene(state);
 | 
					            state->scene = ingame_scene(state);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        input_add_action("debug_toggle");
 | 
					 | 
				
			||||||
        input_bind_action_control("debug_toggle", CONTROL_BACKSPACE);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        input_add_action("debug_dump_atlases");
 | 
					 | 
				
			||||||
        input_bind_action_control("debug_dump_atlases", CONTROL_HOME);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        input_add_action("player_left");
 | 
					 | 
				
			||||||
        input_bind_action_control("player_left", CONTROL_A);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        input_add_action("player_right");
 | 
					 | 
				
			||||||
        input_bind_action_control("player_right", CONTROL_D);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        input_add_action("player_forward");
 | 
					 | 
				
			||||||
        input_bind_action_control("player_forward", CONTROL_W);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        input_add_action("player_backward");
 | 
					 | 
				
			||||||
        input_bind_action_control("player_backward", CONTROL_S);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        input_add_action("player_jump");
 | 
					 | 
				
			||||||
        input_bind_action_control("player_jump", CONTROL_SPACE);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        input_add_action("player_run");
 | 
					 | 
				
			||||||
        input_bind_action_control("player_run", CONTROL_LSHIFT);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        input_add_action("ui_accept");
 | 
					 | 
				
			||||||
        input_bind_action_control("ui_accept", CONTROL_RETURN);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        input_add_action("mouse_capture_toggle");
 | 
					 | 
				
			||||||
        input_bind_action_control("mouse_capture_toggle", CONTROL_ESCAPE);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    State *state = ctx.udata;
 | 
					    State *state = ctx.udata;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (input_is_action_just_pressed("debug_toggle")) {
 | 
					    input_action("debug_toggle", CONTROL_BACKSPACE);
 | 
				
			||||||
 | 
					    input_action("debug_dump_atlases", CONTROL_HOME);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (input_action_just_pressed("debug_toggle")) {
 | 
				
			||||||
        ctx.debug = !ctx.debug;
 | 
					        ctx.debug = !ctx.debug;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (input_is_action_just_pressed("debug_dump_atlases")) {
 | 
					    if (input_action_just_pressed("debug_dump_atlases")) {
 | 
				
			||||||
        textures_dump_atlases();
 | 
					        textures_dump_atlases();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,63 +12,147 @@
 | 
				
			|||||||
#include <stdlib.h>
 | 
					#include <stdlib.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void ingame_tick(State *state) {
 | 
					#define TERRAIN_FREQUENCY 0.15f
 | 
				
			||||||
 | 
					#define TERRAIN_DISTANCE 64
 | 
				
			||||||
 | 
					#define HALF_TERRAIN_DISTANCE ((float)TERRAIN_DISTANCE / 2)
 | 
				
			||||||
 | 
					#define PLAYER_HEIGHT 0.6f
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static float heightmap[TERRAIN_DISTANCE][TERRAIN_DISTANCE];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void process_fly_mode(State *state) {
 | 
				
			||||||
    SceneIngame *scn = (SceneIngame *)state->scene;
 | 
					    SceneIngame *scn = (SceneIngame *)state->scene;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (input_is_mouse_captured()) {
 | 
					    DrawCameraFromPrincipalAxesResult dir_and_up =
 | 
				
			||||||
        const float sensitivity = 0.6f; /* TODO: put this in a better place */
 | 
					        draw_camera_from_principal_axes(scn->pos, scn->roll, scn->pitch, scn->yaw, (float)M_PI_2 * 0.8f, 1);
 | 
				
			||||||
        scn->yaw += (float)ctx.mouse_movement.x * sensitivity;
 | 
					 | 
				
			||||||
        scn->pitch -= (float)ctx.mouse_movement.y * sensitivity;
 | 
					 | 
				
			||||||
        scn->pitch = clampf(scn->pitch, -89.0f, 89.0f);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const float yaw_rad = scn->yaw * (float)DEG2RAD;
 | 
					    const Vec3 right = m_vec_norm(m_vec_cross(dir_and_up.direction, dir_and_up.up));
 | 
				
			||||||
        const float pitch_rad = scn->pitch * (float)DEG2RAD;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        scn->cam.target = m_vec_norm(((Vec3){
 | 
					 | 
				
			||||||
            cosf(yaw_rad) * cosf(pitch_rad),
 | 
					 | 
				
			||||||
            sinf(pitch_rad),
 | 
					 | 
				
			||||||
            sinf(yaw_rad) * cosf(pitch_rad)
 | 
					 | 
				
			||||||
        }));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const Vec3 right = m_vec_norm(m_vec_cross(scn->cam.target, scn->cam.up));
 | 
					 | 
				
			||||||
    const float speed = 0.04f; /* TODO: put this in a better place */
 | 
					    const float speed = 0.04f; /* TODO: put this in a better place */
 | 
				
			||||||
    if (input_is_action_pressed("player_left"))
 | 
					    if (input_action_pressed("player_left"))
 | 
				
			||||||
        scn->cam.pos = vec3_sub(scn->cam.pos, m_vec_scale(right, speed));
 | 
					        scn->pos = vec3_sub(scn->pos, m_vec_scale(right, speed));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (input_is_action_pressed("player_right"))
 | 
					    if (input_action_pressed("player_right"))
 | 
				
			||||||
        scn->cam.pos = vec3_add(scn->cam.pos, m_vec_scale(right, speed));
 | 
					        scn->pos = vec3_add(scn->pos, m_vec_scale(right, speed));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (input_is_action_pressed("player_forward"))
 | 
					    if (input_action_pressed("player_forward"))
 | 
				
			||||||
        scn->cam.pos = vec3_add(scn->cam.pos, m_vec_scale(scn->cam.target, speed));
 | 
					        scn->pos = vec3_add(scn->pos, m_vec_scale(dir_and_up.direction, speed));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (input_is_action_pressed("player_backward"))
 | 
					    if (input_action_pressed("player_backward"))
 | 
				
			||||||
        scn->cam.pos = vec3_sub(scn->cam.pos, m_vec_scale(scn->cam.target, speed));
 | 
					        scn->pos = vec3_sub(scn->pos, m_vec_scale(dir_and_up.direction, speed));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (input_is_action_pressed("player_jump"))
 | 
					    if (input_action_pressed("player_jump"))
 | 
				
			||||||
        scn->cam.pos.y += speed;
 | 
					        scn->pos.y += speed;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (input_is_action_pressed("player_run"))
 | 
					    if (input_action_pressed("player_run"))
 | 
				
			||||||
        scn->cam.pos.y -= speed;
 | 
					        scn->pos.y -= speed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* toggle mouse capture with end key */
 | 
					
 | 
				
			||||||
    if (input_is_action_just_pressed("mouse_capture_toggle")) {
 | 
					static float height_at(SceneIngame *scn, Vec2 position) {
 | 
				
			||||||
        input_set_mouse_captured(!input_is_mouse_captured());
 | 
					    float height0, height1, height2, weight0, weight1, weight2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    int const x = (int)(HALF_TERRAIN_DISTANCE + (position.x - scn->pos.x));
 | 
				
			||||||
 | 
					    int const y = (int)(HALF_TERRAIN_DISTANCE + (position.y - scn->pos.z));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    height0 = heightmap[x][y];
 | 
				
			||||||
 | 
					    height1 = heightmap[x + 1][y + 1];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Vec2 incell = { position.x - floorf(position.x), position.y - floorf(position.y) };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* who needs barycentric coordinates, am i right? */
 | 
				
			||||||
 | 
					    weight0 = 1 / sqrtf(powf(incell.x, 2) + powf(incell.y, 2));
 | 
				
			||||||
 | 
					    weight1 = 1 / sqrtf(powf(1 - incell.x, 2) + powf(1 - incell.y, 2));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* find which triangle we're directly under */
 | 
				
			||||||
 | 
					    /* for this manhattan distance is sufficient */
 | 
				
			||||||
 | 
					    if (incell.x + (1 - incell.y) < (1 - incell.x) + incell.y) {
 | 
				
			||||||
 | 
					        height2 = heightmap[x][y + 1];
 | 
				
			||||||
 | 
					        weight2 = 1 / sqrtf(powf(incell.x, 2) + powf(1 - incell.y, 2));
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        height2 = heightmap[x + 1][y];
 | 
				
			||||||
 | 
					        weight2 = 1 / sqrtf(powf(1 - incell.x, 2) + powf(incell.y, 2));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    draw_camera(&scn->cam);
 | 
					    return (height0 * weight0 + height1 * weight1 + height2 * weight2) / (weight0 + weight1 + weight2);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #define TERRAIN_FREQUENCY 0.1f
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (int ly = 64; ly--;) {
 | 
					static void process_ground_mode(State *state) {
 | 
				
			||||||
        for (int lx = 64; lx--;) {
 | 
					    SceneIngame *scn = (SceneIngame *)state->scene;
 | 
				
			||||||
            float x = SDL_truncf(scn->cam.pos.x + 32 - (float)lx);
 | 
					 | 
				
			||||||
            float y = SDL_truncf(scn->cam.pos.z + 32 - (float)ly);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            float d0 = stb_perlin_noise3((float)x       * TERRAIN_FREQUENCY, (float)y       * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6;
 | 
					    DrawCameraFromPrincipalAxesResult dir_and_up =
 | 
				
			||||||
            float d1 = stb_perlin_noise3((float)(x + 1) * TERRAIN_FREQUENCY, (float)y       * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6;
 | 
					        draw_camera_from_principal_axes(scn->pos, scn->roll, scn->pitch, scn->yaw, (float)M_PI_2 * 0.8f, 1);
 | 
				
			||||||
            float d2 = stb_perlin_noise3((float)(x + 1) * TERRAIN_FREQUENCY, (float)(y - 1) * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6;
 | 
					
 | 
				
			||||||
            float d3 = stb_perlin_noise3((float)x       * TERRAIN_FREQUENCY, (float)(y - 1) * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6;
 | 
					    dir_and_up.direction.y = 0;
 | 
				
			||||||
 | 
					    dir_and_up.direction = vec3_norm(dir_and_up.direction);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const Vec3 right = m_vec_norm(m_vec_cross(dir_and_up.direction, dir_and_up.up));
 | 
				
			||||||
 | 
					    const float speed = 0.18f; /* TODO: put this in a better place */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Vec3 target = scn->pos;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* gravity */
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        float const height = height_at(scn, (Vec2){scn->pos.x, scn->pos.z});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (target.y > height + PLAYER_HEIGHT)
 | 
				
			||||||
 | 
					            target.y = target.y - 0.4f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (target.y < height + PLAYER_HEIGHT)
 | 
				
			||||||
 | 
					            target.y = height + PLAYER_HEIGHT;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* movement */
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Vec3 direction = {0, 0, 0};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (input_action_pressed("player_left"))
 | 
				
			||||||
 | 
					           direction = m_vec_sub(direction, m_vec_scale(right, speed));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (input_action_pressed("player_right"))
 | 
				
			||||||
 | 
					           direction = m_vec_add(direction, m_vec_scale(right, speed));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (input_action_pressed("player_forward"))
 | 
				
			||||||
 | 
					            direction = m_vec_add(direction, m_vec_scale(dir_and_up.direction, speed));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (input_action_pressed("player_backward"))
 | 
				
			||||||
 | 
					            direction = m_vec_sub(direction, m_vec_scale(dir_and_up.direction, speed));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        target = m_vec_add(target, direction);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* interpolate */
 | 
				
			||||||
 | 
					    scn->pos.x = (target.x - scn->pos.x) * (0.13f / 0.9f) + scn->pos.x;
 | 
				
			||||||
 | 
					    scn->pos.y = (target.y - scn->pos.y) * (0.13f / 0.9f) + scn->pos.y;
 | 
				
			||||||
 | 
					    scn->pos.z = (target.z - scn->pos.z) * (0.13f / 0.9f) + scn->pos.z;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void generate_terrain(SceneIngame *scn) {
 | 
				
			||||||
 | 
					    for (int ly = 0; ly < TERRAIN_DISTANCE; ly++) {
 | 
				
			||||||
 | 
					        for (int lx = 0; lx < TERRAIN_DISTANCE; lx++) {
 | 
				
			||||||
 | 
					            float x = floorf(scn->pos.x - HALF_TERRAIN_DISTANCE + (float)lx);
 | 
				
			||||||
 | 
					            float y = floorf(scn->pos.z - HALF_TERRAIN_DISTANCE + (float)ly);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            float height = stb_perlin_noise3((float)x * TERRAIN_FREQUENCY, (float)y * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 1;
 | 
				
			||||||
 | 
					            height += stb_perlin_noise3((float)x * TERRAIN_FREQUENCY / 10, (float)y * TERRAIN_FREQUENCY / 10, 0, 0, 0, 0) * 10 - 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            heightmap[lx][ly] = height;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void draw_terrain(SceneIngame *scn) {
 | 
				
			||||||
 | 
					    for (int ly = TERRAIN_DISTANCE - 1; ly > 0; ly--) {
 | 
				
			||||||
 | 
					        for (int lx = 0; lx < TERRAIN_DISTANCE - 1; lx++) {
 | 
				
			||||||
 | 
					            int32_t x = (int32_t)(floorf(scn->pos.x - HALF_TERRAIN_DISTANCE + (float)lx));
 | 
				
			||||||
 | 
					            int32_t y = (int32_t)(floorf(scn->pos.z - HALF_TERRAIN_DISTANCE + (float)ly));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            float d0 = heightmap[lx][ly];
 | 
				
			||||||
 | 
					            float d1 = heightmap[lx + 1][ly];
 | 
				
			||||||
 | 
					            float d2 = heightmap[lx + 1][ly - 1];
 | 
				
			||||||
 | 
					            float d3 = heightmap[lx][ly - 1];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            draw_triangle("/assets/grass.png",
 | 
					            draw_triangle("/assets/grass.png",
 | 
				
			||||||
                            (Vec3){ (float)x,     d0, (float)y },
 | 
					                            (Vec3){ (float)x,     d0, (float)y },
 | 
				
			||||||
@@ -76,7 +160,10 @@ static void ingame_tick(State *state) {
 | 
				
			|||||||
                            (Vec3){ (float)x,     d3, (float)y - 1 },
 | 
					                            (Vec3){ (float)x,     d3, (float)y - 1 },
 | 
				
			||||||
                            (Vec2){ 128, 128 },
 | 
					                            (Vec2){ 128, 128 },
 | 
				
			||||||
                            (Vec2){ 128, 0 },
 | 
					                            (Vec2){ 128, 0 },
 | 
				
			||||||
                            (Vec2){ 0, 128 });
 | 
					                            (Vec2){ 0, 128 },
 | 
				
			||||||
 | 
					                            (Color){255, 255, 255, 255},
 | 
				
			||||||
 | 
					                            (Color){255, 255, 255, 255},
 | 
				
			||||||
 | 
					                            (Color){255, 255, 255, 255});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            draw_triangle("/assets/grass.png",
 | 
					            draw_triangle("/assets/grass.png",
 | 
				
			||||||
                            (Vec3){ (float)x + 1, d1, (float)y },
 | 
					                            (Vec3){ (float)x + 1, d1, (float)y },
 | 
				
			||||||
@@ -84,12 +171,61 @@ static void ingame_tick(State *state) {
 | 
				
			|||||||
                            (Vec3){ (float)x,     d3, (float)y - 1 },
 | 
					                            (Vec3){ (float)x,     d3, (float)y - 1 },
 | 
				
			||||||
                            (Vec2){ 128, 0 },
 | 
					                            (Vec2){ 128, 0 },
 | 
				
			||||||
                            (Vec2){ 0, 0 },
 | 
					                            (Vec2){ 0, 0 },
 | 
				
			||||||
                            (Vec2){ 0, 128 });
 | 
					                            (Vec2){ 0, 128 },
 | 
				
			||||||
 | 
					                            (Color){255, 255, 255, 255},
 | 
				
			||||||
 | 
					                            (Color){255, 255, 255, 255},
 | 
				
			||||||
 | 
					                            (Color){255, 255, 255, 255});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            draw_billboard("/assets/grasses/10.png",
 | 
				
			||||||
 | 
					                          (Vec3){ (float)x, d0 + 0.15f, (float)y },
 | 
				
			||||||
 | 
					                          (Vec2){0.3f, 0.3f},
 | 
				
			||||||
 | 
					                          (Color){255, 255, 255, 255}, true);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void ingame_tick(State *state) {
 | 
				
			||||||
 | 
					    SceneIngame *scn = (SceneIngame *)state->scene;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    input_action("player_left", CONTROL_A);
 | 
				
			||||||
 | 
					    input_action("player_right", CONTROL_D);
 | 
				
			||||||
 | 
					    input_action("player_forward", CONTROL_W);
 | 
				
			||||||
 | 
					    input_action("player_backward", CONTROL_S);
 | 
				
			||||||
 | 
					    input_action("player_jump", CONTROL_SPACE);
 | 
				
			||||||
 | 
					    input_action("player_run", CONTROL_LSHIFT);
 | 
				
			||||||
 | 
					    input_action("mouse_capture_toggle", CONTROL_ESCAPE);
 | 
				
			||||||
 | 
					    input_action("toggle_camera_mode", CONTROL_C);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (scn->mouse_captured) {
 | 
				
			||||||
 | 
					        const float sensitivity = 0.4f * (float)DEG2RAD; /* TODO: put this in a better place */
 | 
				
			||||||
 | 
					        scn->yaw += (float)ctx.mouse_movement.x * sensitivity;
 | 
				
			||||||
 | 
					        scn->pitch -= (float)ctx.mouse_movement.y * sensitivity;
 | 
				
			||||||
 | 
					        scn->pitch = clampf(scn->pitch, (float)-M_PI * 0.49f, (float)M_PI * 0.49f);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (input_action_just_pressed("toggle_camera_mode"))
 | 
				
			||||||
 | 
					        scn->flying_camera = !scn->flying_camera;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (scn->flying_camera) {
 | 
				
			||||||
 | 
					        process_fly_mode(state);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        process_ground_mode(state);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* toggle mouse capture with end key */
 | 
				
			||||||
 | 
					    if (input_action_just_pressed("mouse_capture_toggle"))
 | 
				
			||||||
 | 
					        scn->mouse_captured = !scn->mouse_captured;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ctx.mouse_capture = scn->mouse_captured;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    generate_terrain(scn);
 | 
				
			||||||
 | 
					    draw_terrain(scn);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    draw_skybox("/assets/miramar/miramar_*.tga");
 | 
					    draw_skybox("/assets/miramar/miramar_*.tga");
 | 
				
			||||||
    draw_fog(0.9f, 1.0f, 0.05f, (Color){ 140, 147, 160, 255 });
 | 
					
 | 
				
			||||||
 | 
					    ctx.fog_color = (Color){ 140, 147, 160, 255 };
 | 
				
			||||||
 | 
					    ctx.fog_density = 0.03f;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -105,13 +241,13 @@ Scene *ingame_scene(State *state) {
 | 
				
			|||||||
    new_scene->base.tick = ingame_tick;
 | 
					    new_scene->base.tick = ingame_tick;
 | 
				
			||||||
    new_scene->base.end = ingame_end;
 | 
					    new_scene->base.end = ingame_end;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    new_scene->cam = (Camera){ .pos = { 32, 0, 1 }, .up = { 0, 1, 0 }, .fov = (float)M_PI_2 };
 | 
					    new_scene->mouse_captured = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    m_audio(m_set(path, "music/mod65.xm"),
 | 
					    m_audio(m_set(path, "music/mod65.xm"),
 | 
				
			||||||
            m_opt(channel, "soundtrack"),
 | 
					            m_opt(channel, "soundtrack"),
 | 
				
			||||||
            m_opt(repeat, true));
 | 
					            m_opt(repeat, true));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    input_set_mouse_captured(true);
 | 
					    new_scene->pos = (Vec3){ 0.1f, 0.0, 0.1f };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (Scene *)new_scene;
 | 
					    return (Scene *)new_scene;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,16 +6,19 @@
 | 
				
			|||||||
#include "../state.h"
 | 
					#include "../state.h"
 | 
				
			||||||
#include "scene.h"
 | 
					#include "scene.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <stdbool.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct SceneIngame {
 | 
					typedef struct SceneIngame {
 | 
				
			||||||
    Scene base;
 | 
					    Scene base;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Camera cam;
 | 
					    Vec3 pos;
 | 
				
			||||||
 | 
					 | 
				
			||||||
    /* TODO: put this in a better place */
 | 
					 | 
				
			||||||
    float yaw;
 | 
					    float yaw;
 | 
				
			||||||
    float pitch;
 | 
					    float pitch;
 | 
				
			||||||
    float roll;
 | 
					    float roll;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool mouse_captured;
 | 
				
			||||||
 | 
					    bool flying_camera;
 | 
				
			||||||
} SceneIngame;
 | 
					} SceneIngame;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,9 @@ static void title_tick(State *state) {
 | 
				
			|||||||
    SceneTitle *scn = (SceneTitle *)state->scene;
 | 
					    SceneTitle *scn = (SceneTitle *)state->scene;
 | 
				
			||||||
    (void)scn;
 | 
					    (void)scn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (input_is_action_just_pressed("ui_accept")) {
 | 
					    input_action("ui_accept", CONTROL_RETURN);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (input_action_just_pressed("ui_accept")) {
 | 
				
			||||||
        switch_to(state, ingame_scene);
 | 
					        switch_to(state, ingame_scene);
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -20,20 +22,20 @@ static void title_tick(State *state) {
 | 
				
			|||||||
            ((float)ctx.resolution.x / 2) - ((float)320 / 2), 64, 320, 128 }));
 | 
					            ((float)ctx.resolution.x / 2) - ((float)320 / 2), 64, 320, 128 }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* draw the tick count as an example of dynamic text */
 | 
					    /* draw the tick count as an example of dynamic text */
 | 
				
			||||||
    size_t text_str_len = snprintf(NULL, 0, "%llu", state->ctx->frame_number) + 1;
 | 
					    size_t text_str_len = snprintf(NULL, 0, "%llu", (unsigned long long)state->ctx->frame_number) + 1;
 | 
				
			||||||
    char *text_str = cmalloc(text_str_len);
 | 
					    char *text_str = cmalloc(text_str_len);
 | 
				
			||||||
    snprintf(text_str, text_str_len, "%llu", state->ctx->frame_number);
 | 
					    snprintf(text_str, text_str_len, "%llu", (unsigned long long)state->ctx->frame_number);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const char *font = "/fonts/kenney-pixel.ttf";
 | 
					    const char *font = "/fonts/kenney-pixel.ttf";
 | 
				
			||||||
    int text_h = 32;
 | 
					    float text_h = 32;
 | 
				
			||||||
    int text_w = draw_text_width(text_str, text_h, font);
 | 
					    float text_w = draw_text_width(text_str, text_h, font);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    draw_rectangle(
 | 
					    draw_rectangle(
 | 
				
			||||||
        (Rect) {
 | 
					        (Rect) {
 | 
				
			||||||
            .x = 0,
 | 
					            .x = 0,
 | 
				
			||||||
            .y = 0,
 | 
					            .y = 0,
 | 
				
			||||||
            .w = (float)text_w,
 | 
					            .w = text_w,
 | 
				
			||||||
            .h = (float)text_h,
 | 
					            .h = text_h,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        (Color) { 0, 0, 0, 255 }
 | 
					        (Color) { 0, 0, 0, 255 }
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										16
									
								
								apps/examples/circle-raster/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								apps/examples/circle-raster/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					cmake_minimum_required(VERSION 3.21)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					project(circle-raster LANGUAGES C)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if(NOT CMAKE_BUILD_TYPE)
 | 
				
			||||||
 | 
					        set(CMAKE_BUILD_TYPE Debug)
 | 
				
			||||||
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set(SOURCE_FILES
 | 
				
			||||||
 | 
					        game.c
 | 
				
			||||||
 | 
					        state.h
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use_townengine(${PROJECT_NAME} "${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR})
 | 
				
			||||||
							
								
								
									
										26
									
								
								apps/examples/circle-raster/data/twn.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								apps/examples/circle-raster/data/twn.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					# This file contains everything about the engine and your game that can be
 | 
				
			||||||
 | 
					# configured before it runs.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Optional settings are commented out, with their default values shown.
 | 
				
			||||||
 | 
					# Invalid values in these settings will be ignored.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Data about your game as an application
 | 
				
			||||||
 | 
					[about]
 | 
				
			||||||
 | 
					title = "Template"
 | 
				
			||||||
 | 
					developer = "You"
 | 
				
			||||||
 | 
					app_id = "template"
 | 
				
			||||||
 | 
					dev_id = "you"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Game runtime details
 | 
				
			||||||
 | 
					[game]
 | 
				
			||||||
 | 
					resolution = [ 640, 480 ]
 | 
				
			||||||
 | 
					#debug = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Engine tweaks. You probably don't need to change these
 | 
				
			||||||
 | 
					[engine]
 | 
				
			||||||
 | 
					#ticks_per_second = 60 # minimum of 8
 | 
				
			||||||
 | 
					#keybind_slots = 3 # minimum of 1
 | 
				
			||||||
 | 
					#texture_atlas_size = 2048 # minimum of 32
 | 
				
			||||||
 | 
					#font_texture_size = 2048 # minimum of 1024
 | 
				
			||||||
 | 
					#font_oversampling = 4 # minimum of 0
 | 
				
			||||||
 | 
					#font_filtering = "linear" # possible values: "nearest", "linear"
 | 
				
			||||||
							
								
								
									
										146
									
								
								apps/examples/circle-raster/game.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								apps/examples/circle-raster/game.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,146 @@
 | 
				
			|||||||
 | 
					#include "twn_game_api.h"
 | 
				
			||||||
 | 
					#include "state.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <malloc.h>
 | 
				
			||||||
 | 
					#include <math.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Emits `x` and `y` for every intersecting cell */
 | 
				
			||||||
 | 
					/* We snap position to the nearest corner, which means there's no aliasing */
 | 
				
			||||||
 | 
					/* It works great for integer radii */
 | 
				
			||||||
 | 
					#define m_iter_circle_pixels(p_center_x, p_center_y, p_radius) \
 | 
				
			||||||
 | 
					    for (float y = (p_center_y + ceilf(p_radius)) - 1; y > (p_center_y - ceilf(p_radius)) - 1; --y) \
 | 
				
			||||||
 | 
					        for (float x = p_center_x - ceilf(sqrtf(p_radius * p_radius - (y - p_center_y + (y <= p_center_y)) * (y - p_center_y + (y <= p_center_y)))); x < p_center_x + ceilf(sqrtf(p_radius * p_radius - (y - p_center_y + (y <= p_center_y)) * (y - p_center_y + (y <= p_center_y)))); ++x)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int32_t ceil_sqrt(int32_t const n) {
 | 
				
			||||||
 | 
					    int32_t res = 1;
 | 
				
			||||||
 | 
					    #pragma clang loop unroll_count(8)
 | 
				
			||||||
 | 
					    while(res * res < n)
 | 
				
			||||||
 | 
					        res++;
 | 
				
			||||||
 | 
					    return res;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void benchmark(struct state *state) {
 | 
				
			||||||
 | 
					    volatile float x, y;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    profile_start("float");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (int i = 0; i < 1000; ++i) {
 | 
				
			||||||
 | 
					        float const rs = state->r * state->r;
 | 
				
			||||||
 | 
					        float const cr = ceilf(state->r);
 | 
				
			||||||
 | 
					        for (float iy = -cr; iy <= cr - 1; ++iy) {
 | 
				
			||||||
 | 
					            float const dx = ceilf(sqrtf(rs - (iy + (iy <= 0)) * (iy + (iy <= 0))));
 | 
				
			||||||
 | 
					            for (float ix = -dx; ix < dx; ++ix) {
 | 
				
			||||||
 | 
					                x = ix;
 | 
				
			||||||
 | 
					                y = iy;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    profile_end("float");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    profile_start("int32_t");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (int i = 0; i < 1000; ++i) {
 | 
				
			||||||
 | 
					        int32_t const rsi = (int32_t)state->r * (int32_t)state->r;
 | 
				
			||||||
 | 
					        for (int32_t iy = -(int32_t)state->r; iy <= (int32_t)state->r - 1; ++iy) {
 | 
				
			||||||
 | 
					            int32_t const dx = ceil_sqrt(rsi - (iy + (iy <= 0)) * (iy + (iy <= 0)));
 | 
				
			||||||
 | 
					            for (int32_t ix = -dx; ix < dx; ++ix) {
 | 
				
			||||||
 | 
					                x = (float)ix;
 | 
				
			||||||
 | 
					                y = (float)iy;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    profile_end("int32_t");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    profile_start("int32_t acc");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (int i = 0; i < 1000; ++i) {
 | 
				
			||||||
 | 
					        int32_t const rsi = (int32_t)state->r * (int32_t)state->r;
 | 
				
			||||||
 | 
					        int32_t acc = 1;
 | 
				
			||||||
 | 
					        for (int32_t iy = (int32_t)state->r - 1; iy >= 0; --iy) {
 | 
				
			||||||
 | 
					            while (acc * acc < rsi - iy * iy) acc++;
 | 
				
			||||||
 | 
					            for (int32_t ix = -acc; ix < acc; ++ix) {
 | 
				
			||||||
 | 
					                /* lower portion */
 | 
				
			||||||
 | 
					                x = (float)ix;
 | 
				
			||||||
 | 
					                y = (float)iy;
 | 
				
			||||||
 | 
					                /* upper portion */
 | 
				
			||||||
 | 
					                x = (float)ix;
 | 
				
			||||||
 | 
					                y = (float)-iy - 1;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    profile_end("int32_t acc");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    profile_start("int32_t acc precalc");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (int i = 0; i < 1000; ++i) {
 | 
				
			||||||
 | 
					        int32_t const rsi = (int32_t)state->r * (int32_t)state->r;
 | 
				
			||||||
 | 
					        int32_t acc = (int32_t)(sqrtf(state->r * state->r - (state->r - 1) * (state->r - 1)));
 | 
				
			||||||
 | 
					        for (int32_t iy = (int32_t)state->r - 1; iy >= 0; --iy) {
 | 
				
			||||||
 | 
					            while (acc * acc < rsi - iy * iy) acc++;
 | 
				
			||||||
 | 
					            for (int32_t ix = -acc; ix < acc; ++ix) {
 | 
				
			||||||
 | 
					                /* lower portion */
 | 
				
			||||||
 | 
					                x = (float)ix;
 | 
				
			||||||
 | 
					                y = (float)iy;
 | 
				
			||||||
 | 
					                /* upper portion */
 | 
				
			||||||
 | 
					                x = (float)ix;
 | 
				
			||||||
 | 
					                y = (float)(int32_t)(~(uint32_t)iy);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    profile_end("int32_t acc precalc");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    (void)x; (void)y;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void game_tick(void) {
 | 
				
			||||||
 | 
					    if (ctx.initialization_needed) {
 | 
				
			||||||
 | 
					        if (!ctx.udata) {
 | 
				
			||||||
 | 
					            ctx.udata = ccalloc(1, sizeof (struct state));
 | 
				
			||||||
 | 
					            struct state *state = ctx.udata;
 | 
				
			||||||
 | 
					            state->r = 24;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    struct state *state = ctx.udata;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Vec2 const mouse_snap = {floorf(ctx.mouse_position.x / 8) * 8, floorf(ctx.mouse_position.y / 8) * 8};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    input_action("up", CONTROL_LEFT_MOUSE);
 | 
				
			||||||
 | 
					    input_action("down", CONTROL_RIGHT_MOUSE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (input_action_just_pressed("up"))
 | 
				
			||||||
 | 
					        state->r += 1;
 | 
				
			||||||
 | 
					    if (input_action_just_pressed("down"))
 | 
				
			||||||
 | 
					        state->r -= 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    int32_t const rsi = (int32_t)state->r * (int32_t)state->r;
 | 
				
			||||||
 | 
					    int32_t acc = 1;
 | 
				
			||||||
 | 
					    for (int32_t iy = (int32_t)state->r - 1; iy >= 0; --iy) {
 | 
				
			||||||
 | 
					        while (acc * acc < rsi - iy * iy) acc++;
 | 
				
			||||||
 | 
					        for (int32_t ix = -acc; ix < acc; ++ix) {
 | 
				
			||||||
 | 
					            /* lower portion */
 | 
				
			||||||
 | 
					            draw_box((Rect){mouse_snap.x + (float)ix * 8, mouse_snap.y + (float)iy * 8, 8, 8}, 1, (Color){125, 125, 0, 255});
 | 
				
			||||||
 | 
					            /* upper portion */
 | 
				
			||||||
 | 
					            draw_box((Rect){mouse_snap.x + (float)ix * 8, mouse_snap.y + (float)(-iy - 1) * 8, 8, 8}, 1, (Color){125, 125, 0, 255});
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    draw_circle(mouse_snap, state->r * 8, (Color){125, 125, 125, 125});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    benchmark(state);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void game_end(void) {
 | 
				
			||||||
 | 
					    /* do your deinitialization here */
 | 
				
			||||||
 | 
					    struct state *state = ctx.udata;
 | 
				
			||||||
 | 
					    free(state);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										11
									
								
								apps/examples/circle-raster/state.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								apps/examples/circle-raster/state.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					#ifndef STATE_H
 | 
				
			||||||
 | 
					#define STATE_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "twn_game_api.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct state {
 | 
				
			||||||
 | 
					    float r;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
@@ -6,7 +6,7 @@ if(NOT CMAKE_BUILD_TYPE)
 | 
				
			|||||||
        set(CMAKE_BUILD_TYPE Debug)
 | 
					        set(CMAKE_BUILD_TYPE Debug)
 | 
				
			||||||
endif()
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNBUILDDIR})
 | 
					add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set(SOURCE_FILES
 | 
					set(SOURCE_FILES
 | 
				
			||||||
        game.c
 | 
					        game.c
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,8 +13,7 @@ dev_id = "you"
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# Game runtime details
 | 
					# Game runtime details
 | 
				
			||||||
[game]
 | 
					[game]
 | 
				
			||||||
base_render_width = 640
 | 
					resolution = [ 640, 480 ]
 | 
				
			||||||
base_render_height = 480
 | 
					 | 
				
			||||||
#debug = true
 | 
					#debug = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Engine tweaks. You probably don't need to change these
 | 
					# Engine tweaks. You probably don't need to change these
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								apps/twnlua/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								apps/twnlua/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					luabind.c
 | 
				
			||||||
@@ -6,12 +6,21 @@ if(NOT CMAKE_BUILD_TYPE)
 | 
				
			|||||||
        set(CMAKE_BUILD_TYPE Debug)
 | 
					        set(CMAKE_BUILD_TYPE Debug)
 | 
				
			||||||
endif()
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNBUILDDIR})
 | 
					add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					add_custom_command(
 | 
				
			||||||
 | 
					   OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/luabind.c
 | 
				
			||||||
 | 
					   COMMAND ${PYTHON3} ${CMAKE_CURRENT_SOURCE_DIR}/bindgen.py $ENV{TWNROOT}/share/twn_api.json > ${CMAKE_CURRENT_SOURCE_DIR}/luabind.c
 | 
				
			||||||
 | 
					   DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/bindgen.py $ENV{TWNROOT}/share/twn_api.json
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set(SOURCE_FILES
 | 
					set(SOURCE_FILES
 | 
				
			||||||
        game.c
 | 
					        game.c
 | 
				
			||||||
        state.h
 | 
					        state.h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ${CMAKE_CURRENT_SOURCE_DIR}/luabind.c
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        lua/src/lapi.c
 | 
					        lua/src/lapi.c
 | 
				
			||||||
        lua/src/lapi.h
 | 
					        lua/src/lapi.h
 | 
				
			||||||
        lua/src/lauxlib.c
 | 
					        lua/src/lauxlib.c
 | 
				
			||||||
@@ -74,4 +83,4 @@ set(SOURCE_FILES
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use_townengine(${PROJECT_NAME} "${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR})
 | 
					use_townengine(${PROJECT_NAME} "${SOURCE_FILES}" ${CMAKE_SOURCE_DIR})
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										212
									
								
								apps/twnlua/bindgen.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										212
									
								
								apps/twnlua/bindgen.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,212 @@
 | 
				
			|||||||
 | 
					#!/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":
 | 
				
			||||||
 | 
					        s = str(parameter["default"])
 | 
				
			||||||
 | 
					        return s + 'f' if '.' in s else s + '.f'
 | 
				
			||||||
 | 
					    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 not parameter["type"] in ("float", "bool"):
 | 
				
			||||||
 | 
					                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 idx, field in enumerate(typedesc["fields"]):
 | 
				
			||||||
 | 
					            converter += "    lua_getfield(L, -%i, \"%s\");\n" % (idx + 1, 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, %i);\n" % len(typedesc["fields"]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # TODO: wild idea: use compile time built hash table
 | 
				
			||||||
 | 
					    elif "enums" in typedesc:
 | 
				
			||||||
 | 
					        storages += ["struct %sHashItem { char *key; %s value; };\nstatic struct %sHashItem *%s_map = NULL;\n" % (typename, typename, typename, typename.lower())]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # TODO: use arena
 | 
				
			||||||
 | 
					        for enum in typedesc["enums"]:
 | 
				
			||||||
 | 
					            initializer = "    shput(%s_map, \"%s\", %s);" % (typename.lower(), enum, typedesc["enums"][enum])
 | 
				
			||||||
 | 
					            initializers += [initializer]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        deinitializers += ["    shfree(%s_map);" % typename.lower()]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        converter += "    char const *value = lua_tostring(L, -1);\n";
 | 
				
			||||||
 | 
					        converter += "        %s = shget(%s_map, value);\n" % (typename.lower(), typename.lower())
 | 
				
			||||||
 | 
					        converter += "    lua_pop(L, 1);\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    converter += "    return %s;\n}\n" % (typename.lower())
 | 
				
			||||||
 | 
					    converters += [converter]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					print('\n'.join(storages))
 | 
				
			||||||
 | 
					print("extern void bindgen_init(void);\n")
 | 
				
			||||||
 | 
					print("void bindgen_init(void) {\n" + '\n'.join(initializers) + "\n}\n")
 | 
				
			||||||
 | 
					print('\n'.join(converters))
 | 
				
			||||||
 | 
					print('\n'.join(bindings))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					loader = "extern void bindgen_load_%s(lua_State *L);\n" % api["name"]
 | 
				
			||||||
 | 
					loader += "void bindgen_load_%s(lua_State *L) {\n" % api["name"]
 | 
				
			||||||
 | 
					loader += "    bindgen_init();\n"
 | 
				
			||||||
 | 
					for procedure, procedure_desc in api["procedures"].items():
 | 
				
			||||||
 | 
					    loader += "    lua_pushcfunction(L, binding_%s);\n" % procedure
 | 
				
			||||||
 | 
					    loader += "    lua_setglobal(L, \"%s\");\n" % procedure
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					loader += "}\n"
 | 
				
			||||||
 | 
					print(loader)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					unloader = "extern void bindgen_unload_%s(lua_State *L);\n" % api["name"]
 | 
				
			||||||
 | 
					unloader += "void bindgen_unload_%s(lua_State *L) {\n    (void)L;\n" % api["name"]
 | 
				
			||||||
 | 
					unloader += '\n'.join(deinitializers)
 | 
				
			||||||
 | 
					unloader += "\n}\n"
 | 
				
			||||||
 | 
					print(unloader)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# exceptions for the base townengine api
 | 
				
			||||||
 | 
					# TODO: is there a way to generalize it? or rather, is there any need to do so?
 | 
				
			||||||
 | 
					if api["name"] == "twn":
 | 
				
			||||||
 | 
					    contexter = "extern void bindgen_build_context(lua_State *L);\n"
 | 
				
			||||||
 | 
					    contexter += "void bindgen_build_context(lua_State *L) {\n"
 | 
				
			||||||
 | 
					    contexter += to_table(api["types"]["Context"], "ctx", 4)
 | 
				
			||||||
 | 
					    contexter += "}\n\n"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    contexter += "extern void bindgen_upload_context(lua_State *L);\n"
 | 
				
			||||||
 | 
					    contexter += "void bindgen_upload_context(lua_State *L) {\n"
 | 
				
			||||||
 | 
					    contexter += from_table(api["types"]["Context"], "ctx", 4)
 | 
				
			||||||
 | 
					    contexter += "}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    print(contexter)
 | 
				
			||||||
@@ -4,13 +4,18 @@ offset = { x = 0, y = 0 }
 | 
				
			|||||||
angle = 0
 | 
					angle = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function game_tick()
 | 
					function game_tick()
 | 
				
			||||||
    rectangle {
 | 
					    input_action {
 | 
				
			||||||
 | 
					        name = "press",
 | 
				
			||||||
 | 
					        control = "A"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    draw_rectangle {
 | 
				
			||||||
        rect = { x = 0, y = 0, w = 640, h = 360 },
 | 
					        rect = { x = 0, y = 0, w = 640, h = 360 },
 | 
				
			||||||
        color = { r = 127, g = 0, b = 127, a = 255 },
 | 
					        color = { r = 127, g = 0, b = 127, a = 255 },
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sprite {
 | 
					    draw_sprite {
 | 
				
			||||||
        path = "/assets/title.png",
 | 
					        texture = "/assets/title.png",
 | 
				
			||||||
        rect = {
 | 
					        rect = {
 | 
				
			||||||
            x = 320 - (320 / 2),
 | 
					            x = 320 - (320 / 2),
 | 
				
			||||||
            y = 180 - (128 / 2),
 | 
					            y = 180 - (128 / 2),
 | 
				
			||||||
@@ -19,11 +24,13 @@ function game_tick()
 | 
				
			|||||||
        },
 | 
					        },
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    text {
 | 
					    if input_action_pressed { name = "press" } then
 | 
				
			||||||
        string = "IT KEEPS HAPPENING",
 | 
					        draw_text {
 | 
				
			||||||
 | 
					            string = "it never happened",
 | 
				
			||||||
            position = offset,
 | 
					            position = offset,
 | 
				
			||||||
            font = "/fonts/kenney-pixel.ttf",
 | 
					            font = "/fonts/kenney-pixel.ttf",
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    offset.x = ORIGIN.x + (math.cos(angle) * RADIUS)
 | 
					    offset.x = ORIGIN.x + (math.cos(angle) * RADIUS)
 | 
				
			||||||
    offset.y = ORIGIN.y + (math.sin(angle) * RADIUS)
 | 
					    offset.y = ORIGIN.y + (math.sin(angle) * RADIUS)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,6 @@ app_id = "twnlua"
 | 
				
			|||||||
dev_id = "somebody"
 | 
					dev_id = "somebody"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[game]
 | 
					[game]
 | 
				
			||||||
base_render_width = 640
 | 
					resolution = [ 640, 360 ]
 | 
				
			||||||
base_render_height = 360
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
[engine]
 | 
					[engine]
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										53
									
								
								apps/twnlua/docgen.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										53
									
								
								apps/twnlua/docgen.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,53 @@
 | 
				
			|||||||
 | 
					#!/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 to_lua_type_annot(typename):
 | 
				
			||||||
 | 
					    basetype = typename.rsplit(' *', 1)[0]
 | 
				
			||||||
 | 
					    if typename == "char *":
 | 
				
			||||||
 | 
					        return "string"
 | 
				
			||||||
 | 
					    elif basetype == "float":
 | 
				
			||||||
 | 
					        return "number"
 | 
				
			||||||
 | 
					    elif basetype == "bool":
 | 
				
			||||||
 | 
					        return "boolean"
 | 
				
			||||||
 | 
					    elif basetype == "Control":
 | 
				
			||||||
 | 
					        return "Control"
 | 
				
			||||||
 | 
					    elif basetype == "Vec2":
 | 
				
			||||||
 | 
					        return r"{ x: number, y: number }"
 | 
				
			||||||
 | 
					    elif basetype == "Vec3":
 | 
				
			||||||
 | 
					        return r"{ x: number, y: number, z: number }"
 | 
				
			||||||
 | 
					    elif basetype == "Color":
 | 
				
			||||||
 | 
					        return r"{ r: number, g: number, b: number, a: number }"
 | 
				
			||||||
 | 
					    elif basetype == "Rect":
 | 
				
			||||||
 | 
					        return r"{ x: number, y: number, w: number, h: number }"
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        return "unknown"
 | 
				
			||||||
 | 
					        # raise BaseException("Unhandled type for annotation: %s" % typename)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type_annotations, enum_annotations = {}, {}
 | 
				
			||||||
 | 
					type_annotations["ctx"] = r"{ %s, udata: table }" % \
 | 
				
			||||||
 | 
					    ', '.join("%s: %s" % (f["name"], to_lua_type_annot(f["type"])) for f in api["types"]["Context"]["fields"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					for annot in type_annotations:
 | 
				
			||||||
 | 
					    print("---@type " + type_annotations[annot])
 | 
				
			||||||
 | 
					    print(r"%s = nil" % annot)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum_annotations["Control"] = \
 | 
				
			||||||
 | 
					    '|'.join('\'"%s"\'' % e for e in api["types"]["Control"]["enums"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					for annot in enum_annotations:
 | 
				
			||||||
 | 
					    print("---@alias %s %s" % (annot, enum_annotations[annot]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					procedure_annotations = {}
 | 
				
			||||||
 | 
					for procedure, procedure_desc in api["procedures"].items():
 | 
				
			||||||
 | 
					    procedure_annotations[procedure] = r"{ %s }" % \
 | 
				
			||||||
 | 
					        ', '.join("%s: %s" % (p["name"], to_lua_type_annot(p["type"]) + '?' * ("default" in p)) for p in procedure_desc["params"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					for annot in procedure_annotations:
 | 
				
			||||||
 | 
					    print("---@param args " + procedure_annotations[annot])
 | 
				
			||||||
 | 
					    print("function %s(args) end" % annot)
 | 
				
			||||||
@@ -7,14 +7,20 @@
 | 
				
			|||||||
#include <lualib.h>
 | 
					#include <lualib.h>
 | 
				
			||||||
#include <lauxlib.h>
 | 
					#include <lauxlib.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <malloc.h>
 | 
					
 | 
				
			||||||
 | 
					/* generated by bindgen.py */
 | 
				
			||||||
 | 
					void bindgen_load_twn(lua_State *L);
 | 
				
			||||||
 | 
					void bindgen_unload_twn(lua_State *L);
 | 
				
			||||||
 | 
					void bindgen_build_context(lua_State *L);
 | 
				
			||||||
 | 
					void bindgen_upload_context(lua_State *L);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* require will go through physicsfs exclusively so that scripts can be in the data dir */
 | 
					/* require will go through physicsfs exclusively so that scripts can be in the data dir */
 | 
				
			||||||
 | 
					/* TODO: allow for bytecode files */
 | 
				
			||||||
static int physfs_loader(lua_State *L) {
 | 
					static int physfs_loader(lua_State *L) {
 | 
				
			||||||
    const char *name = luaL_checkstring(L, 1);
 | 
					    const char *name = luaL_checkstring(L, 1);
 | 
				
			||||||
    char *final_path = NULL;
 | 
					    char *final_path = NULL;
 | 
				
			||||||
    SDL_asprintf(&final_path, "%s.lua", name);
 | 
					    SDL_asprintf(&final_path, "/scripts/%s.lua", name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!file_exists(final_path)) {
 | 
					    if (!file_exists(final_path)) {
 | 
				
			||||||
        char *error_message = NULL;
 | 
					        char *error_message = NULL;
 | 
				
			||||||
@@ -30,6 +36,7 @@ static int physfs_loader(lua_State *L) {
 | 
				
			|||||||
    int64_t buf_size = file_to_bytes(final_path, &buf);
 | 
					    int64_t buf_size = file_to_bytes(final_path, &buf);
 | 
				
			||||||
    free(final_path);
 | 
					    free(final_path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* TODO: use reader interface for streaming instead */
 | 
				
			||||||
    luaL_loadbuffer(L, (char *)buf, buf_size, name);
 | 
					    luaL_loadbuffer(L, (char *)buf, buf_size, name);
 | 
				
			||||||
    free(buf);
 | 
					    free(buf);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -37,198 +44,46 @@ static int physfs_loader(lua_State *L) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static Rect table_to_rect(lua_State *L, int idx, const char *name) {
 | 
					/* WARN! experimental and will probably be removed */
 | 
				
			||||||
    /* types are checked here to help prevent unexpected results */
 | 
					/* it is an attempt to ease memory usage problems posed by using lua, partially successful */
 | 
				
			||||||
    Rect rect;
 | 
					static void *custom_alloc(void *ud, void *ptr, size_t osize, size_t nsize) {
 | 
				
			||||||
    int is_num;
 | 
					    (void)ud;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    lua_getfield(L, idx, "x");
 | 
					    /* small allocations are placed in slots, as there's a big chance they will not need to be resized */
 | 
				
			||||||
    rect.x = (float)lua_tonumberx(L, -1, &is_num);
 | 
					    static char slots[1024][128];
 | 
				
			||||||
    if (!is_num)
 | 
					    static int16_t free_slots[1024] = { [0] = -1 };
 | 
				
			||||||
        luaL_error(L, "bad field 'x' in '%s' (number expected, got %s)", name, luaL_typename(L, -1));
 | 
					    static size_t free_slot_count = 1024;
 | 
				
			||||||
    lua_pop(L, 1);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    lua_getfield(L, idx, "y");
 | 
					    if (free_slots[0] == -1)
 | 
				
			||||||
    rect.y = (float)lua_tonumberx(L, -1, &is_num);
 | 
					        for (int i = 0; i < 1024; i++)
 | 
				
			||||||
    if (!is_num)
 | 
					            free_slots[i] = (int16_t)i;
 | 
				
			||||||
        luaL_error(L, "bad field 'y' in '%s' (number expected, got %s)", name, luaL_typename(L, -1));
 | 
					 | 
				
			||||||
    lua_pop(L, 1);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    lua_getfield(L, idx, "w");
 | 
					    if (nsize == 0) {
 | 
				
			||||||
    rect.w = (float)lua_tonumberx(L, -1, &is_num);
 | 
					        if (ptr && (char *)ptr >= &slots[0][0] && (char *)ptr <= &slots[1024-1][128-1])
 | 
				
			||||||
    if (!is_num)
 | 
					            free_slots[free_slot_count++] = (int16_t)(((uintptr_t)ptr - (uintptr_t)slots) / 128);
 | 
				
			||||||
        luaL_error(L, "bad field 'w' in '%s' (number expected, got %s)", name, luaL_typename(L, -1));
 | 
					        else if (osize)
 | 
				
			||||||
    lua_pop(L, 1);
 | 
					            SDL_free(ptr);
 | 
				
			||||||
 | 
					        return NULL;
 | 
				
			||||||
    lua_getfield(L, idx, "h");
 | 
					    } else {
 | 
				
			||||||
    rect.h = (float)lua_tonumberx(L, -1, &is_num);
 | 
					        if (!ptr && nsize <= 128 && free_slot_count > 0) {
 | 
				
			||||||
    if (!is_num)
 | 
					            /* use a slot */
 | 
				
			||||||
        luaL_error(L, "bad field 'h' in '%s' (number expected, got %s)", name, luaL_typename(L, -1));
 | 
					            return slots[free_slots[--free_slot_count]];
 | 
				
			||||||
    lua_pop(L, 1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return rect;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static Color table_to_color(lua_State *L, int idx) {
 | 
					 | 
				
			||||||
    Color color;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    lua_getfield(L, idx, "r");
 | 
					 | 
				
			||||||
    color.r = (uint8_t)lua_tointeger(L, -1);
 | 
					 | 
				
			||||||
    lua_pop(L, 1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    lua_getfield(L, idx, "g");
 | 
					 | 
				
			||||||
    color.g = (uint8_t)lua_tointeger(L, -1);
 | 
					 | 
				
			||||||
    lua_pop(L, 1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    lua_getfield(L, idx, "b");
 | 
					 | 
				
			||||||
    color.b = (uint8_t)lua_tointeger(L, -1);
 | 
					 | 
				
			||||||
    lua_pop(L, 1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    lua_getfield(L, idx, "a");
 | 
					 | 
				
			||||||
    color.a = (uint8_t)lua_tointeger(L, -1);
 | 
					 | 
				
			||||||
    lua_pop(L, 1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return color;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* sprite(data [table]) */
 | 
					 | 
				
			||||||
/* data should contain the following fields: */
 | 
					 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
 * path [string]
 | 
					 | 
				
			||||||
 * rect [table]
 | 
					 | 
				
			||||||
 * texture_region [table], optional
 | 
					 | 
				
			||||||
 * color [table], optional
 | 
					 | 
				
			||||||
 * rotation [number], optional
 | 
					 | 
				
			||||||
 * flip_x [boolean], optional
 | 
					 | 
				
			||||||
 * flip_y [boolean], optional
 | 
					 | 
				
			||||||
 * stretch [boolean], optional
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
static int b_sprite(lua_State *L) {
 | 
					 | 
				
			||||||
    luaL_checktype(L, 1, LUA_TTABLE);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    DrawSpriteArgs args = { 0 };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    lua_getfield(L, 1, "path");
 | 
					 | 
				
			||||||
    const char *field_path = lua_tostring(L, -1);
 | 
					 | 
				
			||||||
    if (field_path == NULL)
 | 
					 | 
				
			||||||
        luaL_error(L, "bad field 'path' in 'data' (string expected, got %s)", luaL_typename(L, -1));
 | 
					 | 
				
			||||||
    args.path = field_path;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    lua_getfield(L, 1, "rect");
 | 
					 | 
				
			||||||
    if (!lua_istable(L, -1))
 | 
					 | 
				
			||||||
        luaL_error(L, "bad field 'rect' in 'data' (table expected, got %s)", luaL_typename(L, -1));
 | 
					 | 
				
			||||||
    args.rect = table_to_rect(L, -1, "data.rect");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    lua_getfield(L, 1, "texture_region");
 | 
					 | 
				
			||||||
    if (lua_istable(L, -1)) {
 | 
					 | 
				
			||||||
        args.texture_region_opt = table_to_rect(L, -1, "data.texture_region");
 | 
					 | 
				
			||||||
        args.texture_region_opt_set = true;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    lua_getfield(L, 1, "color");
 | 
					        if ((char *)ptr >= &slots[0][0] && (char *)ptr <= &slots[1024-1][128-1]) {
 | 
				
			||||||
    if (lua_istable(L, -1)) {
 | 
					            /* still fits */
 | 
				
			||||||
        args.color_opt = table_to_color(L, -1);
 | 
					            if (nsize <= 128)
 | 
				
			||||||
        args.color_opt_set = true;
 | 
					                return ptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            /* move from slot to dynamic memory */
 | 
				
			||||||
 | 
					            void *mem = SDL_malloc(nsize);
 | 
				
			||||||
 | 
					            SDL_memcpy(mem, ptr, osize);
 | 
				
			||||||
 | 
					            free_slots[free_slot_count++] = (int16_t)(((uintptr_t)ptr - (uintptr_t)slots) / 128);
 | 
				
			||||||
 | 
					            return mem;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    lua_getfield(L, 1, "rotation");
 | 
					        return SDL_realloc(ptr, nsize);
 | 
				
			||||||
    if (lua_isnumber(L, -1)) {
 | 
					 | 
				
			||||||
        args.rotation_opt = (float)lua_tonumber(L, -1);
 | 
					 | 
				
			||||||
        args.rotation_opt_set = true;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    lua_getfield(L, 1, "flip_x");
 | 
					 | 
				
			||||||
    if (lua_isboolean(L, -1)) {
 | 
					 | 
				
			||||||
        args.flip_x_opt = lua_toboolean(L, -1);
 | 
					 | 
				
			||||||
        args.flip_x_opt_set = true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    lua_getfield(L, 1, "flip_y");
 | 
					 | 
				
			||||||
    if (lua_isboolean(L, -1)) {
 | 
					 | 
				
			||||||
        args.flip_y_opt = lua_toboolean(L, -1);
 | 
					 | 
				
			||||||
        args.flip_y_opt_set = true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    lua_getfield(L, 1, "stretch");
 | 
					 | 
				
			||||||
    if (lua_isboolean(L, -1)) {
 | 
					 | 
				
			||||||
        args.stretch_opt = lua_toboolean(L, -1);
 | 
					 | 
				
			||||||
        args.stretch_opt_set = true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    draw_sprite_args(args);
 | 
					 | 
				
			||||||
    return 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* rectangle(data [table]) */
 | 
					 | 
				
			||||||
/* data should contain the following fields: */
 | 
					 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
 * rect [table]
 | 
					 | 
				
			||||||
 * color [table], optional, defaults to white
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
static int b_rectangle(lua_State *L) {
 | 
					 | 
				
			||||||
    luaL_checktype(L, 1, LUA_TTABLE);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    lua_getfield(L, 1, "rect");
 | 
					 | 
				
			||||||
    if (!lua_istable(L, -1))
 | 
					 | 
				
			||||||
        luaL_error(L, "bad field 'rect' in 'data' (table expected, got %s)", luaL_typename(L, -1));
 | 
					 | 
				
			||||||
    Rect rect = table_to_rect(L, -1, "data.rect");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Color color = { 255, 255, 255, 255 };
 | 
					 | 
				
			||||||
    lua_getfield(L, 1, "color");
 | 
					 | 
				
			||||||
    if (lua_istable(L, -1))
 | 
					 | 
				
			||||||
        color = table_to_color(L, -1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    draw_rectangle(rect, color);
 | 
					 | 
				
			||||||
    return 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* text(data [table]) */
 | 
					 | 
				
			||||||
/* data should contain the following fields: */
 | 
					 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
 * string [string]
 | 
					 | 
				
			||||||
 * position [table]
 | 
					 | 
				
			||||||
 * height_px [number], optional, defaults to 22
 | 
					 | 
				
			||||||
 * color [table], optional, defaults to black
 | 
					 | 
				
			||||||
 * font [string]
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
static int b_text(lua_State *L) {
 | 
					 | 
				
			||||||
    luaL_checktype(L, 1, LUA_TTABLE);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    lua_getfield(L, 1, "string");
 | 
					 | 
				
			||||||
    const char *string = lua_tostring(L, -1);
 | 
					 | 
				
			||||||
    if (string == NULL)
 | 
					 | 
				
			||||||
        luaL_error(L, "bad field 'string' in 'data' (string expected, got %s)", luaL_typename(L, -1));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    lua_getfield(L, 1, "position");
 | 
					 | 
				
			||||||
    if (!lua_istable(L, -1))
 | 
					 | 
				
			||||||
        luaL_error(L, "bad field 'position' in 'data' (table expected, got %s)", luaL_typename(L, -1));
 | 
					 | 
				
			||||||
    lua_getfield(L, -1, "x");
 | 
					 | 
				
			||||||
    float x = (float)lua_tonumber(L, -1);
 | 
					 | 
				
			||||||
    lua_getfield(L, -2, "y");
 | 
					 | 
				
			||||||
    float y = (float)lua_tonumber(L, -1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    lua_getfield(L, 1, "height_px");
 | 
					 | 
				
			||||||
    int is_num;
 | 
					 | 
				
			||||||
    int height_px = (int)lua_tointegerx(L, -1, &is_num);
 | 
					 | 
				
			||||||
    if (!is_num)
 | 
					 | 
				
			||||||
        height_px = 22;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    lua_getfield(L, 1, "color");
 | 
					 | 
				
			||||||
    Color color = { 0, 0, 0, 255 };
 | 
					 | 
				
			||||||
    if (lua_istable(L, -1))
 | 
					 | 
				
			||||||
        color = table_to_color(L, -1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    lua_getfield(L, 1, "font");
 | 
					 | 
				
			||||||
    const char *font = lua_tostring(L, -1);
 | 
					 | 
				
			||||||
    if (font == NULL)
 | 
					 | 
				
			||||||
        luaL_error(L, "bad field 'font' in 'data' (string expected, got %s)", luaL_typename(L, -1));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    draw_text(string, (Vec2) { x, y }, height_px, color, font);
 | 
					 | 
				
			||||||
    return 0;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -246,14 +101,15 @@ void game_tick(void) {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        state->L = luaL_newstate();
 | 
					        state->L = luaL_newstate();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /* fakey version of luaL_openlibs() that excludes file i/o */
 | 
					        lua_setallocf(state->L, custom_alloc, NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /* fakey version of luaL_openlibs() that excludes file i/o and os stuff */
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            static const luaL_Reg loaded_libs[] = {
 | 
					            static const luaL_Reg loaded_libs[] = {
 | 
				
			||||||
               { LUA_GNAME, luaopen_base },
 | 
					               { LUA_GNAME, luaopen_base },
 | 
				
			||||||
               { LUA_LOADLIBNAME, luaopen_package },
 | 
					               { LUA_LOADLIBNAME, luaopen_package },
 | 
				
			||||||
               { LUA_COLIBNAME, luaopen_coroutine },
 | 
					               { LUA_COLIBNAME, luaopen_coroutine },
 | 
				
			||||||
               { LUA_TABLIBNAME, luaopen_table },
 | 
					               { LUA_TABLIBNAME, luaopen_table },
 | 
				
			||||||
               { LUA_OSLIBNAME, luaopen_os },
 | 
					 | 
				
			||||||
               { LUA_STRLIBNAME, luaopen_string },
 | 
					               { LUA_STRLIBNAME, luaopen_string },
 | 
				
			||||||
               { LUA_MATHLIBNAME, luaopen_math },
 | 
					               { LUA_MATHLIBNAME, luaopen_math },
 | 
				
			||||||
               { LUA_UTF8LIBNAME, luaopen_utf8 },
 | 
					               { LUA_UTF8LIBNAME, luaopen_utf8 },
 | 
				
			||||||
@@ -269,7 +125,7 @@ void game_tick(void) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        /* package.searchers = { physfs_loader } */
 | 
					        /* package.searchers = { physfs_loader } */
 | 
				
			||||||
        lua_getglobal(state->L, "package");
 | 
					        lua_getglobal(state->L, "package");
 | 
				
			||||||
        lua_newtable(state->L);
 | 
					        lua_createtable(state->L, 0, 1);
 | 
				
			||||||
        lua_setfield(state->L, -2, "searchers");
 | 
					        lua_setfield(state->L, -2, "searchers");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        lua_getfield(state->L, -1, "searchers");
 | 
					        lua_getfield(state->L, -1, "searchers");
 | 
				
			||||||
@@ -280,35 +136,57 @@ void game_tick(void) {
 | 
				
			|||||||
        lua_pop(state->L, 2);
 | 
					        lua_pop(state->L, 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /* binding */
 | 
					        /* binding */
 | 
				
			||||||
        lua_register(state->L, "sprite", b_sprite);
 | 
					        bindgen_load_twn(state->L);
 | 
				
			||||||
        lua_register(state->L, "rectangle", b_rectangle);
 | 
					 | 
				
			||||||
        lua_register(state->L, "text", b_text);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /* now finally get to running the code */
 | 
					        /* now finally get to running the code */
 | 
				
			||||||
        unsigned char *game_buf = NULL;
 | 
					        unsigned char *game_buf = NULL;
 | 
				
			||||||
        size_t game_buf_size = file_to_bytes("/scripts/game.lua", &game_buf);
 | 
					        size_t game_buf_size = file_to_bytes("/scripts/game.lua", &game_buf);
 | 
				
			||||||
 | 
					        /* TODO: use reader interface for streaming instead */
 | 
				
			||||||
        if (luaL_loadbuffer(state->L, (char *)game_buf, game_buf_size, "game.lua") == LUA_OK) {
 | 
					        if (luaL_loadbuffer(state->L, (char *)game_buf, game_buf_size, "game.lua") == LUA_OK) {
 | 
				
			||||||
            if (lua_pcall(state->L, 0, 0, 0) != LUA_OK) {
 | 
					            if (lua_pcall(state->L, 0, 0, 0) != LUA_OK) {
 | 
				
			||||||
                log_critical("%s", lua_tostring(state->L, -1));
 | 
					                log_critical("%s", lua_tostring(state->L, -1));
 | 
				
			||||||
                lua_pop(state->L, 1);
 | 
					                lua_pop(state->L, 1);
 | 
				
			||||||
 | 
					            } else
 | 
				
			||||||
 | 
					                state->loaded_successfully = true;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            /* got some sort of error, it should be pushed on top of the stack */
 | 
				
			||||||
 | 
					            SDL_assert(lua_isstring(state->L, -1));
 | 
				
			||||||
 | 
					            log_critical("Error loading /scripts/game.lua entry: %s", luaL_tolstring(state->L, -1, NULL));
 | 
				
			||||||
 | 
					            lua_pop(state->L, 1);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        }
 | 
					
 | 
				
			||||||
        free(game_buf);
 | 
					        free(game_buf);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /* from this point we have access to everything defined in lua */
 | 
					        /* from this point we have access to everything defined in lua */
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    State *state = ctx.udata;
 | 
					    State *state = ctx.udata;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (state->loaded_successfully) {
 | 
				
			||||||
 | 
					        bindgen_build_context(state->L);
 | 
				
			||||||
 | 
					        lua_getglobal(state->L, "ctx");
 | 
				
			||||||
 | 
					        if (!lua_isnoneornil(state->L, -1)) {
 | 
				
			||||||
 | 
					            lua_getfield(state->L, -1, "udata");
 | 
				
			||||||
 | 
					            lua_setfield(state->L, -3, "udata");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        lua_pop(state->L, 1);
 | 
				
			||||||
 | 
					        lua_setglobal(state->L, "ctx");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        lua_getglobal(state->L, "game_tick");
 | 
					        lua_getglobal(state->L, "game_tick");
 | 
				
			||||||
        if (lua_pcall(state->L, 0, 0, 0) != LUA_OK) {
 | 
					        if (lua_pcall(state->L, 0, 0, 0) != LUA_OK) {
 | 
				
			||||||
            log_critical("%s", lua_tostring(state->L, -1));
 | 
					            log_critical("%s", lua_tostring(state->L, -1));
 | 
				
			||||||
            lua_pop(state->L, 1);
 | 
					            lua_pop(state->L, 1);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        lua_getglobal(state->L, "ctx");
 | 
				
			||||||
 | 
					        bindgen_upload_context(state->L);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void game_end(void) {
 | 
					void game_end(void) {
 | 
				
			||||||
    State *state = ctx.udata;
 | 
					    State *state = ctx.udata;
 | 
				
			||||||
 | 
					    bindgen_unload_twn(state->L);
 | 
				
			||||||
    lua_close(state->L);
 | 
					    lua_close(state->L);
 | 
				
			||||||
    free(state);
 | 
					    free(state);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +1,13 @@
 | 
				
			|||||||
#ifndef STATE_H
 | 
					#ifndef STATE_H
 | 
				
			||||||
#define STATE_H
 | 
					#define STATE_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "twn_game_api.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <lua.h>
 | 
					#include <lua.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <stdbool.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct State {
 | 
					typedef struct State {
 | 
				
			||||||
    lua_State *L;
 | 
					    lua_State *L;
 | 
				
			||||||
 | 
					    bool loaded_successfully;
 | 
				
			||||||
} State;
 | 
					} State;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										15
									
								
								bin/build.sh
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								bin/build.sh
									
									
									
									
									
								
							@@ -1,12 +1,19 @@
 | 
				
			|||||||
#!/bin/env sh
 | 
					#!/bin/env sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set +e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# check whether ninja is around (you better start running)
 | 
					# check whether ninja is around (you better start running)
 | 
				
			||||||
if [ -x "$(command -v ninja)" ]; then
 | 
					if [ -x "$(command -v ninja)" ]; then
 | 
				
			||||||
    generator="-G Ninja"
 | 
					    generator="-G Ninja"
 | 
				
			||||||
fi
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if [ "$1" = "web" ]; then
 | 
					# check whether clang is around (it's just better)
 | 
				
			||||||
    emcmake cmake $generator -B .build-web "${@:2}" && cmake --build .build-web --parallel
 | 
					if [ -x "$(command -v clang)" ]; then
 | 
				
			||||||
else
 | 
					    cc="-DCMAKE_C_COMPILER=clang"
 | 
				
			||||||
    cmake $generator -B .build "$@" && cmake --build .build --parallel
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [ "$1" = "web" ]; then
 | 
				
			||||||
 | 
					    emcmake cmake $generator $cc -B build-web "${@:2}" && cmake --build build-web --parallel
 | 
				
			||||||
 | 
					else
 | 
				
			||||||
 | 
					    cmake $generator $cc -B build "$@" && cmake --build build --parallel
 | 
				
			||||||
fi
 | 
					fi
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,6 @@
 | 
				
			|||||||
#!/bin/env sh
 | 
					#!/bin/env sh
 | 
				
			||||||
# single header api generator with clang
 | 
					# single header api generator with clang
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set +e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
clang -I./ -P -E -nostdinc -nobuiltininc -DTWN_NOT_C $TWNROOT/include/twn_game_api.h 2> /dev/null | clang-format
 | 
					clang -I./ -P -E -nostdinc -nobuiltininc -DTWN_NOT_C $TWNROOT/include/twn_game_api.h 2> /dev/null | clang-format
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								bin/twn
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								bin/twn
									
									
									
									
									
								
							@@ -6,7 +6,6 @@ set +e
 | 
				
			|||||||
exe="$(basename $PWD)"
 | 
					exe="$(basename $PWD)"
 | 
				
			||||||
toolpath="$(dirname -- "${BASH_SOURCE[0]}")"
 | 
					toolpath="$(dirname -- "${BASH_SOURCE[0]}")"
 | 
				
			||||||
export TWNROOT=$(realpath "$toolpath"/../)
 | 
					export TWNROOT=$(realpath "$toolpath"/../)
 | 
				
			||||||
export TWNBUILDDIR=$(realpath "$toolpath"/../.build)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
case "$1" in
 | 
					case "$1" in
 | 
				
			||||||
    build    ) "$toolpath"/build.sh "${@:2}"
 | 
					    build    ) "$toolpath"/build.sh "${@:2}"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										
											BIN
										
									
								
								common-data/assets/grasses/10.png
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								common-data/assets/grasses/10.png
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										19
									
								
								docs/interop.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								docs/interop.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					# 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))
 | 
				
			||||||
 | 
					* enum types are allowed, as they decay to numeric type (but language-specific api can decide to provide simple ways to use them)
 | 
				
			||||||
 | 
					* 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
 | 
				
			||||||
 | 
					* parameter names should not collide with keywords of any language that is targetted; if so happens, parameter alias could be added
 | 
				
			||||||
 | 
					* any procedure can't have more than 8 parameters
 | 
				
			||||||
@@ -1,10 +0,0 @@
 | 
				
			|||||||
api needs to facilitate easy interoperability with other languages and tools,
 | 
					 | 
				
			||||||
for that certain steps are taken:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* number of public api calls is kept at the minimum
 | 
					 | 
				
			||||||
* procedure signatures can only use basic types, no aggregates, with exception of Vec/Matrix types and alike,
 | 
					 | 
				
			||||||
  with no expectation on new additions (see /include/twn_types.h)
 | 
					 | 
				
			||||||
* optionals can be expressed via pointer passage of value primitives, with NULL expressive default
 | 
					 | 
				
			||||||
* /include/twn_game_api.json file is hand-kept with a schema to aid automatic generation and other tooling
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
one of main inspirations for that is opengl model
 | 
					 | 
				
			||||||
							
								
								
									
										1
									
								
								hooks
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								hooks
									
									
									
									
									
								
							@@ -6,3 +6,4 @@ set +e
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# TODO: prevent double hooking
 | 
					# TODO: prevent double hooking
 | 
				
			||||||
export PATH=$PATH:$(realpath $(dirname -- "${BASH_SOURCE[0]}")/bin/)
 | 
					export PATH=$PATH:$(realpath $(dirname -- "${BASH_SOURCE[0]}")/bin/)
 | 
				
			||||||
 | 
					export TWNROOT=$(realpath $(dirname -- "${BASH_SOURCE[0]}"))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,19 +9,14 @@
 | 
				
			|||||||
/* plays audio file at specified channel or at scratch channel if NULL is passed, without ability to refer to it later */
 | 
					/* plays audio file at specified channel or at scratch channel if NULL is passed, without ability to refer to it later */
 | 
				
			||||||
/* path path must contain valid file extension to infer which file format it is */
 | 
					/* path path must contain valid file extension to infer which file format it is */
 | 
				
			||||||
/* supported formats: .ogg, .xm */
 | 
					/* supported formats: .ogg, .xm */
 | 
				
			||||||
TWN_API void audio_play(const char *path,
 | 
					TWN_API void audio_play(const char *audio,
 | 
				
			||||||
                        const char *channel,    /* optional */
 | 
					                        const char *channel,    /* optional */
 | 
				
			||||||
                        bool  repeat,           /* default: false */
 | 
					                        bool  repeat,           /* default: false */
 | 
				
			||||||
                        float volume,           /* default: 1.0f, range: 0.0f to 1.0f */
 | 
					                        float volume,           /* default: 1.0f, range: 0.0f to 1.0f */
 | 
				
			||||||
                        float panning);         /* default: 0.0f, range: -1.0 to 1.0f */
 | 
					                        float panning);         /* default: 0.0f, range: -1.0 to 1.0f */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef enum {
 | 
					/* possible parameter options: "volume", "panning", "repeat" */
 | 
				
			||||||
    AUDIO_PARAM_REPEAT,
 | 
					TWN_API void audio_parameter(const char *channel, const char *parameter, float value);
 | 
				
			||||||
    AUDIO_PARAM_VOLUME,
 | 
					 | 
				
			||||||
    AUDIO_PARAM_PANNING,
 | 
					 | 
				
			||||||
} AudioParam;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
TWN_API void audio_set(const char *channel, AudioParam param, float value);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* TODO */
 | 
					/* TODO */
 | 
				
			||||||
// TWN_API bool audio_ended(const char *channel);
 | 
					// TWN_API bool audio_ended(const char *channel);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,21 +16,24 @@ typedef struct Context {
 | 
				
			|||||||
    void *udata;
 | 
					    void *udata;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* which frame is it, starting from 0 at startup */
 | 
					    /* which frame is it, starting from 0 at startup */
 | 
				
			||||||
    uint64_t frame_number;
 | 
					    float frame_number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* real time spent on one frame (in seconds) */
 | 
					    /* real time spent on one frame (in seconds) */
 | 
				
			||||||
    /* townengine is fixed step based, so you don't have */
 | 
					    /* townengine is fixed step based, so you don't have to use delta */
 | 
				
			||||||
    /* TODO: actually set it */
 | 
					 | 
				
			||||||
    float frame_duration;
 | 
					    float frame_duration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* it is disabled by having fog_density approximately equal to zero */
 | 
				
			||||||
 | 
					    float fog_density;
 | 
				
			||||||
 | 
					    Color fog_color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* resolution is set from config and dictates both logical and drawing space, as they're related */
 | 
					    /* resolution is set from config and dictates both logical and drawing space, as they're related */
 | 
				
			||||||
    /* even if scaling is done, game logic should never change over that */
 | 
					    /* even if scaling is done, game logic should never change over that */
 | 
				
			||||||
    Vec2i resolution;
 | 
					    Vec2 resolution;
 | 
				
			||||||
    Vec2i mouse_position;
 | 
					    Vec2 mouse_position;
 | 
				
			||||||
    Vec2i mouse_movement;
 | 
					    Vec2 mouse_movement;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* is set on startup, should be used as source of randomness */
 | 
					    /* is set on startup, should be used as source of randomness */
 | 
				
			||||||
    uint64_t random_seed;
 | 
					    float random_seed;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* whether debugging logic should be enabled in user code */
 | 
					    /* whether debugging logic should be enabled in user code */
 | 
				
			||||||
    bool debug;
 | 
					    bool debug;
 | 
				
			||||||
@@ -38,6 +41,7 @@ typedef struct Context {
 | 
				
			|||||||
    /* is set to true when state is invalidated and needs to be rebuilt */
 | 
					    /* is set to true when state is invalidated and needs to be rebuilt */
 | 
				
			||||||
    /* watch for it and handle properly! */
 | 
					    /* watch for it and handle properly! */
 | 
				
			||||||
    bool initialization_needed;
 | 
					    bool initialization_needed;
 | 
				
			||||||
 | 
					    bool mouse_capture;
 | 
				
			||||||
} Context;
 | 
					} Context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* when included after twn_engine_context there's an 'ctx' defined already */
 | 
					/* when included after twn_engine_context there's an 'ctx' defined already */
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,20 +3,20 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "twn_types.h"
 | 
					#include "twn_types.h"
 | 
				
			||||||
#include "twn_option.h"
 | 
					#include "twn_option.h"
 | 
				
			||||||
#include "twn_camera.h"
 | 
					 | 
				
			||||||
#include "twn_engine_api.h"
 | 
					#include "twn_engine_api.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <stdbool.h>
 | 
					#include <stdbool.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* pushes a sprite onto the sprite render queue */
 | 
					/* pushes a sprite onto the sprite render queue */
 | 
				
			||||||
TWN_API void draw_sprite(char const *path,
 | 
					/* TODO: combine flip_x and flip_y into a flip_mask with enum */
 | 
				
			||||||
 | 
					TWN_API void draw_sprite(char const *texture,
 | 
				
			||||||
                         Rect        rect,
 | 
					                         Rect        rect,
 | 
				
			||||||
                         Rect const *texture_region,    /* optional, default: NULL */
 | 
					                         Rect const *texture_region,    /* optional, default: NULL */
 | 
				
			||||||
                         Color       color,             /* optional, default: all 255 */
 | 
					                         Color       color,             /* optional, default: all 255 */
 | 
				
			||||||
                         float       rotation,          /* optional, default: 0 */
 | 
					                         float       rotation,          /* optional, default: 0 */
 | 
				
			||||||
                         bool        flip_x,            /* optional, default: false */
 | 
					                         bool        flip_x,            /* optional, default: false */
 | 
				
			||||||
                         bool        flip_y,            /* optional, default: false */
 | 
					                         bool        flip_y,            /* optional, default: false */
 | 
				
			||||||
                         bool        stretch);          /* optional, default: false */
 | 
					                         bool        stretch);          /* optional, default: true */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* pushes a filled rectangle onto the rectangle render queue */
 | 
					/* pushes a filled rectangle onto the rectangle render queue */
 | 
				
			||||||
TWN_API void draw_rectangle(Rect rect, Color color);
 | 
					TWN_API void draw_rectangle(Rect rect, Color color);
 | 
				
			||||||
@@ -28,67 +28,90 @@ TWN_API void draw_circle(Vec2 position, float radius, Color color);
 | 
				
			|||||||
/* TODO: have font optional, with something minimal coming embedded */
 | 
					/* TODO: have font optional, with something minimal coming embedded */
 | 
				
			||||||
TWN_API void draw_text(char const *string,
 | 
					TWN_API void draw_text(char const *string,
 | 
				
			||||||
                       Vec2        position,
 | 
					                       Vec2        position,
 | 
				
			||||||
                       int         height_px,       /* optional, default: 22 */
 | 
					                       float       height,              /* optional, default: 22 */
 | 
				
			||||||
                       Color       color,               /* optional, default: all 0 */
 | 
					                       Color       color,               /* optional, default: all 0 */
 | 
				
			||||||
                       char const *font);
 | 
					                       char const *font);               /* optional, default: NULL */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TWN_API int draw_text_width(char const *string,
 | 
					TWN_API float draw_text_width(char const *string,
 | 
				
			||||||
                            int         height_px,  /* TODO: make optional */
 | 
					                              float       height,       /* optional, default: 22 */
 | 
				
			||||||
                            char const *font);
 | 
					                              char const *font);        /* optional, default: NULL */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TWN_API void draw_9slice(char const *texture_path,
 | 
					TWN_API void draw_nine_slice(char const *texture,
 | 
				
			||||||
                         int         texture_w,
 | 
					                             Vec2        corners,
 | 
				
			||||||
                         int         texture_h,
 | 
					 | 
				
			||||||
                         int         border_thickness,
 | 
					 | 
				
			||||||
                             Rect        rect,
 | 
					                             Rect        rect,
 | 
				
			||||||
                         Color       color);        /* TODO: make optional */
 | 
					                             float       border_thickness,  /* optional, default: 0 */
 | 
				
			||||||
 | 
					                             Color       color);            /* optional, default: all 255 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TWN_API void draw_line(Vec2 start,
 | 
				
			||||||
 | 
					                       Vec2 finish,
 | 
				
			||||||
 | 
					                       float thickness, /* optional, default: 1 */
 | 
				
			||||||
 | 
					                       Color color);    /* optional, default: all 255 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* TODO: combine with draw_rectangle()? */
 | 
				
			||||||
 | 
					TWN_API void draw_box(Rect rect,
 | 
				
			||||||
 | 
					                      float thickness,  /* optional, default: 1 */
 | 
				
			||||||
 | 
					                      Color color);     /* optional, default: all 255 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* pushes a textured 3d triangle onto the render queue */
 | 
					/* pushes a textured 3d triangle onto the render queue */
 | 
				
			||||||
/* vertices are in absolute coordinates, relative to world origin */
 | 
					 | 
				
			||||||
/* texture coordinates are in pixels */
 | 
					/* texture coordinates are in pixels */
 | 
				
			||||||
TWN_API void draw_triangle(char const *path,
 | 
					TWN_API void draw_triangle(char const *texture,
 | 
				
			||||||
                           Vec3        v0,
 | 
					                           Vec3        v0,
 | 
				
			||||||
                           Vec3        v1,
 | 
					                           Vec3        v1,
 | 
				
			||||||
                           Vec3        v2,
 | 
					                           Vec3        v2,
 | 
				
			||||||
                           Vec2        uv0,
 | 
					                           Vec2        uv0,
 | 
				
			||||||
                           Vec2        uv1,
 | 
					                           Vec2        uv1,
 | 
				
			||||||
                           Vec2        uv2);
 | 
					                           Vec2        uv2,
 | 
				
			||||||
 | 
					                           Color       c0,      /* optional, default: all 255 */
 | 
				
			||||||
 | 
					                           Color       c1,      /* optional, default: all 255 */
 | 
				
			||||||
 | 
					                           Color       c2);     /* optional, default: all 255 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: decide whether it's needed to begin with?
 | 
					/* TODO: double sided option */
 | 
				
			||||||
//       intended usage for it is baked lighting, i would think.
 | 
					TWN_API void draw_quad(char const *texture,
 | 
				
			||||||
/* pushes a colored textured 3d triangle onto the render queue */
 | 
					                       Vec3        v0,          /* upper-left */
 | 
				
			||||||
// void unfurl_colored_triangle(const char *path,
 | 
					                       Vec3        v1,          /* bottom-left */
 | 
				
			||||||
//                              Vec3 v0,
 | 
					                       Vec3        v2,          /* bottom-right */
 | 
				
			||||||
//                              Vec3 v1,
 | 
					                       Vec3        v3,          /* upper-right */
 | 
				
			||||||
//                              Vec3 v2,
 | 
					                       Rect        texture_region,
 | 
				
			||||||
//                              Vec2sh uv0,
 | 
					                       Color       color);      /* optional, default: all 255 */
 | 
				
			||||||
//                              Vec2sh uv1,
 | 
					 | 
				
			||||||
//                              Vec2sh uv2,
 | 
					 | 
				
			||||||
//                              Color c0,
 | 
					 | 
				
			||||||
//                              Color c1,
 | 
					 | 
				
			||||||
//                              Color c2);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO:
 | 
					TWN_API void draw_billboard(const char *texture,
 | 
				
			||||||
// http://www.lighthouse3d.com/opengl/billboarding/index.php?billCheat2
 | 
					                            Vec3 position,
 | 
				
			||||||
// void unfurl_billboard(const char *path,
 | 
					                            Vec2 size,
 | 
				
			||||||
//                       Vec2 position,
 | 
					                            Color color,        /* optional, default: all 255 */
 | 
				
			||||||
//                       Vec2 scaling,
 | 
					                            bool cylindrical);  /* optional, default: false */
 | 
				
			||||||
//                       Rect uvs);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* pushes a camera state to be used for all future unfurl_* commands */
 | 
					/* sets a perspective 3d camera to be used for all 3d commands */
 | 
				
			||||||
TWN_API void draw_camera(const Camera *camera);
 | 
					/* fov = 0 corresponds to orthographic projection */
 | 
				
			||||||
 | 
					TWN_API void draw_camera(Vec3 position,
 | 
				
			||||||
 | 
					                         Vec3 direction,        /* optional, default: (0, 0, -1) */
 | 
				
			||||||
 | 
					                         Vec3 up,               /* optional, default: (0, 1, 0) */
 | 
				
			||||||
 | 
					                         float fov,             /* optional, default: PI / 6 * 3 (90 degrees) */
 | 
				
			||||||
 | 
					                         float zoom);           /* optional, default: 1 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* 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 roll,   /* optional, default: 0 */
 | 
				
			||||||
 | 
					                                float pitch,  /* optional, default: 0 */
 | 
				
			||||||
 | 
					                                float yaw,    /* optional, default: 0 */
 | 
				
			||||||
 | 
					                                float fov,    /* optional, default: PI / 6 * 3 (90 degrees) */
 | 
				
			||||||
 | 
					                                float zoom);  /* optional, default: 1 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* expects '*' masks that will be expanded to 6 names: 'up', 'down', 'east', 'west', 'north' and 'south' */
 | 
					/* expects '*' masks that will be expanded to 6 names: 'up', 'down', 'east', 'west', 'north' and 'south' */
 | 
				
			||||||
TWN_API void draw_skybox(const char *paths);
 | 
					TWN_API void draw_skybox(const char *textures);
 | 
				
			||||||
 | 
					 | 
				
			||||||
TWN_API void draw_fog(float start, float end, float density, Color color);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifndef TWN_NOT_C
 | 
					#ifndef TWN_NOT_C
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct DrawSpriteArgs {
 | 
					typedef struct DrawSpriteArgs {
 | 
				
			||||||
    char const *path;
 | 
					    char const *texture;
 | 
				
			||||||
    Rect rect;
 | 
					    Rect rect;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    m_option_list(
 | 
					    m_option_list(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,9 @@
 | 
				
			|||||||
#ifndef TWN_ENGINE_API_H
 | 
					#ifndef TWN_ENGINE_API_H
 | 
				
			||||||
#define TWN_ENGINE_API_H
 | 
					#define TWN_ENGINE_API_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#if defined(__WIN32)
 | 
					#if defined(TWN_NOT_C)
 | 
				
			||||||
 | 
						#define TWN_API
 | 
				
			||||||
 | 
					#elif defined(__WIN32)
 | 
				
			||||||
	#define TWN_API __declspec(dllexport)
 | 
						#define TWN_API __declspec(dllexport)
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
	#define TWN_API __attribute__((visibility("default")))
 | 
						#define TWN_API __attribute__((visibility("default")))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,11 +3,11 @@
 | 
				
			|||||||
#define TWN_GAME_API_H
 | 
					#define TWN_GAME_API_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "twn_input.h"
 | 
					#include "twn_input.h"
 | 
				
			||||||
#include "twn_context.h"
 | 
					 | 
				
			||||||
#include "twn_draw.h"
 | 
					#include "twn_draw.h"
 | 
				
			||||||
#include "twn_audio.h"
 | 
					#include "twn_audio.h"
 | 
				
			||||||
#include "twn_engine_api.h"
 | 
					#include "twn_engine_api.h"
 | 
				
			||||||
#include "twn_util.h"
 | 
					#include "twn_util.h"
 | 
				
			||||||
 | 
					#include "twn_context.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifndef TWN_NOT_C
 | 
					#ifndef TWN_NOT_C
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,19 +10,10 @@
 | 
				
			|||||||
#include <stddef.h>
 | 
					#include <stddef.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TWN_API void input_bind_action_control(const char *action_name, Control control);
 | 
					TWN_API void input_action(const char *name, Control control);
 | 
				
			||||||
TWN_API void input_unbind_action_control(const char *action_name, Control control);
 | 
					TWN_API bool input_action_pressed(const char *name);
 | 
				
			||||||
 | 
					TWN_API bool input_action_just_pressed(const char *name);
 | 
				
			||||||
TWN_API void input_add_action(const char *action_name);
 | 
					TWN_API bool input_action_just_released(const char *name);
 | 
				
			||||||
TWN_API void input_delete_action(const char *action_name);
 | 
					TWN_API Vec2 input_action_position(const char *name);
 | 
				
			||||||
 | 
					 | 
				
			||||||
TWN_API bool input_is_action_pressed(const char *action_name);
 | 
					 | 
				
			||||||
TWN_API bool input_is_action_just_pressed(const char *action_name);
 | 
					 | 
				
			||||||
TWN_API bool input_is_action_just_released(const char *action_name);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
TWN_API Vec2 input_get_action_position(const char *action_name);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
TWN_API void input_set_mouse_captured(bool enabled);
 | 
					 | 
				
			||||||
TWN_API bool input_is_mouse_captured(void);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,8 @@ typedef enum TextureMode {
 | 
				
			|||||||
    TEXTURE_MODE_OPAQUE,        /* all pixels are solid */
 | 
					    TEXTURE_MODE_OPAQUE,        /* all pixels are solid */
 | 
				
			||||||
    TEXTURE_MODE_SEETHROUGH,  	/* some pixels are alpha zero */
 | 
					    TEXTURE_MODE_SEETHROUGH,  	/* some pixels are alpha zero */
 | 
				
			||||||
    TEXTURE_MODE_GHOSTLY,       /* arbitrary alpha values */
 | 
					    TEXTURE_MODE_GHOSTLY,       /* arbitrary alpha values */
 | 
				
			||||||
 | 
					    TEXTURE_MODE_COUNT,
 | 
				
			||||||
 | 
					    TEXTURE_MODE_UNKNOWN = -1,  /* a sentinel */
 | 
				
			||||||
} TextureMode;
 | 
					} TextureMode;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,13 +6,6 @@
 | 
				
			|||||||
/* plain data aggregates that are accepted between public procedure boundaries */
 | 
					/* plain data aggregates that are accepted between public procedure boundaries */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* a point in some space (integer) */
 | 
					 | 
				
			||||||
typedef struct Vec2i {
 | 
					 | 
				
			||||||
    int32_t x;
 | 
					 | 
				
			||||||
    int32_t y;
 | 
					 | 
				
			||||||
} Vec2i;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* a point in some space (floating point) */
 | 
					/* a point in some space (floating point) */
 | 
				
			||||||
typedef struct Vec2 {
 | 
					typedef struct Vec2 {
 | 
				
			||||||
    float x;
 | 
					    float x;
 | 
				
			||||||
@@ -29,16 +22,6 @@ typedef struct Vec3 {
 | 
				
			|||||||
} Vec3;
 | 
					} Vec3;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* a point in some three dimension space (floating point) */
 | 
					 | 
				
			||||||
/* y goes up, x goes to the right */
 | 
					 | 
				
			||||||
typedef struct Vec4 {
 | 
					 | 
				
			||||||
    float x;
 | 
					 | 
				
			||||||
    float y;
 | 
					 | 
				
			||||||
    float z;
 | 
					 | 
				
			||||||
    float w;
 | 
					 | 
				
			||||||
} Vec4;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* 32-bit color data */
 | 
					/* 32-bit color data */
 | 
				
			||||||
typedef struct Color {
 | 
					typedef struct Color {
 | 
				
			||||||
    uint8_t r;
 | 
					    uint8_t r;
 | 
				
			||||||
@@ -48,15 +31,6 @@ typedef struct Color {
 | 
				
			|||||||
} Color;
 | 
					} Color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* a rectangle with the origin at the upper left (integer) */
 | 
					 | 
				
			||||||
typedef struct Recti {
 | 
					 | 
				
			||||||
    int32_t x;
 | 
					 | 
				
			||||||
    int32_t y;
 | 
					 | 
				
			||||||
    int32_t w;
 | 
					 | 
				
			||||||
    int32_t h;
 | 
					 | 
				
			||||||
} Recti;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* a rectangle with the origin at the upper left (floating point) */
 | 
					/* a rectangle with the origin at the upper left (floating point) */
 | 
				
			||||||
typedef struct Rect {
 | 
					typedef struct Rect {
 | 
				
			||||||
    float x;
 | 
					    float x;
 | 
				
			||||||
@@ -66,9 +40,4 @@ typedef struct Rect {
 | 
				
			|||||||
} Rect;
 | 
					} Rect;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct Matrix4 {
 | 
					 | 
				
			||||||
    Vec4 row[4];
 | 
					 | 
				
			||||||
} Matrix4;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,74 +24,68 @@
 | 
				
			|||||||
    TWN_API void *crealloc(void *ptr, size_t size);
 | 
					    TWN_API void *crealloc(void *ptr, size_t size);
 | 
				
			||||||
    TWN_API void *ccalloc(size_t num, size_t size);
 | 
					    TWN_API void *ccalloc(size_t num, size_t size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    TWN_API void log_info(const char *restrict format, ...);
 | 
				
			||||||
 | 
					    TWN_API void log_critical(const char *restrict format, ...);
 | 
				
			||||||
 | 
					    TWN_API void log_warn(const char *restrict format, ...);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* saves all texture atlases as BMP files in the write directory */
 | 
				
			||||||
 | 
					    TWN_API void textures_dump_atlases(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* returns true if str ends with suffix */
 | 
				
			||||||
 | 
					    TWN_API bool strends(const char *str, const char *suffix);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* TODO: this is why generics were invented. sorry, i'm tired today */
 | 
				
			||||||
 | 
					    TWN_API double clamp(double d, double min, double max);
 | 
				
			||||||
 | 
					    TWN_API float clampf(float f, float min, float max);
 | 
				
			||||||
 | 
					    TWN_API int clampi(int i, int min, int max);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* sets buf_out to a pointer to a byte buffer which must be freed. */
 | 
				
			||||||
 | 
					    /* returns the size of this buffer. */
 | 
				
			||||||
 | 
					    TWN_API int64_t file_to_bytes(const char *path, unsigned char **buf_out);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* returns a pointer to a string which must be freed */
 | 
				
			||||||
 | 
					    TWN_API char *file_to_str(const char *path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* returns true if the file exists in the filesystem */
 | 
				
			||||||
 | 
					    TWN_API bool file_exists(const char *path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif /* TWN_NOT_C */
 | 
					#endif /* TWN_NOT_C */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* calculates the overlap of two rectangles */
 | 
				
			||||||
TWN_API void log_info(const char *restrict format, ...);
 | 
					TWN_API Rect rect_overlap(Rect a, Rect b);
 | 
				
			||||||
TWN_API void log_critical(const char *restrict format, ...);
 | 
					 | 
				
			||||||
TWN_API void log_warn(const char *restrict format, ...);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* TODO: this is why generics were invented. sorry, i'm tired today */
 | 
					 | 
				
			||||||
TWN_API double clamp(double d, double min, double max);
 | 
					 | 
				
			||||||
TWN_API float clampf(float f, float min, float max);
 | 
					 | 
				
			||||||
TWN_API int clampi(int i, int min, int max);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* sets buf_out to a pointer to a byte buffer which must be freed. */
 | 
					 | 
				
			||||||
/* returns the size of this buffer. */
 | 
					 | 
				
			||||||
TWN_API int64_t file_to_bytes(const char *path, unsigned char **buf_out);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* returns a pointer to a string which must be freed */
 | 
					 | 
				
			||||||
TWN_API char *file_to_str(const char *path);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* returns true if the file exists in the filesystem */
 | 
					 | 
				
			||||||
TWN_API bool file_exists(const char *path);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* saves all texture atlases as BMP files in the write directory */
 | 
					 | 
				
			||||||
TWN_API void textures_dump_atlases(void);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* returns true if str ends with suffix */
 | 
					 | 
				
			||||||
TWN_API TWN_API bool strends(const char *str, const char *suffix);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/*                      */
 | 
					 | 
				
			||||||
/* GAME LOGIC UTILITIES */
 | 
					 | 
				
			||||||
/*                      */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* calculates the overlap of two rectangles and places it in result. */
 | 
					 | 
				
			||||||
/* result may be NULL. if this is the case, it will simply be ignored. */
 | 
					 | 
				
			||||||
/* returns true if the rectangles are indeed intersecting. */
 | 
					 | 
				
			||||||
TWN_API bool overlap_rect(const Recti *a, const Recti *b, Recti *result);
 | 
					 | 
				
			||||||
TWN_API bool overlap_frect(const Rect *a, const Rect *b, Rect *result);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* returns true if two rectangles are intersecting */
 | 
					/* returns true if two rectangles are intersecting */
 | 
				
			||||||
TWN_API bool intersect_rect(const Recti *a, const Recti *b);
 | 
					TWN_API bool rect_intersects(Rect a, Rect b);
 | 
				
			||||||
TWN_API bool intersect_frect(const Rect *a, const Rect *b);
 | 
					TWN_API Vec2 rect_center(Rect rect);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* TODO: generics and specials (see m_vec2_from() for an example)*/
 | 
					/* decrements an integer value, stopping at 0 */
 | 
				
			||||||
TWN_API Rect to_frect(Recti rect);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
TWN_API Vec2 frect_center(Rect rect);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* decrements an lvalue (which should be an int), stopping at 0 */
 | 
					 | 
				
			||||||
/* meant for tick-based timers in game logic */
 | 
					/* meant for tick-based timers in game logic */
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 * example:
 | 
					 * example:
 | 
				
			||||||
 * tick_timer(&player->jump_air_timer);
 | 
					 * tick_timer(&player->jump_air_timer);
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
TWN_API void tick_timer(int *value);
 | 
					TWN_API int32_t timer_tick_frames(int32_t frames_left);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* decrements a floating point second-based timer, stopping at 0.0 */
 | 
					/* decrements a floating point second-based timer, stopping at 0.0f */
 | 
				
			||||||
/* meant for poll based real time logic in game logic */
 | 
					/* meant for poll based real time logic in game logic */
 | 
				
			||||||
/* note that it should be decremented only on the next tick after its creation */
 | 
					/* note that it should be decremented only on the next tick after its creation */
 | 
				
			||||||
TWN_API void tick_ftimer(float *value);
 | 
					TWN_API float timer_tick_seconds(float seconds_left);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* same as `tick_ftimer` but instead of clamping it repeats */
 | 
					typedef struct TimerElapseFramesResult {
 | 
				
			||||||
/* returns true if value was cycled */
 | 
					    bool elapsed; int32_t frames_left;
 | 
				
			||||||
TWN_API bool repeat_ftimer(float *value, float at);
 | 
					} TimerElapseFramesResult;
 | 
				
			||||||
 | 
					TWN_API TimerElapseFramesResult timer_elapse_frames(int32_t frames_left, int32_t interval);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct TimerElapseSecondsResult {
 | 
				
			||||||
 | 
					    bool elapsed; float seconds_left;
 | 
				
			||||||
 | 
					} TimerElapseSecondsResult;
 | 
				
			||||||
 | 
					TWN_API TimerElapseSecondsResult timer_elapse_seconds(float seconds_left, float interval);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TWN_API void log_vec2(Vec2 value, char const *identity);
 | 
				
			||||||
 | 
					TWN_API void log_vec3(Vec3 value, char const *identity);
 | 
				
			||||||
 | 
					TWN_API void log_rect(Rect value, char const *identity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TWN_API void profile_start(char profile[const static 1]);
 | 
				
			||||||
 | 
					TWN_API void profile_end(char profile[const static 1]);
 | 
				
			||||||
 | 
					TWN_API void profile_list_stats(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,13 +9,28 @@
 | 
				
			|||||||
#include <math.h>
 | 
					#include <math.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* aren't macros to prevent double evaluation with side effects */
 | 
					static inline Vec2 vec2_add(Vec2 a, Vec2 b) {
 | 
				
			||||||
/* maybe could be inlined? i hope LTO will resolve this */
 | 
					    return (Vec2) { a.x + b.x, a.y + b.y };
 | 
				
			||||||
static inline Vec2 vec2_from_vec2i(Vec2i vec) {
 | 
					}
 | 
				
			||||||
    return (Vec2) {
 | 
					
 | 
				
			||||||
        .x = (float)vec.x,
 | 
					static inline Vec2 vec2_sub(Vec2 a, Vec2 b) {
 | 
				
			||||||
        .y = (float)vec.y,
 | 
					    return (Vec2) { a.x - b.x, a.y - b.y };
 | 
				
			||||||
    };
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static inline Vec2 vec2_div(Vec2 a, Vec2 b) {
 | 
				
			||||||
 | 
					    return (Vec2) { a.x / b.x, a.y / b.y };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static inline Vec2 vec2_mul(Vec2 a, Vec2 b) {
 | 
				
			||||||
 | 
					    return (Vec2) { a.x * b.x, a.y * b.y };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static inline Vec2 vec2_scale(Vec2 a, float s) {
 | 
				
			||||||
 | 
					    return (Vec2) { a.x * s, a.y * s };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static inline float vec2_length(Vec2 a) {
 | 
				
			||||||
 | 
					    return sqrtf(a.x * a.x + a.y * a.y);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static inline Vec3 vec3_add(Vec3 a, Vec3 b) {
 | 
					static inline Vec3 vec3_add(Vec3 a, Vec3 b) {
 | 
				
			||||||
@@ -26,12 +41,12 @@ static inline Vec3 vec3_sub(Vec3 a, Vec3 b) {
 | 
				
			|||||||
    return (Vec3) { a.x - b.x, a.y - b.y, a.z - b.z };
 | 
					    return (Vec3) { a.x - b.x, a.y - b.y, a.z - b.z };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static inline Vec2 vec2_div(Vec2 a, Vec2 b) {
 | 
					static inline Vec3 vec3_div(Vec3 a, Vec3 b) {
 | 
				
			||||||
    return (Vec2) { a.x / b.x, a.y / b.y };
 | 
					    return (Vec3) { a.x / b.x, a.y / b.y, a.z / b.z };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static inline Vec2 vec2_scale(Vec2 a, float s) {
 | 
					static inline Vec3 vec3_mul(Vec3 a, Vec3 b) {
 | 
				
			||||||
    return (Vec2) { a.x * s, a.y * s };
 | 
					    return (Vec3) { a.x * b.x, a.y * b.y, a.z * b.z };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static inline Vec3 vec3_scale(Vec3 a, float s) {
 | 
					static inline Vec3 vec3_scale(Vec3 a, float s) {
 | 
				
			||||||
@@ -42,6 +57,10 @@ static inline float vec3_dot(Vec3 a, Vec3 b) {
 | 
				
			|||||||
    return a.x * b.x + a.y * b.y + a.z * b.z;
 | 
					    return a.x * b.x + a.y * b.y + a.z * b.z;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static inline float vec3_length(Vec3 a) {
 | 
				
			||||||
 | 
					    return sqrtf(a.x * a.x + a.y * a.y + a.z * a.z);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static inline Vec3 vec3_cross(Vec3 a, Vec3 b) {
 | 
					static inline Vec3 vec3_cross(Vec3 a, Vec3 b) {
 | 
				
			||||||
    return (Vec3) {
 | 
					    return (Vec3) {
 | 
				
			||||||
        a.y * b.z - a.z * b.y,
 | 
					        a.y * b.z - a.z * b.y,
 | 
				
			||||||
@@ -83,16 +102,26 @@ static inline Vec3 vec3_rotate(Vec3 v, float angle, Vec3 axis) {
 | 
				
			|||||||
    return v;
 | 
					    return v;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define m_vec2_from(p_any_vec2) (_Generic((p_any_vec2),       \
 | 
					
 | 
				
			||||||
                                    Vec2i:  vec2_from_vec2i,  \
 | 
					/* TODO: remove. */
 | 
				
			||||||
                                )(p_any_vec2))
 | 
					#define m_vec_add(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0),   \
 | 
				
			||||||
 | 
					                                    Vec2:   vec2_add,               \
 | 
				
			||||||
 | 
					                                    Vec3:   vec3_add                \
 | 
				
			||||||
 | 
					                                )(p_any_vec0, p_any_vec1))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define m_vec_sub(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0),   \
 | 
					#define m_vec_sub(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0),   \
 | 
				
			||||||
 | 
					                                    Vec2:   vec2_sub,               \
 | 
				
			||||||
                                    Vec3:   vec3_sub                \
 | 
					                                    Vec3:   vec3_sub                \
 | 
				
			||||||
                                )(p_any_vec0, p_any_vec1))
 | 
					                                )(p_any_vec0, p_any_vec1))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define m_vec_div(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0),   \
 | 
					#define m_vec_div(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0),   \
 | 
				
			||||||
                                    Vec2:   vec2_div                \
 | 
					                                    Vec2:   vec2_div,               \
 | 
				
			||||||
 | 
					                                    Vec3:   vec3_div                \
 | 
				
			||||||
 | 
					                                )(p_any_vec0, p_any_vec1))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define m_vec_mul(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0),   \
 | 
				
			||||||
 | 
					                                    Vec2:   vec2_mul,               \
 | 
				
			||||||
 | 
					                                    Vec3:   vec3_mul                \
 | 
				
			||||||
                                )(p_any_vec0, p_any_vec1))
 | 
					                                )(p_any_vec0, p_any_vec1))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define m_vec_scale(p_any_vec, p_any_scalar) (_Generic((p_any_vec),    \
 | 
					#define m_vec_scale(p_any_vec, p_any_scalar) (_Generic((p_any_vec),    \
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										565
									
								
								share/twn_api.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										565
									
								
								share/twn_api.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,565 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "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": "direction", "type": "Vec3", "default": { "x": 0, "y": 0, "z": -1 } },
 | 
				
			||||||
 | 
					                { "name": "up", "type": "Vec3", "default": { "x": 0, "y": 1, "z": 0 } },
 | 
				
			||||||
 | 
					                { "name": "fov", "type": "float", "default": 1.57079632679 },
 | 
				
			||||||
 | 
					                { "name": "zoom", "type": "float", "default": 1 }
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        "draw_camera_from_principal_axes": {
 | 
				
			||||||
 | 
					            "module": "draw",
 | 
				
			||||||
 | 
					            "symbol": "camera_from_principal_axes",
 | 
				
			||||||
 | 
					            "header": "twn_draw.h",
 | 
				
			||||||
 | 
					            "params": [
 | 
				
			||||||
 | 
					                { "name": "position", "type": "Vec3" },
 | 
				
			||||||
 | 
					                { "name": "roll", "type": "float", "default": 0 },
 | 
				
			||||||
 | 
					                { "name": "pitch", "type": "float", "default": 0 },
 | 
				
			||||||
 | 
					                { "name": "yaw", "type": "float", "default": 0 },
 | 
				
			||||||
 | 
					                { "name": "fov", "type": "float", "default": 1.57079632679 },
 | 
				
			||||||
 | 
					                { "name": "zoom", "type": "float", "default": 1 }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "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": "loops", "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" }
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        "log_vec2": {
 | 
				
			||||||
 | 
					            "module": "util",
 | 
				
			||||||
 | 
					            "symbol": "log_vec2",
 | 
				
			||||||
 | 
					            "header": "twn_util.h",
 | 
				
			||||||
 | 
					            "params": [
 | 
				
			||||||
 | 
					                { "name": "value", "type": "Vec2" },
 | 
				
			||||||
 | 
					                { "name": "identity", "type": "char *" }
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        "log_vec3": {
 | 
				
			||||||
 | 
					            "module": "util",
 | 
				
			||||||
 | 
					            "symbol": "log_vec3",
 | 
				
			||||||
 | 
					            "header": "twn_util.h",
 | 
				
			||||||
 | 
					            "params": [
 | 
				
			||||||
 | 
					                { "name": "value", "type": "Vec3" },
 | 
				
			||||||
 | 
					                { "name": "identity", "type": "char *" }
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        "log_rect": {
 | 
				
			||||||
 | 
					            "module": "util",
 | 
				
			||||||
 | 
					            "symbol": "log_rect",
 | 
				
			||||||
 | 
					            "header": "twn_util.h",
 | 
				
			||||||
 | 
					            "params": [
 | 
				
			||||||
 | 
					                { "name": "value", "type": "Rect" },
 | 
				
			||||||
 | 
					                { "name": "identity", "type": "char *" }
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    "types": {
 | 
				
			||||||
 | 
					        "Vec2": {
 | 
				
			||||||
 | 
					            "fields": [
 | 
				
			||||||
 | 
					                { "name": "x", "type": "float" },
 | 
				
			||||||
 | 
					                { "name": "y", "type": "float" }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "c_type": "Vec2"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        "Vec3": {
 | 
				
			||||||
 | 
					            "fields": [
 | 
				
			||||||
 | 
					                { "name": "x", "type": "float" },
 | 
				
			||||||
 | 
					                { "name": "y", "type": "float" },
 | 
				
			||||||
 | 
					                { "name": "z", "type": "float" }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "c_type": "Vec3"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        "Color": {
 | 
				
			||||||
 | 
					            "fields": [
 | 
				
			||||||
 | 
					                { "name": "r", "type": "uint8_t" },
 | 
				
			||||||
 | 
					                { "name": "g", "type": "uint8_t" },
 | 
				
			||||||
 | 
					                { "name": "b", "type": "uint8_t" },
 | 
				
			||||||
 | 
					                { "name": "a", "type": "uint8_t" }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "c_type": "Color"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        "Rect": {
 | 
				
			||||||
 | 
					            "fields": [
 | 
				
			||||||
 | 
					                { "name": "x", "type": "float" },
 | 
				
			||||||
 | 
					                { "name": "y", "type": "float" },
 | 
				
			||||||
 | 
					                { "name": "w", "type": "float" },
 | 
				
			||||||
 | 
					                { "name": "h", "type": "float" }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "c_type": "Rect"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        "Context": {
 | 
				
			||||||
 | 
					            "fields": [
 | 
				
			||||||
 | 
					                { "name": "frame_number", "type": "float" },
 | 
				
			||||||
 | 
					                { "name": "frame_duration", "type": "float" },
 | 
				
			||||||
 | 
					                { "name": "fog_density", "type": "float" },
 | 
				
			||||||
 | 
					                { "name": "fog_color", "type": "Color" },
 | 
				
			||||||
 | 
					                { "name": "resolution", "type": "Vec2" },
 | 
				
			||||||
 | 
					                { "name": "mouse_position", "type": "Vec2" },
 | 
				
			||||||
 | 
					                { "name": "mouse_movement", "type": "Vec2" },
 | 
				
			||||||
 | 
					                { "name": "random_seed", "type": "float" },
 | 
				
			||||||
 | 
					                { "name": "debug", "type": "bool" },
 | 
				
			||||||
 | 
					                { "name": "initialization_needed", "type": "bool" },
 | 
				
			||||||
 | 
					                { "name": "mouse_capture", "type": "bool" }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "c_type": "Context"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        "Control": {
 | 
				
			||||||
 | 
					            "enums": {
 | 
				
			||||||
 | 
					                "A": 4,
 | 
				
			||||||
 | 
					                "B": 5,
 | 
				
			||||||
 | 
					                "C": 6,
 | 
				
			||||||
 | 
					                "D": 7,
 | 
				
			||||||
 | 
					                "E": 8,
 | 
				
			||||||
 | 
					                "F": 9,
 | 
				
			||||||
 | 
					                "G": 10,
 | 
				
			||||||
 | 
					                "H": 11,
 | 
				
			||||||
 | 
					                "I": 12,
 | 
				
			||||||
 | 
					                "J": 13,
 | 
				
			||||||
 | 
					                "K": 14,
 | 
				
			||||||
 | 
					                "L": 15,
 | 
				
			||||||
 | 
					                "M": 16,
 | 
				
			||||||
 | 
					                "N": 17,
 | 
				
			||||||
 | 
					                "O": 18,
 | 
				
			||||||
 | 
					                "P": 19,
 | 
				
			||||||
 | 
					                "Q": 20,
 | 
				
			||||||
 | 
					                "R": 21,
 | 
				
			||||||
 | 
					                "S": 22,
 | 
				
			||||||
 | 
					                "T": 23,
 | 
				
			||||||
 | 
					                "U": 24,
 | 
				
			||||||
 | 
					                "V": 25,
 | 
				
			||||||
 | 
					                "W": 26,
 | 
				
			||||||
 | 
					                "X": 27,
 | 
				
			||||||
 | 
					                "Y": 28,
 | 
				
			||||||
 | 
					                "Z": 29,
 | 
				
			||||||
 | 
					                "1": 30,
 | 
				
			||||||
 | 
					                "2": 31,
 | 
				
			||||||
 | 
					                "3": 32,
 | 
				
			||||||
 | 
					                "4": 33,
 | 
				
			||||||
 | 
					                "5": 34,
 | 
				
			||||||
 | 
					                "6": 35,
 | 
				
			||||||
 | 
					                "7": 36,
 | 
				
			||||||
 | 
					                "8": 37,
 | 
				
			||||||
 | 
					                "9": 38,
 | 
				
			||||||
 | 
					                "0": 39,
 | 
				
			||||||
 | 
					                "RETURN": 40,
 | 
				
			||||||
 | 
					                "ESCAPE": 41,
 | 
				
			||||||
 | 
					                "BACKSPACE": 42,
 | 
				
			||||||
 | 
					                "TAB": 43,
 | 
				
			||||||
 | 
					                "SPACE": 44,
 | 
				
			||||||
 | 
					                "MINUS": 45,
 | 
				
			||||||
 | 
					                "EQUALS": 46,
 | 
				
			||||||
 | 
					                "LEFTBRACKET": 47,
 | 
				
			||||||
 | 
					                "RIGHTBRACKET": 48,
 | 
				
			||||||
 | 
					                "BACKSLASH": 49,
 | 
				
			||||||
 | 
					                "NONUSHASH": 50,
 | 
				
			||||||
 | 
					                "SEMICOLON": 51,
 | 
				
			||||||
 | 
					                "APOSTROPHE": 52,
 | 
				
			||||||
 | 
					                "GRAVE": 53,
 | 
				
			||||||
 | 
					                "COMMA": 54,
 | 
				
			||||||
 | 
					                "PERIOD": 55,
 | 
				
			||||||
 | 
					                "SLASH": 56,
 | 
				
			||||||
 | 
					                "CAPSLOCK": 57,
 | 
				
			||||||
 | 
					                "F1": 58,
 | 
				
			||||||
 | 
					                "F2": 59,
 | 
				
			||||||
 | 
					                "F3": 60,
 | 
				
			||||||
 | 
					                "F4": 61,
 | 
				
			||||||
 | 
					                "F5": 62,
 | 
				
			||||||
 | 
					                "F6": 63,
 | 
				
			||||||
 | 
					                "F7": 64,
 | 
				
			||||||
 | 
					                "F8": 65,
 | 
				
			||||||
 | 
					                "F9": 66,
 | 
				
			||||||
 | 
					                "F10": 67,
 | 
				
			||||||
 | 
					                "F11": 68,
 | 
				
			||||||
 | 
					                "F12": 69,
 | 
				
			||||||
 | 
					                "PRINTSCREEN": 70,
 | 
				
			||||||
 | 
					                "SCROLLLOCK": 71,
 | 
				
			||||||
 | 
					                "PAUSE": 72,
 | 
				
			||||||
 | 
					                "INSERT": 73,
 | 
				
			||||||
 | 
					                "HOME": 74,
 | 
				
			||||||
 | 
					                "PAGEUP": 75,
 | 
				
			||||||
 | 
					                "DELETE": 76,
 | 
				
			||||||
 | 
					                "END": 77,
 | 
				
			||||||
 | 
					                "PAGEDOWN": 78,
 | 
				
			||||||
 | 
					                "RIGHT": 79,
 | 
				
			||||||
 | 
					                "LEFT": 80,
 | 
				
			||||||
 | 
					                "DOWN": 81,
 | 
				
			||||||
 | 
					                "UP": 82,
 | 
				
			||||||
 | 
					                "NUMLOCKCLEAR": 83,
 | 
				
			||||||
 | 
					                "KP_DIVIDE": 84,
 | 
				
			||||||
 | 
					                "KP_MULTIPLY": 85,
 | 
				
			||||||
 | 
					                "KP_MINUS": 86,
 | 
				
			||||||
 | 
					                "KP_PLUS": 87,
 | 
				
			||||||
 | 
					                "KP_ENTER": 88,
 | 
				
			||||||
 | 
					                "KP_1": 89,
 | 
				
			||||||
 | 
					                "KP_2": 90,
 | 
				
			||||||
 | 
					                "KP_3": 91,
 | 
				
			||||||
 | 
					                "KP_4": 92,
 | 
				
			||||||
 | 
					                "KP_5": 93,
 | 
				
			||||||
 | 
					                "KP_6": 94,
 | 
				
			||||||
 | 
					                "KP_7": 95,
 | 
				
			||||||
 | 
					                "KP_8": 96,
 | 
				
			||||||
 | 
					                "KP_9": 97,
 | 
				
			||||||
 | 
					                "KP_0": 98,
 | 
				
			||||||
 | 
					                "KP_PERIOD": 99,
 | 
				
			||||||
 | 
					                "NONUSBACKSLASH": 100,
 | 
				
			||||||
 | 
					                "APPLICATION": 101,
 | 
				
			||||||
 | 
					                "POWER": 102,
 | 
				
			||||||
 | 
					                "KP_EQUALS": 103,
 | 
				
			||||||
 | 
					                "F13": 104,
 | 
				
			||||||
 | 
					                "F14": 105,
 | 
				
			||||||
 | 
					                "F15": 106,
 | 
				
			||||||
 | 
					                "F16": 107,
 | 
				
			||||||
 | 
					                "F17": 108,
 | 
				
			||||||
 | 
					                "F18": 109,
 | 
				
			||||||
 | 
					                "F19": 110,
 | 
				
			||||||
 | 
					                "F20": 111,
 | 
				
			||||||
 | 
					                "F21": 112,
 | 
				
			||||||
 | 
					                "F22": 113,
 | 
				
			||||||
 | 
					                "F23": 114,
 | 
				
			||||||
 | 
					                "F24": 115,
 | 
				
			||||||
 | 
					                "EXECUTE": 116,
 | 
				
			||||||
 | 
					                "HELP": 117,
 | 
				
			||||||
 | 
					                "MENU": 118,
 | 
				
			||||||
 | 
					                "SELECT": 119,
 | 
				
			||||||
 | 
					                "STOP": 120,
 | 
				
			||||||
 | 
					                "AGAIN": 121,
 | 
				
			||||||
 | 
					                "UNDO": 122,
 | 
				
			||||||
 | 
					                "CUT": 123,
 | 
				
			||||||
 | 
					                "COPY": 124,
 | 
				
			||||||
 | 
					                "PASTE": 125,
 | 
				
			||||||
 | 
					                "FIND": 126,
 | 
				
			||||||
 | 
					                "MUTE": 127,
 | 
				
			||||||
 | 
					                "VOLUMEUP": 128,
 | 
				
			||||||
 | 
					                "VOLUMEDOWN": 129,
 | 
				
			||||||
 | 
					                "KP_COMMA": 133,
 | 
				
			||||||
 | 
					                "KP_EQUALSAS400": 134,
 | 
				
			||||||
 | 
					                "INTERNATIONAL1": 135,
 | 
				
			||||||
 | 
					                "INTERNATIONAL2": 136,
 | 
				
			||||||
 | 
					                "INTERNATIONAL3": 137,
 | 
				
			||||||
 | 
					                "INTERNATIONAL4": 138,
 | 
				
			||||||
 | 
					                "INTERNATIONAL5": 139,
 | 
				
			||||||
 | 
					                "INTERNATIONAL6": 140,
 | 
				
			||||||
 | 
					                "INTERNATIONAL7": 141,
 | 
				
			||||||
 | 
					                "INTERNATIONAL8": 142,
 | 
				
			||||||
 | 
					                "INTERNATIONAL9": 143,
 | 
				
			||||||
 | 
					                "LANG1": 144,
 | 
				
			||||||
 | 
					                "LANG2": 145,
 | 
				
			||||||
 | 
					                "LANG3": 146,
 | 
				
			||||||
 | 
					                "LANG4": 147,
 | 
				
			||||||
 | 
					                "LANG5": 148,
 | 
				
			||||||
 | 
					                "LANG6": 149,
 | 
				
			||||||
 | 
					                "LANG7": 150,
 | 
				
			||||||
 | 
					                "LANG8": 151,
 | 
				
			||||||
 | 
					                "LANG9": 152,
 | 
				
			||||||
 | 
					                "ALTERASE": 153,
 | 
				
			||||||
 | 
					                "SYSREQ": 154,
 | 
				
			||||||
 | 
					                "CANCEL": 155,
 | 
				
			||||||
 | 
					                "CLEAR": 156,
 | 
				
			||||||
 | 
					                "PRIOR": 157,
 | 
				
			||||||
 | 
					                "RETURN2": 158,
 | 
				
			||||||
 | 
					                "SEPARATOR": 159,
 | 
				
			||||||
 | 
					                "OUT": 160,
 | 
				
			||||||
 | 
					                "OPER": 161,
 | 
				
			||||||
 | 
					                "CLEARAGAIN": 162,
 | 
				
			||||||
 | 
					                "CRSEL": 163,
 | 
				
			||||||
 | 
					                "EXSEL": 164,
 | 
				
			||||||
 | 
					                "KP_00": 176,
 | 
				
			||||||
 | 
					                "KP_000": 177,
 | 
				
			||||||
 | 
					                "THOUSANDSSEPARATOR": 178,
 | 
				
			||||||
 | 
					                "DECIMALSEPARATOR": 179,
 | 
				
			||||||
 | 
					                "CURRENCYUNIT": 180,
 | 
				
			||||||
 | 
					                "CURRENCYSUBUNIT": 181,
 | 
				
			||||||
 | 
					                "KP_LEFTPAREN": 182,
 | 
				
			||||||
 | 
					                "KP_RIGHTPAREN": 183,
 | 
				
			||||||
 | 
					                "KP_LEFTBRACE": 184,
 | 
				
			||||||
 | 
					                "KP_RIGHTBRACE": 185,
 | 
				
			||||||
 | 
					                "KP_TAB": 186,
 | 
				
			||||||
 | 
					                "KP_BACKSPACE": 187,
 | 
				
			||||||
 | 
					                "KP_A": 188,
 | 
				
			||||||
 | 
					                "KP_B": 189,
 | 
				
			||||||
 | 
					                "KP_C": 190,
 | 
				
			||||||
 | 
					                "KP_D": 191,
 | 
				
			||||||
 | 
					                "KP_E": 192,
 | 
				
			||||||
 | 
					                "KP_F": 193,
 | 
				
			||||||
 | 
					                "KP_XOR": 194,
 | 
				
			||||||
 | 
					                "KP_POWER": 195,
 | 
				
			||||||
 | 
					                "KP_PERCENT": 196,
 | 
				
			||||||
 | 
					                "KP_LESS": 197,
 | 
				
			||||||
 | 
					                "KP_GREATER": 198,
 | 
				
			||||||
 | 
					                "KP_AMPERSAND": 199,
 | 
				
			||||||
 | 
					                "KP_DBLAMPERSAND": 200,
 | 
				
			||||||
 | 
					                "KP_VERTICALBAR": 201,
 | 
				
			||||||
 | 
					                "KP_DBLVERTICALBAR": 202,
 | 
				
			||||||
 | 
					                "KP_COLON": 203,
 | 
				
			||||||
 | 
					                "KP_HASH": 204,
 | 
				
			||||||
 | 
					                "KP_SPACE": 205,
 | 
				
			||||||
 | 
					                "KP_AT": 206,
 | 
				
			||||||
 | 
					                "KP_EXCLAM": 207,
 | 
				
			||||||
 | 
					                "KP_MEMSTORE": 208,
 | 
				
			||||||
 | 
					                "KP_MEMRECALL": 209,
 | 
				
			||||||
 | 
					                "KP_MEMCLEAR": 210,
 | 
				
			||||||
 | 
					                "KP_MEMADD": 211,
 | 
				
			||||||
 | 
					                "KP_MEMSUBTRACT": 212,
 | 
				
			||||||
 | 
					                "KP_MEMMULTIPLY": 213,
 | 
				
			||||||
 | 
					                "KP_MEMDIVIDE": 214,
 | 
				
			||||||
 | 
					                "KP_PLUSMINUS": 215,
 | 
				
			||||||
 | 
					                "KP_CLEAR": 216,
 | 
				
			||||||
 | 
					                "KP_CLEARENTRY": 217,
 | 
				
			||||||
 | 
					                "KP_BINARY": 218,
 | 
				
			||||||
 | 
					                "KP_OCTAL": 219,
 | 
				
			||||||
 | 
					                "KP_DECIMAL": 220,
 | 
				
			||||||
 | 
					                "KP_HEXADECIMAL": 221,
 | 
				
			||||||
 | 
					                "LCTRL": 224,
 | 
				
			||||||
 | 
					                "LSHIFT": 225,
 | 
				
			||||||
 | 
					                "LALT": 226,
 | 
				
			||||||
 | 
					                "LGUI": 227,
 | 
				
			||||||
 | 
					                "RCTRL": 228,
 | 
				
			||||||
 | 
					                "RSHIFT": 229,
 | 
				
			||||||
 | 
					                "RALT": 230,
 | 
				
			||||||
 | 
					                "RGUI": 231,
 | 
				
			||||||
 | 
					                "MODE": 257,
 | 
				
			||||||
 | 
					                "KBDILLUMTOGGLE": 278,
 | 
				
			||||||
 | 
					                "KBDILLUMDOWN": 279,
 | 
				
			||||||
 | 
					                "KBDILLUMUP": 280,
 | 
				
			||||||
 | 
					                "EJECT": 281,
 | 
				
			||||||
 | 
					                "SLEEP": 282,
 | 
				
			||||||
 | 
					                "APP1": 283,
 | 
				
			||||||
 | 
					                "APP2": 284,
 | 
				
			||||||
 | 
					                "AUDIOREWIND": 285,
 | 
				
			||||||
 | 
					                "AUDIOFASTFORWARD": 286,
 | 
				
			||||||
 | 
					                "SOFTLEFT": 287,
 | 
				
			||||||
 | 
					                "SOFTRIGHT": 288,
 | 
				
			||||||
 | 
					                "CALL": 289,
 | 
				
			||||||
 | 
					                "ENDCALL": 290,
 | 
				
			||||||
 | 
					                "LEFT_MOUSE": 513,
 | 
				
			||||||
 | 
					                "RIGHT_MOUSE": 515,
 | 
				
			||||||
 | 
					                "MIDDLE_MOUSE": 514,
 | 
				
			||||||
 | 
					                "X1": 516,
 | 
				
			||||||
 | 
					                "X2": 517
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
#include "twn_game_object_c.h"
 | 
					#include "twn_game_object_c.h"
 | 
				
			||||||
#include "twn_engine_context_c.h"
 | 
					#include "twn_engine_context_c.h"
 | 
				
			||||||
#include "twn_util_c.h"
 | 
					#include "twn_util_c.h"
 | 
				
			||||||
 | 
					#include "twn_util.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <x-watcher.h>
 | 
					#include <x-watcher.h>
 | 
				
			||||||
#include <SDL2/SDL.h>
 | 
					#include <SDL2/SDL.h>
 | 
				
			||||||
@@ -18,7 +19,7 @@ static void (*game_end_callback)(void);
 | 
				
			|||||||
static x_watcher *watcher;
 | 
					static x_watcher *watcher;
 | 
				
			||||||
static void *handle = NULL;
 | 
					static void *handle = NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static uint64_t last_tick_modified;
 | 
					static float last_tick_modified;
 | 
				
			||||||
static bool loaded_after_modification = true;
 | 
					static bool loaded_after_modification = true;
 | 
				
			||||||
static SDL_mutex *lock;
 | 
					static SDL_mutex *lock;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -58,7 +59,7 @@ static void load_game_object(void) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    handle = new_handle;
 | 
					    handle = new_handle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (ctx.game.frame_number != 0)
 | 
					    if (fabsf(0.0f - ctx.game.frame_number) > 0.00001f)
 | 
				
			||||||
        log_info("Game object was reloaded\n");
 | 
					        log_info("Game object was reloaded\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
#include "twn_game_object_c.h"
 | 
					#include "twn_game_object_c.h"
 | 
				
			||||||
#include "twn_engine_context_c.h"
 | 
					#include "twn_engine_context_c.h"
 | 
				
			||||||
#include "twn_util_c.h"
 | 
					#include "twn_util_c.h"
 | 
				
			||||||
 | 
					#include "twn_util.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <errhandlingapi.h>
 | 
					#include <errhandlingapi.h>
 | 
				
			||||||
#include <libloaderapi.h>
 | 
					#include <libloaderapi.h>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										185
									
								
								src/rendering/twn_billboards.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								src/rendering/twn_billboards.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,185 @@
 | 
				
			|||||||
 | 
					#include "twn_draw.h"
 | 
				
			||||||
 | 
					#include "twn_draw_c.h"
 | 
				
			||||||
 | 
					#include "twn_engine_context_c.h"
 | 
				
			||||||
 | 
					#include "twn_textures_c.h"
 | 
				
			||||||
 | 
					#include "twn_types.h"
 | 
				
			||||||
 | 
					#include "twn_util_c.h"
 | 
				
			||||||
 | 
					#include "twn_vec.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <stb_ds.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void draw_billboard(const char *texture,
 | 
				
			||||||
 | 
					                    Vec3 position,
 | 
				
			||||||
 | 
					                    Vec2 size,
 | 
				
			||||||
 | 
					                    Color color,
 | 
				
			||||||
 | 
					                    bool cylindrical)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    // TODO: order drawing by atlas id as well, so that texture rebinding is not as common
 | 
				
			||||||
 | 
					    const TextureKey texture_key = textures_get_key(&ctx.texture_cache, texture);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    struct MeshBatchItem *batch_p = hmgetp_null(ctx.billboard_batches, texture_key);
 | 
				
			||||||
 | 
					    if (!batch_p) {
 | 
				
			||||||
 | 
					        struct MeshBatch item = {0};
 | 
				
			||||||
 | 
					        hmput(ctx.billboard_batches, texture_key, item);
 | 
				
			||||||
 | 
					        batch_p = &ctx.billboard_batches[hmlenu(ctx.billboard_batches) - 1]; /* TODO: can last index be used? */
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    struct SpaceBillboard billboard = {
 | 
				
			||||||
 | 
					        .color = color,
 | 
				
			||||||
 | 
					        .cylindrical = cylindrical,
 | 
				
			||||||
 | 
					        .position = position,
 | 
				
			||||||
 | 
					        .size = size,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    struct SpaceBillboard *billboards = (struct SpaceBillboard *)(void *)batch_p->value.primitives;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    arrpush(billboards, billboard);
 | 
				
			||||||
 | 
					    batch_p->value.primitives = (uint8_t *)billboards;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* reused for all billboards */
 | 
				
			||||||
 | 
					static Vec3 right_plus_up;
 | 
				
			||||||
 | 
					static Vec3 right_minus_up;
 | 
				
			||||||
 | 
					static Vec3 right_plus_up_cylindrical;
 | 
				
			||||||
 | 
					static Vec3 right_minus_up_cylindrical;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* precalculate (right + up) and (right - up) that are used with all batches */
 | 
				
			||||||
 | 
					static void calculate_intermediates(void) {
 | 
				
			||||||
 | 
					    Vec3 const right = { camera_look_at_matrix.row[0].x, camera_look_at_matrix.row[1].x, camera_look_at_matrix.row[2].x };
 | 
				
			||||||
 | 
					    Vec3 const up = { camera_look_at_matrix.row[0].y, camera_look_at_matrix.row[1].y, camera_look_at_matrix.row[2].y };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    right_plus_up = m_vec_add(right, up);
 | 
				
			||||||
 | 
					    right_minus_up = m_vec_sub(right, up);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Vec3 const up_cylindrical = { 0, 1, 0 };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    right_plus_up_cylindrical = m_vec_add(right, up_cylindrical);
 | 
				
			||||||
 | 
					    right_minus_up_cylindrical = m_vec_sub(right, up_cylindrical);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* http://www.lighthouse3d.com/opengl/billboarding/index.php?billCheat2 */
 | 
				
			||||||
 | 
					void finally_draw_billboard_batch(struct MeshBatch const *batch,
 | 
				
			||||||
 | 
					                                  TextureKey texture_key)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    const size_t primitives_len = arrlenu(batch->primitives);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* nothing to do */
 | 
				
			||||||
 | 
					    if (primitives_len == 0)
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* TODO: only do it once per frame */
 | 
				
			||||||
 | 
					    calculate_intermediates();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const Rect srcrect = textures_get_srcrect(&ctx.texture_cache, texture_key);
 | 
				
			||||||
 | 
					    const Rect dims    = textures_get_dims(&ctx.texture_cache, texture_key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const float wr = srcrect.w / dims.w;
 | 
				
			||||||
 | 
					    const float hr = srcrect.h / dims.h;
 | 
				
			||||||
 | 
					    const float xr = srcrect.x / dims.w;
 | 
				
			||||||
 | 
					    const float yr = srcrect.y / dims.h;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const Vec2 uv0 = { xr,           yr      };
 | 
				
			||||||
 | 
					    const Vec2 uv1 = { xr,           yr + hr };
 | 
				
			||||||
 | 
					    const Vec2 uv2 = { xr + wr,      yr + hr };
 | 
				
			||||||
 | 
					    const Vec2 uv3 = { xr + wr,      yr      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (size_t batch_n = 0; batch_n <= (primitives_len - 1) / QUAD_ELEMENT_BUFFER_LENGTH; batch_n++) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /* emit vertex data */
 | 
				
			||||||
 | 
					        VertexBuffer const buffer = get_scratch_vertex_array();
 | 
				
			||||||
 | 
					        VertexBufferBuilder builder = build_vertex_buffer(
 | 
				
			||||||
 | 
					            buffer,
 | 
				
			||||||
 | 
					            sizeof (ElementIndexedBillboard) * MIN(primitives_len - batch_n * QUAD_ELEMENT_BUFFER_LENGTH, QUAD_ELEMENT_BUFFER_LENGTH));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (size_t i = 0; i < primitives_len; ++i) {
 | 
				
			||||||
 | 
					            struct SpaceBillboard const billboard = ((SpaceBillboard *)(void *)batch->primitives)[batch_n * QUAD_ELEMENT_BUFFER_LENGTH + i];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            /* a = (right + up) * size, b = (right - up) * size*/
 | 
				
			||||||
 | 
					            Vec3 a, b;
 | 
				
			||||||
 | 
					            if (billboard.cylindrical) {
 | 
				
			||||||
 | 
					                a = vec3_mul(right_plus_up_cylindrical, ((Vec3){billboard.size.x, billboard.size.y, billboard.size.x }));
 | 
				
			||||||
 | 
					                b = vec3_mul(right_minus_up_cylindrical, ((Vec3){billboard.size.x, billboard.size.y, billboard.size.x }));
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                a = vec3_mul(right_plus_up, ((Vec3){billboard.size.x, billboard.size.y, billboard.size.x }));
 | 
				
			||||||
 | 
					                b = vec3_mul(right_minus_up, ((Vec3){billboard.size.x, billboard.size.y, billboard.size.x }));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            struct ElementIndexedBillboard const payload = {
 | 
				
			||||||
 | 
					                /* flat shading is assumed, so we can skip setting the duplicates */
 | 
				
			||||||
 | 
					                .c0 = billboard.color,
 | 
				
			||||||
 | 
					                // .c1 = billboard.color,
 | 
				
			||||||
 | 
					                .c2 = billboard.color,
 | 
				
			||||||
 | 
					                // .c3 = billboard.color,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                .uv0 = uv0,
 | 
				
			||||||
 | 
					                .uv1 = uv1,
 | 
				
			||||||
 | 
					                .uv2 = uv2,
 | 
				
			||||||
 | 
					                .uv3 = uv3,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                .v0 = vec3_sub(billboard.position, b),
 | 
				
			||||||
 | 
					                .v1 = vec3_sub(billboard.position, a),
 | 
				
			||||||
 | 
					                .v2 = vec3_add(billboard.position, b),
 | 
				
			||||||
 | 
					                .v3 = vec3_add(billboard.position, a),
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            ((struct ElementIndexedBillboard *)builder.base)[i] = payload;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        finish_vertex_builder(&builder);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /* commit to drawing */
 | 
				
			||||||
 | 
					        DeferredCommandDraw command = {0};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        command.vertices = (AttributeArrayPointer) {
 | 
				
			||||||
 | 
					            .arity = 3,
 | 
				
			||||||
 | 
					            .type = TWN_FLOAT,
 | 
				
			||||||
 | 
					            .stride = offsetof(ElementIndexedBillboard, v1),
 | 
				
			||||||
 | 
					            .offset = offsetof(ElementIndexedBillboard, v0),
 | 
				
			||||||
 | 
					            .buffer = buffer
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        command.texcoords = (AttributeArrayPointer) {
 | 
				
			||||||
 | 
					            .arity = 2,
 | 
				
			||||||
 | 
					            .type = TWN_FLOAT,
 | 
				
			||||||
 | 
					            .stride = offsetof(ElementIndexedBillboard, v1),
 | 
				
			||||||
 | 
					            .offset = offsetof(ElementIndexedBillboard, uv0),
 | 
				
			||||||
 | 
					            .buffer = buffer
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        command.colors = (AttributeArrayPointer) {
 | 
				
			||||||
 | 
					            .arity = 4,
 | 
				
			||||||
 | 
					            .type = TWN_UNSIGNED_BYTE,
 | 
				
			||||||
 | 
					            .stride = offsetof(ElementIndexedBillboard, v1),
 | 
				
			||||||
 | 
					            .offset = offsetof(ElementIndexedBillboard, c0),
 | 
				
			||||||
 | 
					            .buffer = buffer
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        command.textured = true;
 | 
				
			||||||
 | 
					        command.texture_key = texture_key;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        command.element_buffer = get_quad_element_buffer();
 | 
				
			||||||
 | 
					        command.element_count = 6 * (uint32_t)primitives_len;
 | 
				
			||||||
 | 
					        command.range_end = 6 * (uint32_t)primitives_len;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /* TODO: support alpha blended case, with distance sort */
 | 
				
			||||||
 | 
					        TextureMode mode = textures_get_mode(&ctx.texture_cache, texture_key);
 | 
				
			||||||
 | 
					        if (mode == TEXTURE_MODE_GHOSTLY)
 | 
				
			||||||
 | 
					            mode = TEXTURE_MODE_SEETHROUGH;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        command.texture_mode = mode;
 | 
				
			||||||
 | 
					        command.pipeline = PIPELINE_SPACE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        command.depth_range_high = depth_range_high;
 | 
				
			||||||
 | 
					        command.depth_range_low = depth_range_low;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        DeferredCommand final_command = {
 | 
				
			||||||
 | 
					            .type = DEFERRED_COMMAND_TYPE_DRAW,
 | 
				
			||||||
 | 
					            .draw = command
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        arrpush(deferred_commands, final_command);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
#include "twn_engine_context_c.h"
 | 
					#include "twn_engine_context_c.h"
 | 
				
			||||||
#include "twn_draw_c.h"
 | 
					#include "twn_draw_c.h"
 | 
				
			||||||
#include "twn_draw.h"
 | 
					#include "twn_draw.h"
 | 
				
			||||||
 | 
					#include "twn_util_c.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <SDL2/SDL.h>
 | 
					#include <SDL2/SDL.h>
 | 
				
			||||||
#include <stb_ds.h>
 | 
					#include <stb_ds.h>
 | 
				
			||||||
@@ -27,8 +28,10 @@ void create_circle_geometry(Vec2 position,
 | 
				
			|||||||
                            size_t num_vertices,
 | 
					                            size_t num_vertices,
 | 
				
			||||||
                            Vec2 vertices[])
 | 
					                            Vec2 vertices[])
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					    SDL_assert(num_vertices <= CIRCLE_VERTICES_MAX);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* the angle (in radians) to rotate by on each iteration */
 | 
					    /* the angle (in radians) to rotate by on each iteration */
 | 
				
			||||||
    float seg_rotation_angle = (360.0f / (float)num_vertices) * ((float)M_PI / 180);
 | 
					    float seg_rotation_angle = (360.0f / (float)(num_vertices - 2)) * ((float)M_PI / 180);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    vertices[0].x = (float)position.x;
 | 
					    vertices[0].x = (float)position.x;
 | 
				
			||||||
    vertices[0].y = (float)position.y;
 | 
					    vertices[0].y = (float)position.y;
 | 
				
			||||||
@@ -37,7 +40,7 @@ void create_circle_geometry(Vec2 position,
 | 
				
			|||||||
    float start_x = 0.0f - radius;
 | 
					    float start_x = 0.0f - radius;
 | 
				
			||||||
    float start_y = 0.0f;
 | 
					    float start_y = 0.0f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (size_t i = 1; i < num_vertices + 1; ++i) {
 | 
					    for (size_t i = 1; i < num_vertices - 1; ++i) {
 | 
				
			||||||
        float final_seg_rotation_angle = (float)i * seg_rotation_angle;
 | 
					        float final_seg_rotation_angle = (float)i * seg_rotation_angle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        float c, s;
 | 
					        float c, s;
 | 
				
			||||||
@@ -49,4 +52,76 @@ void create_circle_geometry(Vec2 position,
 | 
				
			|||||||
        vertices[i].x += position.x;
 | 
					        vertices[i].x += position.x;
 | 
				
			||||||
        vertices[i].y += position.y;
 | 
					        vertices[i].y += position.y;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // place a redundant vertex to make proper circling over shared element buffer
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        float final_seg_rotation_angle = (float)1 * seg_rotation_angle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        float c, s;
 | 
				
			||||||
 | 
					        sincosf(final_seg_rotation_angle, &s, &c);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        vertices[num_vertices - 1].x = c * start_x - s * start_y;
 | 
				
			||||||
 | 
					        vertices[num_vertices - 1].y = c * start_y + s * start_x;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        vertices[num_vertices - 1].x += position.x;
 | 
				
			||||||
 | 
					        vertices[num_vertices - 1].y += position.y;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void render_circle(const CirclePrimitive *circle) {
 | 
				
			||||||
 | 
					    static Vec2 vertices[CIRCLE_VERTICES_MAX];
 | 
				
			||||||
 | 
					    static int prev_num_vertices = 0;
 | 
				
			||||||
 | 
					    static Vec2 prev_position = {0};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    int const num_vertices = MIN((int)circle->radius, CIRCLE_VERTICES_MAX);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (prev_num_vertices != num_vertices) {
 | 
				
			||||||
 | 
					        create_circle_geometry(circle->position,
 | 
				
			||||||
 | 
					                               circle->radius,
 | 
				
			||||||
 | 
					                               num_vertices,
 | 
				
			||||||
 | 
					                               vertices);
 | 
				
			||||||
 | 
					        prev_num_vertices = num_vertices;
 | 
				
			||||||
 | 
					        prev_position = circle->position;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        /* reuse the data, but offset it by difference with previously generated position */
 | 
				
			||||||
 | 
					        /* no evil cos sin ops this way, if radius is shared in sequential calls */
 | 
				
			||||||
 | 
					        Vec2 const d = { prev_position.x - circle->position.x, prev_position.y - circle->position.y };
 | 
				
			||||||
 | 
					        for (int i = 0; i < num_vertices; ++i)
 | 
				
			||||||
 | 
					            vertices[i] = (Vec2){ vertices[i].x - d.x, vertices[i].y - d.y };
 | 
				
			||||||
 | 
					        prev_position = circle->position;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    VertexBuffer buffer = get_scratch_vertex_array();
 | 
				
			||||||
 | 
					    specify_vertex_buffer(buffer, vertices, sizeof (Vec2) * num_vertices);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    DeferredCommandDraw command = {0};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    command.vertices = (AttributeArrayPointer) {
 | 
				
			||||||
 | 
					        .arity = 2,
 | 
				
			||||||
 | 
					        .type = TWN_FLOAT,
 | 
				
			||||||
 | 
					        .stride = sizeof (Vec2),
 | 
				
			||||||
 | 
					        .offset = 0,
 | 
				
			||||||
 | 
					        .buffer = buffer
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    command.constant_colored = true;
 | 
				
			||||||
 | 
					    command.color = circle->color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    command.element_buffer = get_circle_element_buffer();
 | 
				
			||||||
 | 
					    command.element_count = (num_vertices - 2) * 3;
 | 
				
			||||||
 | 
					    command.range_end = (num_vertices - 2) * 3;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    command.texture_mode = circle->color.a == 255 ? TEXTURE_MODE_OPAQUE : TEXTURE_MODE_GHOSTLY;
 | 
				
			||||||
 | 
					    command.pipeline = PIPELINE_2D;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    command.depth_range_high = depth_range_high;
 | 
				
			||||||
 | 
					    command.depth_range_low = depth_range_low;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    DeferredCommand final_command = {
 | 
				
			||||||
 | 
					        .type = DEFERRED_COMMAND_TYPE_DRAW,
 | 
				
			||||||
 | 
					        .draw = command
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    arrpush(deferred_commands, final_command);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										87
									
								
								src/rendering/twn_deferred_commands.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								src/rendering/twn_deferred_commands.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
				
			|||||||
 | 
					#ifndef TWN_DEFERRED_COMMANDS_H
 | 
				
			||||||
 | 
					#define TWN_DEFERRED_COMMANDS_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "twn_types.h"
 | 
				
			||||||
 | 
					#include "twn_gpu_texture_c.h"
 | 
				
			||||||
 | 
					#include "twn_textures_c.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <stddef.h>
 | 
				
			||||||
 | 
					#include <stdbool.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef enum {
 | 
				
			||||||
 | 
					    PIPELINE_NO,
 | 
				
			||||||
 | 
					    PIPELINE_SPACE,
 | 
				
			||||||
 | 
					    PIPELINE_2D, /* TODO: rename to PIPELINE_PLANE? */
 | 
				
			||||||
 | 
					} Pipeline;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    size_t offset;
 | 
				
			||||||
 | 
					    uint32_t type;
 | 
				
			||||||
 | 
					    uint32_t stride;
 | 
				
			||||||
 | 
					    uint32_t buffer;
 | 
				
			||||||
 | 
					    uint8_t arity; /* leave at 0 to signal pointer as unused */
 | 
				
			||||||
 | 
					} AttributeArrayPointer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* allows us to have generic way to issue draws as well as */
 | 
				
			||||||
 | 
					/* deferring new draw calls while previous frame is still being drawn */
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    AttributeArrayPointer vertices;
 | 
				
			||||||
 | 
					    AttributeArrayPointer texcoords;
 | 
				
			||||||
 | 
					    union {
 | 
				
			||||||
 | 
					        AttributeArrayPointer colors;
 | 
				
			||||||
 | 
					        Color color;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    double depth_range_low, depth_range_high;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Pipeline pipeline;
 | 
				
			||||||
 | 
					    TextureMode texture_mode;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    TextureKey texture_key;
 | 
				
			||||||
 | 
					    GPUTexture gpu_texture;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* could be either `element_count` with supplied `element_buffer`, or this, but not both */
 | 
				
			||||||
 | 
					    uint32_t primitive_count;
 | 
				
			||||||
 | 
					    uint32_t element_buffer;
 | 
				
			||||||
 | 
					    uint32_t element_count;
 | 
				
			||||||
 | 
					    uint32_t range_start, range_end;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool constant_colored;
 | 
				
			||||||
 | 
					    bool textured, texture_repeat, uses_gpu_key;
 | 
				
			||||||
 | 
					} DeferredCommandDraw;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    char *paths;
 | 
				
			||||||
 | 
					} DeferredCommandDrawSkybox;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    Color color;
 | 
				
			||||||
 | 
					    bool clear_color;
 | 
				
			||||||
 | 
					    bool clear_depth;
 | 
				
			||||||
 | 
					    bool clear_stencil;
 | 
				
			||||||
 | 
					} DeferredCommandClear;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    enum DeferredCommandType {
 | 
				
			||||||
 | 
					        DEFERRED_COMMAND_TYPE_DRAW,
 | 
				
			||||||
 | 
					        DEFERRED_COMMAND_TYPE_DRAW_SKYBOX,
 | 
				
			||||||
 | 
					        DEFERRED_COMMAND_TYPE_CLEAR,
 | 
				
			||||||
 | 
					    } type;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    union {
 | 
				
			||||||
 | 
					        DeferredCommandDraw draw;
 | 
				
			||||||
 | 
					        DeferredCommandDrawSkybox draw_skybox;
 | 
				
			||||||
 | 
					        DeferredCommandClear clear;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					} DeferredCommand;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern DeferredCommand *deferred_commands;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
@@ -1,26 +1,29 @@
 | 
				
			|||||||
#include "twn_draw_c.h"
 | 
					#include "twn_draw_c.h"
 | 
				
			||||||
#include "twn_draw.h"
 | 
					#include "twn_draw.h"
 | 
				
			||||||
#include "twn_engine_context_c.h"
 | 
					#include "twn_engine_context_c.h"
 | 
				
			||||||
#include "twn_camera.h"
 | 
					#include "twn_camera_c.h"
 | 
				
			||||||
#include "twn_types.h"
 | 
					#include "twn_types.h"
 | 
				
			||||||
 | 
					#include "twn_util.h"
 | 
				
			||||||
 | 
					#include "twn_vec.h"
 | 
				
			||||||
 | 
					#include "twn_deferred_commands.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <SDL2/SDL.h>
 | 
					#include <SDL2/SDL.h>
 | 
				
			||||||
#include <stb_ds.h>
 | 
					#include <stb_ds.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef EMSCRIPTEN
 | 
					 | 
				
			||||||
#include <GLES2/gl2.h>
 | 
					 | 
				
			||||||
#else
 | 
					 | 
				
			||||||
#include <glad/glad.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <stddef.h>
 | 
					#include <stddef.h>
 | 
				
			||||||
 | 
					#include <math.h>
 | 
				
			||||||
#include <tgmath.h>
 | 
					#include <tgmath.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DeferredCommand *deferred_commands;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* TODO: have a default initialized one */
 | 
					/* TODO: have a default initialized one */
 | 
				
			||||||
 | 
					/* TODO: with buffered render, don't we use camera of wrong frame right now ? */
 | 
				
			||||||
Matrix4 camera_projection_matrix;
 | 
					Matrix4 camera_projection_matrix;
 | 
				
			||||||
Matrix4 camera_look_at_matrix;
 | 
					Matrix4 camera_look_at_matrix;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					double depth_range_low, depth_range_high;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void render_queue_clear(void) {
 | 
					void render_queue_clear(void) {
 | 
				
			||||||
    text_cache_reset_arena(&ctx.text_cache);
 | 
					    text_cache_reset_arena(&ctx.text_cache);
 | 
				
			||||||
@@ -30,13 +33,17 @@ void render_queue_clear(void) {
 | 
				
			|||||||
    /* and start overwriting the existing data */
 | 
					    /* and start overwriting the existing data */
 | 
				
			||||||
    arrsetlen(ctx.render_queue_2d, 0);
 | 
					    arrsetlen(ctx.render_queue_2d, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* TODO: free memory if it isn't used for a while */
 | 
				
			||||||
    for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i)
 | 
					    for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i)
 | 
				
			||||||
        arrsetlen(ctx.uncolored_mesh_batches[i].value.primitives, 0);
 | 
					        arrsetlen(ctx.uncolored_mesh_batches[i].value.primitives, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (size_t i = 0; i < hmlenu(ctx.billboard_batches); ++i)
 | 
				
			||||||
 | 
					        arrsetlen(ctx.billboard_batches[i].value.primitives, 0);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void draw_9slice(const char *texture_path, int texture_w, int texture_h, int border_thickness, Rect rect, Color color) {
 | 
					void draw_nine_slice(const char *texture, Vec2 corners, Rect rect, float border_thickness, Color color) {
 | 
				
			||||||
    const float bt = (float)border_thickness; /* i know! */
 | 
					    const float bt = border_thickness;
 | 
				
			||||||
    const float bt2 = bt * 2; /* combined size of the two borders in an axis */
 | 
					    const float bt2 = bt * 2; /* combined size of the two borders in an axis */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -48,7 +55,7 @@ void draw_9slice(const char *texture_path, int texture_w, int texture_h, int bor
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    m_sprite(
 | 
					    m_sprite(
 | 
				
			||||||
        m_set(path, texture_path),
 | 
					        m_set(texture, texture),
 | 
				
			||||||
        m_set(rect, top_left),
 | 
					        m_set(rect, top_left),
 | 
				
			||||||
        m_opt(texture_region, ((Rect) { 0, 0, bt, bt })),
 | 
					        m_opt(texture_region, ((Rect) { 0, 0, bt, bt })),
 | 
				
			||||||
        m_opt(color, color),
 | 
					        m_opt(color, color),
 | 
				
			||||||
@@ -63,9 +70,9 @@ void draw_9slice(const char *texture_path, int texture_w, int texture_h, int bor
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    m_sprite(
 | 
					    m_sprite(
 | 
				
			||||||
        m_set(path, texture_path),
 | 
					        m_set(texture, texture),
 | 
				
			||||||
        m_set(rect, top_center),
 | 
					        m_set(rect, top_center),
 | 
				
			||||||
        m_opt(texture_region, ((Rect) { bt, 0, (float)texture_w - bt2, bt })),
 | 
					        m_opt(texture_region, ((Rect) { bt, 0, corners.x - bt2, bt })),
 | 
				
			||||||
        m_opt(color, color),
 | 
					        m_opt(color, color),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -78,9 +85,9 @@ void draw_9slice(const char *texture_path, int texture_w, int texture_h, int bor
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    m_sprite(
 | 
					    m_sprite(
 | 
				
			||||||
        m_set(path, texture_path),
 | 
					        m_set(texture, texture),
 | 
				
			||||||
        m_set(rect, top_right),
 | 
					        m_set(rect, top_right),
 | 
				
			||||||
        m_opt(texture_region, ((Rect) { (float)texture_w - bt, 0, bt, bt })),
 | 
					        m_opt(texture_region, ((Rect) { corners.x - bt, 0, bt, bt })),
 | 
				
			||||||
        m_opt(color, color),
 | 
					        m_opt(color, color),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -93,9 +100,9 @@ void draw_9slice(const char *texture_path, int texture_w, int texture_h, int bor
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    m_sprite(
 | 
					    m_sprite(
 | 
				
			||||||
        m_set(path, texture_path),
 | 
					        m_set(texture, texture),
 | 
				
			||||||
        m_set(rect, center_left),
 | 
					        m_set(rect, center_left),
 | 
				
			||||||
        m_opt(texture_region, ((Rect) { 0, bt, bt, (float)texture_h - bt2 })),
 | 
					        m_opt(texture_region, ((Rect) { 0, bt, bt, corners.y - bt2 })),
 | 
				
			||||||
        m_opt(color, color),
 | 
					        m_opt(color, color),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -108,9 +115,9 @@ void draw_9slice(const char *texture_path, int texture_w, int texture_h, int bor
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    m_sprite(
 | 
					    m_sprite(
 | 
				
			||||||
        m_set(path, texture_path),
 | 
					        m_set(texture, texture),
 | 
				
			||||||
        m_set(rect, center_right),
 | 
					        m_set(rect, center_right),
 | 
				
			||||||
        m_opt(texture_region, ((Rect) { (float)texture_w - bt, bt, bt, (float)texture_h - bt2 })),
 | 
					        m_opt(texture_region, ((Rect) { corners.x - bt, bt, bt, corners.y - bt2 })),
 | 
				
			||||||
        m_opt(color, color),
 | 
					        m_opt(color, color),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -123,9 +130,9 @@ void draw_9slice(const char *texture_path, int texture_w, int texture_h, int bor
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    m_sprite(
 | 
					    m_sprite(
 | 
				
			||||||
        m_set(path, texture_path),
 | 
					        m_set(texture, texture),
 | 
				
			||||||
        m_set(rect, bottom_left),
 | 
					        m_set(rect, bottom_left),
 | 
				
			||||||
        m_opt(texture_region, ((Rect) { 0, (float)texture_h - bt, bt, bt })),
 | 
					        m_opt(texture_region, ((Rect) { 0, corners.y - bt, bt, bt })),
 | 
				
			||||||
        m_opt(color, color),
 | 
					        m_opt(color, color),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -138,9 +145,9 @@ void draw_9slice(const char *texture_path, int texture_w, int texture_h, int bor
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    m_sprite(
 | 
					    m_sprite(
 | 
				
			||||||
        m_set(path, texture_path),
 | 
					        m_set(texture, texture),
 | 
				
			||||||
        m_set(rect, bottom_center),
 | 
					        m_set(rect, bottom_center),
 | 
				
			||||||
        m_opt(texture_region, ((Rect) { bt, (float)texture_h - bt, (float)texture_w - bt2, bt })),
 | 
					        m_opt(texture_region, ((Rect) { bt, corners.y - bt, corners.x - bt2, bt })),
 | 
				
			||||||
        m_opt(color, color),
 | 
					        m_opt(color, color),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -153,9 +160,9 @@ void draw_9slice(const char *texture_path, int texture_w, int texture_h, int bor
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    m_sprite(
 | 
					    m_sprite(
 | 
				
			||||||
        m_set(path, texture_path),
 | 
					        m_set(texture, texture),
 | 
				
			||||||
        m_set(rect, bottom_right),
 | 
					        m_set(rect, bottom_right),
 | 
				
			||||||
        m_opt(texture_region, ((Rect) { (float)texture_w - bt, (float)texture_h - bt, bt, bt })),
 | 
					        m_opt(texture_region, ((Rect) { corners.x - bt, corners.y - bt, bt, bt })),
 | 
				
			||||||
        m_opt(color, color),
 | 
					        m_opt(color, color),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -168,17 +175,40 @@ void draw_9slice(const char *texture_path, int texture_w, int texture_h, int bor
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    m_sprite(
 | 
					    m_sprite(
 | 
				
			||||||
        m_set(path, texture_path),
 | 
					        m_set(texture, texture),
 | 
				
			||||||
        m_set(rect, center),
 | 
					        m_set(rect, center),
 | 
				
			||||||
        m_opt(texture_region, ((Rect) { bt, bt, (float)texture_w - bt2, (float)texture_h - bt2 })),
 | 
					        m_opt(texture_region, ((Rect) { bt, bt, corners.x - bt2, corners.y - bt2 })),
 | 
				
			||||||
        m_opt(color, color),
 | 
					        m_opt(color, color),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void render_2d(void) {
 | 
					TWN_API void draw_quad(char const *texture,
 | 
				
			||||||
    use_2d_pipeline();
 | 
					                       Vec3        v0,          /* upper-left */
 | 
				
			||||||
 | 
					                       Vec3        v1,          /* bottom-left */
 | 
				
			||||||
 | 
					                       Vec3        v2,          /* bottom-right */
 | 
				
			||||||
 | 
					                       Vec3        v3,          /* upper-right */
 | 
				
			||||||
 | 
					                       Rect        texture_region,
 | 
				
			||||||
 | 
					                       Color       color)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    Vec2 const uv0 = { texture_region.x,                    texture_region.y };
 | 
				
			||||||
 | 
					    Vec2 const uv1 = { texture_region.x,                    texture_region.y + texture_region.h };
 | 
				
			||||||
 | 
					    Vec2 const uv2 = { texture_region.x + texture_region.w, texture_region.y + texture_region.h };
 | 
				
			||||||
 | 
					    Vec2 const uv3 = { texture_region.x + texture_region.w, texture_region.y };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    draw_triangle(texture,
 | 
				
			||||||
 | 
					                  v0, v1, v3,
 | 
				
			||||||
 | 
					                  uv0, uv1, uv3,
 | 
				
			||||||
 | 
					                  color, color, color);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    draw_triangle(texture,
 | 
				
			||||||
 | 
					                  v3, v1, v2,
 | 
				
			||||||
 | 
					                  uv3, uv1, uv2,
 | 
				
			||||||
 | 
					                  color, color, color);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void render_2d(void) {
 | 
				
			||||||
    const size_t render_queue_len = arrlenu(ctx.render_queue_2d);
 | 
					    const size_t render_queue_len = arrlenu(ctx.render_queue_2d);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    struct Render2DInvocation {
 | 
					    struct Render2DInvocation {
 | 
				
			||||||
@@ -255,6 +285,20 @@ static void render_2d(void) {
 | 
				
			|||||||
                break;
 | 
					                break;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            /* TODO: batching */
 | 
				
			||||||
 | 
					            case PRIMITIVE_2D_LINE: {
 | 
				
			||||||
 | 
					                struct Render2DInvocation const invocation = {
 | 
				
			||||||
 | 
					                    .primitive = current,
 | 
				
			||||||
 | 
					                    .layer = layer,
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (current->line.color.a != 255)
 | 
				
			||||||
 | 
					                    arrput(ghostly_invocations, invocation);
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                    arrput(opaque_invocations, invocation);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            case PRIMITIVE_2D_TEXT: {
 | 
					            case PRIMITIVE_2D_TEXT: {
 | 
				
			||||||
                struct Render2DInvocation const invocation = {
 | 
					                struct Render2DInvocation const invocation = {
 | 
				
			||||||
                    .primitive = current,
 | 
					                    .primitive = current,
 | 
				
			||||||
@@ -291,6 +335,9 @@ static void render_2d(void) {
 | 
				
			|||||||
            case PRIMITIVE_2D_CIRCLE:
 | 
					            case PRIMITIVE_2D_CIRCLE:
 | 
				
			||||||
                render_circle(&invocation.primitive->circle);
 | 
					                render_circle(&invocation.primitive->circle);
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
 | 
					            case PRIMITIVE_2D_LINE:
 | 
				
			||||||
 | 
					                render_line(&invocation.primitive->line);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
            case PRIMITIVE_2D_TEXT:
 | 
					            case PRIMITIVE_2D_TEXT:
 | 
				
			||||||
            default:
 | 
					            default:
 | 
				
			||||||
                SDL_assert(false);
 | 
					                SDL_assert(false);
 | 
				
			||||||
@@ -320,6 +367,9 @@ static void render_2d(void) {
 | 
				
			|||||||
            case PRIMITIVE_2D_TEXT:
 | 
					            case PRIMITIVE_2D_TEXT:
 | 
				
			||||||
                render_text(&invocation.primitive->text);
 | 
					                render_text(&invocation.primitive->text);
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
 | 
					            case PRIMITIVE_2D_LINE:
 | 
				
			||||||
 | 
					                render_line(&invocation.primitive->line);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
            default:
 | 
					            default:
 | 
				
			||||||
                SDL_assert(false);
 | 
					                SDL_assert(false);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -333,18 +383,18 @@ static void render_2d(void) {
 | 
				
			|||||||
static void render_space(void) {
 | 
					static void render_space(void) {
 | 
				
			||||||
    /* nothing to do, abort */
 | 
					    /* nothing to do, abort */
 | 
				
			||||||
    /* as space pipeline isn't used we can have fewer changes and initialization costs */
 | 
					    /* as space pipeline isn't used we can have fewer changes and initialization costs */
 | 
				
			||||||
    if (hmlenu(ctx.uncolored_mesh_batches) == 0)
 | 
					    if (hmlenu(ctx.uncolored_mesh_batches) != 0 || hmlenu(ctx.billboard_batches) != 0) {
 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    use_space_pipeline();
 | 
					 | 
				
			||||||
    apply_fog();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i) {
 | 
					        for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i) {
 | 
				
			||||||
        draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches[i].value,
 | 
					            finally_draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches[i].value,
 | 
				
			||||||
                                                        ctx.uncolored_mesh_batches[i].key);
 | 
					                                                        ctx.uncolored_mesh_batches[i].key);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pop_fog();
 | 
					        for (size_t i = 0; i < hmlenu(ctx.billboard_batches); ++i) {
 | 
				
			||||||
 | 
					            finally_draw_billboard_batch(&ctx.billboard_batches[i].value, ctx.billboard_batches[i].key);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    render_skybox(); /* after everything else, as to use depth buffer for early z rejection */
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -353,37 +403,167 @@ void render(void) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /* fit rendering context onto the resizable screen */
 | 
					    /* fit rendering context onto the resizable screen */
 | 
				
			||||||
    if (ctx.window_size_has_changed) {
 | 
					    if (ctx.window_size_has_changed) {
 | 
				
			||||||
        if ((float)ctx.window_dims.x / (float)ctx.window_dims.y > (float)ctx.base_render_width / (float)ctx.base_render_height) {
 | 
					        setup_viewport((int)ctx.viewport_rect.x, (int)ctx.viewport_rect.y, (int)ctx.viewport_rect.w, (int)ctx.viewport_rect.h);
 | 
				
			||||||
            float ratio = (float)ctx.window_dims.y / (float)ctx.base_render_height;
 | 
					 | 
				
			||||||
            int w = (int)((float)ctx.base_render_width * ratio);
 | 
					 | 
				
			||||||
            setup_viewport(
 | 
					 | 
				
			||||||
                ctx.window_dims.x / 2 - w / 2,
 | 
					 | 
				
			||||||
                0,
 | 
					 | 
				
			||||||
                w,
 | 
					 | 
				
			||||||
                ctx.window_dims.y
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            float ratio = (float)ctx.window_dims.x / (float)ctx.base_render_width;
 | 
					 | 
				
			||||||
            int h = (int)((float)ctx.base_render_height * ratio);
 | 
					 | 
				
			||||||
            setup_viewport(
 | 
					 | 
				
			||||||
                0,
 | 
					 | 
				
			||||||
                ctx.window_dims.y / 2 - h / 2,
 | 
					 | 
				
			||||||
                ctx.window_dims.x,
 | 
					 | 
				
			||||||
                h
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    start_render_frame(); {
 | 
					    start_render_frame(); {
 | 
				
			||||||
        render_space();
 | 
					        render_space();
 | 
				
			||||||
        render_skybox(); /* after space, as to use depth buffer for early z rejection */
 | 
					 | 
				
			||||||
        render_2d();
 | 
					        render_2d();
 | 
				
			||||||
    } end_render_frame();
 | 
					    } end_render_frame();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void draw_camera(const Camera *const camera) {
 | 
					void draw_camera(Vec3 position, Vec3 direction, Vec3 up, float fov, float zoom) {
 | 
				
			||||||
    /* TODO: skip recaulculating if it's the same? */
 | 
					    bool const orthographic = fabsf(0.0f - fov) < 0.00001f;
 | 
				
			||||||
    camera_projection_matrix = camera_perspective(camera);
 | 
					    if (!orthographic && fov >= M_PIf)
 | 
				
			||||||
    camera_look_at_matrix    = camera_look_at(camera);
 | 
					        log_warn("Invalid fov given (%f)", (double)fov);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Camera const camera = {
 | 
				
			||||||
 | 
					        .fov = fov,
 | 
				
			||||||
 | 
					        .pos = position,
 | 
				
			||||||
 | 
					        .target = vec3_norm(direction),
 | 
				
			||||||
 | 
					        .up = up,
 | 
				
			||||||
 | 
					        .viewbox = {
 | 
				
			||||||
 | 
					            (Vec2){ 1/-zoom, 1/zoom },
 | 
				
			||||||
 | 
					            (Vec2){ 1/zoom, 1/-zoom }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!orthographic)
 | 
				
			||||||
 | 
					        camera_projection_matrix = camera_perspective(&camera);
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					        camera_projection_matrix = camera_orthographic(&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 roll,
 | 
				
			||||||
 | 
					                                                                  float pitch,
 | 
				
			||||||
 | 
					                                                                  float yaw,
 | 
				
			||||||
 | 
					                                                                  float fov,
 | 
				
			||||||
 | 
					                                                                  float zoom)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    bool const orthographic = fabsf(0.0f - fov) < 0.00001f;
 | 
				
			||||||
 | 
					    if (!orthographic && fov >= M_PIf)
 | 
				
			||||||
 | 
					        log_warn("Invalid fov given (%f)", (double)fov);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    (void)roll;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    float yawc, yaws, pitchc, pitchs;
 | 
				
			||||||
 | 
					    sincosf(yaw, &yaws, &yawc);
 | 
				
			||||||
 | 
					    sincosf(pitch, &pitchs, &pitchc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Camera const camera = {
 | 
				
			||||||
 | 
					        .fov = fov,
 | 
				
			||||||
 | 
					        .pos = position,
 | 
				
			||||||
 | 
					        .target = vec3_norm(((Vec3){
 | 
				
			||||||
 | 
					            yawc * pitchc,
 | 
				
			||||||
 | 
					            pitchs,
 | 
				
			||||||
 | 
					            yaws * pitchc,
 | 
				
			||||||
 | 
					        })),
 | 
				
			||||||
 | 
					        .up = (Vec3){0, 1, 0},
 | 
				
			||||||
 | 
					        .viewbox = {
 | 
				
			||||||
 | 
					            (Vec2){ 1/-zoom, 1/zoom },
 | 
				
			||||||
 | 
					            (Vec2){ 1/zoom, 1/-zoom }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!orthographic)
 | 
				
			||||||
 | 
					        camera_projection_matrix = camera_perspective(&camera);
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					        camera_projection_matrix = camera_orthographic(&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 = ctx.background_color,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    arrpush(deferred_commands, command);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void issue_deferred_draw_commands(void) {
 | 
				
			||||||
 | 
					    for (size_t i = 0; i < arrlenu(deferred_commands); ++i) {
 | 
				
			||||||
 | 
					        switch (deferred_commands[i].type) {
 | 
				
			||||||
 | 
					            case DEFERRED_COMMAND_TYPE_CLEAR: {
 | 
				
			||||||
 | 
					                finally_clear_draw_buffer(deferred_commands[i].clear);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case DEFERRED_COMMAND_TYPE_DRAW: {
 | 
				
			||||||
 | 
					                finally_draw_command(deferred_commands[i].draw);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case DEFERRED_COMMAND_TYPE_DRAW_SKYBOX: {
 | 
				
			||||||
 | 
					                finally_render_skybox(deferred_commands[i].draw_skybox);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            default:
 | 
				
			||||||
 | 
					                SDL_assert(false);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* TODO: Support thickness */
 | 
				
			||||||
 | 
					void draw_line(Vec2 start,
 | 
				
			||||||
 | 
					               Vec2 finish,
 | 
				
			||||||
 | 
					               float thickness,
 | 
				
			||||||
 | 
					               Color color)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    if (fabsf(1.0f - thickness) >= 0.00001f)
 | 
				
			||||||
 | 
					        log_warn("Thickness isn't yet implemented for line drawing (got %f)", (double)thickness);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    LinePrimitive line = {
 | 
				
			||||||
 | 
					        .start = start,
 | 
				
			||||||
 | 
					        .finish = finish,
 | 
				
			||||||
 | 
					        .thickness = thickness,
 | 
				
			||||||
 | 
					        .color = color,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Primitive2D primitive = {
 | 
				
			||||||
 | 
					        .type = PRIMITIVE_2D_LINE,
 | 
				
			||||||
 | 
					        .line = line,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    arrput(ctx.render_queue_2d, primitive);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void draw_box(Rect rect,
 | 
				
			||||||
 | 
					              float thickness,
 | 
				
			||||||
 | 
					              Color color)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    draw_line((Vec2){rect.x, rect.y}, (Vec2){rect.x + rect.w, rect.y}, thickness, color);
 | 
				
			||||||
 | 
					    draw_line((Vec2){rect.x + rect.w, rect.y}, (Vec2){rect.x + rect.w, rect.y + rect.h}, thickness, color);
 | 
				
			||||||
 | 
					    draw_line((Vec2){rect.x + rect.w, rect.y + rect.h}, (Vec2){rect.x, rect.y + rect.h}, thickness, color);
 | 
				
			||||||
 | 
					    draw_line((Vec2){rect.x, rect.y + rect.h}, (Vec2){rect.x, rect.y}, thickness, color);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,36 +1,44 @@
 | 
				
			|||||||
#ifndef TWN_DRAW_C_H
 | 
					#ifndef TWN_DRAW_C_H
 | 
				
			||||||
#define TWN_DRAW_C_H
 | 
					#define TWN_DRAW_C_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* TODO: structure more categorically */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "twn_textures_c.h"
 | 
					#include "twn_textures_c.h"
 | 
				
			||||||
 | 
					#include "twn_types_c.h"
 | 
				
			||||||
#include "twn_text_c.h"
 | 
					#include "twn_text_c.h"
 | 
				
			||||||
#include "twn_util.h"
 | 
					 | 
				
			||||||
#include "twn_option.h"
 | 
					#include "twn_option.h"
 | 
				
			||||||
 | 
					#include "twn_deferred_commands.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <SDL2/SDL.h>
 | 
					#include <SDL2/SDL.h>
 | 
				
			||||||
#include <stb_truetype.h>
 | 
					#include <stb_truetype.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef EMSCRIPTEN
 | 
					 | 
				
			||||||
#include <GLES2/gl2.h>
 | 
					 | 
				
			||||||
#else
 | 
					 | 
				
			||||||
#include <glad/glad.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <stdbool.h>
 | 
					#include <stdbool.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
extern Matrix4 camera_projection_matrix;
 | 
					extern Matrix4 camera_projection_matrix;
 | 
				
			||||||
extern Matrix4 camera_look_at_matrix;
 | 
					extern Matrix4 camera_look_at_matrix;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern double depth_range_low, depth_range_high;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define QUAD_ELEMENT_BUFFER_LENGTH (65536 / 6)
 | 
					#define QUAD_ELEMENT_BUFFER_LENGTH (65536 / 6)
 | 
				
			||||||
#define CIRCLE_VERTICES_MAX 2048
 | 
					#define CIRCLE_VERTICES_MAX 2048
 | 
				
			||||||
#define CIRCLE_ELEMENT_BUFFER_LENGTH (CIRCLE_VERTICES_MAX * 3)
 | 
					
 | 
				
			||||||
 | 
					/* TODO: limit to only most necessary */
 | 
				
			||||||
 | 
					enum {
 | 
				
			||||||
 | 
					    TWN_FLOAT,
 | 
				
			||||||
 | 
					    TWN_INT,
 | 
				
			||||||
 | 
					    TWN_SHORT,
 | 
				
			||||||
 | 
					    TWN_UNSIGNED_SHORT,
 | 
				
			||||||
 | 
					    TWN_UNSIGNED_INT,
 | 
				
			||||||
 | 
					    TWN_BYTE,
 | 
				
			||||||
 | 
					    TWN_UNSIGNED_BYTE,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef GLuint VertexBuffer;
 | 
					typedef uint32_t VertexBuffer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct VertexBufferBuilder {
 | 
					typedef struct VertexBufferBuilder {
 | 
				
			||||||
    size_t bytes_left;
 | 
					    size_t size;
 | 
				
			||||||
    void *mapping;
 | 
					    void *base;
 | 
				
			||||||
} VertexBufferBuilder;
 | 
					} VertexBufferBuilder;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -39,45 +47,55 @@ typedef struct SpritePrimitive {
 | 
				
			|||||||
    Color color;
 | 
					    Color color;
 | 
				
			||||||
    float rotation;
 | 
					    float rotation;
 | 
				
			||||||
    TextureKey texture_key;
 | 
					    TextureKey texture_key;
 | 
				
			||||||
    bool flip_x;
 | 
					 | 
				
			||||||
    bool flip_y;
 | 
					 | 
				
			||||||
    bool repeat;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    m_option_list(
 | 
					    m_option_list(
 | 
				
			||||||
        Rect, texture_region )
 | 
					        Rect, texture_region )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool flip_x;
 | 
				
			||||||
 | 
					    bool flip_y;
 | 
				
			||||||
 | 
					    bool repeat;
 | 
				
			||||||
} SpritePrimitive;
 | 
					} SpritePrimitive;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct LinePrimitive {
 | 
				
			||||||
 | 
					    Vec2 start;
 | 
				
			||||||
 | 
					    Vec2 finish;
 | 
				
			||||||
 | 
					    float thickness;
 | 
				
			||||||
 | 
					    Color color;
 | 
				
			||||||
 | 
					} LinePrimitive;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct RectPrimitive {
 | 
					typedef struct RectPrimitive {
 | 
				
			||||||
    Rect rect;
 | 
					    Rect rect;
 | 
				
			||||||
    Color color;
 | 
					    Color color;
 | 
				
			||||||
} RectPrimitive;
 | 
					} RectPrimitive;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct CirclePrimitive {
 | 
					typedef struct CirclePrimitive {
 | 
				
			||||||
 | 
					    Vec2 position;
 | 
				
			||||||
    float radius;
 | 
					    float radius;
 | 
				
			||||||
    Color color;
 | 
					    Color color;
 | 
				
			||||||
    Vec2 position;
 | 
					 | 
				
			||||||
} CirclePrimitive;
 | 
					} CirclePrimitive;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct TextPrimitive {
 | 
					typedef struct TextPrimitive {
 | 
				
			||||||
    Color color;
 | 
					 | 
				
			||||||
    Vec2 position;
 | 
					    Vec2 position;
 | 
				
			||||||
    char *text;
 | 
					    char *text;
 | 
				
			||||||
    const char *font;
 | 
					    const char *font;
 | 
				
			||||||
 | 
					    Color color;
 | 
				
			||||||
    int height_px;
 | 
					    int height_px;
 | 
				
			||||||
} TextPrimitive;
 | 
					} TextPrimitive;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef enum Primitive2DType {
 | 
					typedef enum Primitive2DType {
 | 
				
			||||||
    PRIMITIVE_2D_SPRITE,
 | 
					    PRIMITIVE_2D_SPRITE,
 | 
				
			||||||
 | 
					    PRIMITIVE_2D_LINE,
 | 
				
			||||||
    PRIMITIVE_2D_RECT,
 | 
					    PRIMITIVE_2D_RECT,
 | 
				
			||||||
    PRIMITIVE_2D_CIRCLE,
 | 
					    PRIMITIVE_2D_CIRCLE,
 | 
				
			||||||
    PRIMITIVE_2D_TEXT,
 | 
					    PRIMITIVE_2D_TEXT,
 | 
				
			||||||
} Primitive2DType;
 | 
					} Primitive2DType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct Primitive2D {
 | 
					typedef struct Primitive2D {
 | 
				
			||||||
    Primitive2DType type;
 | 
					    Primitive2DType type; /* TODO: separate to structure of arrays for more efficient memory usage? */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    union {
 | 
					    union {
 | 
				
			||||||
        SpritePrimitive sprite;
 | 
					        SpritePrimitive sprite;
 | 
				
			||||||
 | 
					        LinePrimitive line;
 | 
				
			||||||
        RectPrimitive rect;
 | 
					        RectPrimitive rect;
 | 
				
			||||||
        CirclePrimitive circle;
 | 
					        CirclePrimitive circle;
 | 
				
			||||||
        TextPrimitive text;
 | 
					        TextPrimitive text;
 | 
				
			||||||
@@ -85,34 +103,30 @@ typedef struct Primitive2D {
 | 
				
			|||||||
} Primitive2D;
 | 
					} Primitive2D;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* union for in-place recalculation of texture coordinates */
 | 
					/* union for in-place recalculation of texture coordinates */
 | 
				
			||||||
union UncoloredSpaceTriangle {
 | 
					/* needs to be later resolved in texture atlas */
 | 
				
			||||||
    /* pending for sending, uvs are not final as texture atlases could update */
 | 
					typedef struct UncoloredSpaceTriangle {
 | 
				
			||||||
    struct UncoloredSpaceTrianglePrimitive {
 | 
					 | 
				
			||||||
    Vec3 v0;
 | 
					    Vec3 v0;
 | 
				
			||||||
    Vec2 uv0; /* in pixels */
 | 
					    Vec2 uv0; /* in pixels */
 | 
				
			||||||
    Vec3 v1;
 | 
					    Vec3 v1;
 | 
				
			||||||
    Vec2 uv1; /* in pixels */
 | 
					    Vec2 uv1; /* in pixels */
 | 
				
			||||||
    Vec3 v2;
 | 
					    Vec3 v2;
 | 
				
			||||||
    Vec2 uv2; /* in pixels */
 | 
					    Vec2 uv2; /* in pixels */
 | 
				
			||||||
    } primitive;
 | 
					} UncoloredSpaceTriangle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* TODO: have it packed? */
 | 
					typedef struct SpaceBillboard {
 | 
				
			||||||
    /* structure that is passed in opengl vertex array */
 | 
					    Vec3 position;
 | 
				
			||||||
    struct UncoloredSpaceTrianglePayload {
 | 
					    Vec2 size;
 | 
				
			||||||
        Vec3 v0;
 | 
					    Color color;
 | 
				
			||||||
        Vec2 uv0;
 | 
					    // TextureKey texture; /* is assumed from other places */
 | 
				
			||||||
        Vec3 v1;
 | 
					    bool cylindrical;
 | 
				
			||||||
        Vec2 uv1;
 | 
					} SpaceBillboard;
 | 
				
			||||||
        Vec3 v2;
 | 
					 | 
				
			||||||
        Vec2 uv2;
 | 
					 | 
				
			||||||
    } payload;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* batch of primitives with overlapping properties */
 | 
					/* batch of primitives with overlapping properties */
 | 
				
			||||||
typedef struct MeshBatch {
 | 
					typedef struct MeshBatch {
 | 
				
			||||||
    uint8_t *primitives;
 | 
					    uint8_t *primitives; /* note: interpretation of it is arbitrary */
 | 
				
			||||||
} MeshBatch;
 | 
					} MeshBatch;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* TODO: use atlas id instead */
 | 
				
			||||||
typedef struct MeshBatchItem {
 | 
					typedef struct MeshBatchItem {
 | 
				
			||||||
    TextureKey key;
 | 
					    TextureKey key;
 | 
				
			||||||
    struct MeshBatch value;
 | 
					    struct MeshBatch value;
 | 
				
			||||||
@@ -123,6 +137,95 @@ typedef struct TextCache {
 | 
				
			|||||||
} TextCache;
 | 
					} TextCache;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* TODO: try using the fact we utilize edge coloring and step virtual color attributes to bogus points */
 | 
				
			||||||
 | 
					/*       this is only doable is we take out color attribute to separate array or a portion of it */
 | 
				
			||||||
 | 
					/* interleaved vertex array data */
 | 
				
			||||||
 | 
					typedef struct ElementIndexedQuad {
 | 
				
			||||||
 | 
					    /* upper-left */
 | 
				
			||||||
 | 
					    Vec2 v0;
 | 
				
			||||||
 | 
					    Vec2 uv0;
 | 
				
			||||||
 | 
					    Color c0;
 | 
				
			||||||
 | 
					    /* bottom-left */
 | 
				
			||||||
 | 
					    Vec2 v1;
 | 
				
			||||||
 | 
					    Vec2 uv1;
 | 
				
			||||||
 | 
					    Color c1;
 | 
				
			||||||
 | 
					    /* bottom-right */
 | 
				
			||||||
 | 
					    Vec2 v2;
 | 
				
			||||||
 | 
					    Vec2 uv2;
 | 
				
			||||||
 | 
					    Color c2;
 | 
				
			||||||
 | 
					    /* upper-right */
 | 
				
			||||||
 | 
					    Vec2 v3;
 | 
				
			||||||
 | 
					    Vec2 uv3;
 | 
				
			||||||
 | 
					    Color c3;
 | 
				
			||||||
 | 
					} ElementIndexedQuad;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct ElementIndexedQuadWithoutColor {
 | 
				
			||||||
 | 
					    /* upper-left */
 | 
				
			||||||
 | 
					    Vec2 v0;
 | 
				
			||||||
 | 
					    Vec2 uv0;
 | 
				
			||||||
 | 
					    /* bottom-left */
 | 
				
			||||||
 | 
					    Vec2 v1;
 | 
				
			||||||
 | 
					    Vec2 uv1;
 | 
				
			||||||
 | 
					    /* bottom-right */
 | 
				
			||||||
 | 
					    Vec2 v2;
 | 
				
			||||||
 | 
					    Vec2 uv2;
 | 
				
			||||||
 | 
					    /* upper-right */
 | 
				
			||||||
 | 
					    Vec2 v3;
 | 
				
			||||||
 | 
					    Vec2 uv3;
 | 
				
			||||||
 | 
					} ElementIndexedQuadWithoutColor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct ElementIndexedQuadWithoutTexture {
 | 
				
			||||||
 | 
					    /* upper-left */
 | 
				
			||||||
 | 
					    Vec2 v0;
 | 
				
			||||||
 | 
					    Color c0;
 | 
				
			||||||
 | 
					    /* bottom-left */
 | 
				
			||||||
 | 
					    Vec2 v1;
 | 
				
			||||||
 | 
					    Color c1;
 | 
				
			||||||
 | 
					    /* bottom-right */
 | 
				
			||||||
 | 
					    Vec2 v2;
 | 
				
			||||||
 | 
					    Color c2;
 | 
				
			||||||
 | 
					    /* upper-right */
 | 
				
			||||||
 | 
					    Vec2 v3;
 | 
				
			||||||
 | 
					    Color c3;
 | 
				
			||||||
 | 
					} ElementIndexedQuadWithoutTexture;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct ElementIndexedQuadWithoutColorWithoutTexture {
 | 
				
			||||||
 | 
					    /* upper-left */
 | 
				
			||||||
 | 
					    Vec2 v0;
 | 
				
			||||||
 | 
					    /* bottom-left */
 | 
				
			||||||
 | 
					    Vec2 v1;
 | 
				
			||||||
 | 
					    /* bottom-right */
 | 
				
			||||||
 | 
					    Vec2 v2;
 | 
				
			||||||
 | 
					    /* upper-right */
 | 
				
			||||||
 | 
					    Vec2 v3;
 | 
				
			||||||
 | 
					} ElementIndexedQuadWithoutColorWithoutTexture;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* TODO: no color variant */
 | 
				
			||||||
 | 
					typedef struct ElementIndexedBillboard {
 | 
				
			||||||
 | 
					    /* upper-left */
 | 
				
			||||||
 | 
					    Vec3 v0;
 | 
				
			||||||
 | 
					    Vec2 uv0;
 | 
				
			||||||
 | 
					    Color c0;
 | 
				
			||||||
 | 
					    /* bottom-left */
 | 
				
			||||||
 | 
					    Vec3 v1;
 | 
				
			||||||
 | 
					    Vec2 uv1;
 | 
				
			||||||
 | 
					    Color c1;
 | 
				
			||||||
 | 
					    /* bottom-right */
 | 
				
			||||||
 | 
					    Vec3 v2;
 | 
				
			||||||
 | 
					    Vec2 uv2;
 | 
				
			||||||
 | 
					    Color c2;
 | 
				
			||||||
 | 
					    /* upper-right */
 | 
				
			||||||
 | 
					    Vec3 v3;
 | 
				
			||||||
 | 
					    Vec2 uv3;
 | 
				
			||||||
 | 
					    Color c3;
 | 
				
			||||||
 | 
					} ElementIndexedBillboard;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool render_init(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* renders the background, then the primitives in all render queues */
 | 
					/* renders the background, then the primitives in all render queues */
 | 
				
			||||||
void render(void);
 | 
					void render(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -138,6 +241,7 @@ void create_circle_geometry(Vec2 position,
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
struct QuadBatch {
 | 
					struct QuadBatch {
 | 
				
			||||||
    size_t size;             /* how many primitives are in current batch */
 | 
					    size_t size;             /* how many primitives are in current batch */
 | 
				
			||||||
 | 
					    TextureKey texture_key;
 | 
				
			||||||
    TextureMode mode;        /* how color should be applied */
 | 
					    TextureMode mode;        /* how color should be applied */
 | 
				
			||||||
    bool constant_colored;   /* whether colored batch is uniformly colored */
 | 
					    bool constant_colored;   /* whether colored batch is uniformly colored */
 | 
				
			||||||
    bool repeat;             /* whether repeat is needed */
 | 
					    bool repeat;             /* whether repeat is needed */
 | 
				
			||||||
@@ -149,9 +253,6 @@ struct QuadBatch collect_rect_batch(const Primitive2D primitives[], size_t len);
 | 
				
			|||||||
void render_sprite_batch(const Primitive2D primitives[], struct QuadBatch batch);
 | 
					void render_sprite_batch(const Primitive2D primitives[], struct QuadBatch batch);
 | 
				
			||||||
void render_rect_batch(const Primitive2D primitives[], struct QuadBatch batch);
 | 
					void render_rect_batch(const Primitive2D primitives[], struct QuadBatch batch);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void draw_uncolored_space_traingle_batch(MeshBatch *batch,
 | 
					 | 
				
			||||||
                                         TextureKey texture_key);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* text */
 | 
					/* text */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void render_text(const TextPrimitive *text);
 | 
					void render_text(const TextPrimitive *text);
 | 
				
			||||||
@@ -166,6 +267,8 @@ void text_cache_reset_arena(TextCache *cache);
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
VertexBuffer create_vertex_buffer(void);
 | 
					VertexBuffer create_vertex_buffer(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void restart_scratch_vertex_arrays(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
VertexBuffer get_scratch_vertex_array(void);
 | 
					VertexBuffer get_scratch_vertex_array(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void delete_vertex_buffer(VertexBuffer buffer);
 | 
					void delete_vertex_buffer(VertexBuffer buffer);
 | 
				
			||||||
@@ -175,16 +278,14 @@ void specify_vertex_buffer(VertexBuffer buffer, void const *data, size_t bytes);
 | 
				
			|||||||
/* uses present in 1.5 buffer mapping feature */
 | 
					/* uses present in 1.5 buffer mapping feature */
 | 
				
			||||||
VertexBufferBuilder build_vertex_buffer(VertexBuffer buffer, size_t bytes);
 | 
					VertexBufferBuilder build_vertex_buffer(VertexBuffer buffer, size_t bytes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* collects bytes for sending to the gpu until all is pushed, which is when false is returned */
 | 
					void finish_vertex_builder(VertexBufferBuilder *builder);
 | 
				
			||||||
bool push_to_vertex_buffer_builder(VertexBufferBuilder *builder,
 | 
					 | 
				
			||||||
                                   void const *bytes,
 | 
					 | 
				
			||||||
                                   size_t size);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* state */
 | 
					/* state */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void setup_viewport(int x, int y, int width, int height);
 | 
					void setup_viewport(int x, int y, int width, int height);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void clear_draw_buffer(void);
 | 
					void clear_draw_buffer(void);
 | 
				
			||||||
 | 
					void finally_clear_draw_buffer(DeferredCommandClear command);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void swap_buffers(void);
 | 
					void swap_buffers(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -196,35 +297,28 @@ VertexBuffer get_circle_element_buffer(void);
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void render_circle(const CirclePrimitive *circle);
 | 
					void render_circle(const CirclePrimitive *circle);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void render_line(const LinePrimitive *line);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void render_rectangle(const RectPrimitive *rectangle);
 | 
					void render_rectangle(const RectPrimitive *rectangle);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void use_space_pipeline(void);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void use_2d_pipeline(void);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void use_texture_mode(TextureMode mode);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void finally_render_quads(Primitive2D const primitives[],
 | 
					void finally_render_quads(Primitive2D const primitives[],
 | 
				
			||||||
                          struct QuadBatch batch,
 | 
					                          struct QuadBatch batch,
 | 
				
			||||||
                          VertexBuffer buffer);
 | 
					                          VertexBuffer buffer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
size_t get_quad_payload_size(struct QuadBatch batch);
 | 
					size_t get_quad_payload_size(struct QuadBatch batch);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool push_quad_payload_to_vertex_buffer_builder(struct QuadBatch batch,
 | 
					void push_quad_payload_to_vertex_buffer_builder(struct QuadBatch batch,
 | 
				
			||||||
 | 
					                                                size_t index,
 | 
				
			||||||
                                                VertexBufferBuilder *builder,
 | 
					                                                VertexBufferBuilder *builder,
 | 
				
			||||||
                                                Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3,
 | 
					                                                Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3,
 | 
				
			||||||
                                                Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3,
 | 
					                                                Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3,
 | 
				
			||||||
                                                Color color);
 | 
					                                                Color color);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void finally_draw_uncolored_space_traingle_batch(MeshBatch const *batch,
 | 
					void finally_draw_uncolored_space_traingle_batch(MeshBatch const *batch,
 | 
				
			||||||
                                                 TextureKey texture_key,
 | 
					                                                 TextureKey texture_key);
 | 
				
			||||||
                                                 VertexBuffer buffer);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
size_t get_text_payload_size(void);
 | 
					void finally_draw_billboard_batch(MeshBatch const *batch,
 | 
				
			||||||
 | 
					                                  TextureKey texture_key);
 | 
				
			||||||
bool push_text_payload_to_vertex_buffer_builder(FontData const *font_data,
 | 
					 | 
				
			||||||
                                                VertexBufferBuilder *builder,
 | 
					 | 
				
			||||||
                                                stbtt_aligned_quad quad);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
void finally_draw_text(FontData const *font_data,
 | 
					void finally_draw_text(FontData const *font_data,
 | 
				
			||||||
                       size_t len,
 | 
					                       size_t len,
 | 
				
			||||||
@@ -232,19 +326,13 @@ void finally_draw_text(FontData const *font_data,
 | 
				
			|||||||
                       VertexBuffer buffer);
 | 
					                       VertexBuffer buffer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void render_skybox(void);
 | 
					void render_skybox(void);
 | 
				
			||||||
 | 
					void finally_render_skybox(DeferredCommandDrawSkybox);
 | 
				
			||||||
void finally_render_skybox(char *paths_in_use);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void apply_fog(void);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void finally_apply_fog(float start, float end, float density, Color color);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void pop_fog(void);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void finally_pop_fog(void);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
void start_render_frame(void);
 | 
					void start_render_frame(void);
 | 
				
			||||||
 | 
					 | 
				
			||||||
void end_render_frame(void);
 | 
					void end_render_frame(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void finally_draw_command(DeferredCommandDraw command);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void issue_deferred_draw_commands(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,34 +0,0 @@
 | 
				
			|||||||
#include "twn_draw.h"
 | 
					 | 
				
			||||||
#include "twn_draw_c.h"
 | 
					 | 
				
			||||||
#include "twn_util.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <stdbool.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static float start_cache, end_cache, density_cache;
 | 
					 | 
				
			||||||
static Color color_cache;
 | 
					 | 
				
			||||||
static bool fog_used = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void draw_fog(float start, float end, float density, Color color) {
 | 
					 | 
				
			||||||
    start_cache = start;
 | 
					 | 
				
			||||||
    end_cache = end;
 | 
					 | 
				
			||||||
    density_cache = density;
 | 
					 | 
				
			||||||
    color_cache = color;
 | 
					 | 
				
			||||||
    fog_used = true;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void apply_fog(void) {
 | 
					 | 
				
			||||||
    if (!fog_used)
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    finally_apply_fog(start_cache, end_cache, density_cache, color_cache);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void pop_fog(void) {
 | 
					 | 
				
			||||||
    if (!fog_used)
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    finally_pop_fog();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,71 +0,0 @@
 | 
				
			|||||||
#include "twn_gpu_texture_c.h"
 | 
					 | 
				
			||||||
#include "twn_util_c.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
GPUTexture create_gpu_texture(TextureFilter filter, bool generate_mipmaps) {
 | 
					 | 
				
			||||||
    GLuint texture;
 | 
					 | 
				
			||||||
    glGenTextures(1, &texture);
 | 
					 | 
				
			||||||
    glBindTexture(GL_TEXTURE_2D, texture);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, (GLboolean)generate_mipmaps);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (filter == TEXTURE_FILTER_NEAREAST) {
 | 
					 | 
				
			||||||
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
 | 
					 | 
				
			||||||
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
 | 
					 | 
				
			||||||
    } else if (filter == TEXTURE_FILTER_LINEAR) {
 | 
					 | 
				
			||||||
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
 | 
					 | 
				
			||||||
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
 | 
					 | 
				
			||||||
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if !defined(EMSCRIPTEN)
 | 
					 | 
				
			||||||
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    glBindTexture(GL_TEXTURE_2D, 0);
 | 
					 | 
				
			||||||
    return texture;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void delete_gpu_texture(GPUTexture texture) {
 | 
					 | 
				
			||||||
    glDeleteTextures(1, &texture);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void upload_gpu_texture(GPUTexture texture, void *pixels, int channels, int width, int height) {
 | 
					 | 
				
			||||||
    glBindTexture(GL_TEXTURE_2D, texture);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    int format_internal, format;
 | 
					 | 
				
			||||||
    if (channels == 4) {
 | 
					 | 
				
			||||||
        format_internal = GL_RGBA8;
 | 
					 | 
				
			||||||
        format = GL_RGBA;
 | 
					 | 
				
			||||||
    } else if (channels == 3) {
 | 
					 | 
				
			||||||
        format_internal = GL_RGBA8;
 | 
					 | 
				
			||||||
        format = GL_RGB;
 | 
					 | 
				
			||||||
    } else if (channels == 1) {
 | 
					 | 
				
			||||||
        format_internal = GL_ALPHA;
 | 
					 | 
				
			||||||
        format = GL_ALPHA;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        CRY("upload_gpu_texture", "Unsupported channel count");
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    glTexImage2D(GL_TEXTURE_2D,
 | 
					 | 
				
			||||||
                 0,
 | 
					 | 
				
			||||||
                 format_internal,
 | 
					 | 
				
			||||||
                 width,
 | 
					 | 
				
			||||||
                 height,
 | 
					 | 
				
			||||||
                 0,
 | 
					 | 
				
			||||||
                 format,
 | 
					 | 
				
			||||||
                 GL_UNSIGNED_BYTE,
 | 
					 | 
				
			||||||
                 pixels);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    glBindTexture(GL_TEXTURE_2D, 0);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void bind_gpu_texture(GPUTexture texture) {
 | 
					 | 
				
			||||||
    glBindTexture(GL_TEXTURE_2D, texture);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,6 +1,8 @@
 | 
				
			|||||||
#include "twn_draw_c.h"
 | 
					 | 
				
			||||||
#include "twn_engine_context_c.h"
 | 
					#include "twn_engine_context_c.h"
 | 
				
			||||||
#include "twn_util_c.h"
 | 
					#include "twn_util_c.h"
 | 
				
			||||||
 | 
					#include "twn_draw_c.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <stb_ds.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef EMSCRIPTEN
 | 
					#ifdef EMSCRIPTEN
 | 
				
			||||||
#include <GLES2/gl2.h>
 | 
					#include <GLES2/gl2.h>
 | 
				
			||||||
@@ -42,16 +44,15 @@ VertexBuffer get_quad_element_buffer(void) {
 | 
				
			|||||||
        VertexBufferBuilder builder = build_vertex_buffer(buffer, sizeof (GLshort) * QUAD_ELEMENT_BUFFER_LENGTH * 6 );
 | 
					        VertexBufferBuilder builder = build_vertex_buffer(buffer, sizeof (GLshort) * QUAD_ELEMENT_BUFFER_LENGTH * 6 );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (size_t i = 0; i < QUAD_ELEMENT_BUFFER_LENGTH; ++i) {
 | 
					        for (size_t i = 0; i < QUAD_ELEMENT_BUFFER_LENGTH; ++i) {
 | 
				
			||||||
            GLshort indices[6];
 | 
					            ((GLshort *)builder.base)[i * 6 + 0] = (GLshort)(i * 4 + 0);
 | 
				
			||||||
            indices[0] = (GLshort)(i * 4 + 0);
 | 
					            ((GLshort *)builder.base)[i * 6 + 1] = (GLshort)(i * 4 + 1);
 | 
				
			||||||
            indices[1] = (GLshort)(i * 4 + 1);
 | 
					            ((GLshort *)builder.base)[i * 6 + 2] = (GLshort)(i * 4 + 2);
 | 
				
			||||||
            indices[2] = (GLshort)(i * 4 + 2);
 | 
					            ((GLshort *)builder.base)[i * 6 + 3] = (GLshort)(i * 4 + 2);
 | 
				
			||||||
            indices[3] = (GLshort)(i * 4 + 2);
 | 
					            ((GLshort *)builder.base)[i * 6 + 4] = (GLshort)(i * 4 + 3);
 | 
				
			||||||
            indices[4] = (GLshort)(i * 4 + 3);
 | 
					            ((GLshort *)builder.base)[i * 6 + 5] = (GLshort)(i * 4 + 0);
 | 
				
			||||||
            indices[5] = (GLshort)(i * 4 + 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            push_to_vertex_buffer_builder(&builder, indices, sizeof indices);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        finish_vertex_builder(&builder);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    SDL_assert_always(buffer);
 | 
					    SDL_assert_always(buffer);
 | 
				
			||||||
@@ -65,27 +66,135 @@ VertexBuffer get_circle_element_buffer(void) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if (buffer == 0) {
 | 
					    if (buffer == 0) {
 | 
				
			||||||
        buffer = create_vertex_buffer();
 | 
					        buffer = create_vertex_buffer();
 | 
				
			||||||
        VertexBufferBuilder builder = build_vertex_buffer(buffer, sizeof (GLshort) * CIRCLE_ELEMENT_BUFFER_LENGTH);
 | 
					        VertexBufferBuilder builder = build_vertex_buffer(buffer, sizeof (GLshort) * (CIRCLE_VERTICES_MAX - 2) * 3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (size_t i = 1; i < CIRCLE_VERTICES_MAX; ++i) {
 | 
					        for (size_t i = 1; i < CIRCLE_VERTICES_MAX - 1; ++i) {
 | 
				
			||||||
            /* first one is center point index, always zero */
 | 
					            /* first one is center point index, always zero */
 | 
				
			||||||
            GLshort indices[3];
 | 
					            ((GLshort *)builder.base)[(i - 1) * 3 + 0] = 0;
 | 
				
			||||||
 | 
					 | 
				
			||||||
            indices[0] = 0;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            /* generated point index */
 | 
					            /* generated point index */
 | 
				
			||||||
            indices[1] = (GLshort)i;
 | 
					            ((GLshort *)builder.base)[(i - 1) * 3 + 1] = (GLshort)i;
 | 
				
			||||||
 | 
					            ((GLshort *)builder.base)[(i - 1) * 3 + 2] = (GLshort)i + 1;
 | 
				
			||||||
            size_t index = (i + 1) % (CIRCLE_VERTICES_MAX - 1);
 | 
					 | 
				
			||||||
            if (index == 0) /* don't use center for outer ring */
 | 
					 | 
				
			||||||
                index = (CIRCLE_VERTICES_MAX - 1);
 | 
					 | 
				
			||||||
            indices[2] = (GLshort)index;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            push_to_vertex_buffer_builder(&builder, indices, sizeof indices);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        finish_vertex_builder(&builder);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    SDL_assert_always(buffer);
 | 
					    SDL_assert_always(buffer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return buffer;
 | 
					    return buffer;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* potentially double buffered array of vertex array handles */
 | 
				
			||||||
 | 
					/* we assume they will be refilled fully each frame */
 | 
				
			||||||
 | 
					static size_t scratch_va_front_used, scratch_va_back_used;
 | 
				
			||||||
 | 
					static GLuint *front_scratch_vertex_arrays, *back_scratch_vertex_arrays;
 | 
				
			||||||
 | 
					static GLuint **current_scratch_vertex_array = &front_scratch_vertex_arrays;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void restart_scratch_vertex_arrays(void) {
 | 
				
			||||||
 | 
					    scratch_va_front_used = 0;
 | 
				
			||||||
 | 
					    scratch_va_back_used = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (ctx.render_double_buffered) {
 | 
				
			||||||
 | 
					        current_scratch_vertex_array = current_scratch_vertex_array == &front_scratch_vertex_arrays ?
 | 
				
			||||||
 | 
					                &back_scratch_vertex_arrays : &front_scratch_vertex_arrays;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					GLuint get_scratch_vertex_array(void) {
 | 
				
			||||||
 | 
					    size_t *used = current_scratch_vertex_array == &front_scratch_vertex_arrays ?
 | 
				
			||||||
 | 
					            &scratch_va_front_used : &scratch_va_back_used;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (arrlenu(*current_scratch_vertex_array) <= *used) {
 | 
				
			||||||
 | 
					        GLuint handle;
 | 
				
			||||||
 | 
					        glGenBuffers(1, &handle);
 | 
				
			||||||
 | 
					        arrpush(*current_scratch_vertex_array, handle);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    (*used)++;
 | 
				
			||||||
 | 
					    return (*current_scratch_vertex_array)[*used - 1];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					GPUTexture create_gpu_texture(TextureFilter filter, bool generate_mipmaps) {
 | 
				
			||||||
 | 
					    GLuint texture;
 | 
				
			||||||
 | 
					    glGenTextures(1, &texture);
 | 
				
			||||||
 | 
					    glBindTexture(GL_TEXTURE_2D, texture);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if !defined(EMSCRIPTEN)
 | 
				
			||||||
 | 
					    glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, (GLboolean)generate_mipmaps);
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					    if (generate_mipmaps)
 | 
				
			||||||
 | 
					        glGenerateMipmap(GL_TEXTURE_2D);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (filter == TEXTURE_FILTER_NEAREAST) {
 | 
				
			||||||
 | 
					        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
 | 
				
			||||||
 | 
					        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
 | 
				
			||||||
 | 
					    } else if (filter == TEXTURE_FILTER_LINEAR) {
 | 
				
			||||||
 | 
					        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
 | 
				
			||||||
 | 
					        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
 | 
				
			||||||
 | 
					    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if !defined(EMSCRIPTEN)
 | 
				
			||||||
 | 
					    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    glBindTexture(GL_TEXTURE_2D, 0);
 | 
				
			||||||
 | 
					    return texture;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void delete_gpu_texture(GPUTexture texture) {
 | 
				
			||||||
 | 
					    glDeleteTextures(1, &texture);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void upload_gpu_texture(GPUTexture texture, void *pixels, int channels, int width, int height) {
 | 
				
			||||||
 | 
					    glBindTexture(GL_TEXTURE_2D, texture);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    int format_internal, format;
 | 
				
			||||||
 | 
					    if (channels == 4) {
 | 
				
			||||||
 | 
					        #ifdef EMSCRIPTEN
 | 
				
			||||||
 | 
					        format_internal = GL_RGBA;
 | 
				
			||||||
 | 
					        #else
 | 
				
			||||||
 | 
					        format_internal = GL_RGBA8;
 | 
				
			||||||
 | 
					        #endif
 | 
				
			||||||
 | 
					        format = GL_RGBA;
 | 
				
			||||||
 | 
					    } else if (channels == 3) {
 | 
				
			||||||
 | 
					        #ifdef EMSCRIPTEN
 | 
				
			||||||
 | 
					        format_internal = GL_RGBA;
 | 
				
			||||||
 | 
					        #else
 | 
				
			||||||
 | 
					        format_internal = GL_RGBA8;
 | 
				
			||||||
 | 
					        #endif
 | 
				
			||||||
 | 
					        format = GL_RGB;
 | 
				
			||||||
 | 
					    } else if (channels == 1) {
 | 
				
			||||||
 | 
					        format_internal = GL_ALPHA;
 | 
				
			||||||
 | 
					        format = GL_ALPHA;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        CRY("upload_gpu_texture", "Unsupported channel count");
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    glTexImage2D(GL_TEXTURE_2D,
 | 
				
			||||||
 | 
					                 0,
 | 
				
			||||||
 | 
					                 format_internal,
 | 
				
			||||||
 | 
					                 width,
 | 
				
			||||||
 | 
					                 height,
 | 
				
			||||||
 | 
					                 0,
 | 
				
			||||||
 | 
					                 format,
 | 
				
			||||||
 | 
					                 GL_UNSIGNED_BYTE,
 | 
				
			||||||
 | 
					                 pixels);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    glBindTexture(GL_TEXTURE_2D, 0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void bind_gpu_texture(GPUTexture texture) {
 | 
				
			||||||
 | 
					    glBindTexture(GL_TEXTURE_2D, texture);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,8 +2,9 @@
 | 
				
			|||||||
#define TWN_GPU_TEXTURE_C_H
 | 
					#define TWN_GPU_TEXTURE_C_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <stdbool.h>
 | 
					#include <stdbool.h>
 | 
				
			||||||
 | 
					#include <stdint.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef GLuint GPUTexture;
 | 
					typedef uint32_t GPUTexture;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef enum TextureFilter {
 | 
					typedef enum TextureFilter {
 | 
				
			||||||
    TEXTURE_FILTER_NEAREAST,
 | 
					    TEXTURE_FILTER_NEAREAST,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,30 +1,173 @@
 | 
				
			|||||||
#include "twn_draw_c.h"
 | 
					#include "twn_draw_c.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <stb_ds.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <stddef.h>
 | 
					#include <stddef.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct QuadBatch collect_quad_batch(const Primitive2D primitives[], size_t len) {
 | 
					void finally_render_quads(const Primitive2D primitives[],
 | 
				
			||||||
    if (primitives[0].type == PRIMITIVE_2D_SPRITE)
 | 
					                          const struct QuadBatch batch,
 | 
				
			||||||
        return collect_sprite_batch(primitives, len);
 | 
					                          const VertexBuffer buffer)
 | 
				
			||||||
    else if (primitives[0].type == PRIMITIVE_2D_RECT)
 | 
					 | 
				
			||||||
        return collect_rect_batch(primitives, len);
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
        SDL_assert(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return (struct QuadBatch){0};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* assumes that orthogonal matrix setup is done already */
 | 
					 | 
				
			||||||
void render_quad_batch(const Primitive2D primitives[],
 | 
					 | 
				
			||||||
                       const struct QuadBatch batch)
 | 
					 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    if (primitives[0].type == PRIMITIVE_2D_SPRITE)
 | 
					    DeferredCommandDraw command = {0};
 | 
				
			||||||
        render_sprite_batch(primitives, batch);
 | 
					 | 
				
			||||||
    else if (primitives[0].type == PRIMITIVE_2D_RECT)
 | 
					 | 
				
			||||||
        render_rect_batch(primitives, batch);
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
        SDL_assert(false);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (struct QuadBatch){0};
 | 
					    uint32_t off = 0, voff = 0, uvoff = 0, coff = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!batch.constant_colored && batch.textured) {
 | 
				
			||||||
 | 
					        off   = offsetof(ElementIndexedQuad, v1);
 | 
				
			||||||
 | 
					        voff  = offsetof(ElementIndexedQuad, v0);
 | 
				
			||||||
 | 
					        uvoff = offsetof(ElementIndexedQuad, uv0);
 | 
				
			||||||
 | 
					        coff  = offsetof(ElementIndexedQuad, c0);
 | 
				
			||||||
 | 
					    } else if (batch.constant_colored && batch.textured) {
 | 
				
			||||||
 | 
					        off   = offsetof(ElementIndexedQuadWithoutColor, v1);
 | 
				
			||||||
 | 
					        voff  = offsetof(ElementIndexedQuadWithoutColor, v0);
 | 
				
			||||||
 | 
					        uvoff = offsetof(ElementIndexedQuadWithoutColor, uv0);
 | 
				
			||||||
 | 
					    } else if (!batch.constant_colored && !batch.textured) {
 | 
				
			||||||
 | 
					        off   = offsetof(ElementIndexedQuadWithoutTexture, v1);
 | 
				
			||||||
 | 
					        voff  = offsetof(ElementIndexedQuadWithoutTexture, v0);
 | 
				
			||||||
 | 
					        coff  = offsetof(ElementIndexedQuad, c0);
 | 
				
			||||||
 | 
					    } else if (batch.constant_colored && !batch.textured) {
 | 
				
			||||||
 | 
					        off   = offsetof(ElementIndexedQuadWithoutColorWithoutTexture, v1);
 | 
				
			||||||
 | 
					        voff  = offsetof(ElementIndexedQuadWithoutColorWithoutTexture, v0);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    command.vertices = (AttributeArrayPointer) {
 | 
				
			||||||
 | 
					        .arity = 2,
 | 
				
			||||||
 | 
					        .type = TWN_FLOAT,
 | 
				
			||||||
 | 
					        .stride = off,
 | 
				
			||||||
 | 
					        .offset = voff,
 | 
				
			||||||
 | 
					        .buffer = buffer
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (batch.textured)
 | 
				
			||||||
 | 
					        command.texcoords = (AttributeArrayPointer) {
 | 
				
			||||||
 | 
					            .arity = 2,
 | 
				
			||||||
 | 
					            .type = TWN_FLOAT,
 | 
				
			||||||
 | 
					            .stride = off,
 | 
				
			||||||
 | 
					            .offset = uvoff,
 | 
				
			||||||
 | 
					            .buffer = buffer
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!batch.constant_colored) {
 | 
				
			||||||
 | 
					        command.colors = (AttributeArrayPointer) {
 | 
				
			||||||
 | 
					            .arity = 4,
 | 
				
			||||||
 | 
					            .type = TWN_UNSIGNED_BYTE,
 | 
				
			||||||
 | 
					            .stride = off,
 | 
				
			||||||
 | 
					            .offset = coff,
 | 
				
			||||||
 | 
					            .buffer = buffer
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        command.constant_colored = true;
 | 
				
			||||||
 | 
					        command.color = primitives[0].sprite.color;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (batch.textured) {
 | 
				
			||||||
 | 
					        command.textured = true;
 | 
				
			||||||
 | 
					        command.texture_key = batch.texture_key;
 | 
				
			||||||
 | 
					        command.texture_repeat = batch.repeat;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    command.element_buffer = get_quad_element_buffer();
 | 
				
			||||||
 | 
					    command.element_count = 6 * (uint32_t)batch.size;
 | 
				
			||||||
 | 
					    command.range_end = 6 * (uint32_t)batch.size;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    command.texture_mode = batch.mode;
 | 
				
			||||||
 | 
					    command.pipeline = PIPELINE_2D;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    command.depth_range_high = depth_range_high;
 | 
				
			||||||
 | 
					    command.depth_range_low = depth_range_low;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    DeferredCommand final_command = {
 | 
				
			||||||
 | 
					        .type = DEFERRED_COMMAND_TYPE_DRAW,
 | 
				
			||||||
 | 
					        .draw = command
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    arrpush(deferred_commands, final_command);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					size_t get_quad_payload_size(struct QuadBatch batch) {
 | 
				
			||||||
 | 
					    if (batch.constant_colored && batch.textured)
 | 
				
			||||||
 | 
					        return sizeof (ElementIndexedQuadWithoutColor);
 | 
				
			||||||
 | 
					    else if (!batch.constant_colored && batch.textured)
 | 
				
			||||||
 | 
					        return sizeof (ElementIndexedQuad);
 | 
				
			||||||
 | 
					    else if (batch.constant_colored && !batch.textured)
 | 
				
			||||||
 | 
					        return sizeof (ElementIndexedQuadWithoutColorWithoutTexture);
 | 
				
			||||||
 | 
					    else if (!batch.constant_colored && !batch.textured)
 | 
				
			||||||
 | 
					        return sizeof (ElementIndexedQuadWithoutTexture);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    SDL_assert(false);
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void push_quad_payload_to_vertex_buffer_builder(struct QuadBatch batch,
 | 
				
			||||||
 | 
					                                                size_t index,
 | 
				
			||||||
 | 
					                                                VertexBufferBuilder *builder,
 | 
				
			||||||
 | 
					                                                Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3,
 | 
				
			||||||
 | 
					                                                Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3,
 | 
				
			||||||
 | 
					                                                Color color)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    if (!batch.constant_colored && batch.textured) {
 | 
				
			||||||
 | 
					        ElementIndexedQuad const payload = {
 | 
				
			||||||
 | 
					            .v0 = v0,
 | 
				
			||||||
 | 
					            .v1 = v1,
 | 
				
			||||||
 | 
					            .v2 = v2,
 | 
				
			||||||
 | 
					            .v3 = v3,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            .uv0 = uv0,
 | 
				
			||||||
 | 
					            .uv1 = uv1,
 | 
				
			||||||
 | 
					            .uv2 = uv2,
 | 
				
			||||||
 | 
					            .uv3 = uv3,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            /* equal for all (flat shaded) */
 | 
				
			||||||
 | 
					            .c0 = color,
 | 
				
			||||||
 | 
					            // .c1 = color,
 | 
				
			||||||
 | 
					            .c2 = color,
 | 
				
			||||||
 | 
					            // .c3 = color,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ((ElementIndexedQuad *)builder->base)[index] = payload;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    } else if (batch.constant_colored && batch.textured) {
 | 
				
			||||||
 | 
					        ElementIndexedQuadWithoutColor const payload = {
 | 
				
			||||||
 | 
					            .v0 = v0,
 | 
				
			||||||
 | 
					            .v1 = v1,
 | 
				
			||||||
 | 
					            .v2 = v2,
 | 
				
			||||||
 | 
					            .v3 = v3,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            .uv0 = uv0,
 | 
				
			||||||
 | 
					            .uv1 = uv1,
 | 
				
			||||||
 | 
					            .uv2 = uv2,
 | 
				
			||||||
 | 
					            .uv3 = uv3,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ((ElementIndexedQuadWithoutColor *)builder->base)[index] = payload;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    } else if (!batch.constant_colored && !batch.textured) {
 | 
				
			||||||
 | 
					        ElementIndexedQuadWithoutTexture const payload = {
 | 
				
			||||||
 | 
					            .v0 = v0,
 | 
				
			||||||
 | 
					            .v1 = v1,
 | 
				
			||||||
 | 
					            .v2 = v2,
 | 
				
			||||||
 | 
					            .v3 = v3,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            /* equal for all (flat shaded) */
 | 
				
			||||||
 | 
					            .c0 = color,
 | 
				
			||||||
 | 
					            // .c1 = color,
 | 
				
			||||||
 | 
					            .c2 = color,
 | 
				
			||||||
 | 
					            // .c3 = color,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ((ElementIndexedQuadWithoutTexture *)builder->base)[index] = payload;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    } else if (batch.constant_colored && !batch.textured) {
 | 
				
			||||||
 | 
					        ElementIndexedQuadWithoutColorWithoutTexture const payload = {
 | 
				
			||||||
 | 
					            .v0 = v0,
 | 
				
			||||||
 | 
					            .v1 = v1,
 | 
				
			||||||
 | 
					            .v2 = v2,
 | 
				
			||||||
 | 
					            .v3 = v3,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ((ElementIndexedQuadWithoutColorWithoutTexture *)builder->base)[index] = payload;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,6 @@
 | 
				
			|||||||
#include "twn_draw.h"
 | 
					#include "twn_draw.h"
 | 
				
			||||||
#include "twn_draw_c.h"
 | 
					#include "twn_draw_c.h"
 | 
				
			||||||
#include "twn_engine_context_c.h"
 | 
					#include "twn_engine_context_c.h"
 | 
				
			||||||
#include "twn_util.h"
 | 
					 | 
				
			||||||
#include "twn_util_c.h"
 | 
					 | 
				
			||||||
#include "twn_textures_c.h"
 | 
					 | 
				
			||||||
#include "twn_option.h"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <stb_ds.h>
 | 
					#include <stb_ds.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -36,7 +32,7 @@ struct QuadBatch collect_rect_batch(const Primitive2D primitives[], size_t len)
 | 
				
			|||||||
        .constant_colored = true,
 | 
					        .constant_colored = true,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const uint32_t uniform_color = *(const uint32_t *)&primitives[0].rect.color;
 | 
					    const uint32_t uniform_color = *(const uint32_t *)(void const *)&primitives[0].rect.color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* batch size is clamped so that reallocated short indices could be used */
 | 
					    /* batch size is clamped so that reallocated short indices could be used */
 | 
				
			||||||
    if (len >= QUAD_ELEMENT_BUFFER_LENGTH)
 | 
					    if (len >= QUAD_ELEMENT_BUFFER_LENGTH)
 | 
				
			||||||
@@ -54,7 +50,7 @@ struct QuadBatch collect_rect_batch(const Primitive2D primitives[], size_t len)
 | 
				
			|||||||
            break;
 | 
					            break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /* if all are modulated the same we can skip sending the color data */
 | 
					        /* if all are modulated the same we can skip sending the color data */
 | 
				
			||||||
        if (*(const uint32_t *)¤t->rect.color != uniform_color)
 | 
					        if (*(const uint32_t *)(void const *)¤t->rect.color != uniform_color)
 | 
				
			||||||
            batch.constant_colored = false;
 | 
					            batch.constant_colored = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ++batch.size;
 | 
					        ++batch.size;
 | 
				
			||||||
@@ -74,8 +70,6 @@ void render_rect_batch(const Primitive2D primitives[],
 | 
				
			|||||||
    /* single vertex array is used for every batch with NULL glBufferData() trick at the end */
 | 
					    /* single vertex array is used for every batch with NULL glBufferData() trick at the end */
 | 
				
			||||||
    VertexBuffer const vertex_array = get_scratch_vertex_array();
 | 
					    VertexBuffer const vertex_array = get_scratch_vertex_array();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    use_texture_mode(batch.mode);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /* vertex population over a vertex buffer builder interface */
 | 
					    /* vertex population over a vertex buffer builder interface */
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        VertexBufferBuilder payload = build_vertex_buffer(vertex_array, get_quad_payload_size(batch) * batch.size);
 | 
					        VertexBufferBuilder payload = build_vertex_buffer(vertex_array, get_quad_payload_size(batch) * batch.size);
 | 
				
			||||||
@@ -91,7 +85,7 @@ void render_rect_batch(const Primitive2D primitives[],
 | 
				
			|||||||
            Vec2 v3 = { rect.rect.x + rect.rect.w, rect.rect.y };
 | 
					            Vec2 v3 = { rect.rect.x + rect.rect.w, rect.rect.y };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            push_quad_payload_to_vertex_buffer_builder(
 | 
					            push_quad_payload_to_vertex_buffer_builder(
 | 
				
			||||||
                batch, &payload,
 | 
					                batch, i, &payload,
 | 
				
			||||||
                v0, v1, v2, v3,
 | 
					                v0, v1, v2, v3,
 | 
				
			||||||
                (Vec2){0}, (Vec2){0}, (Vec2){0}, (Vec2){0},
 | 
					                (Vec2){0}, (Vec2){0}, (Vec2){0}, (Vec2){0},
 | 
				
			||||||
                rect.color);
 | 
					                rect.color);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@
 | 
				
			|||||||
#include "twn_draw_c.h"
 | 
					#include "twn_draw_c.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <SDL2/SDL.h>
 | 
					#include <SDL2/SDL.h>
 | 
				
			||||||
 | 
					#include <stb_ds.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static char *paths_in_use;
 | 
					static char *paths_in_use;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -21,6 +22,14 @@ void render_skybox(void) {
 | 
				
			|||||||
        return;
 | 
					        return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* note: ownership of 'paths_in_use' goes there */
 | 
					    /* note: ownership of 'paths_in_use' goes there */
 | 
				
			||||||
    finally_render_skybox(paths_in_use);
 | 
					    DeferredCommand command = {
 | 
				
			||||||
 | 
					        .type = DEFERRED_COMMAND_TYPE_DRAW_SKYBOX,
 | 
				
			||||||
 | 
					        .draw_skybox = (DeferredCommandDrawSkybox){
 | 
				
			||||||
 | 
					            .paths = paths_in_use
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    arrpush(deferred_commands, command);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    paths_in_use = NULL;
 | 
					    paths_in_use = NULL;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -56,7 +56,7 @@ void draw_sprite_args(const DrawSpriteArgs args) {
 | 
				
			|||||||
    bool const stretch = m_or(args, stretch, false);
 | 
					    bool const stretch = m_or(args, stretch, false);
 | 
				
			||||||
    Rect const *texture_region = m_is_set(args, texture_region) ? &args.texture_region_opt : NULL;
 | 
					    Rect const *texture_region = m_is_set(args, texture_region) ? &args.texture_region_opt : NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    draw_sprite(args.path, args.rect, texture_region, color, rotation, flip_x, flip_y, stretch);
 | 
					    draw_sprite(args.texture, args.rect, texture_region, color, rotation, flip_x, flip_y, stretch);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -69,12 +69,13 @@ struct QuadBatch collect_sprite_batch(const Primitive2D primitives[], size_t len
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    struct QuadBatch batch = {
 | 
					    struct QuadBatch batch = {
 | 
				
			||||||
        .mode = textures_get_mode(&ctx.texture_cache, primitives[0].sprite.texture_key),
 | 
					        .mode = textures_get_mode(&ctx.texture_cache, primitives[0].sprite.texture_key),
 | 
				
			||||||
 | 
					        .texture_key = primitives[0].sprite.texture_key,
 | 
				
			||||||
        .constant_colored = true,
 | 
					        .constant_colored = true,
 | 
				
			||||||
        .repeat = primitives[0].sprite.repeat,
 | 
					        .repeat = primitives[0].sprite.repeat,
 | 
				
			||||||
        .textured = true,
 | 
					        .textured = true,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const uint32_t uniform_color = *(const uint32_t *)&primitives[0].sprite.color;
 | 
					    const uint32_t uniform_color = *(const uint32_t *)(void const*)&primitives[0].sprite.color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* batch size is clamped so that reallocated short indices could be used */
 | 
					    /* batch size is clamped so that reallocated short indices could be used */
 | 
				
			||||||
    if (len >= QUAD_ELEMENT_BUFFER_LENGTH)
 | 
					    if (len >= QUAD_ELEMENT_BUFFER_LENGTH)
 | 
				
			||||||
@@ -108,7 +109,7 @@ struct QuadBatch collect_sprite_batch(const Primitive2D primitives[], size_t len
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /* if all are modulated the same we can skip sending the color data */
 | 
					        /* if all are modulated the same we can skip sending the color data */
 | 
				
			||||||
        if (*(const uint32_t *)¤t->sprite.color != uniform_color)
 | 
					        if (*(const uint32_t *)(void const *)¤t->sprite.color != uniform_color)
 | 
				
			||||||
            batch.constant_colored = false;
 | 
					            batch.constant_colored = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ++batch.size;
 | 
					        ++batch.size;
 | 
				
			||||||
@@ -131,7 +132,7 @@ void render_sprite_batch(const Primitive2D primitives[],
 | 
				
			|||||||
        textures_get_dims(&ctx.texture_cache, primitives->sprite.texture_key);
 | 
					        textures_get_dims(&ctx.texture_cache, primitives->sprite.texture_key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* cached srcrect */
 | 
					    /* cached srcrect */
 | 
				
			||||||
    Rect cached_srcrect;
 | 
					    Rect cached_srcrect = {0};
 | 
				
			||||||
    TextureKey cached_srcrect_key = TEXTURE_KEY_INVALID;
 | 
					    TextureKey cached_srcrect_key = TEXTURE_KEY_INVALID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* vertex population over a vertex buffer builder interface */
 | 
					    /* vertex population over a vertex buffer builder interface */
 | 
				
			||||||
@@ -215,7 +216,7 @@ void render_sprite_batch(const Primitive2D primitives[],
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#pragma GCC diagnostic pop
 | 
					#pragma GCC diagnostic pop
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                const Vec2 c = frect_center(sprite.rect);
 | 
					                const Vec2 c = rect_center(sprite.rect);
 | 
				
			||||||
                const Vec2 t = fast_cossine(sprite.rotation + (float)M_PI_4);
 | 
					                const Vec2 t = fast_cossine(sprite.rotation + (float)M_PI_4);
 | 
				
			||||||
                const Vec2 d = {
 | 
					                const Vec2 d = {
 | 
				
			||||||
                    .x = t.x * sprite.rect.w * (float)M_SQRT1_2,
 | 
					                    .x = t.x * sprite.rect.w * (float)M_SQRT1_2,
 | 
				
			||||||
@@ -230,7 +231,7 @@ void render_sprite_batch(const Primitive2D primitives[],
 | 
				
			|||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                /* rotated non-square case*/
 | 
					                /* rotated non-square case*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                const Vec2 c = frect_center(sprite.rect);
 | 
					                const Vec2 c = rect_center(sprite.rect);
 | 
				
			||||||
                const Vec2 t = fast_cossine(sprite.rotation);
 | 
					                const Vec2 t = fast_cossine(sprite.rotation);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                const Vec2 h = { sprite.rect.w / 2, sprite.rect.h / 2 };
 | 
					                const Vec2 h = { sprite.rect.w / 2, sprite.rect.h / 2 };
 | 
				
			||||||
@@ -241,7 +242,7 @@ void render_sprite_batch(const Primitive2D primitives[],
 | 
				
			|||||||
                v3 = (Vec2){ c.x + t.x * +h.x - t.y * -h.y, c.y + t.y * +h.x + t.x * -h.y };
 | 
					                v3 = (Vec2){ c.x + t.x * +h.x - t.y * -h.y, c.y + t.y * +h.x + t.x * -h.y };
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            push_quad_payload_to_vertex_buffer_builder(batch, &payload, v0, v1, v2, v3, uv0, uv1, uv2, uv3, sprite.color);
 | 
					            push_quad_payload_to_vertex_buffer_builder(batch, i, &payload, v0, v1, v2, v3, uv0, uv1, uv2, uv3, sprite.color);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -175,7 +175,7 @@ static void text_draw_with(FontData* font_data, char* text, Vec2 position, Color
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const size_t len = SDL_strlen(text);
 | 
					    const size_t len = SDL_strlen(text);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    VertexBufferBuilder payload = build_vertex_buffer(vertex_array, get_text_payload_size() * len);
 | 
					    VertexBufferBuilder builder = build_vertex_buffer(vertex_array, sizeof (ElementIndexedQuadWithoutColor) * len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (size_t i = 0; i < len; ++i) {
 | 
					    for (size_t i = 0; i < len; ++i) {
 | 
				
			||||||
        const char c = text[i];
 | 
					        const char c = text[i];
 | 
				
			||||||
@@ -204,9 +204,22 @@ static void text_draw_with(FontData* font_data, char* text, Vec2 position, Color
 | 
				
			|||||||
        quad.y0 += (float)font_data->ascent;
 | 
					        quad.y0 += (float)font_data->ascent;
 | 
				
			||||||
        quad.y1 += (float)font_data->ascent;
 | 
					        quad.y1 += (float)font_data->ascent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        push_text_payload_to_vertex_buffer_builder(font_data, &payload, quad);
 | 
					        ElementIndexedQuadWithoutColor const payload = {
 | 
				
			||||||
 | 
					            .v0 = (Vec2){ quad.x0, quad.y0 },
 | 
				
			||||||
 | 
					            .v1 = (Vec2){ quad.x1, quad.y0 },
 | 
				
			||||||
 | 
					            .v2 = (Vec2){ quad.x1, quad.y1 },
 | 
				
			||||||
 | 
					            .v3 = (Vec2){ quad.x0, quad.y1 },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            .uv0 = (Vec2){ quad.s0, quad.t0 },
 | 
				
			||||||
 | 
					            .uv1 = (Vec2){ quad.s1, quad.t0 },
 | 
				
			||||||
 | 
					            .uv2 = (Vec2){ quad.s1, quad.t1 },
 | 
				
			||||||
 | 
					            .uv3 = (Vec2){ quad.s0, quad.t1 },
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ((ElementIndexedQuadWithoutColor *)builder.base)[i] = payload;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    finish_vertex_builder(&builder);
 | 
				
			||||||
    finally_draw_text(font_data, len, color, vertex_array);
 | 
					    finally_draw_text(font_data, len, color, vertex_array);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -275,8 +288,8 @@ void text_cache_reset_arena(TextCache *cache) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void draw_text(const char *string, Vec2 position, int height_px, Color color, const char *font_path) {
 | 
					void draw_text(const char *string, Vec2 position, float height, Color color, const char *font) {
 | 
				
			||||||
    ensure_font_cache(font_path, height_px);
 | 
					    ensure_font_cache(font, (int)height);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* the original string might not be around by the time it's used, so copy it */
 | 
					    /* the original string might not be around by the time it's used, so copy it */
 | 
				
			||||||
    size_t str_length = SDL_strlen(string) + 1;
 | 
					    size_t str_length = SDL_strlen(string) + 1;
 | 
				
			||||||
@@ -290,8 +303,8 @@ void draw_text(const char *string, Vec2 position, int height_px, Color color, co
 | 
				
			|||||||
        .color = color,
 | 
					        .color = color,
 | 
				
			||||||
        .position = position,
 | 
					        .position = position,
 | 
				
			||||||
        .text = dup_string,
 | 
					        .text = dup_string,
 | 
				
			||||||
        .font = font_path,
 | 
					        .font = font,
 | 
				
			||||||
        .height_px = height_px,
 | 
					        .height_px = (int)height,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Primitive2D primitive = {
 | 
					    Primitive2D primitive = {
 | 
				
			||||||
@@ -303,9 +316,9 @@ void draw_text(const char *string, Vec2 position, int height_px, Color color, co
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int draw_text_width(const char *string, int height_px, const char *font_path) {
 | 
					float draw_text_width(const char *string, float height, const char *font) {
 | 
				
			||||||
    ensure_font_cache(font_path, height_px);
 | 
					    ensure_font_cache(font, (int)height);
 | 
				
			||||||
    FontData *font_data = get_font_data(font_path, height_px);
 | 
					    FontData *font_data = get_font_data(font, (int)height);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    int length = 0;
 | 
					    int length = 0;
 | 
				
			||||||
    for (const char *p = string; *p != '\0'; ++p) {
 | 
					    for (const char *p = string; *p != '\0'; ++p) {
 | 
				
			||||||
@@ -316,5 +329,57 @@ int draw_text_width(const char *string, int height_px, const char *font_path) {
 | 
				
			|||||||
        length += advance_width;
 | 
					        length += advance_width;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    return (int)((float)length * font_data->scale_factor);
 | 
					    return (float)length * font_data->scale_factor;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void finally_draw_text(FontData const *font_data,
 | 
				
			||||||
 | 
					                       size_t len,
 | 
				
			||||||
 | 
					                       Color color,
 | 
				
			||||||
 | 
					                       VertexBuffer buffer)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    DeferredCommandDraw command = {0};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    command.vertices = (AttributeArrayPointer) {
 | 
				
			||||||
 | 
					        .arity = 2,
 | 
				
			||||||
 | 
					        .type = TWN_FLOAT,
 | 
				
			||||||
 | 
					        .stride = offsetof(ElementIndexedQuadWithoutColor, v1),
 | 
				
			||||||
 | 
					        .offset = offsetof(ElementIndexedQuadWithoutColor, v0),
 | 
				
			||||||
 | 
					        .buffer = buffer
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    command.texcoords = (AttributeArrayPointer) {
 | 
				
			||||||
 | 
					        .arity = 2,
 | 
				
			||||||
 | 
					        .type = TWN_FLOAT,
 | 
				
			||||||
 | 
					        .stride = offsetof(ElementIndexedQuadWithoutColor, v1),
 | 
				
			||||||
 | 
					        .offset = offsetof(ElementIndexedQuadWithoutColor, uv0),
 | 
				
			||||||
 | 
					        .buffer = buffer
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    command.constant_colored = true;
 | 
				
			||||||
 | 
					    command.color = color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    command.gpu_texture = font_data->texture;
 | 
				
			||||||
 | 
					    command.uses_gpu_key = true;
 | 
				
			||||||
 | 
					    command.textured = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    command.element_buffer = get_quad_element_buffer();
 | 
				
			||||||
 | 
					    command.element_count = 6 * (uint32_t)len;
 | 
				
			||||||
 | 
					    command.range_end = 6 * (uint32_t)len;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    command.texture_mode = TEXTURE_MODE_GHOSTLY;
 | 
				
			||||||
 | 
					    command.pipeline = PIPELINE_2D;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    command.depth_range_high = depth_range_high;
 | 
				
			||||||
 | 
					    command.depth_range_low = depth_range_low;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    DeferredCommand final_command = {
 | 
				
			||||||
 | 
					        .type = DEFERRED_COMMAND_TYPE_DRAW,
 | 
				
			||||||
 | 
					        .draw = command
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    arrpush(deferred_commands, final_command);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* TODO: why doesn't it get restored if not placed here? */
 | 
				
			||||||
 | 
					    // glDepthMask(GL_TRUE);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,44 +15,51 @@ void draw_triangle(const char *path,
 | 
				
			|||||||
                   Vec3 v2,
 | 
					                   Vec3 v2,
 | 
				
			||||||
                   Vec2 uv0,
 | 
					                   Vec2 uv0,
 | 
				
			||||||
                   Vec2 uv1,
 | 
					                   Vec2 uv1,
 | 
				
			||||||
                   Vec2 uv2)
 | 
					                   Vec2 uv2,
 | 
				
			||||||
 | 
					                   Color c0,
 | 
				
			||||||
 | 
					                   Color c1,
 | 
				
			||||||
 | 
					                   Color c2)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					    // TODO: support color
 | 
				
			||||||
 | 
					    (void)c0; (void)c1; (void)c2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO: order drawing by atlas id as well, so that texture rebinding is not as common
 | 
				
			||||||
    const TextureKey texture_key = textures_get_key(&ctx.texture_cache, path);
 | 
					    const TextureKey texture_key = textures_get_key(&ctx.texture_cache, path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    struct MeshBatchItem *batch_p = hmgetp_null(ctx.uncolored_mesh_batches, texture_key);
 | 
					    struct MeshBatchItem *batch_p = hmgetp_null(ctx.uncolored_mesh_batches, texture_key);
 | 
				
			||||||
    if (!batch_p) {
 | 
					    if (!batch_p) {
 | 
				
			||||||
        struct MeshBatch item = {0};
 | 
					        struct MeshBatch item = {0};
 | 
				
			||||||
        hmput(ctx.uncolored_mesh_batches, texture_key, item);
 | 
					        hmput(ctx.uncolored_mesh_batches, texture_key, item);
 | 
				
			||||||
        batch_p = &ctx.uncolored_mesh_batches[hmlenu(ctx.uncolored_mesh_batches) - 1]; /* TODO: can last index be used? */
 | 
					        batch_p = &ctx.uncolored_mesh_batches[hmlenu(ctx.uncolored_mesh_batches) - 1];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    union UncoloredSpaceTriangle triangle = { .primitive = {
 | 
					    UncoloredSpaceTriangle const triangle = {
 | 
				
			||||||
        .v0 = v0,
 | 
					        .v0 = v0,
 | 
				
			||||||
        .v1 = v1,
 | 
					        .v1 = v1,
 | 
				
			||||||
        .v2 = v2,
 | 
					        .v2 = v2,
 | 
				
			||||||
        .uv1 = uv1,
 | 
					        .uv1 = uv1,
 | 
				
			||||||
        .uv0 = uv0,
 | 
					        .uv0 = uv0,
 | 
				
			||||||
        .uv2 = uv2,
 | 
					        .uv2 = uv2,
 | 
				
			||||||
    }};
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    union UncoloredSpaceTriangle *triangles = (union UncoloredSpaceTriangle *)(void *)batch_p->value.primitives;
 | 
					    UncoloredSpaceTriangle *triangles = (UncoloredSpaceTriangle *)(void *)batch_p->value.primitives;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    arrpush(triangles, triangle);
 | 
					    arrpush(triangles, triangle);
 | 
				
			||||||
    batch_p->value.primitives = (uint8_t *)triangles;
 | 
					    batch_p->value.primitives = (uint8_t *)triangles;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void draw_uncolored_space_traingle_batch(struct MeshBatch *batch,
 | 
					void finally_draw_uncolored_space_traingle_batch(const MeshBatch *batch,
 | 
				
			||||||
                                         TextureKey texture_key)
 | 
					                                                 const TextureKey texture_key)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    VertexBuffer const vertex_array = get_scratch_vertex_array();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const size_t primitives_len = arrlenu(batch->primitives);
 | 
					    const size_t primitives_len = arrlenu(batch->primitives);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* nothing to do */
 | 
					    /* nothing to do */
 | 
				
			||||||
    if (primitives_len == 0)
 | 
					    if (primitives_len == 0)
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    VertexBuffer const buffer = get_scratch_vertex_array();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const Rect srcrect = textures_get_srcrect(&ctx.texture_cache, texture_key);
 | 
					    const Rect srcrect = textures_get_srcrect(&ctx.texture_cache, texture_key);
 | 
				
			||||||
    const Rect dims    = textures_get_dims(&ctx.texture_cache, texture_key);
 | 
					    const Rect dims    = textures_get_dims(&ctx.texture_cache, texture_key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -63,8 +70,8 @@ void draw_uncolored_space_traingle_batch(struct MeshBatch *batch,
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /* update pixel-based uvs to correspond with texture atlases */
 | 
					    /* update pixel-based uvs to correspond with texture atlases */
 | 
				
			||||||
    for (size_t i = 0; i < primitives_len; ++i) {
 | 
					    for (size_t i = 0; i < primitives_len; ++i) {
 | 
				
			||||||
        struct UncoloredSpaceTrianglePayload *payload =
 | 
					        UncoloredSpaceTriangle *payload =
 | 
				
			||||||
            &((union UncoloredSpaceTriangle *)(void *)batch->primitives)[i].payload;
 | 
					            &((UncoloredSpaceTriangle *)(void *)batch->primitives)[i];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        payload->uv0.x = xr + ((float)payload->uv0.x / srcrect.w) * wr;
 | 
					        payload->uv0.x = xr + ((float)payload->uv0.x / srcrect.w) * wr;
 | 
				
			||||||
        payload->uv0.y = yr + ((float)payload->uv0.y / srcrect.h) * hr;
 | 
					        payload->uv0.y = yr + ((float)payload->uv0.y / srcrect.h) * hr;
 | 
				
			||||||
@@ -74,7 +81,46 @@ void draw_uncolored_space_traingle_batch(struct MeshBatch *batch,
 | 
				
			|||||||
        payload->uv2.y = yr + ((float)payload->uv2.y / srcrect.h) * hr;
 | 
					        payload->uv2.y = yr + ((float)payload->uv2.y / srcrect.h) * hr;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    specify_vertex_buffer(vertex_array, batch->primitives, primitives_len * sizeof (struct UncoloredSpaceTrianglePayload));
 | 
					    specify_vertex_buffer(buffer, batch->primitives, primitives_len * sizeof (UncoloredSpaceTriangle));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    finally_draw_uncolored_space_traingle_batch(batch, texture_key, vertex_array);
 | 
					    DeferredCommandDraw command = {0};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    command.vertices = (AttributeArrayPointer) {
 | 
				
			||||||
 | 
					        .arity = 3,
 | 
				
			||||||
 | 
					        .type = TWN_FLOAT,
 | 
				
			||||||
 | 
					        .stride = offsetof(UncoloredSpaceTriangle, v1),
 | 
				
			||||||
 | 
					        .offset = offsetof(UncoloredSpaceTriangle, v0),
 | 
				
			||||||
 | 
					        .buffer = buffer
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    command.texcoords = (AttributeArrayPointer) {
 | 
				
			||||||
 | 
					        .arity = 2,
 | 
				
			||||||
 | 
					        .type = TWN_FLOAT,
 | 
				
			||||||
 | 
					        .stride = offsetof(UncoloredSpaceTriangle, v1),
 | 
				
			||||||
 | 
					        .offset = offsetof(UncoloredSpaceTriangle, uv0),
 | 
				
			||||||
 | 
					        .buffer = buffer
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    command.textured = true;
 | 
				
			||||||
 | 
					    command.texture_key = texture_key;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    command.primitive_count = (uint32_t)(3 * primitives_len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* TODO: support alpha blended case, with distance sort */
 | 
				
			||||||
 | 
					    TextureMode mode = textures_get_mode(&ctx.texture_cache, texture_key);
 | 
				
			||||||
 | 
					    if (mode == TEXTURE_MODE_GHOSTLY)
 | 
				
			||||||
 | 
					        mode = TEXTURE_MODE_SEETHROUGH;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    command.texture_mode = mode;
 | 
				
			||||||
 | 
					    command.pipeline = PIPELINE_SPACE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    command.depth_range_high = depth_range_high;
 | 
				
			||||||
 | 
					    command.depth_range_low = depth_range_low;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    DeferredCommand final_command = {
 | 
				
			||||||
 | 
					        .type = DEFERRED_COMMAND_TYPE_DRAW,
 | 
				
			||||||
 | 
					        .draw = command
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    arrpush(deferred_commands, final_command);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										76
									
								
								src/system/linux/twn_timer.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								src/system/linux/twn_timer.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
				
			|||||||
 | 
					#include "../twn_timer.h"
 | 
				
			||||||
 | 
					#include "twn_engine_context_c.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <SDL_messagebox.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <signal.h>
 | 
				
			||||||
 | 
					#include <stdint.h>
 | 
				
			||||||
 | 
					#include <time.h>
 | 
				
			||||||
 | 
					#include <unistd.h>
 | 
				
			||||||
 | 
					#include <stdbool.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static timer_t            timerid;
 | 
				
			||||||
 | 
					static struct sigaction   sa;
 | 
				
			||||||
 | 
					static struct sigevent    sev;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool created;
 | 
				
			||||||
 | 
					static uint64_t used_milliseconds_to_expire;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define SANITY_TIMER_MESSAGE_FMT "Game tick exeeded its allocated time (%lu milliseconds), application is closing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* stop application */
 | 
				
			||||||
 | 
					static void sanity_timer_handler(int sig, siginfo_t *si, void *uc) {
 | 
				
			||||||
 | 
					    (void)uc; (void)sig; (void)si;
 | 
				
			||||||
 | 
					    size_t text_str_len = snprintf(NULL, 0, SANITY_TIMER_MESSAGE_FMT, used_milliseconds_to_expire) + 1;
 | 
				
			||||||
 | 
					    char *text_str = SDL_malloc(text_str_len);
 | 
				
			||||||
 | 
					    snprintf(text_str, text_str_len, SANITY_TIMER_MESSAGE_FMT, used_milliseconds_to_expire);
 | 
				
			||||||
 | 
					    SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "sanity timer", text_str, ctx.window);
 | 
				
			||||||
 | 
					    SDL_free(text_str);
 | 
				
			||||||
 | 
					    raise(SIGKILL);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool start_sanity_timer(uint64_t milliseconds_to_expire) {
 | 
				
			||||||
 | 
					    if (!created) {
 | 
				
			||||||
 | 
					        sa.sa_flags = SA_SIGINFO;
 | 
				
			||||||
 | 
					        sa.sa_sigaction = sanity_timer_handler;
 | 
				
			||||||
 | 
					        sigemptyset(&sa.sa_mask);
 | 
				
			||||||
 | 
					        if (sigaction(SIGRTMIN, &sa, NULL) == -1)
 | 
				
			||||||
 | 
					            goto ERR_SIGACTION;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       sev.sigev_notify = SIGEV_SIGNAL;
 | 
				
			||||||
 | 
					       sev.sigev_signo = SIGRTMIN;
 | 
				
			||||||
 | 
					       sev.sigev_value.sival_ptr = &timerid;
 | 
				
			||||||
 | 
					       if (timer_create(CLOCK_MONOTONIC, &sev, &timerid) == -1)
 | 
				
			||||||
 | 
					            goto ERR_TIMERCREATE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        created = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ERR_TIMERCREATE:
 | 
				
			||||||
 | 
					    // ERR_SIGPROCMASK:
 | 
				
			||||||
 | 
					    ERR_SIGACTION:
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    struct itimerspec its = {0};
 | 
				
			||||||
 | 
					    its.it_value.tv_sec = milliseconds_to_expire / 1000;
 | 
				
			||||||
 | 
					    its.it_value.tv_nsec = (milliseconds_to_expire * 1000000 % 1000000000);
 | 
				
			||||||
 | 
					    if (timer_settime(timerid, 0, &its, NULL) == -1)
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    used_milliseconds_to_expire = milliseconds_to_expire;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool end_sanity_timer(void) {
 | 
				
			||||||
 | 
					    struct itimerspec its = {0};
 | 
				
			||||||
 | 
					    its.it_value.tv_sec = 0;
 | 
				
			||||||
 | 
					    its.it_value.tv_nsec = 0;
 | 
				
			||||||
 | 
					    if (timer_settime(timerid, 0, &its, NULL) == -1)
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										10
									
								
								src/system/twn_timer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/system/twn_timer.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					#ifndef TWN_TIMER_H
 | 
				
			||||||
 | 
					#define TWN_TIMER_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <stdbool.h>
 | 
				
			||||||
 | 
					#include <stdbit.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool start_sanity_timer(uint64_t milliseconds_to_expire);
 | 
				
			||||||
 | 
					bool end_sanity_timer(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif // TWN_TIMER_H
 | 
				
			||||||
@@ -12,9 +12,10 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "rendering/twn_circles.c"
 | 
					#include "rendering/twn_circles.c"
 | 
				
			||||||
#include "rendering/twn_draw.c"
 | 
					#include "rendering/twn_draw.c"
 | 
				
			||||||
#include "rendering/twn_fog.c"
 | 
					 | 
				
			||||||
#include "rendering/twn_skybox.c"
 | 
					#include "rendering/twn_skybox.c"
 | 
				
			||||||
#include "rendering/twn_sprites.c"
 | 
					#include "rendering/twn_sprites.c"
 | 
				
			||||||
#include "rendering/twn_rects.c"
 | 
					#include "rendering/twn_rects.c"
 | 
				
			||||||
#include "rendering/twn_text.c"
 | 
					#include "rendering/twn_text.c"
 | 
				
			||||||
 | 
					#include "rendering/twn_quads.c"
 | 
				
			||||||
#include "rendering/twn_triangles.c"
 | 
					#include "rendering/twn_triangles.c"
 | 
				
			||||||
 | 
					#include "rendering/twn_billboards.c"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										309
									
								
								src/twn_audio.c
									
									
									
									
									
								
							
							
						
						
									
										309
									
								
								src/twn_audio.c
									
									
									
									
									
								
							@@ -2,10 +2,12 @@
 | 
				
			|||||||
#include "twn_engine_context_c.h"
 | 
					#include "twn_engine_context_c.h"
 | 
				
			||||||
#include "twn_util.h"
 | 
					#include "twn_util.h"
 | 
				
			||||||
#include "twn_util_c.h"
 | 
					#include "twn_util_c.h"
 | 
				
			||||||
 | 
					#include "twn_audio.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <SDL2/SDL.h>
 | 
					#include <SDL2/SDL.h>
 | 
				
			||||||
#include <stb_ds.h>
 | 
					#include <stb_ds.h>
 | 
				
			||||||
#include <physfs.h>
 | 
					#include <physfs.h>
 | 
				
			||||||
 | 
					#include <physfsrwops.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define STB_VORBIS_NO_PUSHDATA_API
 | 
					#define STB_VORBIS_NO_PUSHDATA_API
 | 
				
			||||||
#define STB_VORBIS_HEADER_ONLY
 | 
					#define STB_VORBIS_HEADER_ONLY
 | 
				
			||||||
@@ -16,9 +18,17 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
static const char *audio_exts[AUDIO_FILE_TYPE_COUNT] = {
 | 
					static const char *audio_exts[AUDIO_FILE_TYPE_COUNT] = {
 | 
				
			||||||
    ".ogg", /* AUDIO_FILE_TYPE_OGG */
 | 
					    ".ogg", /* AUDIO_FILE_TYPE_OGG */
 | 
				
			||||||
 | 
					    ".wav", /* AUDIO_FILE_TYPE_WAV */
 | 
				
			||||||
    ".xm",  /* AUDIO_FILE_TYPE_XM */
 | 
					    ".xm",  /* AUDIO_FILE_TYPE_XM */
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const uint8_t audio_exts_len[AUDIO_FILE_TYPE_COUNT] = {
 | 
				
			||||||
 | 
					    sizeof ".ogg" - 1,
 | 
				
			||||||
 | 
					    sizeof ".wav" - 1,
 | 
				
			||||||
 | 
					    sizeof ".xm" - 1,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* TODO: allow for vectorization and packed vectors (alignment care and alike) */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* TODO: count frames without use, free the memory when threshold is met */
 | 
					/* TODO: count frames without use, free the memory when threshold is met */
 | 
				
			||||||
/* TODO: count repeated usages for sound effect cases with rendering to ram? */
 | 
					/* TODO: count repeated usages for sound effect cases with rendering to ram? */
 | 
				
			||||||
@@ -55,14 +65,10 @@ static int64_t get_audio_data(const char *path, unsigned char **data) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static AudioFileType infer_audio_file_type(const char *path) {
 | 
					static AudioFileType infer_audio_file_type(const char *path) {
 | 
				
			||||||
    size_t path_len = SDL_strlen(path);
 | 
					    size_t const path_len = SDL_strlen(path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (int i = 0; i < AUDIO_FILE_TYPE_COUNT; ++i) {
 | 
					    for (int i = 0; i < AUDIO_FILE_TYPE_COUNT; ++i) {
 | 
				
			||||||
        size_t ext_length = SDL_strlen(audio_exts[i]);
 | 
					        if (SDL_strncmp(&path[path_len - audio_exts_len[i]], audio_exts[i], audio_exts_len[i]) == 0)
 | 
				
			||||||
        if (path_len <= ext_length)
 | 
					 | 
				
			||||||
            continue;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (SDL_strcmp(&path[path_len - ext_length], audio_exts[i]) == 0)
 | 
					 | 
				
			||||||
            return (AudioFileType)i;
 | 
					            return (AudioFileType)i;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -72,6 +78,7 @@ static AudioFileType infer_audio_file_type(const char *path) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/* TODO: error propagation and clearing of resources on partial success? */
 | 
					/* TODO: error propagation and clearing of resources on partial success? */
 | 
				
			||||||
/*       or should we expect things to simply fail? */
 | 
					/*       or should we expect things to simply fail? */
 | 
				
			||||||
 | 
					/* TODO: reuse often used decoded/decompressed data */
 | 
				
			||||||
static union AudioContext init_audio_context(const char *path, AudioFileType type) {
 | 
					static union AudioContext init_audio_context(const char *path, AudioFileType type) {
 | 
				
			||||||
    switch (type) {
 | 
					    switch (type) {
 | 
				
			||||||
    case AUDIO_FILE_TYPE_OGG: {
 | 
					    case AUDIO_FILE_TYPE_OGG: {
 | 
				
			||||||
@@ -101,6 +108,54 @@ static union AudioContext init_audio_context(const char *path, AudioFileType typ
 | 
				
			|||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* TODO: transform to destination format immediately? */
 | 
				
			||||||
 | 
					    case AUDIO_FILE_TYPE_WAV: {
 | 
				
			||||||
 | 
					        SDL_AudioSpec spec;
 | 
				
			||||||
 | 
					        uint8_t *data;
 | 
				
			||||||
 | 
					        uint32_t len;
 | 
				
			||||||
 | 
					        if (!SDL_LoadWAV_RW(PHYSFSRWOPS_openRead(path), 1, &spec, &data, &len)) {
 | 
				
			||||||
 | 
					            CRY_SDL("Cannot load .wav file:");
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        SDL_AudioCVT cvt;
 | 
				
			||||||
 | 
					        int conv = SDL_BuildAudioCVT(&cvt,
 | 
				
			||||||
 | 
					                                     spec.format,
 | 
				
			||||||
 | 
					                                     spec.channels,
 | 
				
			||||||
 | 
					                                     spec.freq,
 | 
				
			||||||
 | 
					                                     AUDIO_F32,
 | 
				
			||||||
 | 
					                                     2,
 | 
				
			||||||
 | 
					                                     AUDIO_FREQUENCY);
 | 
				
			||||||
 | 
					        if (conv < 0) {
 | 
				
			||||||
 | 
					            CRY_SDL("Cannot resample .wav:");
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (conv != 0) {
 | 
				
			||||||
 | 
					            data = SDL_realloc(data, len * cvt.len_mult);
 | 
				
			||||||
 | 
					            cvt.buf = data;
 | 
				
			||||||
 | 
					            cvt.len = len;
 | 
				
			||||||
 | 
					            if (SDL_ConvertAudio(&cvt) < 0) {
 | 
				
			||||||
 | 
					                CRY_SDL("Error resampling .wav:");
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            spec.channels = 2;
 | 
				
			||||||
 | 
					            spec.freq = AUDIO_FREQUENCY;
 | 
				
			||||||
 | 
					            /* TODO: test this */
 | 
				
			||||||
 | 
					            spec.samples = (uint16_t)((size_t)(SDL_floor((double)len * cvt.len_ratio)) / sizeof (float) / 2);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            spec.samples = (uint16_t)((size_t)(SDL_floor((double)len * cvt.len_ratio)) / sizeof (float) / 2);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return (union AudioContext) {
 | 
				
			||||||
 | 
					            .wav = {
 | 
				
			||||||
 | 
					                .position = 0,
 | 
				
			||||||
 | 
					                .samples = data,
 | 
				
			||||||
 | 
					                .spec = spec,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    case AUDIO_FILE_TYPE_XM: {
 | 
					    case AUDIO_FILE_TYPE_XM: {
 | 
				
			||||||
        unsigned char *data;
 | 
					        unsigned char *data;
 | 
				
			||||||
        int64_t len = get_audio_data(path, &data);
 | 
					        int64_t len = get_audio_data(path, &data);
 | 
				
			||||||
@@ -137,6 +192,29 @@ static union AudioContext init_audio_context(const char *path, AudioFileType typ
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void free_audio_channel(AudioChannel channel) {
 | 
				
			||||||
 | 
					    switch (channel.file_type) {
 | 
				
			||||||
 | 
					    case AUDIO_FILE_TYPE_OGG: {
 | 
				
			||||||
 | 
					        SDL_free(channel.context.vorbis.data);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case AUDIO_FILE_TYPE_WAV: {
 | 
				
			||||||
 | 
					        SDL_free(channel.context.wav.samples);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case AUDIO_FILE_TYPE_XM: {
 | 
				
			||||||
 | 
					        xm_free_context(channel.context.xm.handle);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case AUDIO_FILE_TYPE_COUNT:
 | 
				
			||||||
 | 
					    case AUDIO_FILE_TYPE_UNKNOWN:
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					        SDL_assert_always(false);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void repeat_audio(AudioChannel *channel) {
 | 
					static void repeat_audio(AudioChannel *channel) {
 | 
				
			||||||
    switch (channel->file_type) {
 | 
					    switch (channel->file_type) {
 | 
				
			||||||
    case AUDIO_FILE_TYPE_OGG: {
 | 
					    case AUDIO_FILE_TYPE_OGG: {
 | 
				
			||||||
@@ -144,6 +222,11 @@ static void repeat_audio(AudioChannel *channel) {
 | 
				
			|||||||
        break;
 | 
					        break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case AUDIO_FILE_TYPE_WAV: {
 | 
				
			||||||
 | 
					        channel->context.wav.position = 0;
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    case AUDIO_FILE_TYPE_XM: {
 | 
					    case AUDIO_FILE_TYPE_XM: {
 | 
				
			||||||
        xm_restart(channel->context.xm.handle);
 | 
					        xm_restart(channel->context.xm.handle);
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
@@ -164,11 +247,42 @@ void audio_play(const char *path,
 | 
				
			|||||||
                float volume,
 | 
					                float volume,
 | 
				
			||||||
                float panning)
 | 
					                float panning)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					    if (!ctx.audio_initialized) {
 | 
				
			||||||
 | 
					        profile_start("audio initialization");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        SDL_AudioSpec request, got;
 | 
				
			||||||
 | 
					        SDL_zero(request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        request.freq = AUDIO_FREQUENCY;
 | 
				
			||||||
 | 
					        request.format = AUDIO_F32;
 | 
				
			||||||
 | 
					        request.channels = 2;
 | 
				
			||||||
 | 
					        request.samples = 4096;
 | 
				
			||||||
 | 
					        #ifndef TWN_FEATURE_PUSH_AUDIO
 | 
				
			||||||
 | 
					        request.callback = audio_callback;
 | 
				
			||||||
 | 
					        #endif
 | 
				
			||||||
 | 
					        /* TODO: check for errors */
 | 
				
			||||||
 | 
					        ctx.audio_device = SDL_OpenAudioDevice(NULL, 0, &request, &got, 0);
 | 
				
			||||||
 | 
					        ctx.audio_stream_format = got.format;
 | 
				
			||||||
 | 
					        ctx.audio_stream_frequency = got.freq;
 | 
				
			||||||
 | 
					        ctx.audio_stream_channel_count = got.channels;
 | 
				
			||||||
 | 
					        /* TODO: relax this */
 | 
				
			||||||
 | 
					        SDL_assert_always(got.freq == AUDIO_FREQUENCY);
 | 
				
			||||||
 | 
					        SDL_assert_always(got.format == AUDIO_F32);
 | 
				
			||||||
 | 
					        SDL_assert_always(got.channels == 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        SDL_PauseAudioDevice(ctx.audio_device, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        profile_end("audio initialization");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.audio_initialized = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (channel) {
 | 
				
			||||||
        AudioChannelItem *pair = shgetp_null(ctx.audio_channels, channel);
 | 
					        AudioChannelItem *pair = shgetp_null(ctx.audio_channels, channel);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /* create a channel if it doesn't exist */
 | 
					        /* create a channel if it doesn't exist */
 | 
				
			||||||
        if (!pair) {
 | 
					        if (!pair) {
 | 
				
			||||||
        AudioFileType file_type = infer_audio_file_type(path);
 | 
					            AudioFileType const file_type = infer_audio_file_type(path);
 | 
				
			||||||
            AudioChannel new_channel = {
 | 
					            AudioChannel new_channel = {
 | 
				
			||||||
                .file_type = file_type,
 | 
					                .file_type = file_type,
 | 
				
			||||||
                .context = init_audio_context(path, file_type),
 | 
					                .context = init_audio_context(path, file_type),
 | 
				
			||||||
@@ -187,53 +301,63 @@ void audio_play(const char *path,
 | 
				
			|||||||
        /* works for both restarts and new audio */
 | 
					        /* works for both restarts and new audio */
 | 
				
			||||||
        if (strcmp(pair->value.path, path) == 0)
 | 
					        if (strcmp(pair->value.path, path) == 0)
 | 
				
			||||||
            repeat_audio(&pair->value);
 | 
					            repeat_audio(&pair->value);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        /* audio without channel plays without repeat and ability to change parameters over its course, nor stop it */
 | 
				
			||||||
 | 
					        AudioFileType const file_type = infer_audio_file_type(path);
 | 
				
			||||||
 | 
					        AudioChannel new_channel = {
 | 
				
			||||||
 | 
					            .file_type = file_type,
 | 
				
			||||||
 | 
					            .context = init_audio_context(path, file_type),
 | 
				
			||||||
 | 
					            .path = path,
 | 
				
			||||||
 | 
					            .name = NULL,
 | 
				
			||||||
 | 
					            .repeat = false,
 | 
				
			||||||
 | 
					            .volume = volume,
 | 
				
			||||||
 | 
					            .panning = panning,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (repeat)
 | 
				
			||||||
 | 
					            log_warn("Cannot repeat audio played on unnamed scratch channel (for %s)", path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        arrpush(ctx.unnamed_audio_channels, new_channel);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TWN_API void audio_set(const char *channel, AudioParam param, float value) {
 | 
					TWN_API void audio_parameter(const char *channel, const char *param, float value) {
 | 
				
			||||||
    AudioChannelItem *pair = shgetp_null(ctx.audio_channels, channel);
 | 
					    AudioChannelItem *pair = shgetp_null(ctx.audio_channels, channel);
 | 
				
			||||||
    if (!pair) {
 | 
					    if (!pair) {
 | 
				
			||||||
        log_warn("No channel by the name of %s to set a parameter for", channel);
 | 
					        log_warn("No channel by the name of %s to set a parameter for", channel);
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    switch (param) {
 | 
					    if (SDL_strncmp(param, "repeat", sizeof "repeat" - 1) == 0) {
 | 
				
			||||||
    case AUDIO_PARAM_REPEAT:
 | 
					 | 
				
			||||||
        pair->value.repeat = (bool)value;
 | 
					        pair->value.repeat = (bool)value;
 | 
				
			||||||
        break;
 | 
					
 | 
				
			||||||
    case AUDIO_PARAM_VOLUME:
 | 
					    } else if (SDL_strncmp(param, "volume", sizeof "volume" - 1) == 0) {
 | 
				
			||||||
        if (value > 1.0f) {
 | 
					        if (value > 1.0f || value < 0.0f) {
 | 
				
			||||||
            log_warn("Out of range volume for channel %s set", channel);
 | 
					            log_warn("Out of range volume for channel %s set", channel);
 | 
				
			||||||
            value = 1.0f;
 | 
					            value = clampf(value, 0.0f, 1.0f);
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (value < 0.0f) {
 | 
					 | 
				
			||||||
            log_warn("Out of range volume for channel %s set", channel);
 | 
					 | 
				
			||||||
            value = 0.0f;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        pair->value.volume = value;
 | 
					        pair->value.volume = value;
 | 
				
			||||||
        break;
 | 
					
 | 
				
			||||||
    case AUDIO_PARAM_PANNING:
 | 
					    } else if (SDL_strncmp(param, "panning", sizeof "panning" - 1) == 0) {
 | 
				
			||||||
        if (value > 1.0f) {
 | 
					        if (value > 1.0f || value < -1.0f) {
 | 
				
			||||||
            log_warn("Out of range panning for channel %s set", channel);
 | 
					            log_warn("Out of range panning for channel %s set", channel);
 | 
				
			||||||
            value = 1.0f;
 | 
					            value = clampf(value, -1.0f, +1.0f);
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (value < -1.0f) {
 | 
					 | 
				
			||||||
            log_warn("Out of range panning for channel %s set", channel);
 | 
					 | 
				
			||||||
            value = -1.0f;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        pair->value.panning = value;
 | 
					        pair->value.panning = value;
 | 
				
			||||||
        break;
 | 
					
 | 
				
			||||||
    default:
 | 
					    } else
 | 
				
			||||||
        CRY("Audio channel parameter setting failed", "Invalid parameter enum given");
 | 
					        CRY("Audio channel parameter setting failed", "Invalid parameter enum given");
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* TODO: handle it more properly in regards to clipping and alike */
 | 
				
			||||||
/* this assumes float based streams */
 | 
					/* this assumes float based streams */
 | 
				
			||||||
static void audio_mixin_streams(const AudioChannel *channel,
 | 
					static void audio_mixin_streams(const AudioChannel *channel,
 | 
				
			||||||
                                uint8_t *restrict a,
 | 
					                                uint8_t *restrict a,
 | 
				
			||||||
                                uint8_t *restrict b,
 | 
					                                uint8_t *restrict b,
 | 
				
			||||||
                                size_t frames)
 | 
					                                size_t frames,
 | 
				
			||||||
 | 
					                                bool last)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    float *const sa = (float *)(void *)a;
 | 
					    float *const sa = (float *)(void *)a;
 | 
				
			||||||
    float *const sb = (float *)(void *)b;
 | 
					    float *const sb = (float *)(void *)b;
 | 
				
			||||||
@@ -241,37 +365,47 @@ static void audio_mixin_streams(const AudioChannel *channel,
 | 
				
			|||||||
    const float left_panning = fminf(fabsf(channel->panning - 1.0f), 1.0f);
 | 
					    const float left_panning = fminf(fabsf(channel->panning - 1.0f), 1.0f);
 | 
				
			||||||
    const float right_panning = fminf(fabsf(channel->panning + 1.0f), 1.0f);
 | 
					    const float right_panning = fminf(fabsf(channel->panning + 1.0f), 1.0f);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (size_t s = 0; s < frames; s += 2) {
 | 
					    if (last) {
 | 
				
			||||||
 | 
					        for (size_t s = 0; s < frames; ++s) {
 | 
				
			||||||
            /* left channel */
 | 
					            /* left channel */
 | 
				
			||||||
        sa[s] += (float)(sb[s] * channel->volume * left_panning);
 | 
					            sa[s * 2 + 0] += (float)(sb[s * 2 + 0] * channel->volume * left_panning);
 | 
				
			||||||
 | 
					 | 
				
			||||||
            /* right channel */
 | 
					            /* right channel */
 | 
				
			||||||
        sa[s + 1] += (float)(sb[s + 1] * channel->volume * right_panning);
 | 
					            sa[s * 2 + 1] += (float)(sb[s * 2 + 1] * channel->volume * right_panning);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        for (size_t s = 0; s < frames; ++s) {
 | 
				
			||||||
 | 
					            /* left channel */
 | 
				
			||||||
 | 
					            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 * 2 + 1] += (float)(sb[s * 2 + 1] * channel->volume * right_panning);
 | 
				
			||||||
 | 
					            sa[s * 2 + 1] *= 1 / (float)M_2_SQRTPI;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* remember: sample is data for all channels where frame is a part of it */
 | 
					/* remember: frame consists of sample * channel_count */
 | 
				
			||||||
static void audio_sample_and_mixin_channel(const AudioChannel *channel,
 | 
					static void audio_sample_and_mixin_channel(AudioChannel *channel,
 | 
				
			||||||
                                           uint8_t *stream,
 | 
					                                           uint8_t *stream,
 | 
				
			||||||
                                           int len)
 | 
					                                           int len, bool last)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    static uint8_t buffer[16384];
 | 
					    static uint8_t buffer[16384]; /* TODO: better make it a growable scratch instead, which will simplify things */
 | 
				
			||||||
    const int float_buffer_frames = sizeof (buffer) / sizeof (float);
 | 
					    const size_t float_buffer_frames = sizeof (buffer) / sizeof (float) / 2;
 | 
				
			||||||
    const int stream_frames = len / (int)(sizeof (float));
 | 
					    const size_t stream_frames = len / sizeof (float) / 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    switch (channel->file_type) {
 | 
					    switch (channel->file_type) {
 | 
				
			||||||
    case AUDIO_FILE_TYPE_OGG: {
 | 
					    case AUDIO_FILE_TYPE_OGG: {
 | 
				
			||||||
        /* feed stream for needed conversions */
 | 
					        /* feed stream for needed conversions */
 | 
				
			||||||
        for (int i = 0; i < stream_frames; ) {
 | 
					        for (size_t i = 0; i < stream_frames; ) {
 | 
				
			||||||
            const int n_frames = (stream_frames - i) > float_buffer_frames ?
 | 
					            const size_t n_frames = (stream_frames - i) > float_buffer_frames ?
 | 
				
			||||||
                float_buffer_frames : stream_frames - i;
 | 
					                float_buffer_frames : stream_frames - i;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const int samples_per_channel = stb_vorbis_get_samples_float_interleaved(
 | 
					            const size_t samples_per_channel = stb_vorbis_get_samples_float_interleaved(
 | 
				
			||||||
                channel->context.vorbis.handle,
 | 
					                channel->context.vorbis.handle,
 | 
				
			||||||
                channel->context.vorbis.channel_count,
 | 
					                2,
 | 
				
			||||||
                (float *)buffer,
 | 
					                (float *)buffer,
 | 
				
			||||||
                n_frames);
 | 
					                (int)n_frames * 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            /* handle end of file */
 | 
					            /* handle end of file */
 | 
				
			||||||
            if (samples_per_channel == 0) {
 | 
					            if (samples_per_channel == 0) {
 | 
				
			||||||
@@ -279,30 +413,63 @@ static void audio_sample_and_mixin_channel(const AudioChannel *channel,
 | 
				
			|||||||
                    /* seek to start and try sampling some more */
 | 
					                    /* seek to start and try sampling some more */
 | 
				
			||||||
                    stb_vorbis_seek_start(channel->context.vorbis.handle);
 | 
					                    stb_vorbis_seek_start(channel->context.vorbis.handle);
 | 
				
			||||||
                    continue;
 | 
					                    continue;
 | 
				
			||||||
                } else
 | 
					                } else {
 | 
				
			||||||
                    /* leave silence */
 | 
					                    /* leave silence */
 | 
				
			||||||
 | 
					                    channel->finished = true;
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            /* panning and mixing */
 | 
					            /* panning and mixing */
 | 
				
			||||||
            audio_mixin_streams(channel,
 | 
					            audio_mixin_streams(channel,
 | 
				
			||||||
                                &stream[i * sizeof(float)], buffer,
 | 
					                                &stream[i * sizeof(float) * 2], buffer,
 | 
				
			||||||
                                samples_per_channel * 2);
 | 
					                                samples_per_channel,
 | 
				
			||||||
 | 
					                                last);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            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,
 | 
				
			||||||
 | 
					                                last);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            channel->context.wav.position += limit;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (channel->context.wav.position >= channel->context.wav.spec.samples) {
 | 
				
			||||||
 | 
					                if (channel->repeat)
 | 
				
			||||||
 | 
					                    channel->context.wav.position = 0;
 | 
				
			||||||
 | 
					                else {
 | 
				
			||||||
 | 
					                    /* leave silence */
 | 
				
			||||||
 | 
					                    channel->finished = true;
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            i += limit;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    case AUDIO_FILE_TYPE_XM: {
 | 
					    case AUDIO_FILE_TYPE_XM: {
 | 
				
			||||||
        for (int i = 0; i < stream_frames; ) {
 | 
					        for (size_t i = 0; i < stream_frames; ) {
 | 
				
			||||||
            const int n_frames = (stream_frames - i) > float_buffer_frames ?
 | 
					            const size_t n_frames = (stream_frames - i) > float_buffer_frames ?
 | 
				
			||||||
                float_buffer_frames : stream_frames - i;
 | 
					                float_buffer_frames : stream_frames - i;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const int samples_per_channel = xm_generate_samples(channel->context.xm.handle,
 | 
					            const size_t samples_per_channel = xm_generate_samples(channel->context.xm.handle,
 | 
				
			||||||
                                                                   (float *)buffer,
 | 
					                                                                   (float *)buffer,
 | 
				
			||||||
                                                                n_frames / 2);
 | 
					                                                                   n_frames);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            /* handle end of file */
 | 
					            /* handle end of file */
 | 
				
			||||||
            if (samples_per_channel == 0) {
 | 
					            if (samples_per_channel == 0) {
 | 
				
			||||||
@@ -310,18 +477,21 @@ static void audio_sample_and_mixin_channel(const AudioChannel *channel,
 | 
				
			|||||||
                    /* seek to start and try sampling some more */
 | 
					                    /* seek to start and try sampling some more */
 | 
				
			||||||
                    xm_restart(channel->context.xm.handle);
 | 
					                    xm_restart(channel->context.xm.handle);
 | 
				
			||||||
                    continue;
 | 
					                    continue;
 | 
				
			||||||
                } else
 | 
					                } else {
 | 
				
			||||||
 | 
					                    channel->finished = true;
 | 
				
			||||||
                    /* leave silence */
 | 
					                    /* leave silence */
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            /* panning and mixing */
 | 
					            /* panning and mixing */
 | 
				
			||||||
            audio_mixin_streams(channel,
 | 
					            audio_mixin_streams(channel,
 | 
				
			||||||
                                &stream[i * sizeof(float)],
 | 
					                                &stream[i * sizeof(float) * 2],
 | 
				
			||||||
                                buffer,
 | 
					                                buffer,
 | 
				
			||||||
                                samples_per_channel * 2);
 | 
					                                samples_per_channel,
 | 
				
			||||||
 | 
					                                last);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            i += samples_per_channel * 2;
 | 
					            i += samples_per_channel;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
@@ -351,12 +521,33 @@ void audio_callback(void *userdata, uint8_t *stream, int len) {
 | 
				
			|||||||
    /* prepare for mixing */
 | 
					    /* prepare for mixing */
 | 
				
			||||||
    SDL_memset(stream, 0, len);
 | 
					    SDL_memset(stream, 0, len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (int i = 0; i < shlen(ctx.audio_channels); ++i) {
 | 
					    size_t const audio_channels_len = shlen(ctx.audio_channels);
 | 
				
			||||||
 | 
					    for (size_t i = 0; i < audio_channels_len; ++i) {
 | 
				
			||||||
        sanity_check_channel(&ctx.audio_channels[i].value);
 | 
					        sanity_check_channel(&ctx.audio_channels[i].value);
 | 
				
			||||||
        audio_sample_and_mixin_channel(&ctx.audio_channels[i].value, stream, len);
 | 
					        audio_sample_and_mixin_channel(&ctx.audio_channels[i].value,
 | 
				
			||||||
 | 
					                                       stream, len,
 | 
				
			||||||
 | 
					                                       i == audio_channels_len - 1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    size_t const unnamed_audio_channels_len = shlen(ctx.unnamed_audio_channels);
 | 
				
			||||||
 | 
					    for (size_t i = 0; i < unnamed_audio_channels_len; ++i) {
 | 
				
			||||||
 | 
					        sanity_check_channel(&ctx.unnamed_audio_channels[i]);
 | 
				
			||||||
 | 
					        audio_sample_and_mixin_channel(&ctx.unnamed_audio_channels[i],
 | 
				
			||||||
 | 
					                                       stream, len,
 | 
				
			||||||
 | 
					                                       i == unnamed_audio_channels_len - 1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* ditch finished unnamed */
 | 
				
			||||||
 | 
					    size_t i = 0;
 | 
				
			||||||
 | 
					    while (i < unnamed_audio_channels_len) {
 | 
				
			||||||
 | 
					        if (ctx.unnamed_audio_channels[i].finished) {
 | 
				
			||||||
 | 
					            free_audio_channel(ctx.unnamed_audio_channels[i]);
 | 
				
			||||||
 | 
					            arrdelswap(ctx.unnamed_audio_channels, i);
 | 
				
			||||||
 | 
					        } else i++;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TWN_API void audio_play_args(PlayAudioArgs args) {
 | 
					TWN_API void audio_play_args(PlayAudioArgs args) {
 | 
				
			||||||
    const char *channel = m_or(args, channel, NULL);
 | 
					    const char *channel = m_or(args, channel, NULL);
 | 
				
			||||||
    const bool repeat = m_or(args, repeat, false);
 | 
					    const bool repeat = m_or(args, repeat, false);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,6 @@
 | 
				
			|||||||
#ifndef TWN_AUDIO_C_H
 | 
					#ifndef TWN_AUDIO_C_H
 | 
				
			||||||
#define TWN_AUDIO_C_H
 | 
					#define TWN_AUDIO_C_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "twn_audio.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <SDL2/SDL_audio.h>
 | 
					#include <SDL2/SDL_audio.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define STB_VORBIS_HEADER_ONLY
 | 
					#define STB_VORBIS_HEADER_ONLY
 | 
				
			||||||
@@ -15,9 +13,12 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#define AUDIO_FREQUENCY 48000
 | 
					#define AUDIO_FREQUENCY 48000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* TODO: specify which PCM formats are usable with WAV */
 | 
				
			||||||
 | 
					/* TODO: specify limitations of libxm */
 | 
				
			||||||
 | 
					/* TODO: specify limitations of stb_vorbis */
 | 
				
			||||||
typedef enum AudioFileType {
 | 
					typedef enum AudioFileType {
 | 
				
			||||||
    AUDIO_FILE_TYPE_OGG,
 | 
					    AUDIO_FILE_TYPE_OGG,
 | 
				
			||||||
 | 
					    AUDIO_FILE_TYPE_WAV,
 | 
				
			||||||
    AUDIO_FILE_TYPE_XM,
 | 
					    AUDIO_FILE_TYPE_XM,
 | 
				
			||||||
    AUDIO_FILE_TYPE_COUNT,
 | 
					    AUDIO_FILE_TYPE_COUNT,
 | 
				
			||||||
    AUDIO_FILE_TYPE_UNKNOWN,
 | 
					    AUDIO_FILE_TYPE_UNKNOWN,
 | 
				
			||||||
@@ -32,6 +33,12 @@ union AudioContext {
 | 
				
			|||||||
        uint8_t channel_count;
 | 
					        uint8_t channel_count;
 | 
				
			||||||
    } vorbis;
 | 
					    } vorbis;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    struct {
 | 
				
			||||||
 | 
					        void *samples;
 | 
				
			||||||
 | 
					        SDL_AudioSpec spec;
 | 
				
			||||||
 | 
					        size_t position;
 | 
				
			||||||
 | 
					    } wav;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    struct {
 | 
					    struct {
 | 
				
			||||||
        xm_context_t *handle;
 | 
					        xm_context_t *handle;
 | 
				
			||||||
    } xm;
 | 
					    } xm;
 | 
				
			||||||
@@ -46,6 +53,7 @@ typedef struct AudioChannel {
 | 
				
			|||||||
    bool repeat;
 | 
					    bool repeat;
 | 
				
			||||||
    float volume;
 | 
					    float volume;
 | 
				
			||||||
    float panning;
 | 
					    float panning;
 | 
				
			||||||
 | 
					    bool finished;
 | 
				
			||||||
} AudioChannel;
 | 
					} AudioChannel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
#include "twn_camera.h"
 | 
					#include "twn_camera_c.h"
 | 
				
			||||||
#include "twn_vec.h"
 | 
					#include "twn_vec.h"
 | 
				
			||||||
#include "twn_engine_context_c.h"
 | 
					#include "twn_engine_context_c.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -28,9 +28,10 @@ Matrix4 camera_look_at(const Camera *const camera) {
 | 
				
			|||||||
    result.row[3].x = -m_vec_dot(r, camera->pos);
 | 
					    result.row[3].x = -m_vec_dot(r, camera->pos);
 | 
				
			||||||
    result.row[3].y = -m_vec_dot(u, camera->pos);
 | 
					    result.row[3].y = -m_vec_dot(u, camera->pos);
 | 
				
			||||||
    result.row[3].z =  m_vec_dot(camera->target, camera->pos);
 | 
					    result.row[3].z =  m_vec_dot(camera->target, camera->pos);
 | 
				
			||||||
    result.row[0].w =  result.row[1].w = result.row[2].w = 0.0f;
 | 
					 | 
				
			||||||
    result.row[3].w =  1.0f;
 | 
					    result.row[3].w =  1.0f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    result.row[0].w =  result.row[1].w = result.row[2].w = 0.0f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return result;
 | 
					    return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -38,7 +39,7 @@ Matrix4 camera_perspective(const Camera *const camera) {
 | 
				
			|||||||
    /* from cglm */
 | 
					    /* from cglm */
 | 
				
			||||||
    Matrix4 result = {0};
 | 
					    Matrix4 result = {0};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const float aspect = (float)(ctx.base_render_width / ctx.base_render_height);
 | 
					    const float aspect = ((float)ctx.base_render_width / (float)ctx.base_render_height);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const float f  = 1.0f / tanf(camera->fov * 0.5f);
 | 
					    const float f  = 1.0f / tanf(camera->fov * 0.5f);
 | 
				
			||||||
    const float fn = 1.0f / (CAMERA_NEAR_Z - CAMERA_FAR_Z);
 | 
					    const float fn = 1.0f / (CAMERA_NEAR_Z - CAMERA_FAR_Z);
 | 
				
			||||||
@@ -51,3 +52,24 @@ Matrix4 camera_perspective(const Camera *const camera) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return result;
 | 
					    return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Matrix4 camera_orthographic(const Camera *const camera) {
 | 
				
			||||||
 | 
					    /* from cglm */
 | 
				
			||||||
 | 
					    (void)camera;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Matrix4 result = {0};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const float rl =  1.0f / (camera->viewbox[0].y - camera->viewbox[0].x);
 | 
				
			||||||
 | 
					    const float tb =  1.0f / (camera->viewbox[1].x - camera->viewbox[1].y);
 | 
				
			||||||
 | 
					    const float fn = -1.0f / (CAMERA_FAR_Z - -CAMERA_FAR_Z);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    result.row[0].x =  2.0f * rl;
 | 
				
			||||||
 | 
					    result.row[1].y =  2.0f * tb;
 | 
				
			||||||
 | 
					    result.row[2].z =  2.0f * fn;
 | 
				
			||||||
 | 
					    result.row[3].x = -(camera->viewbox[0].y + camera->viewbox[0].x)  * rl;
 | 
				
			||||||
 | 
					    result.row[3].y = -(camera->viewbox[1].x + camera->viewbox[1].y)  * tb;
 | 
				
			||||||
 | 
					    result.row[3].z =  (CAMERA_FAR_Z + -CAMERA_FAR_Z) * fn;
 | 
				
			||||||
 | 
					    result.row[3].w =  1.0f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@
 | 
				
			|||||||
#define TWN_CAMERA_H
 | 
					#define TWN_CAMERA_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "twn_types.h"
 | 
					#include "twn_types.h"
 | 
				
			||||||
#include "twn_engine_api.h"
 | 
					#include "twn_types_c.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* TODO: make it cached? */
 | 
					/* TODO: make it cached? */
 | 
				
			||||||
/*       for example, perspective matrix only needs recaluclation on FOV change */
 | 
					/*       for example, perspective matrix only needs recaluclation on FOV change */
 | 
				
			||||||
@@ -13,10 +13,11 @@ typedef struct Camera {
 | 
				
			|||||||
    Vec3 target;     /* normalized target vector */
 | 
					    Vec3 target;     /* normalized target vector */
 | 
				
			||||||
    Vec3 up;         /* normalized up vector */
 | 
					    Vec3 up;         /* normalized up vector */
 | 
				
			||||||
    float fov;       /* field of view, in radians */
 | 
					    float fov;       /* field of view, in radians */
 | 
				
			||||||
 | 
					    Vec2 viewbox[2]; /* othrographic aabb, ((left, right), (top, bottom)) */
 | 
				
			||||||
} Camera;
 | 
					} Camera;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TWN_API Matrix4 camera_look_at(const Camera *camera);
 | 
					Matrix4 camera_look_at(const Camera *camera);
 | 
				
			||||||
 | 
					Matrix4 camera_perspective(const Camera *const camera);
 | 
				
			||||||
TWN_API Matrix4 camera_perspective(const Camera *const camera);
 | 
					Matrix4 camera_orthographic(const Camera *const camera);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
@@ -28,8 +28,11 @@ typedef struct EngineContext {
 | 
				
			|||||||
    char **argv;
 | 
					    char **argv;
 | 
				
			||||||
    /* where the app was run from, used as the root for packs */
 | 
					    /* where the app was run from, used as the root for packs */
 | 
				
			||||||
    char *base_dir;
 | 
					    char *base_dir;
 | 
				
			||||||
 | 
					    char *title;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Vec2i window_dims;
 | 
					    Vec2 window_dims;
 | 
				
			||||||
 | 
					    Rect viewport_rect;
 | 
				
			||||||
 | 
					    float viewport_scale;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* configuration */
 | 
					    /* configuration */
 | 
				
			||||||
    toml_table_t *config_table;
 | 
					    toml_table_t *config_table;
 | 
				
			||||||
@@ -45,11 +48,13 @@ typedef struct EngineContext {
 | 
				
			|||||||
    /* rendering */
 | 
					    /* rendering */
 | 
				
			||||||
    Primitive2D *render_queue_2d;
 | 
					    Primitive2D *render_queue_2d;
 | 
				
			||||||
    MeshBatchItem *uncolored_mesh_batches;
 | 
					    MeshBatchItem *uncolored_mesh_batches;
 | 
				
			||||||
 | 
					    MeshBatchItem *billboard_batches;
 | 
				
			||||||
    TextCache text_cache;
 | 
					    TextCache text_cache;
 | 
				
			||||||
    TextureCache texture_cache;
 | 
					    TextureCache texture_cache;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* audio */
 | 
					    /* audio */
 | 
				
			||||||
    AudioChannelItem *audio_channels;
 | 
					    AudioChannelItem *audio_channels;
 | 
				
			||||||
 | 
					    AudioChannel *unnamed_audio_channels;
 | 
				
			||||||
    SDL_AudioDeviceID audio_device;
 | 
					    SDL_AudioDeviceID audio_device;
 | 
				
			||||||
    int audio_stream_frequency;
 | 
					    int audio_stream_frequency;
 | 
				
			||||||
    SDL_AudioFormat audio_stream_format;
 | 
					    SDL_AudioFormat audio_stream_format;
 | 
				
			||||||
@@ -64,20 +69,21 @@ typedef struct EngineContext {
 | 
				
			|||||||
    int64_t delta_averager_residual;
 | 
					    int64_t delta_averager_residual;
 | 
				
			||||||
    int64_t time_averager[4];
 | 
					    int64_t time_averager[4];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* this should be a multiple of the current ticks per second */
 | 
					 | 
				
			||||||
    /* use it to simulate low framerate (e.g. at 60 tps, set to 2 for 30 fps) */
 | 
					 | 
				
			||||||
    /* it can be changed at runtime; any resulting logic anomalies are bugs */
 | 
					 | 
				
			||||||
    uint32_t update_multiplicity;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    SDL_GLContext *gl_context;
 | 
					    SDL_GLContext *gl_context;
 | 
				
			||||||
    SDL_Window *window;
 | 
					    SDL_Window *window;
 | 
				
			||||||
    uint32_t window_id;
 | 
					    uint32_t window_id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Color background_color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    bool is_running;
 | 
					    bool is_running;
 | 
				
			||||||
    bool window_size_has_changed;
 | 
					    bool window_size_has_changed;
 | 
				
			||||||
    bool resync_flag;
 | 
					    bool resync_flag;
 | 
				
			||||||
    bool was_successful;
 | 
					    bool was_successful;
 | 
				
			||||||
    bool render_double_buffered;
 | 
					    bool render_double_buffered;
 | 
				
			||||||
 | 
					    /* signals mouse focus, used to disable mouse capture */
 | 
				
			||||||
 | 
					    bool window_mouse_resident;
 | 
				
			||||||
 | 
					    bool audio_initialized;
 | 
				
			||||||
 | 
					    bool cull_faces;
 | 
				
			||||||
} EngineContext;
 | 
					} EngineContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* TODO: does it need to be marked with TWN_API? */
 | 
					/* TODO: does it need to be marked with TWN_API? */
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										167
									
								
								src/twn_input.c
									
									
									
									
									
								
							
							
						
						
									
										167
									
								
								src/twn_input.c
									
									
									
									
									
								
							@@ -1,13 +1,14 @@
 | 
				
			|||||||
#include "twn_input_c.h"
 | 
					#include "twn_input_c.h"
 | 
				
			||||||
#include "twn_util.h"
 | 
					#include "twn_util.h"
 | 
				
			||||||
 | 
					#include "twn_util_c.h"
 | 
				
			||||||
#include "twn_control.h"
 | 
					#include "twn_control.h"
 | 
				
			||||||
#include "twn_engine_context_c.h"
 | 
					#include "twn_engine_context_c.h"
 | 
				
			||||||
 | 
					#include "twn_input.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <SDL2/SDL.h>
 | 
					#include <SDL2/SDL.h>
 | 
				
			||||||
#include <stb_ds.h>
 | 
					#include <stb_ds.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <stdbool.h>
 | 
					#include <stdbool.h>
 | 
				
			||||||
#include <stdlib.h>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void update_action_pressed_state(InputState *input, Action *action) {
 | 
					static void update_action_pressed_state(InputState *input, Action *action) {
 | 
				
			||||||
@@ -44,8 +45,8 @@ static void update_action_pressed_state(InputState *input, Action *action) {
 | 
				
			|||||||
            else {
 | 
					            else {
 | 
				
			||||||
                action->just_changed = !action->is_pressed;
 | 
					                action->just_changed = !action->is_pressed;
 | 
				
			||||||
                action->is_pressed = true;
 | 
					                action->is_pressed = true;
 | 
				
			||||||
                action->position.x = (float)input->mouse_window_position.x;
 | 
					                action->position.x = ((float)input->mouse_window_position.x - ctx.viewport_rect.x) / ctx.viewport_scale;
 | 
				
			||||||
                action->position.y = (float)input->mouse_window_position.y;
 | 
					                action->position.y = ((float)input->mouse_window_position.y - ctx.viewport_rect.y) / ctx.viewport_scale;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                /* TODO: */
 | 
					                /* TODO: */
 | 
				
			||||||
                /*
 | 
					                /*
 | 
				
			||||||
@@ -69,20 +70,40 @@ static void update_action_pressed_state(InputState *input, Action *action) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static ActionHashItem *input_add_action(char const *action_name) {
 | 
				
			||||||
 | 
					    SDL_assert(action_name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Action new_action = { 0 };
 | 
				
			||||||
 | 
					    new_action.bindings = SDL_calloc(ctx.keybind_slots, sizeof *new_action.bindings);
 | 
				
			||||||
 | 
					    shput(ctx.input.action_hash, action_name, new_action);
 | 
				
			||||||
 | 
					    return shgetp(ctx.input.action_hash, action_name);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void input_delete_action(char const *action_name) {
 | 
				
			||||||
 | 
					    SDL_assert(action_name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
 | 
				
			||||||
 | 
					    SDL_assert(action);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    SDL_free(action->value.bindings);
 | 
				
			||||||
 | 
					    shdel(ctx.input.action_hash, action_name);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void input_bind_code_to_action(InputState *input,
 | 
					static void input_bind_code_to_action(InputState *input,
 | 
				
			||||||
                                      char const *action_name,
 | 
					                                      char const *action_name,
 | 
				
			||||||
                                      ButtonSource source,
 | 
					                                      ButtonSource source,
 | 
				
			||||||
                                      union ButtonCode code)
 | 
					                                      union ButtonCode code)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    ActionHashItem *action_item = shgetp_null(input->action_hash, action_name);
 | 
					    ActionHashItem *action_item = shgetp_null(input->action_hash, action_name);
 | 
				
			||||||
    if (action_item == NULL) {
 | 
					    if (!action_item)
 | 
				
			||||||
        log_warn("(%s) Action \"%s\" does not exist.", __func__, action_name);
 | 
					        action_item = input_add_action(action_name);
 | 
				
			||||||
        return;
 | 
					
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    Action *action = &action_item->value;
 | 
					    Action *action = &action_item->value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* check every binding to make sure this code isn't already bound */
 | 
					    /* check every binding to make sure this code isn't already bound */
 | 
				
			||||||
    for (size_t i = 0; i < (uint64_t)ctx.keybind_slots; ++i) {
 | 
					    for (size_t i = 0; i < action->num_bindings; ++i) {
 | 
				
			||||||
        Button *binding = &action->bindings[i];
 | 
					        Button *binding = &action->bindings[i];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (binding->source != source)
 | 
					        if (binding->source != source)
 | 
				
			||||||
@@ -109,7 +130,8 @@ static void input_bind_code_to_action(InputState *input,
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (is_already_bound) {
 | 
					        if (is_already_bound) {
 | 
				
			||||||
            log_warn("(%s) Code already bound to action \"%s\".", __func__, action_name);
 | 
					            /* keep it alive */
 | 
				
			||||||
 | 
					            binding->in_use = true;
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -117,13 +139,14 @@ static void input_bind_code_to_action(InputState *input,
 | 
				
			|||||||
    /* if we're at max bindings, forget the first element and shift the rest */
 | 
					    /* if we're at max bindings, forget the first element and shift the rest */
 | 
				
			||||||
    if (action->num_bindings == (uint64_t)ctx.keybind_slots) {
 | 
					    if (action->num_bindings == (uint64_t)ctx.keybind_slots) {
 | 
				
			||||||
        --action->num_bindings;
 | 
					        --action->num_bindings;
 | 
				
			||||||
        size_t shifted_size = (sizeof action->bindings) - (sizeof action->bindings[0]);
 | 
					        size_t shifted_size = sizeof action->bindings[0] * (ctx.keybind_slots - 1);
 | 
				
			||||||
        SDL_memmove(action->bindings, action->bindings + 1, shifted_size);
 | 
					        SDL_memmove(action->bindings, action->bindings + 1, shifted_size);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    action->bindings[action->num_bindings++] = (Button) {
 | 
					    action->bindings[action->num_bindings++] = (Button) {
 | 
				
			||||||
        .source = source,
 | 
					        .source = source,
 | 
				
			||||||
        .code = code,
 | 
					        .code = code,
 | 
				
			||||||
 | 
					        .in_use = true,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -134,16 +157,15 @@ static void input_unbind_code_from_action(InputState *input,
 | 
				
			|||||||
                                          union ButtonCode code)
 | 
					                                          union ButtonCode code)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    ActionHashItem *action_item = shgetp_null(input->action_hash, action_name);
 | 
					    ActionHashItem *action_item = shgetp_null(input->action_hash, action_name);
 | 
				
			||||||
    if (action_item == NULL) {
 | 
					    if (!action_item)
 | 
				
			||||||
        log_warn("(%s) Action \"%s\" does not exist.", __func__, action_name);
 | 
					        action_item = input_add_action(action_name);
 | 
				
			||||||
        return;
 | 
					
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    Action *action = &action_item->value;
 | 
					    Action *action = &action_item->value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* check every binding to make sure this code is bound */
 | 
					    /* check every binding to make sure this code is bound */
 | 
				
			||||||
    size_t index = 0;
 | 
					    size_t index = 0;
 | 
				
			||||||
    bool is_bound = false;
 | 
					    bool is_bound = false;
 | 
				
			||||||
    for (index = 0; index < (uint64_t)ctx.keybind_slots; ++index) {
 | 
					    for (index = 0; index < action->num_bindings; ++index) {
 | 
				
			||||||
        Button *binding = &action->bindings[index];
 | 
					        Button *binding = &action->bindings[index];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (binding->source != source)
 | 
					        if (binding->source != source)
 | 
				
			||||||
@@ -173,10 +195,8 @@ static void input_unbind_code_from_action(InputState *input,
 | 
				
			|||||||
            break;
 | 
					            break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!is_bound) {
 | 
					    if (!is_bound)
 | 
				
			||||||
        log_warn("(%s) Code is not bound to action \"%s\".", __func__, action_name);
 | 
					 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* remove the element to unbind and shift the rest so there isn't a gap */
 | 
					    /* remove the element to unbind and shift the rest so there isn't a gap */
 | 
				
			||||||
    size_t elements_after_index = action->num_bindings - index;
 | 
					    size_t elements_after_index = action->num_bindings - index;
 | 
				
			||||||
@@ -196,25 +216,55 @@ void input_state_deinit(InputState *input) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void input_state_update_postframe(InputState *input) {
 | 
				
			||||||
 | 
					    (void)input;
 | 
				
			||||||
 | 
					    /* TODO: don't spam it if it happens */
 | 
				
			||||||
 | 
					    if (SDL_SetRelativeMouseMode(ctx.game_copy.mouse_capture && ctx.window_mouse_resident) != 0)
 | 
				
			||||||
 | 
					        log_warn("(%s) Mouse capture isn't supported.", __func__);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void input_state_update(InputState *input) {
 | 
					void input_state_update(InputState *input) {
 | 
				
			||||||
 | 
					    int x, y;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    input->keyboard_state = SDL_GetKeyboardState(NULL);
 | 
					    input->keyboard_state = SDL_GetKeyboardState(NULL);
 | 
				
			||||||
    input->mouse_state = SDL_GetMouseState(&input->mouse_window_position.x,
 | 
					    input->mouse_state = SDL_GetMouseState(&x, &y);
 | 
				
			||||||
                                           &input->mouse_window_position.y);
 | 
					    input->mouse_window_position = (Vec2){ (float)x, (float)y };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    SDL_GetRelativeMouseState(&input->mouse_relative_position.x,
 | 
					    SDL_GetRelativeMouseState(&x, &y);
 | 
				
			||||||
                              &input->mouse_relative_position.y);
 | 
					    input->mouse_relative_position = (Vec2){ (float)x, (float)y };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ctx.game.mouse_position = input->mouse_window_position;
 | 
					    ctx.game.mouse_position.x = ((float)input->mouse_window_position.x - ctx.viewport_rect.x) / ctx.viewport_scale;
 | 
				
			||||||
 | 
					    ctx.game.mouse_position.y = ((float)input->mouse_window_position.y - ctx.viewport_rect.y) / ctx.viewport_scale;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (ctx.window_mouse_resident)
 | 
				
			||||||
        ctx.game.mouse_movement = input->mouse_relative_position;
 | 
					        ctx.game.mouse_movement = input->mouse_relative_position;
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					        ctx.game.mouse_movement = (Vec2){0};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (size_t i = 0; i < shlenu(input->action_hash); ++i) {
 | 
					    for (size_t i = 0; i < shlenu(input->action_hash); ++i) {
 | 
				
			||||||
        Action *action = &input->action_hash[i].value;
 | 
					        Action *action = &input->action_hash[i].value;
 | 
				
			||||||
 | 
					        /* collect unused */
 | 
				
			||||||
 | 
					        for (size_t u = 0; u < action->num_bindings; ++u) {
 | 
				
			||||||
 | 
					            Button *button = &action->bindings[u];
 | 
				
			||||||
 | 
					            if (!button->in_use)
 | 
				
			||||||
 | 
					                input_unbind_code_from_action(input, input->action_hash[i].key, button->source, button->code);
 | 
				
			||||||
 | 
					            else 
 | 
				
			||||||
 | 
					                button->in_use = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        update_action_pressed_state(input, action);
 | 
					        update_action_pressed_state(input, action);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    size_t removed = 0;
 | 
				
			||||||
 | 
					    for (size_t i = 0; i < shlenu(input->action_hash); ++i) {
 | 
				
			||||||
 | 
					        if (input->action_hash[i - removed].value.num_bindings == 0)
 | 
				
			||||||
 | 
					            input_delete_action(input->action_hash[i - removed].key);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void input_bind_action_control(char const *action_name,
 | 
					void input_action(char const *action_name,
 | 
				
			||||||
                               Control control)
 | 
					                               Control control)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    SDL_assert_always(action_name);
 | 
					    SDL_assert_always(action_name);
 | 
				
			||||||
@@ -236,57 +286,7 @@ void input_bind_action_control(char const *action_name,
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void input_unbind_action_control(char const *action_name,
 | 
					bool input_action_pressed(char const *action_name) {
 | 
				
			||||||
                                 Control control)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    SDL_assert_always(action_name);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (CONTROL_SCANCODE_START <= control && control < CONTROL_SCANCODE_LIMIT)
 | 
					 | 
				
			||||||
        input_unbind_code_from_action(&ctx.input,
 | 
					 | 
				
			||||||
                                      action_name,
 | 
					 | 
				
			||||||
                                      BUTTON_SOURCE_KEYBOARD_PHYSICAL,
 | 
					 | 
				
			||||||
                                      (union ButtonCode) { .scancode = (SDL_Scancode)control });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    else if (CONTROL_MOUSECODE_START <= control && control < CONTROL_MOUSECODE_LIMIT) {
 | 
					 | 
				
			||||||
        uint8_t const mouse_button = (uint8_t)(control - CONTROL_MOUSECODE_START);
 | 
					 | 
				
			||||||
        input_unbind_code_from_action(&ctx.input,
 | 
					 | 
				
			||||||
                                      action_name,
 | 
					 | 
				
			||||||
                                      BUTTON_SOURCE_MOUSE,
 | 
					 | 
				
			||||||
                                      (union ButtonCode) { .mouse_button = (uint8_t)SDL_BUTTON(mouse_button)});
 | 
					 | 
				
			||||||
    } else
 | 
					 | 
				
			||||||
        log_warn("(%s) Invalid control value given: %i.", __func__, control);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void input_add_action(char const *action_name) {
 | 
					 | 
				
			||||||
    SDL_assert_always(action_name);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (shgeti(ctx.input.action_hash, action_name) >= 0) {
 | 
					 | 
				
			||||||
        log_warn("(%s) Action \"%s\" is already registered.", __func__, action_name);
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Action new_action = { 0 };
 | 
					 | 
				
			||||||
    new_action.bindings = ccalloc(ctx.keybind_slots, sizeof *new_action.bindings);
 | 
					 | 
				
			||||||
    shput(ctx.input.action_hash, action_name, new_action);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void input_delete_action(char const *action_name) {
 | 
					 | 
				
			||||||
    SDL_assert_always(action_name);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
 | 
					 | 
				
			||||||
    if (action == NULL) {
 | 
					 | 
				
			||||||
        log_warn("(%s) Action \"%s\" is not registered.", __func__, action_name);
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    SDL_free(action->value.bindings);
 | 
					 | 
				
			||||||
    shdel(ctx.input.action_hash, action_name);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool input_is_action_pressed(char const *action_name) {
 | 
					 | 
				
			||||||
    SDL_assert_always(action_name);
 | 
					    SDL_assert_always(action_name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
 | 
					    ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
 | 
				
			||||||
@@ -298,7 +298,7 @@ bool input_is_action_pressed(char const *action_name) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool input_is_action_just_pressed(char const *action_name) {
 | 
					bool input_action_just_pressed(char const *action_name) {
 | 
				
			||||||
    SDL_assert_always(action_name);
 | 
					    SDL_assert_always(action_name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
 | 
					    ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
 | 
				
			||||||
@@ -310,7 +310,7 @@ bool input_is_action_just_pressed(char const *action_name) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool input_is_action_just_released(char const *action_name) {
 | 
					bool input_action_just_released(char const *action_name) {
 | 
				
			||||||
    SDL_assert_always(action_name);
 | 
					    SDL_assert_always(action_name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
 | 
					    ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
 | 
				
			||||||
@@ -322,7 +322,7 @@ bool input_is_action_just_released(char const *action_name) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Vec2 input_get_action_position(char const *action_name) {
 | 
					Vec2 input_action_position(char const *action_name) {
 | 
				
			||||||
    SDL_assert_always(action_name);
 | 
					    SDL_assert_always(action_name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
 | 
					    ActionHashItem *action = shgetp_null(ctx.input.action_hash, action_name);
 | 
				
			||||||
@@ -335,17 +335,6 @@ Vec2 input_get_action_position(char const *action_name) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void input_set_mouse_captured(bool enabled) {
 | 
					 | 
				
			||||||
    if (SDL_SetRelativeMouseMode(enabled) != 0)
 | 
					 | 
				
			||||||
        log_warn("(%s) Mouse capture isn't supported.", __func__);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool input_is_mouse_captured(void) {
 | 
					 | 
				
			||||||
    return SDL_GetRelativeMouseMode();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void input_reset_state(InputState *input) {
 | 
					void input_reset_state(InputState *input) {
 | 
				
			||||||
    for (size_t i = 0; i < shlenu(input->action_hash); ++i) {
 | 
					    for (size_t i = 0; i < shlenu(input->action_hash); ++i) {
 | 
				
			||||||
        Action *action = &input->action_hash[i].value;
 | 
					        Action *action = &input->action_hash[i].value;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,12 @@
 | 
				
			|||||||
#ifndef TWN_INPUT_C_H
 | 
					#ifndef TWN_INPUT_C_H
 | 
				
			||||||
#define TWN_INPUT_C_H
 | 
					#define TWN_INPUT_C_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "twn_input.h"
 | 
					#include "twn_types.h"
 | 
				
			||||||
#include "twn_vec.h"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <SDL2/SDL.h>
 | 
					#include <SDL2/SDL.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <stdbool.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define KEYBIND_SLOTS_DEFAULT 3
 | 
					#define KEYBIND_SLOTS_DEFAULT 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -32,6 +33,7 @@ typedef enum ButtonSource {
 | 
				
			|||||||
typedef struct Button {
 | 
					typedef struct Button {
 | 
				
			||||||
    enum ButtonSource source;
 | 
					    enum ButtonSource source;
 | 
				
			||||||
    union ButtonCode code;
 | 
					    union ButtonCode code;
 | 
				
			||||||
 | 
					    bool in_use;
 | 
				
			||||||
} Button;
 | 
					} Button;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -59,8 +61,8 @@ typedef struct ActionHashItem {
 | 
				
			|||||||
typedef struct InputState {
 | 
					typedef struct InputState {
 | 
				
			||||||
    const uint8_t *keyboard_state; /* array of booleans indexed by scancode */
 | 
					    const uint8_t *keyboard_state; /* array of booleans indexed by scancode */
 | 
				
			||||||
    ActionHashItem *action_hash;
 | 
					    ActionHashItem *action_hash;
 | 
				
			||||||
    Vec2i mouse_window_position;
 | 
					    Vec2 mouse_window_position;
 | 
				
			||||||
    Vec2i mouse_relative_position;
 | 
					    Vec2 mouse_relative_position;
 | 
				
			||||||
    uint32_t mouse_state;          /* SDL mouse button bitmask */
 | 
					    uint32_t mouse_state;          /* SDL mouse button bitmask */
 | 
				
			||||||
    ButtonSource last_active_source;
 | 
					    ButtonSource last_active_source;
 | 
				
			||||||
    bool is_anything_just_pressed;
 | 
					    bool is_anything_just_pressed;
 | 
				
			||||||
@@ -74,6 +76,8 @@ void input_state_deinit(InputState *input);
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void input_state_update(InputState *input);
 | 
					void input_state_update(InputState *input);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void input_state_update_postframe(InputState *input);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void input_reset_state(InputState *input);
 | 
					void input_reset_state(InputState *input);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										409
									
								
								src/twn_loop.c
									
									
									
									
									
								
							
							
						
						
									
										409
									
								
								src/twn_loop.c
									
									
									
									
									
								
							@@ -4,21 +4,14 @@
 | 
				
			|||||||
#include "twn_util.h"
 | 
					#include "twn_util.h"
 | 
				
			||||||
#include "twn_util_c.h"
 | 
					#include "twn_util_c.h"
 | 
				
			||||||
#include "twn_game_object_c.h"
 | 
					#include "twn_game_object_c.h"
 | 
				
			||||||
#include "twn_audio_c.h"
 | 
					 | 
				
			||||||
#include "twn_textures_c.h"
 | 
					#include "twn_textures_c.h"
 | 
				
			||||||
 | 
					#include "system/twn_timer.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <SDL2/SDL.h>
 | 
					#include <SDL2/SDL.h>
 | 
				
			||||||
#include <physfs.h>
 | 
					#include <physfs.h>
 | 
				
			||||||
#include <stb_ds.h>
 | 
					#include <stb_ds.h>
 | 
				
			||||||
#include <toml.h>
 | 
					#include <toml.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* TODO: should not be used here directly */
 | 
					 | 
				
			||||||
#ifdef EMSCRIPTEN
 | 
					 | 
				
			||||||
#include <GLES2/gl2.h>
 | 
					 | 
				
			||||||
#else
 | 
					 | 
				
			||||||
#include <glad/glad.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <stdbool.h>
 | 
					#include <stdbool.h>
 | 
				
			||||||
#include <limits.h>
 | 
					#include <limits.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -27,6 +20,9 @@
 | 
				
			|||||||
#define PACKAGE_EXTENSION "btw"
 | 
					#define PACKAGE_EXTENSION "btw"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static SDL_Thread *opengl_load_thread;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int event_callback(void *userdata, SDL_Event *event) {
 | 
					static int event_callback(void *userdata, SDL_Event *event) {
 | 
				
			||||||
    (void)userdata;
 | 
					    (void)userdata;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -37,11 +33,21 @@ static int event_callback(void *userdata, SDL_Event *event) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        switch (event->window.event) {
 | 
					        switch (event->window.event) {
 | 
				
			||||||
        case SDL_WINDOWEVENT_SIZE_CHANGED:
 | 
					        case SDL_WINDOWEVENT_SIZE_CHANGED:
 | 
				
			||||||
            ctx.window_dims.x = event->window.data1;
 | 
					            ctx.window_dims.x = (float)event->window.data1;
 | 
				
			||||||
            ctx.window_dims.y = event->window.data2;
 | 
					            ctx.window_dims.y = (float)event->window.data2;
 | 
				
			||||||
            ctx.resync_flag = true;
 | 
					            ctx.resync_flag = true;
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case SDL_WINDOWEVENT_FOCUS_LOST: {
 | 
				
			||||||
 | 
					            ctx.window_mouse_resident = false;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case SDL_WINDOWEVENT_FOCUS_GAINED: {
 | 
				
			||||||
 | 
					            ctx.window_mouse_resident = true;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        default:
 | 
					        default:
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -90,33 +96,36 @@ static void poll_events(void) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifndef EMSCRIPTEN
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void APIENTRY opengl_log(GLenum source,
 | 
					 | 
				
			||||||
                                GLenum type,
 | 
					 | 
				
			||||||
                                GLuint id,
 | 
					 | 
				
			||||||
                                GLenum severity,
 | 
					 | 
				
			||||||
                                GLsizei length,
 | 
					 | 
				
			||||||
                                const GLchar* message,
 | 
					 | 
				
			||||||
                                const void* userParam)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    (void)source;
 | 
					 | 
				
			||||||
    (void)type;
 | 
					 | 
				
			||||||
    (void)id;
 | 
					 | 
				
			||||||
    (void)severity;
 | 
					 | 
				
			||||||
    (void)userParam;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    log_info("OpenGL: %.*s\n", length, message);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void preserve_persistent_ctx_fields(void) {
 | 
					static void preserve_persistent_ctx_fields(void) {
 | 
				
			||||||
    ctx.game.udata = ctx.game_copy.udata;
 | 
					    ctx.game.udata = ctx.game_copy.udata;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void update_viewport(void) {
 | 
				
			||||||
 | 
					    Rect new_viewport;
 | 
				
			||||||
 | 
					    float new_scale;
 | 
				
			||||||
 | 
					    if ((float)ctx.window_dims.x / (float)ctx.window_dims.y > (float)ctx.base_render_width / (float)ctx.base_render_height) {
 | 
				
			||||||
 | 
					        float ratio = (float)ctx.window_dims.y / (float)ctx.base_render_height;
 | 
				
			||||||
 | 
					        float w = ((float)ctx.base_render_width * ratio);
 | 
				
			||||||
 | 
					        new_viewport.x = ctx.window_dims.x / 2 - w / 2;
 | 
				
			||||||
 | 
					        new_viewport.y = 0;
 | 
				
			||||||
 | 
					        new_viewport.w = w;
 | 
				
			||||||
 | 
					        new_viewport.h = ctx.window_dims.y;
 | 
				
			||||||
 | 
					        new_scale = ratio;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        float ratio = (float)ctx.window_dims.x / (float)ctx.base_render_width;
 | 
				
			||||||
 | 
					        float h = ((float)ctx.base_render_height * ratio);
 | 
				
			||||||
 | 
					        new_viewport.x = 0;
 | 
				
			||||||
 | 
					        new_viewport.y = ctx.window_dims.y / 2 - h / 2;
 | 
				
			||||||
 | 
					        new_viewport.w = ctx.window_dims.x;
 | 
				
			||||||
 | 
					        new_viewport.h = h;
 | 
				
			||||||
 | 
					        new_scale = ratio;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    ctx.viewport_rect = new_viewport;
 | 
				
			||||||
 | 
					    ctx.viewport_scale = new_scale;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void main_loop(void) {
 | 
					static void main_loop(void) {
 | 
				
			||||||
    /*
 | 
					    /*
 | 
				
			||||||
    if (!ctx.is_running) {
 | 
					    if (!ctx.is_running) {
 | 
				
			||||||
@@ -198,26 +207,55 @@ static void main_loop(void) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /* finally, let's get to work */
 | 
					    /* finally, let's get to work */
 | 
				
			||||||
    int frames = 0;
 | 
					    int frames = 0;
 | 
				
			||||||
    while (ctx.frame_accumulator >= ctx.desired_frametime * ctx.update_multiplicity) {
 | 
					    while (ctx.frame_accumulator >= ctx.desired_frametime) {
 | 
				
			||||||
        frames += 1;
 | 
					        frames += 1;
 | 
				
			||||||
        for (size_t i = 0; i < ctx.update_multiplicity; ++i) {
 | 
					 | 
				
			||||||
        /* TODO: disable rendering pushes on not-last ? */
 | 
					        /* TODO: disable rendering pushes on not-last ? */
 | 
				
			||||||
        render_queue_clear();
 | 
					        render_queue_clear();
 | 
				
			||||||
        poll_events();
 | 
					        poll_events();
 | 
				
			||||||
 | 
					        if (ctx.window_size_has_changed)
 | 
				
			||||||
 | 
					            update_viewport();
 | 
				
			||||||
        input_state_update(&ctx.input);
 | 
					        input_state_update(&ctx.input);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        profile_start("game tick");
 | 
				
			||||||
 | 
					        start_sanity_timer(2000);
 | 
				
			||||||
        game_object_tick();
 | 
					        game_object_tick();
 | 
				
			||||||
 | 
					        end_sanity_timer();
 | 
				
			||||||
 | 
					        profile_end("game tick");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        input_state_update_postframe(&ctx.input);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /* TODO: make it works when ctx.ticks_per_second != 60 */
 | 
				
			||||||
 | 
					        #ifdef TWN_FEATURE_PUSH_AUDIO
 | 
				
			||||||
 | 
					        uint64_t const queued_frames = SDL_GetQueuedAudioSize(ctx.audio_device) / sizeof (float) / 2;
 | 
				
			||||||
 | 
					        if (queued_frames >= 4096 * 2)
 | 
				
			||||||
 | 
					            SDL_ClearQueuedAudio(ctx.audio_device);
 | 
				
			||||||
 | 
					        static uint8_t audio_buffer[(AUDIO_FREQUENCY / 60) * sizeof (float) * 2];
 | 
				
			||||||
 | 
					        if (ctx.audio_initialized) {
 | 
				
			||||||
 | 
					            audio_callback(NULL, audio_buffer, sizeof audio_buffer);
 | 
				
			||||||
 | 
					            if (SDL_QueueAudio(ctx.audio_device, audio_buffer, sizeof audio_buffer))
 | 
				
			||||||
 | 
					                CRY_SDL("Error queueing audio: ");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        #endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /* TODO: ctx.game_copy = ctx.game should be placed after it, but it messes with state used in render() */
 | 
				
			||||||
        preserve_persistent_ctx_fields();
 | 
					        preserve_persistent_ctx_fields();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ctx.frame_accumulator -= ctx.desired_frametime;
 | 
					        ctx.frame_accumulator -= ctx.desired_frametime;
 | 
				
			||||||
            ctx.game.frame_number = (ctx.game.frame_number % ULLONG_MAX) + 1;
 | 
					        ctx.game.frame_number++;
 | 
				
			||||||
 | 
					        /* TODO: should we ask for reinitialization in such case? */
 | 
				
			||||||
 | 
					        if (ctx.game.frame_number > 16777216)
 | 
				
			||||||
 | 
					            ctx.game.frame_number = 0;
 | 
				
			||||||
        ctx.game.initialization_needed = false;
 | 
					        ctx.game.initialization_needed = false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* TODO: in some cases machine might want to assume frames will be fed as much as possible */
 | 
					    /* TODO: in some cases machine might want to assume frames will be fed as much as possible */
 | 
				
			||||||
    /*       which for now is broken as glBufferData with NULL is used all over right after render */
 | 
					    /*       which for now is broken as glBufferData with NULL is used all over right after render */
 | 
				
			||||||
    if (frames != 0)
 | 
					    if (frames != 0)
 | 
				
			||||||
        render();
 | 
					        render();
 | 
				
			||||||
 | 
					    else if (ctx.desired_frametime - ctx.frame_accumulator > 1000000)
 | 
				
			||||||
 | 
					        /* don't waste clock cycles on useless work */
 | 
				
			||||||
 | 
					        /* TODO: make it adjustable from config */
 | 
				
			||||||
 | 
					        SDL_Delay((uint32_t)(ctx.desired_frametime - ctx.frame_accumulator) / 1250000);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -320,19 +358,25 @@ ERR_PACK_MANIFEST_PATH_ALLOC_FAIL:
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static bool initialize(void) {
 | 
					static int opengl_load_thread_fn(void *data) {
 | 
				
			||||||
    if (SDL_Init(SDL_INIT_EVERYTHING & ~SDL_INIT_HAPTIC) == -1) {
 | 
					    (void)data;
 | 
				
			||||||
        CRY_SDL("SDL initialization failed.");
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    SDL_GL_LoadLibrary(NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool initialize(void) {
 | 
				
			||||||
    /* first things first, most things here will be loaded from the config file */
 | 
					    /* first things first, most things here will be loaded from the config file */
 | 
				
			||||||
    /* it's expected to be present in the data directory, no matter what */
 | 
					    /* it's expected to be present in the data directory, no matter what */
 | 
				
			||||||
    /* that is why PhysicsFS is initialized before anything else */
 | 
					    /* that is why PhysicsFS is initialized before anything else */
 | 
				
			||||||
    toml_set_memutil(SDL_malloc, SDL_free);
 | 
					    toml_set_memutil(SDL_malloc, SDL_free);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    profile_start("pack dependency resolution");
 | 
				
			||||||
    /* time to orderly resolve any dependencies present */
 | 
					    /* time to orderly resolve any dependencies present */
 | 
				
			||||||
    resolve_pack_dependencies("data");
 | 
					    resolve_pack_dependencies("data");
 | 
				
			||||||
 | 
					    profile_end("pack dependency resolution");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* load the config file into an opaque table */
 | 
					    /* load the config file into an opaque table */
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@@ -396,11 +440,7 @@ static bool initialize(void) {
 | 
				
			|||||||
    /* debug mode defaults to being enabled */
 | 
					    /* debug mode defaults to being enabled */
 | 
				
			||||||
    /* pass --debug or --release to force a mode, ignoring configuration */
 | 
					    /* pass --debug or --release to force a mode, ignoring configuration */
 | 
				
			||||||
    toml_datum_t datum_debug = toml_bool_in(game, "debug");
 | 
					    toml_datum_t datum_debug = toml_bool_in(game, "debug");
 | 
				
			||||||
    if (!datum_debug.ok) {
 | 
					    ctx.game.debug = datum_debug.ok ? datum_debug.u.b : true;
 | 
				
			||||||
        ctx.game.debug = true;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        ctx.game.debug = datum_debug.u.b;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef EMSCRIPTEN
 | 
					#ifdef EMSCRIPTEN
 | 
				
			||||||
    /* emscripten interpretes those as GL ES version against WebGL */
 | 
					    /* emscripten interpretes those as GL ES version against WebGL */
 | 
				
			||||||
@@ -424,13 +464,12 @@ static bool initialize(void) {
 | 
				
			|||||||
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
 | 
					    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
 | 
				
			||||||
    SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
 | 
					    SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* init got far enough to create a window */
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
    toml_datum_t datum_title = toml_string_in(about, "title");
 | 
					    toml_datum_t datum_title = toml_string_in(about, "title");
 | 
				
			||||||
        if (!datum_title.ok) {
 | 
					    if (!datum_title.ok)
 | 
				
			||||||
            CRY("Initialization failed", "Valid about.title expected in configuration file");
 | 
					        datum_title.u.s = SDL_strdup("townengine project");
 | 
				
			||||||
            goto fail;
 | 
					
 | 
				
			||||||
        }
 | 
					    ctx.title = datum_title.u.s;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* not yet used
 | 
					    /* not yet used
 | 
				
			||||||
    toml_datum_t datum_developer = toml_string_in(about, "developer");
 | 
					    toml_datum_t datum_developer = toml_string_in(about, "developer");
 | 
				
			||||||
    if (!datum_developer.ok) {
 | 
					    if (!datum_developer.ok) {
 | 
				
			||||||
@@ -438,121 +477,83 @@ static bool initialize(void) {
 | 
				
			|||||||
        goto fail;
 | 
					        goto fail;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    */
 | 
					    */
 | 
				
			||||||
        toml_datum_t datum_base_render_width = toml_int_in(game, "base_render_width");
 | 
					
 | 
				
			||||||
 | 
					    toml_array_t *datum_resolution = toml_array_in(game, "resolution");
 | 
				
			||||||
 | 
					    if (datum_resolution) {
 | 
				
			||||||
 | 
					        toml_datum_t datum_base_render_width = toml_int_at(datum_resolution, 0);
 | 
				
			||||||
        if (!datum_base_render_width.ok) {
 | 
					        if (!datum_base_render_width.ok) {
 | 
				
			||||||
            CRY("Initialization failed", "Valid game.base_render_width expected in configuration file");
 | 
					            CRY("Initialization failed", "Valid game.resolution expected in configuration file");
 | 
				
			||||||
            goto fail;
 | 
					            goto fail;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        ctx.base_render_width = datum_base_render_width.u.i;
 | 
					 | 
				
			||||||
        ctx.game.resolution.x = (int)ctx.base_render_width;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        toml_datum_t datum_base_render_height = toml_int_in(game, "base_render_height");
 | 
					        toml_datum_t datum_base_render_height = toml_int_at(datum_resolution, 1);
 | 
				
			||||||
        if (!datum_base_render_height.ok) {
 | 
					        if (!datum_base_render_height.ok) {
 | 
				
			||||||
            CRY("Initialization failed", "Valid game.base_render_height expected in configuration file");
 | 
					            CRY("Initialization failed", "Valid game.resolution expected in configuration file");
 | 
				
			||||||
            goto fail;
 | 
					            goto fail;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ctx.base_render_width = datum_base_render_width.u.i;
 | 
				
			||||||
        ctx.base_render_height = datum_base_render_height.u.i;
 | 
					        ctx.base_render_height = datum_base_render_height.u.i;
 | 
				
			||||||
        ctx.game.resolution.y = (int)ctx.base_render_height;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ctx.window = SDL_CreateWindow(datum_title.u.s,
 | 
					    } else {
 | 
				
			||||||
                                      SDL_WINDOWPOS_CENTERED,
 | 
					        ctx.base_render_width = 640;
 | 
				
			||||||
                                      SDL_WINDOWPOS_CENTERED,
 | 
					        ctx.base_render_height = 360;
 | 
				
			||||||
                                      (int)datum_base_render_width.u.i,
 | 
					    }
 | 
				
			||||||
                                      (int)datum_base_render_height.u.i,
 | 
					
 | 
				
			||||||
                                      //SDL_WINDOW_ALLOW_HIGHDPI |
 | 
					    ctx.game.resolution.x = (float)ctx.base_render_width;
 | 
				
			||||||
                                          SDL_WINDOW_RESIZABLE |
 | 
					    ctx.game.resolution.y = (float)ctx.base_render_height;
 | 
				
			||||||
                                          SDL_WINDOW_OPENGL);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        SDL_free(datum_title.u.s);
 | 
					 | 
				
			||||||
    //SDL_free(datum_developer.u.s);
 | 
					    //SDL_free(datum_developer.u.s);
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (ctx.window == NULL) {
 | 
					 | 
				
			||||||
        CRY_SDL("Window creation failed.");
 | 
					 | 
				
			||||||
        goto fail;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ctx.gl_context = SDL_GL_CreateContext(ctx.window);
 | 
					 | 
				
			||||||
    if (!ctx.gl_context) {
 | 
					 | 
				
			||||||
        CRY_SDL("GL context creation failed.");
 | 
					 | 
				
			||||||
        goto fail;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (SDL_GL_MakeCurrent(ctx.window, ctx.gl_context)) {
 | 
					 | 
				
			||||||
        CRY_SDL("GL context binding failed.");
 | 
					 | 
				
			||||||
        goto fail;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (SDL_GL_SetSwapInterval(-1))
 | 
					 | 
				
			||||||
        SDL_GL_SetSwapInterval(1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#ifndef EMSCRIPTEN
 | 
					 | 
				
			||||||
    if (gladLoadGL() == 0) {
 | 
					 | 
				
			||||||
        CRY("Init", "GLAD failed");
 | 
					 | 
				
			||||||
        goto fail;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    log_info("OpenGL context: %s\n", glGetString(GL_VERSION));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#ifndef EMSCRIPTEN
 | 
					 | 
				
			||||||
    glHint(GL_TEXTURE_COMPRESSION_HINT, GL_NICEST);
 | 
					 | 
				
			||||||
    glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
 | 
					 | 
				
			||||||
    glHint(GL_FOG_HINT, GL_FASTEST);
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /* might need this to have multiple windows */
 | 
					 | 
				
			||||||
    ctx.window_id = SDL_GetWindowID(ctx.window);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    glViewport(0, 0, (GLsizei)ctx.base_render_width, (GLsizei)ctx.base_render_height);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* TODO: */
 | 
					    /* TODO: */
 | 
				
			||||||
    // SDL_GetRendererOutputSize(ctx.renderer, &ctx.window_w, &ctx.window_h);
 | 
					    // SDL_GetRendererOutputSize(ctx.renderer, &ctx.window_w, &ctx.window_h);
 | 
				
			||||||
    ctx.window_dims.x = (int)ctx.base_render_width;
 | 
					    ctx.window_dims.x = (float)ctx.base_render_width;
 | 
				
			||||||
    ctx.window_dims.y = (int)ctx.base_render_height;
 | 
					    ctx.window_dims.y = (float)ctx.base_render_height;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ctx.background_color = (Color){ 230, 230, 230, 255 };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    toml_array_t *datum_background_color = toml_array_in(game, "background_color");
 | 
				
			||||||
 | 
					    if (datum_background_color) do {
 | 
				
			||||||
 | 
					        toml_datum_t datum_background_color_red = toml_int_at(datum_background_color, 0);
 | 
				
			||||||
 | 
					        if (!datum_background_color_red.ok || datum_background_color_red.u.i < 0 || datum_background_color_red.u.i >= 256) {
 | 
				
			||||||
 | 
					            log_warn("Invalid value for red channel of game.background_color");
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        toml_datum_t datum_background_color_green = toml_int_at(datum_background_color, 1);
 | 
				
			||||||
 | 
					        if (!datum_background_color_green.ok || datum_background_color_green.u.i < 0 || datum_background_color_green.u.i >= 256) {
 | 
				
			||||||
 | 
					            log_warn("Invalid value for green channel of game.background_color");
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        toml_datum_t datum_background_color_blue = toml_int_at(datum_background_color, 2);
 | 
				
			||||||
 | 
					        if (!datum_background_color_blue.ok || datum_background_color_blue.u.i < 0 || datum_background_color_blue.u.i >= 256) {
 | 
				
			||||||
 | 
					            log_warn("Invalid value for blue channel of game.background_color");
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        toml_datum_t datum_background_color_alpha = toml_int_at(datum_background_color, 3);
 | 
				
			||||||
 | 
					        if (!datum_background_color_alpha.ok || datum_background_color_alpha.u.i < 0 || datum_background_color_alpha.u.i >= 256) {
 | 
				
			||||||
 | 
					            log_warn("Invalid value for alpha channel of game.background_color");
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        ctx.background_color = (Color){
 | 
				
			||||||
 | 
					            (uint8_t)datum_background_color_red.u.i,
 | 
				
			||||||
 | 
					            (uint8_t)datum_background_color_green.u.i,
 | 
				
			||||||
 | 
					            (uint8_t)datum_background_color_blue.u.i,
 | 
				
			||||||
 | 
					            (uint8_t)datum_background_color_alpha.u.i,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    } while (0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ctx.game.resolution.x = (float)ctx.base_render_width;
 | 
				
			||||||
 | 
					    ctx.game.resolution.y = (float)ctx.base_render_height;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* add a watcher for immediate updates on window size */
 | 
					    /* add a watcher for immediate updates on window size */
 | 
				
			||||||
    SDL_AddEventWatch(event_callback, NULL);
 | 
					    SDL_AddEventWatch(event_callback, NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* audio initialization */
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    SDL_AudioSpec request, got;
 | 
					 | 
				
			||||||
    SDL_zero(request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    request.freq = AUDIO_FREQUENCY;
 | 
					 | 
				
			||||||
    request.format = AUDIO_F32;
 | 
					 | 
				
			||||||
    request.channels = 2;
 | 
					 | 
				
			||||||
    request.callback = audio_callback;
 | 
					 | 
				
			||||||
    /* TODO: check for errors */
 | 
					 | 
				
			||||||
    ctx.audio_device = SDL_OpenAudioDevice(NULL, 0, &request, &got, 0);
 | 
					 | 
				
			||||||
    ctx.audio_stream_format = got.format;
 | 
					 | 
				
			||||||
    ctx.audio_stream_frequency = got.freq;
 | 
					 | 
				
			||||||
    ctx.audio_stream_channel_count = got.channels;
 | 
					 | 
				
			||||||
    SDL_assert_always(got.format == AUDIO_F32);
 | 
					 | 
				
			||||||
    SDL_assert_always(got.channels == 2);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    SDL_PauseAudioDevice(ctx.audio_device, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /* you could change this at runtime if you wanted */
 | 
					 | 
				
			||||||
    ctx.update_multiplicity = 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#ifndef EMSCRIPTEN
 | 
					 | 
				
			||||||
    /* hook up opengl debugging callback */
 | 
					 | 
				
			||||||
    if (ctx.game.debug) {
 | 
					 | 
				
			||||||
        glEnable(GL_DEBUG_OUTPUT);
 | 
					 | 
				
			||||||
        glDebugMessageCallback(opengl_log, NULL);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /* random seeding */
 | 
					    /* random seeding */
 | 
				
			||||||
    /* SDL_GetPerformanceCounter returns some platform-dependent number. */
 | 
					    /* SDL_GetPerformanceCounter returns some platform-dependent number. */
 | 
				
			||||||
    /* it should vary between game instances. i checked! random enough for me. */
 | 
					    /* it should vary between game instances. i checked! random enough for me. */
 | 
				
			||||||
    ctx.game.random_seed = SDL_GetPerformanceCounter();
 | 
					    ctx.game.random_seed = (float)(SDL_GetPerformanceCounter() % 16777216);
 | 
				
			||||||
    srand((unsigned int)ctx.game.random_seed);
 | 
					    srand((unsigned int)(SDL_GetPerformanceCounter()));
 | 
				
			||||||
    stbds_rand_seed(ctx.game.random_seed);
 | 
					    stbds_rand_seed((size_t)(SDL_GetPerformanceCounter()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* main loop machinery */
 | 
					    /* main loop machinery */
 | 
				
			||||||
    toml_datum_t datum_ticks_per_second = toml_int_in(engine, "ticks_per_second");
 | 
					    toml_datum_t datum_ticks_per_second = toml_int_in(engine, "ticks_per_second");
 | 
				
			||||||
@@ -573,6 +574,7 @@ static bool initialize(void) {
 | 
				
			|||||||
    ctx.desired_frametime = ctx.clocks_per_second / ctx.ticks_per_second;
 | 
					    ctx.desired_frametime = ctx.clocks_per_second / ctx.ticks_per_second;
 | 
				
			||||||
    ctx.frame_accumulator = 0;
 | 
					    ctx.frame_accumulator = 0;
 | 
				
			||||||
    ctx.game.frame_number = 0;
 | 
					    ctx.game.frame_number = 0;
 | 
				
			||||||
 | 
					    ctx.game.frame_duration = 1.0f / (float)ctx.ticks_per_second;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* delta time averaging */
 | 
					    /* delta time averaging */
 | 
				
			||||||
    ctx.delta_averager_residual = 0;
 | 
					    ctx.delta_averager_residual = 0;
 | 
				
			||||||
@@ -620,24 +622,28 @@ static bool initialize(void) {
 | 
				
			|||||||
        if (!datum_font_filtering.ok) {
 | 
					        if (!datum_font_filtering.ok) {
 | 
				
			||||||
            ctx.font_filtering = TEXT_FONT_FILTERING_DEFAULT;
 | 
					            ctx.font_filtering = TEXT_FONT_FILTERING_DEFAULT;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            if (SDL_strcmp(datum_font_filtering.u.s, "nearest") == 0) {
 | 
					            if (SDL_strncmp(datum_font_filtering.u.s, "nearest", sizeof "nearest" - 1) == 0) {
 | 
				
			||||||
                ctx.font_filtering = TEXTURE_FILTER_NEAREAST;
 | 
					                ctx.font_filtering = TEXTURE_FILTER_NEAREAST;
 | 
				
			||||||
            } else if (SDL_strcmp(datum_font_filtering.u.s, "linear") == 0) {
 | 
					            } else if (SDL_strncmp(datum_font_filtering.u.s, "linear", sizeof "linear" - 1) == 0) {
 | 
				
			||||||
                ctx.font_filtering = TEXTURE_FILTER_LINEAR;
 | 
					                ctx.font_filtering = TEXTURE_FILTER_LINEAR;
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                ctx.font_filtering = TEXT_FONT_FILTERING_DEFAULT;
 | 
					                ctx.font_filtering = TEXT_FONT_FILTERING_DEFAULT;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        SDL_free(datum_font_filtering.u.s);
 | 
					        SDL_free(datum_font_filtering.u.s);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        toml_datum_t datum_cull_faces = toml_bool_in(engine, "cull_faces");
 | 
				
			||||||
 | 
					        if (!datum_cull_faces.ok) {
 | 
				
			||||||
 | 
					            ctx.cull_faces = true;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            ctx.cull_faces = datum_cull_faces.u.b;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* these are dynamic arrays and will be allocated lazily by stb_ds */
 | 
					    /* these are dynamic arrays and will be allocated lazily by stb_ds */
 | 
				
			||||||
    ctx.render_queue_2d = NULL;
 | 
					    ctx.render_queue_2d = NULL;
 | 
				
			||||||
    ctx.uncolored_mesh_batches = NULL;
 | 
					    ctx.uncolored_mesh_batches = NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    textures_cache_init(&ctx.texture_cache, ctx.window);
 | 
					 | 
				
			||||||
    text_cache_init(&ctx.text_cache);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /* input */
 | 
					    /* input */
 | 
				
			||||||
    toml_datum_t datum_keybind_slots = toml_int_in(engine, "keybind_slots");
 | 
					    toml_datum_t datum_keybind_slots = toml_int_in(engine, "keybind_slots");
 | 
				
			||||||
    if (!datum_keybind_slots.ok) {
 | 
					    if (!datum_keybind_slots.ok) {
 | 
				
			||||||
@@ -651,14 +657,67 @@ static bool initialize(void) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    input_state_init(&ctx.input);
 | 
					    input_state_init(&ctx.input);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* scripting */
 | 
					    ctx.render_double_buffered = true;
 | 
				
			||||||
    /*
 | 
					    ctx.window_mouse_resident = true;
 | 
				
			||||||
    if (!scripting_init(ctx)) {
 | 
					
 | 
				
			||||||
 | 
					    ctx.game.fog_color = (Color){ 255, 255, 255, 255 }; /* TODO: pick some grey? */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    update_viewport();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    profile_start("game object load");
 | 
				
			||||||
 | 
					    /* now we can actually start doing stuff */
 | 
				
			||||||
 | 
					    game_object_load();
 | 
				
			||||||
 | 
					    profile_end("game object load");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* delayed as further as possible so that more work is done before we have to wait */
 | 
				
			||||||
 | 
					    SDL_WaitThread(opengl_load_thread, NULL);
 | 
				
			||||||
 | 
					    profile_end("opengl loading");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* TODO: investigate viability of detached thread driver and window creation, as it's the worst load time offender */
 | 
				
			||||||
 | 
					    profile_start("window creation");
 | 
				
			||||||
 | 
					    ctx.window = SDL_CreateWindow(ctx.title,
 | 
				
			||||||
 | 
					                                  SDL_WINDOWPOS_CENTERED,
 | 
				
			||||||
 | 
					                                  SDL_WINDOWPOS_CENTERED,
 | 
				
			||||||
 | 
					                                  (int)ctx.base_render_width,
 | 
				
			||||||
 | 
					                                  (int)ctx.base_render_height,
 | 
				
			||||||
 | 
					                                  //SDL_WINDOW_ALLOW_HIGHDPI |
 | 
				
			||||||
 | 
					                                      SDL_WINDOW_RESIZABLE |
 | 
				
			||||||
 | 
					                                      SDL_WINDOW_OPENGL);
 | 
				
			||||||
 | 
					    profile_end("window creation");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (ctx.window == NULL) {
 | 
				
			||||||
 | 
					        CRY_SDL("Window creation failed.");
 | 
				
			||||||
        goto fail;
 | 
					        goto fail;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    */
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ctx.render_double_buffered = true;
 | 
					    profile_start("opengl context creation");
 | 
				
			||||||
 | 
					    ctx.gl_context = SDL_GL_CreateContext(ctx.window);
 | 
				
			||||||
 | 
					    if (!ctx.gl_context) {
 | 
				
			||||||
 | 
					        CRY_SDL("GL context creation failed.");
 | 
				
			||||||
 | 
					        goto fail;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (SDL_GL_MakeCurrent(ctx.window, ctx.gl_context)) {
 | 
				
			||||||
 | 
					        CRY_SDL("GL context binding failed.");
 | 
				
			||||||
 | 
					        goto fail;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* TODO: figure out what we ultimately prefer */
 | 
				
			||||||
 | 
					    SDL_GL_SetSwapInterval(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!render_init())
 | 
				
			||||||
 | 
					        goto fail;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setup_viewport(0, 0, (int)ctx.base_render_width, (int)ctx.base_render_height);
 | 
				
			||||||
 | 
					    profile_end("opengl context creation");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* might need this to have multiple windows */
 | 
				
			||||||
 | 
					    ctx.window_id = SDL_GetWindowID(ctx.window);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    profile_start("texture and text cache initialization");
 | 
				
			||||||
 | 
					    textures_cache_init(&ctx.texture_cache, ctx.window);
 | 
				
			||||||
 | 
					    text_cache_init(&ctx.text_cache);
 | 
				
			||||||
 | 
					    profile_end("texture and text cache initialization");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return true;
 | 
					    return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -670,10 +729,6 @@ fail:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/* will not be called on an abnormal exit */
 | 
					/* will not be called on an abnormal exit */
 | 
				
			||||||
static void clean_up(void) {
 | 
					static void clean_up(void) {
 | 
				
			||||||
    /*
 | 
					 | 
				
			||||||
    scripting_deinit(ctx);
 | 
					 | 
				
			||||||
    */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    input_state_deinit(&ctx.input);
 | 
					    input_state_deinit(&ctx.input);
 | 
				
			||||||
    text_cache_deinit(&ctx.text_cache);
 | 
					    text_cache_deinit(&ctx.text_cache);
 | 
				
			||||||
    textures_cache_deinit(&ctx.texture_cache);
 | 
					    textures_cache_deinit(&ctx.texture_cache);
 | 
				
			||||||
@@ -686,7 +741,9 @@ static void clean_up(void) {
 | 
				
			|||||||
    PHYSFS_deinit();
 | 
					    PHYSFS_deinit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    SDL_free(ctx.base_dir);
 | 
					    SDL_free(ctx.base_dir);
 | 
				
			||||||
 | 
					    SDL_free(ctx.title);
 | 
				
			||||||
    SDL_GL_DeleteContext(ctx.gl_context);
 | 
					    SDL_GL_DeleteContext(ctx.gl_context);
 | 
				
			||||||
 | 
					    SDL_GL_UnloadLibrary();
 | 
				
			||||||
    SDL_Quit();
 | 
					    SDL_Quit();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -698,6 +755,22 @@ static void reset_state(void) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int enter_loop(int argc, char **argv) {
 | 
					int enter_loop(int argc, char **argv) {
 | 
				
			||||||
 | 
					    profile_start("startup");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    profile_start("SDL initialization");
 | 
				
			||||||
 | 
					    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_EVENTS) == -1) {
 | 
				
			||||||
 | 
					        CRY_SDL("SDL initialization failed.");
 | 
				
			||||||
 | 
					        return EXIT_FAILURE;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    profile_end("SDL initialization");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    profile_start("opengl loading");
 | 
				
			||||||
 | 
					    opengl_load_thread = SDL_CreateThread(opengl_load_thread_fn, "opengl loader", NULL);
 | 
				
			||||||
 | 
					    if (!opengl_load_thread) {
 | 
				
			||||||
 | 
					        CRY_SDL("Cannot create opengl loading thread: ");
 | 
				
			||||||
 | 
					        return EXIT_FAILURE;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ctx.argc = argc;
 | 
					    ctx.argc = argc;
 | 
				
			||||||
    ctx.argv = argv;
 | 
					    ctx.argv = argv;
 | 
				
			||||||
    ctx.base_dir = SDL_GetBasePath();
 | 
					    ctx.base_dir = SDL_GetBasePath();
 | 
				
			||||||
@@ -716,7 +789,7 @@ int enter_loop(int argc, char **argv) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    for (int i = 1; i < argc; ++i) {
 | 
					    for (int i = 1; i < argc; ++i) {
 | 
				
			||||||
        /* override data directory */
 | 
					        /* override data directory */
 | 
				
			||||||
        if (SDL_strcmp(argv[i], "--data-dir") == 0) {
 | 
					        if (SDL_strncmp(argv[i], "--data-dir", sizeof "--data-dir" - 1) == 0) {
 | 
				
			||||||
            if (argv[i+1] == NULL || SDL_strncmp(argv[i+1], "--", 2) == 0) {
 | 
					            if (argv[i+1] == NULL || SDL_strncmp(argv[i+1], "--", 2) == 0) {
 | 
				
			||||||
                CRY("Data dir mount override failed.", "No arguments passed (expected 1).");
 | 
					                CRY("Data dir mount override failed.", "No arguments passed (expected 1).");
 | 
				
			||||||
                return EXIT_FAILURE;
 | 
					                return EXIT_FAILURE;
 | 
				
			||||||
@@ -733,14 +806,14 @@ int enter_loop(int argc, char **argv) {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /* force debug mode */
 | 
					        /* force debug mode */
 | 
				
			||||||
        if (SDL_strcmp(argv[i], "--debug") == 0) {
 | 
					        if (SDL_strncmp(argv[i], "--debug", sizeof "--debug" - 1) == 0) {
 | 
				
			||||||
            force_debug = true;
 | 
					            force_debug = true;
 | 
				
			||||||
            continue;
 | 
					            continue;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /* force release mode */
 | 
					        /* force release mode */
 | 
				
			||||||
        if (SDL_strcmp(argv[i], "--release") == 0) {
 | 
					        if (SDL_strncmp(argv[i], "--release", sizeof "--release" - 1) == 0) {
 | 
				
			||||||
            force_release = false;
 | 
					            force_release = true;
 | 
				
			||||||
            continue;
 | 
					            continue;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -773,12 +846,11 @@ int enter_loop(int argc, char **argv) {
 | 
				
			|||||||
        ctx.game.debug = false;
 | 
					        ctx.game.debug = false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* now we can actually start doing stuff */
 | 
					 | 
				
			||||||
    game_object_load();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ctx.was_successful = true;
 | 
					    ctx.was_successful = true;
 | 
				
			||||||
    ctx.game.initialization_needed = true;
 | 
					    ctx.game.initialization_needed = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    profile_end("startup");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    while (ctx.is_running) {
 | 
					    while (ctx.is_running) {
 | 
				
			||||||
        if (game_object_try_reloading()) {
 | 
					        if (game_object_try_reloading()) {
 | 
				
			||||||
            ctx.game.initialization_needed = true;
 | 
					            ctx.game.initialization_needed = true;
 | 
				
			||||||
@@ -788,6 +860,9 @@ int enter_loop(int argc, char **argv) {
 | 
				
			|||||||
        main_loop();
 | 
					        main_loop();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (ctx.game.debug)
 | 
				
			||||||
 | 
					        profile_list_stats();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* loop is over */
 | 
					    /* loop is over */
 | 
				
			||||||
    game_object_unload();
 | 
					    game_object_unload();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,8 @@
 | 
				
			|||||||
#include "twn_loop.h"
 | 
					#include "twn_loop.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef EMSCRIPTEN
 | 
				
			||||||
#define SDL_MAIN_HANDLED
 | 
					#define SDL_MAIN_HANDLED
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
#include <SDL2/SDL.h>
 | 
					#include <SDL2/SDL.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -40,6 +40,44 @@ static int load_eof_callback(void *user) {
 | 
				
			|||||||
    return context->position == context->size;
 | 
					    return context->position == context->size;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static SDL_Surface *missing_texture_surface;
 | 
				
			||||||
 | 
					static uint16_t missing_texture_id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static SDL_Surface *gen_missing_texture_surface(void) {
 | 
				
			||||||
 | 
					    Uint32 rmask, gmask, bmask;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #if SDL_BYTEORDER == SDL_BIG_ENDIAN
 | 
				
			||||||
 | 
					        rmask = 0xff000000;
 | 
				
			||||||
 | 
					        gmask = 0x00ff0000;
 | 
				
			||||||
 | 
					        bmask = 0x0000ff00;
 | 
				
			||||||
 | 
					    #else
 | 
				
			||||||
 | 
					        rmask = 0x000000ff;
 | 
				
			||||||
 | 
					        gmask = 0x0000ff00;
 | 
				
			||||||
 | 
					        bmask = 0x00ff0000;
 | 
				
			||||||
 | 
					    #endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!missing_texture_surface) {
 | 
				
			||||||
 | 
					        uint8_t *data = SDL_malloc(64 * 64 * 3);
 | 
				
			||||||
 | 
					        for (int y = 0; y < 64; ++y) {
 | 
				
			||||||
 | 
					            for (int x = 0; x < 64; ++x) {
 | 
				
			||||||
 | 
					                /* diagonal stripes, chosen so that asked pixel uvs are corresponding to final output */
 | 
				
			||||||
 | 
					                data[(y * 64 + x) * 3 + 0] = (x / 2 + y / 2) % 2 == 0 ? 175 : 0;
 | 
				
			||||||
 | 
					                data[(y * 64 + x) * 3 + 1] = (x / 2 + y / 2) % 2 == 0 ? 0 : 0;
 | 
				
			||||||
 | 
					                data[(y * 64 + x) * 3 + 2] = (x / 2 + y / 2) % 2 == 0 ? 175 : 0;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        missing_texture_surface = SDL_CreateRGBSurfaceFrom(data, 64, 64,
 | 
				
			||||||
 | 
					                                                           3 * 8,
 | 
				
			||||||
 | 
					                                                           64 * 3,
 | 
				
			||||||
 | 
					                                                           rmask, gmask, bmask, 0);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return missing_texture_surface;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
SDL_Surface *textures_load_surface(const char *path) {
 | 
					SDL_Surface *textures_load_surface(const char *path) {
 | 
				
			||||||
    SDL_RWops *handle = PHYSFSRWOPS_openRead(path);
 | 
					    SDL_RWops *handle = PHYSFSRWOPS_openRead(path);
 | 
				
			||||||
    if (handle == NULL)
 | 
					    if (handle == NULL)
 | 
				
			||||||
@@ -93,8 +131,9 @@ ERR_CANNOT_CREATE_SURFACE:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
ERR_CANNOT_READ_IMAGE:
 | 
					ERR_CANNOT_READ_IMAGE:
 | 
				
			||||||
ERR_CANNOT_OPEN_FILE:
 | 
					ERR_CANNOT_OPEN_FILE:
 | 
				
			||||||
    CRY(path, "Failed to load image. Aborting...");
 | 
					    /* something didn't worked out, use a stub texture */
 | 
				
			||||||
    die_abruptly();
 | 
					    log_warn("Cannot open image: %s, using a stub", path);
 | 
				
			||||||
 | 
					    return gen_missing_texture_surface();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -147,6 +186,8 @@ static void upload_texture_from_surface(GPUTexture texture, SDL_Surface *surface
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void recreate_current_atlas_texture(TextureCache *cache) {
 | 
					static void recreate_current_atlas_texture(TextureCache *cache) {
 | 
				
			||||||
 | 
					    profile_start("atlas recreation");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* TODO: should surfaces be freed after they cannot be referenced in atlas builing? */
 | 
					    /* TODO: should surfaces be freed after they cannot be referenced in atlas builing? */
 | 
				
			||||||
    /*       for example, if full page of 64x64 tiles was already filled, there's no real reason to process them further */
 | 
					    /*       for example, if full page of 64x64 tiles was already filled, there's no real reason to process them further */
 | 
				
			||||||
    SDL_Surface *atlas_surface = cache->atlas_surfaces[cache->atlas_index];
 | 
					    SDL_Surface *atlas_surface = cache->atlas_surfaces[cache->atlas_index];
 | 
				
			||||||
@@ -177,6 +218,8 @@ static void recreate_current_atlas_texture(TextureCache *cache) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /* texturize it! */
 | 
					    /* texturize it! */
 | 
				
			||||||
    upload_texture_from_surface(cache->atlas_textures[cache->atlas_index], atlas_surface);
 | 
					    upload_texture_from_surface(cache->atlas_textures[cache->atlas_index], atlas_surface);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    profile_end("atlas recreation");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -268,10 +311,9 @@ void textures_cache_init(TextureCache *cache, SDL_Window *window) {
 | 
				
			|||||||
    cache->window = window;
 | 
					    cache->window = window;
 | 
				
			||||||
    sh_new_arena(cache->hash);
 | 
					    sh_new_arena(cache->hash);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    cache->node_buffer = SDL_calloc(ctx.texture_atlas_size, sizeof *cache->node_buffer);
 | 
					    cache->node_buffer = SDL_malloc(ctx.texture_atlas_size * sizeof *cache->node_buffer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    add_new_atlas(cache);
 | 
					    add_new_atlas(cache);
 | 
				
			||||||
    recreate_current_atlas_texture(cache);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -290,7 +332,10 @@ void textures_cache_deinit(TextureCache *cache) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /* free cache hashes */
 | 
					    /* free cache hashes */
 | 
				
			||||||
    for (size_t i = 0; i < shlenu(cache->hash); ++i) {
 | 
					    for (size_t i = 0; i < shlenu(cache->hash); ++i) {
 | 
				
			||||||
 | 
					        if (missing_texture_surface && cache->hash[i].value.data->pixels != missing_texture_surface->pixels)
 | 
				
			||||||
            stbi_image_free(cache->hash[i].value.data->pixels);
 | 
					            stbi_image_free(cache->hash[i].value.data->pixels);
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					            SDL_free(cache->hash[i].value.data->pixels);
 | 
				
			||||||
        SDL_FreeSurface(cache->hash[i].value.data);
 | 
					        SDL_FreeSurface(cache->hash[i].value.data);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    shfree(cache->hash);
 | 
					    shfree(cache->hash);
 | 
				
			||||||
@@ -334,6 +379,9 @@ static TextureKey textures_load(TextureCache *cache, const char *path) {
 | 
				
			|||||||
        return (TextureKey){ (uint16_t)i };
 | 
					        return (TextureKey){ (uint16_t)i };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    SDL_Surface *surface = textures_load_surface(path);
 | 
					    SDL_Surface *surface = textures_load_surface(path);
 | 
				
			||||||
 | 
					    if (surface == missing_texture_surface && missing_texture_id != 0)
 | 
				
			||||||
 | 
					        return (TextureKey){ missing_texture_id };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Texture new_texture = {
 | 
					    Texture new_texture = {
 | 
				
			||||||
        .data = surface,
 | 
					        .data = surface,
 | 
				
			||||||
        .mode = infer_texture_mode(surface),
 | 
					        .mode = infer_texture_mode(surface),
 | 
				
			||||||
@@ -350,13 +398,22 @@ static TextureKey textures_load(TextureCache *cache, const char *path) {
 | 
				
			|||||||
        new_texture.loner_texture = create_gpu_texture(TEXTURE_FILTER_NEAREAST, true);
 | 
					        new_texture.loner_texture = create_gpu_texture(TEXTURE_FILTER_NEAREAST, true);
 | 
				
			||||||
        upload_texture_from_surface(new_texture.loner_texture, surface); 
 | 
					        upload_texture_from_surface(new_texture.loner_texture, surface); 
 | 
				
			||||||
        new_texture.srcrect = (Rect) { .w = (float)surface->w, .h = (float)surface->h };
 | 
					        new_texture.srcrect = (Rect) { .w = (float)surface->w, .h = (float)surface->h };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        /* will be fully populated as the atlas updates */
 | 
					        /* will be fully populated as the atlas updates */
 | 
				
			||||||
        new_texture.atlas_index = cache->atlas_index;
 | 
					        new_texture.atlas_index = cache->atlas_index;
 | 
				
			||||||
        cache->is_dirty = true;
 | 
					        cache->is_dirty = true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    shput(cache->hash, path, new_texture);
 | 
					    shput(cache->hash, path, new_texture);
 | 
				
			||||||
    return (TextureKey){ (uint16_t)shgeti(cache->hash, path) };
 | 
					
 | 
				
			||||||
 | 
					    uint16_t const id = (uint16_t)shlenu(cache->hash) - 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* reuse this id for every later missing texture */
 | 
				
			||||||
 | 
					    if (surface == missing_texture_surface)
 | 
				
			||||||
 | 
					        missing_texture_id = id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (TextureKey){ id };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -405,7 +462,7 @@ void textures_update_atlas(TextureCache *cache) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* EXPERIMANTAL: LIKELY TO BE REMOVED! */
 | 
					/* EXPERIMANTAL: LIKELY TO BE REMOVED! */
 | 
				
			||||||
#if defined(__linux__) /* use rodata elf section for fast lookups of repeating textures */
 | 
					#if defined(__linux_x_) /* use rodata elf section for fast lookups of repeating textures */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "system/linux/twn_elf.h"
 | 
					#include "system/linux/twn_elf.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -439,6 +496,8 @@ TextureKey textures_get_key(TextureCache *cache, const char *path) {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* TODO: this will be bad when dynamic strings are involved */
 | 
				
			||||||
 | 
					    /* to mitigate that we could free ptr_to_texture each frame */
 | 
				
			||||||
    /* try loading */
 | 
					    /* try loading */
 | 
				
			||||||
    last_texture = textures_load(cache, path);
 | 
					    last_texture = textures_load(cache, path);
 | 
				
			||||||
    hmput(ptr_to_texture, path, last_texture);
 | 
					    hmput(ptr_to_texture, path, last_texture);
 | 
				
			||||||
@@ -533,7 +592,7 @@ void textures_bind_repeating(const TextureCache *cache, TextureKey key) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            upload_gpu_texture(repeating_texture,
 | 
					            upload_gpu_texture(repeating_texture,
 | 
				
			||||||
                               texture.data->pixels,
 | 
					                               texture.data->pixels,
 | 
				
			||||||
                               4,
 | 
					                               texture.data->format->BytesPerPixel,
 | 
				
			||||||
                               texture.data->w,
 | 
					                               texture.data->w,
 | 
				
			||||||
                               texture.data->h);
 | 
					                               texture.data->h);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -569,7 +628,7 @@ size_t textures_get_num_atlases(const TextureCache *cache) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void textures_reset_state(void) {
 | 
					void textures_reset_state(void) {
 | 
				
			||||||
#if defined(__linux__) && !defined(HOT_RELOAD_SUPPORT)
 | 
					#if defined(__linux__x) && !defined(HOT_RELOAD_SUPPORT)
 | 
				
			||||||
    last_path = NULL;
 | 
					    last_path = NULL;
 | 
				
			||||||
    last_texture = (TextureKey){0};
 | 
					    last_texture = (TextureKey){0};
 | 
				
			||||||
    shfree(ptr_to_texture);
 | 
					    shfree(ptr_to_texture);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
#ifndef TWN_TEXTURES_C_H
 | 
					#ifndef TWN_TEXTURES_C_H
 | 
				
			||||||
#define TWN_TEXTURES_C_H
 | 
					#define TWN_TEXTURES_C_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "twn_util.h"
 | 
					#include "twn_types.h"
 | 
				
			||||||
#include "twn_texture_modes.h"
 | 
					#include "twn_texture_modes.h"
 | 
				
			||||||
#include "rendering/twn_gpu_texture_c.h"
 | 
					#include "rendering/twn_gpu_texture_c.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										19
									
								
								src/twn_types_c.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/twn_types_c.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					#ifndef TWN_TYPES_C_H
 | 
				
			||||||
 | 
					#define TWN_TYPES_C_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <stdint.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct Vec4 {
 | 
				
			||||||
 | 
					    float x;
 | 
				
			||||||
 | 
					    float y;
 | 
				
			||||||
 | 
					    float z;
 | 
				
			||||||
 | 
					    float w;
 | 
				
			||||||
 | 
					} Vec4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct Matrix4 {
 | 
				
			||||||
 | 
					    Vec4 row[4];
 | 
				
			||||||
 | 
					} Matrix4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
							
								
								
									
										209
									
								
								src/twn_util.c
									
									
									
									
									
								
							
							
						
						
									
										209
									
								
								src/twn_util.c
									
									
									
									
									
								
							@@ -9,6 +9,18 @@
 | 
				
			|||||||
#include <stdarg.h>
 | 
					#include <stdarg.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct ProfileItem {
 | 
				
			||||||
 | 
					    char const *key;
 | 
				
			||||||
 | 
					    struct Profile {
 | 
				
			||||||
 | 
					        uint64_t tick_start;
 | 
				
			||||||
 | 
					        uint64_t tick_accum;
 | 
				
			||||||
 | 
					        uint64_t sample_count;
 | 
				
			||||||
 | 
					        uint64_t worst_tick;
 | 
				
			||||||
 | 
					        bool active;
 | 
				
			||||||
 | 
					    } value;
 | 
				
			||||||
 | 
					} *profiles;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void cry_impl(const char *file, const int line, const char *title, const char *text) {
 | 
					void cry_impl(const char *file, const int line, const char *title, const char *text) {
 | 
				
			||||||
    SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION,
 | 
					    SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION,
 | 
				
			||||||
                    "TEARS AT %s:%d: %s ... %s", file, line, title, text);
 | 
					                    "TEARS AT %s:%d: %s ... %s", file, line, title, text);
 | 
				
			||||||
@@ -197,59 +209,27 @@ bool strends(const char *str, const char *suffix) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool overlap_rect(const Recti *a, const Recti *b, Recti *result) {
 | 
					/* TODO: have our own */
 | 
				
			||||||
    SDL_Rect a_sdl = { a->x, a->y, a->w, a->h };
 | 
					Rect rect_overlap(const Rect a, const Rect b) {
 | 
				
			||||||
    SDL_Rect b_sdl = { b->x, b->y, b->w, b->h };
 | 
					    SDL_FRect a_sdl = { a.x, a.y, a.w, a.h };
 | 
				
			||||||
    SDL_Rect result_sdl = { 0 };
 | 
					    SDL_FRect b_sdl = { b.x, b.y, b.w, b.h };
 | 
				
			||||||
 | 
					 | 
				
			||||||
    bool intersection = SDL_IntersectRect(&a_sdl, &b_sdl, &result_sdl);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (result != NULL)
 | 
					 | 
				
			||||||
        *result = (Recti){ result_sdl.x, result_sdl.y, result_sdl.w, result_sdl.h };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return intersection;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool overlap_frect(const Rect *a, const Rect *b, Rect *result) {
 | 
					 | 
				
			||||||
    SDL_FRect a_sdl = { a->x, a->y, a->w, a->h };
 | 
					 | 
				
			||||||
    SDL_FRect b_sdl = { b->x, b->y, b->w, b->h };
 | 
					 | 
				
			||||||
    SDL_FRect result_sdl = { 0 };
 | 
					    SDL_FRect result_sdl = { 0 };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    bool intersection = SDL_IntersectFRect(&a_sdl, &b_sdl, &result_sdl);
 | 
					    (void)SDL_IntersectFRect(&a_sdl, &b_sdl, &result_sdl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (result != NULL)
 | 
					    return (Rect){ result_sdl.x, result_sdl.y, result_sdl.w, result_sdl.h };
 | 
				
			||||||
        *result = (Rect){ result_sdl.x, result_sdl.y, result_sdl.w, result_sdl.h };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return intersection;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool intersect_rect(const Recti *a, const Recti *b) {
 | 
					/* TODO: have our own */
 | 
				
			||||||
    SDL_Rect a_sdl = { a->x, a->y, a->w, a->h };
 | 
					bool rect_intersects(const Rect a, const Rect b) {
 | 
				
			||||||
    SDL_Rect b_sdl = { b->x, b->y, b->w, b->h };
 | 
					    SDL_FRect a_sdl = { a.x, a.y, a.w, a.h };
 | 
				
			||||||
    return SDL_HasIntersection(&a_sdl, &b_sdl);
 | 
					    SDL_FRect b_sdl = { b.x, b.y, b.w, b.h };
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool intersect_frect(const Rect *a, const Rect *b) {
 | 
					 | 
				
			||||||
    SDL_FRect a_sdl = { a->x, a->y, a->w, a->h };
 | 
					 | 
				
			||||||
    SDL_FRect b_sdl = { b->x, b->y, b->w, b->h };
 | 
					 | 
				
			||||||
    return SDL_HasIntersectionF(&a_sdl, &b_sdl);
 | 
					    return SDL_HasIntersectionF(&a_sdl, &b_sdl);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Rect to_frect(Recti rect) {
 | 
					Vec2 rect_center(Rect rect) {
 | 
				
			||||||
    return (Rect) {
 | 
					 | 
				
			||||||
        .h = (float)rect.h,
 | 
					 | 
				
			||||||
        .w = (float)rect.w,
 | 
					 | 
				
			||||||
        .x = (float)rect.x,
 | 
					 | 
				
			||||||
        .y = (float)rect.y,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Vec2 frect_center(Rect rect) {
 | 
					 | 
				
			||||||
    return (Vec2){
 | 
					    return (Vec2){
 | 
				
			||||||
        .x = rect.x + rect.w / 2,
 | 
					        .x = rect.x + rect.w / 2,
 | 
				
			||||||
        .y = rect.y + rect.h / 2,
 | 
					        .y = rect.y + rect.h / 2,
 | 
				
			||||||
@@ -257,25 +237,52 @@ Vec2 frect_center(Rect rect) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void tick_timer(int *value) {
 | 
					int32_t timer_tick_frames(int32_t frames_left) {
 | 
				
			||||||
    *value = MAX(*value - 1, 0);
 | 
					    SDL_assert(frames_left >= 0);
 | 
				
			||||||
 | 
					    return MAX(frames_left - 1, 0);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void tick_ftimer(float *value) {
 | 
					float timer_tick_seconds(float seconds_left) {
 | 
				
			||||||
    *value = MAX(*value - ((float)ctx.delta_time / (float)ctx.clocks_per_second), 0.0f);
 | 
					    SDL_assert(seconds_left >= 0);
 | 
				
			||||||
 | 
					    return MAX(seconds_left - ((float)ctx.delta_time / (float)ctx.clocks_per_second), 0.0f);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool repeat_ftimer(float *value, float at) {
 | 
					TimerElapseFramesResult timer_elapse_frames(int32_t frames_left, int32_t interval) {
 | 
				
			||||||
    *value -= (float)ctx.delta_time / (float)ctx.clocks_per_second;
 | 
					    SDL_assert(frames_left >= 0);
 | 
				
			||||||
    if (*value < 0.0f) {
 | 
					    SDL_assert(interval > 0);
 | 
				
			||||||
        *value += at;
 | 
					
 | 
				
			||||||
        return true;
 | 
					    frames_left -= 1;
 | 
				
			||||||
 | 
					    bool elapsed = false;
 | 
				
			||||||
 | 
					    if (frames_left <= 0) {
 | 
				
			||||||
 | 
					        elapsed = true;
 | 
				
			||||||
 | 
					        frames_left += interval;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return false;
 | 
					    return (TimerElapseFramesResult) {
 | 
				
			||||||
 | 
					        .elapsed = elapsed,
 | 
				
			||||||
 | 
					        .frames_left = frames_left
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TimerElapseSecondsResult timer_elapse_seconds(float seconds_left, float interval) {
 | 
				
			||||||
 | 
					    SDL_assert(seconds_left >= 0);
 | 
				
			||||||
 | 
					    SDL_assert(interval > 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    seconds_left -= (float)ctx.delta_time / (float)ctx.clocks_per_second;
 | 
				
			||||||
 | 
					    bool elapsed = false;
 | 
				
			||||||
 | 
					    if (seconds_left <= 0.0f) {
 | 
				
			||||||
 | 
					        elapsed = true;
 | 
				
			||||||
 | 
					        seconds_left += interval;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return (TimerElapseSecondsResult) {
 | 
				
			||||||
 | 
					        .elapsed = elapsed,
 | 
				
			||||||
 | 
					        .seconds_left = seconds_left
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* TODO: handle utf8 */
 | 
					/* TODO: handle utf8 */
 | 
				
			||||||
char *expand_asterisk(const char *mask, const char *to) {
 | 
					char *expand_asterisk(const char *mask, const char *to) {
 | 
				
			||||||
    const char *offset = SDL_strchr(mask, '*');
 | 
					    const char *offset = SDL_strchr(mask, '*');
 | 
				
			||||||
@@ -292,3 +299,99 @@ char *expand_asterisk(const char *mask, const char *to) {
 | 
				
			|||||||
    SDL_memcpy(str + (offset - mask) + to_len, offset + 1, mask_len - (offset - mask));
 | 
					    SDL_memcpy(str + (offset - mask) + to_len, offset + 1, mask_len - (offset - mask));
 | 
				
			||||||
    return str;
 | 
					    return str;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void profile_start(char profile[const static 1]) {
 | 
				
			||||||
 | 
					    /* stamp time immediately, so to not have influence of our profile lookup */
 | 
				
			||||||
 | 
					    uint64_t const counter = SDL_GetPerformanceCounter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    struct ProfileItem *p = shgetp_null(profiles, profile);
 | 
				
			||||||
 | 
					    if (p) {
 | 
				
			||||||
 | 
					        p->value.tick_start = counter;
 | 
				
			||||||
 | 
					        p->value.active = true;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        shput(profiles, profile, ((struct Profile) {
 | 
				
			||||||
 | 
					            .tick_start = counter,
 | 
				
			||||||
 | 
					            .active = true,
 | 
				
			||||||
 | 
					        }));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void profile_end(char profile[const static 1]) {
 | 
				
			||||||
 | 
					    /* stamp time immediately, so to not have influence of our profile lookup */
 | 
				
			||||||
 | 
					    uint64_t const counter = SDL_GetPerformanceCounter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    struct ProfileItem *p = shgetp_null(profiles, profile);
 | 
				
			||||||
 | 
					    if (!p || !p->value.active) {
 | 
				
			||||||
 | 
					        log_warn("Profile %s wasn't started!", profile);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint64_t const took = counter - p->value.tick_start;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    p->value.tick_accum += took;
 | 
				
			||||||
 | 
					    p->value.sample_count++;
 | 
				
			||||||
 | 
					    p->value.active = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (p->value.worst_tick < took)
 | 
				
			||||||
 | 
					        p->value.worst_tick = took;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static char const *format_profile_time(double ticks) {
 | 
				
			||||||
 | 
					    static char strings[2][128];
 | 
				
			||||||
 | 
					    static char *current = strings[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    double const seconds = (double)ticks / (double)(SDL_GetPerformanceFrequency());
 | 
				
			||||||
 | 
					    /* display in seconds */
 | 
				
			||||||
 | 
					    if (seconds >= 0.1)
 | 
				
			||||||
 | 
					        SDL_snprintf(current, 128, "%fs", seconds);
 | 
				
			||||||
 | 
					    /* display in milliseconds */
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					        SDL_snprintf(current, 128, "%fms", seconds * 1000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const char *const result = current;
 | 
				
			||||||
 | 
					    current += 128;
 | 
				
			||||||
 | 
					    if (current >= strings[1]) current = strings[0];
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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: %s",
 | 
				
			||||||
 | 
					                     profiles[i].key,
 | 
				
			||||||
 | 
					                     format_profile_time((double)profiles[i].value.tick_accum));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        else if (profiles[i].value.sample_count > 1) {
 | 
				
			||||||
 | 
					            log_info("Profile '%s' on average took: %s, worst case: %s, sample count: %llu",
 | 
				
			||||||
 | 
					                     profiles[i].key,
 | 
				
			||||||
 | 
					                     format_profile_time((double)profiles[i].value.tick_accum / (double)profiles[i].value.sample_count),
 | 
				
			||||||
 | 
					                     format_profile_time((double)profiles[i].value.worst_tick),
 | 
				
			||||||
 | 
					                     profiles[i].value.sample_count);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void log_vec2(Vec2 vector, char const *message) {
 | 
				
			||||||
 | 
					    if (!message) message = "Vec2";
 | 
				
			||||||
 | 
					    log_info("%s = (%f, %f)", message, (double)vector.x, (double)vector.y);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void log_vec3(Vec3 vector, char const *message) {
 | 
				
			||||||
 | 
					    if (!message) message = "Vec3";
 | 
				
			||||||
 | 
					    log_info("%s = (%f, %f, %f)", message, (double)vector.x, (double)vector.y, (double)vector.z);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void log_rect(Rect rect, char const *message) {
 | 
				
			||||||
 | 
					    if (!message) message = "Rect";
 | 
				
			||||||
 | 
					    log_info("%s = (%f, %f, %f, %f)", message, (double)rect.x, (double)rect.y, (double)rect.w, (double)rect.h);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,7 @@
 | 
				
			|||||||
#include <SDL2/SDL.h>
 | 
					#include <SDL2/SDL.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <stdbool.h>
 | 
					#include <stdbool.h>
 | 
				
			||||||
 | 
					#include <math.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define MAX SDL_max
 | 
					#define MAX SDL_max
 | 
				
			||||||
#define MIN SDL_min
 | 
					#define MIN SDL_min
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								third-party/libxm/CMakeLists.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								third-party/libxm/CMakeLists.txt
									
									
									
									
										vendored
									
									
								
							@@ -1,4 +1,4 @@
 | 
				
			|||||||
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
 | 
					CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
 | 
				
			||||||
PROJECT(libxm C)
 | 
					PROJECT(libxm C)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FUNCTION(OPTION_AND_DEFINE name description default_value)
 | 
					FUNCTION(OPTION_AND_DEFINE name description default_value)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								third-party/physfs/CMakeLists.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								third-party/physfs/CMakeLists.txt
									
									
									
									
										vendored
									
									
								
							@@ -11,7 +11,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
set(PHYSFS_VERSION 3.2.0)
 | 
					set(PHYSFS_VERSION 3.2.0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
cmake_minimum_required(VERSION 3.0)
 | 
					cmake_minimum_required(VERSION 3.5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
project(PhysicsFS VERSION ${PHYSFS_VERSION} LANGUAGES C )
 | 
					project(PhysicsFS VERSION ${PHYSFS_VERSION} LANGUAGES C )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								third-party/stb/stb_ds.h
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								third-party/stb/stb_ds.h
									
									
									
									
										vendored
									
									
								
							@@ -548,7 +548,7 @@ extern void * stbds_shmode_func(size_t elemsize, int mode);
 | 
				
			|||||||
#define stbds_arraddnindex(a,n)(stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), stbds_header(a)->length-(n)) : stbds_arrlen(a))
 | 
					#define stbds_arraddnindex(a,n)(stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), stbds_header(a)->length-(n)) : stbds_arrlen(a))
 | 
				
			||||||
#define stbds_arraddnoff       stbds_arraddnindex
 | 
					#define stbds_arraddnoff       stbds_arraddnindex
 | 
				
			||||||
#define stbds_arrlast(a)       ((a)[stbds_header(a)->length-1])
 | 
					#define stbds_arrlast(a)       ((a)[stbds_header(a)->length-1])
 | 
				
			||||||
#define stbds_arrfree(a)       ((void) ((a) ? STBDS_FREE(NULL,stbds_header(a)) : (void)0), (a)=NULL)
 | 
					#define stbds_arrfree(a)       ((void) ((a) ? stbds_arrfreef(a) : (void)0), (a)=NULL)
 | 
				
			||||||
#define stbds_arrdel(a,i)      stbds_arrdeln(a,i,1)
 | 
					#define stbds_arrdel(a,i)      stbds_arrdeln(a,i,1)
 | 
				
			||||||
#define stbds_arrdeln(a,i,n)   (memmove(&(a)[i], &(a)[(i)+(n)], sizeof *(a) * (stbds_header(a)->length-(n)-(i))), stbds_header(a)->length -= (n))
 | 
					#define stbds_arrdeln(a,i,n)   (memmove(&(a)[i], &(a)[(i)+(n)], sizeof *(a) * (stbds_header(a)->length-(n)-(i))), stbds_header(a)->length -= (n))
 | 
				
			||||||
#define stbds_arrdelswap(a,i)  ((a)[i] = stbds_arrlast(a), stbds_header(a)->length -= 1)
 | 
					#define stbds_arrdelswap(a,i)  ((a)[i] = stbds_arrlast(a), stbds_header(a)->length -= 1)
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user