Compare commits

...

137 Commits

Author SHA1 Message Date
9ddc5c4a66 twn_timer.c: time limiter for game ticks 2025-01-26 02:37:21 +03:00
16bd49b42e twn_util.c: show profiles in milliseconds when appropriate, don't include time taken for profile internals 2025-01-26 01:42:12 +03:00
dd158dee01 apps/examples/circle-raster: add acc precalc method 2025-01-25 05:48:11 +03:00
ab3c032313 /apps/examples/circle-raster: acc variant 2025-01-25 02:28:40 +03:00
34a3de73c6 twn_utils.c: make profiling public 2025-01-25 00:53:44 +03:00
597168c282 /apps/examples/circle-raster: benchmarking and int32_t variant 2025-01-25 00:53:28 +03:00
37cd8cf2cf /apps/examples/circle-raster: simple code for grid-centered circle rasterization 2025-01-24 23:26:32 +03:00
cb5f207761 twn_util.c: fix profile worst case, update formatting 2025-01-24 23:06:34 +03:00
6e421543c4 cmake ctx.mouse_position viewport and resolution based 2025-01-24 21:52:11 +03:00
c97d9b2568 twn_draw.c: fix upper limit for camera fov warn 2025-01-24 16:28:06 +03:00
e281ba593c twn_vec.h: add vec2_length(), remove legacy code 2025-01-24 04:28:09 +03:00
a20be2c523 twn_draw.c: add warning for erroneous fov parameters 2025-01-24 04:27:45 +03:00
b20e7202fe twn_util.c: add handling of NULL in log_<type> 2025-01-23 22:15:09 +03:00
045d2764fa twn_util.c: add logging over base types 2025-01-23 22:13:01 +03:00
53917b05b7 twn_draw.c: draw_box() 2025-01-23 21:53:15 +03:00
0dc0a18019 fix color for line rendering 2025-01-23 20:05:29 +03:00
2df5616410 add line drawing 2025-01-23 04:29:59 +03:00
3f9906a918 remove fog start and fog end parameters 2025-01-20 19:30:17 +03:00
8a5d639f95 remove indirection in vertex builder 2025-01-17 22:48:35 +03:00
40aef0a1f9 scale in mixing to prevent excessive gain 2025-01-15 21:31:19 +03:00
2de536210b fix --release switch 2025-01-15 07:58:42 +03:00
449d4d3c32 move opengl library loading to a thread that starts as soon as possible and is awaited as late as we can allow 2025-01-15 07:54:45 +03:00
8233f31269 oopsie 2025-01-15 06:50:19 +03:00
45b8b21ec3 add todo 2025-01-15 06:08:55 +03:00
4659cf2aef profile atlas recreation 2025-01-15 06:04:02 +03:00
db530ca3a0 make audio device init delayed until first use 2025-01-15 05:43:56 +03:00
f0dfd5627a more startup profiling, removal of irrelevant calls and zeroing in textures_cache_init() 2025-01-15 05:39:18 +03:00
0da1e413aa only init the necessary with SDL, speeding up the startup 2025-01-15 05:00:45 +03:00
8c165974c7 twn_util.c: make profiler collect worst case, dispaly the stats with awareness of sample count 2025-01-15 04:55:18 +03:00
1ba33bdc26 collect profile of startup and pack resolution 2025-01-15 04:47:36 +03:00
760515c551 minor optimization of strncmp for literal comparison 2025-01-15 04:36:00 +03:00
9d0a2cab81 expose audio to twnlua 2025-01-15 04:15:08 +03:00
d5b42fa242 add a todo 2025-01-15 01:01:16 +03:00
851ab80292 remove ctx.update_multiplicity 2025-01-15 00:52:42 +03:00
688d71953a make inputs up-to-date for game tick 2025-01-15 00:43:46 +03:00
63abf3d374 disable vsync, make us rule over frames fully 2025-01-15 00:31:17 +03:00
80c77424e2 /apps/demos/scenery: more detailed terrain 2025-01-15 00:30:46 +03:00
82d4f21a4b twn_textures.c: minor optimization 2025-01-15 00:11:47 +03:00
3990f78a74 twn_textures.c: make missing texture single and reused 2025-01-15 00:10:30 +03:00
f0ad9b9a8a twn_textures.c: fix repeated bind to work over varying channel count 2025-01-14 23:47:59 +03:00
ea0af5159f only enable fog for 3d 2025-01-14 23:28:48 +03:00
5059802d09 big rendering overhaul (cleaning and api abstraction) 2025-01-14 23:20:54 +03:00
b7cb37c06a no TWNBUILDDIR 2025-01-14 04:47:53 +03:00
664f123a85 twn_input.c: zero ctx.mouse_movement on focus lost 2025-01-14 02:56:55 +03:00
2351d4114c twn_draw.c: add draw_quad() 2025-01-14 02:53:18 +03:00
86bf16b680 make mouse movement and capture depend on window focus 2025-01-14 02:07:54 +03:00
dbe6217e24 /apps/twnlua: add .gitignore 2025-01-14 01:37:16 +03:00
b037d7a0b9 /apps/twnlua: ctx uploading 2025-01-14 01:35:54 +03:00
e984e95fa8 /apps/twnlua: make dependent on twn_api.json for rebuilding 2025-01-14 00:30:48 +03:00
4ed3764c1d twn_input.c: remove input_mouse_captured(), add ctx.mouse_capture 2025-01-14 00:28:21 +03:00
6d19d2d819 /apps/twnlua: make no warnings 2025-01-14 00:06:55 +03:00
5bce3e5238 twn_textures.c: remove unused amask 2025-01-13 23:56:22 +03:00
6298394957 twn_audio.c: a lot of fixes, optional TWN_FEATURE_PUSH_AUDIO for converging game ticks and audio, proper .wav handling with resample 2025-01-13 23:52:55 +03:00
eefd53a630 twn_audio.c: .wav support and scratch channels 2025-01-13 19:56:20 +03:00
87ae1a7312 missing textures: fix double free 2025-01-13 18:09:06 +03:00
3052bb693a /apps/demos/scenery: skip title scene 2025-01-13 17:36:18 +03:00
5f6c8dd8e6 missing texture when loading fails 2025-01-13 17:35:50 +03:00
c694dfff82 use flatshading for space and skip setting irrelevant vertex color 2025-01-13 09:18:51 +03:00
b6ca9bedb4 /apps/twnlua: don't use module tables 2025-01-13 08:57:21 +03:00
8d67e44009 /apps/twnlua: use lua_numberx for slightly more optimized defaults 2025-01-12 03:51:02 +03:00
192907a0db use slot size of 128 for twnlua allocator 2025-01-12 03:21:05 +03:00
e8b02570a2 slot based allocator for lua, usage of lua_createtable 2025-01-12 02:44:41 +03:00
46e077ba63 make ctx.frame_number overflow to 0 2025-01-11 17:33:05 +03:00
41d0e24780 /apps/twnlua: ctx.udata preservation (not yet for reload case) 2025-01-11 16:22:41 +03:00
777a06a002 /apps/twnlua: expose ctx 2025-01-11 16:01:41 +03:00
313108092b don't use clamped float random_seed internally 2025-01-10 02:52:04 +03:00
83e2dc5468 make vec4 and matrix types internal 2025-01-10 02:40:52 +03:00
951d9c76c8 use floats for ctx.frame_number and ctx.random_seed 2025-01-10 02:20:21 +03:00
f3848d2d52 progress on twnlua bindgen 2025-01-09 21:47:08 +03:00
8c401eda75 api changes and progress on filling in twn_api.json 2025-01-07 14:14:21 +03:00
5c89c55b3e /apps/twnlua: support out of tree usage 2025-01-07 13:22:31 +03:00
5b05386bb0 changes to twn.toml specification of resolution, make it optional as well 2025-01-06 21:19:26 +03:00
b0549612a9 /apps/demos/scenery: lock movement direction to a plane 2025-01-06 15:38:40 +03:00
6463ac3dd7 /apps/demos/scenery: separate height_at(), position grass right 2025-01-06 15:34:12 +03:00
e914cad0dd infer texture mode for triangles 2025-01-05 23:43:30 +03:00
a9d9936cb7 /apps/demos/scenery: reduce fov 2025-01-05 23:37:08 +03:00
4dd028aeae support arbitrary count of billboards per batch (in regards to preallocated short index buffer) 2025-01-05 23:36:48 +03:00
d7a119a592 fix aspect ratio as well as billboard scaling 2025-01-05 21:08:54 +03:00
cb6c1df0be disallow ghostly billboards, reenable skybox in scenery demo 2025-01-05 19:59:23 +03:00
3bfa86066e billboards! 2025-01-05 19:46:05 +03:00
7c0bf39f12 CMakeLists.txt: fix no amalgam build 2025-01-03 22:04:16 +03:00
4f2b8ccd01 separate the rest of general drawing code 2025-01-03 21:59:00 +03:00
472a0657f3 twn_draw.c: remove gl headers 2025-01-03 21:10:15 +03:00
0f368e2700 move render_circle to twn_draw.c 2025-01-03 21:08:54 +03:00
33471b4c46 generalization of deferred commands and any_gl rendering where appropriate 2025-01-03 21:01:26 +03:00
edcb7fc39c make deferred draw primitives agnostic to backend 2025-01-03 19:59:37 +03:00
f9a8448782 make SpritePrimitive take less space (52 -> 48 bytes) 2025-01-03 19:48:00 +03:00
6d5732cc2b tweaks to tooling 2025-01-03 11:55:39 +03:00
62d738cbbe /docs/interop.md: update 2025-01-03 11:49:00 +03:00
f4a3298906 disallow pointer to pointer in api 2025-01-03 11:45:10 +03:00
8ec5a96333 /apps/demos/scenery: cleanup 2025-01-02 13:35:13 +03:00
4277852fc5 /apps/demos/scenery: add walking 2025-01-02 13:29:28 +03:00
dc2535358e make input coordinates respect the viewport 2024-12-24 10:24:50 +03:00
190eb1f107 twnlua: use stb_ds.h for enum conversions 2024-12-24 10:03:19 +03:00
e06c879869 twnlua: partial impl for return propagation, input now works 2024-12-23 22:02:17 +03:00
e7ed72dfc0 twnlua: bindgen.py capable of converting share/twn_api.h spec to lua bindings 2024-12-23 20:59:00 +03:00
c4c097f050 Revert "amalgam of batch and bash scripts for twn basetool"
This reverts commit cd9c65212d.
2024-11-06 17:20:38 +03:00
1d34c91106 add .editorconfig 2024-11-05 16:51:48 +03:00
0d81236331 set eol=lf in .gitattributes 2024-11-05 16:47:30 +03:00
f9bb6412b7 make build directory now prefixed with dot, as it what clangd expects, remove .clangd file 2024-11-05 01:56:50 +03:00
b18f6f1d87 CMakeLists.txt: also allow GNU linker to use thinlto cache 2024-11-04 16:26:56 +03:00
2f94e17852 twn_util_c.h: profile_list_stats() for average summary, without spam in console 2024-11-04 16:22:13 +03:00
26a2bf293f twn_util_c.h: internal profiling api 2024-11-04 16:04:02 +03:00
4b0d584b7e mark scripts executable again 2024-11-04 13:36:17 +03:00
19215d5795 thinlto with either GNUgold or LDD supported 2024-11-04 08:32:10 +03:00
1c97053675 stb_ds.h: fix STBDS_FREE used outside of implementation 2024-11-04 08:09:15 +03:00
ee7fc42fbc inclide math.h so that it works under windows 2024-11-04 07:13:29 +03:00
cd9c65212d amalgam of batch and bash scripts for twn basetool 2024-11-04 07:13:10 +03:00
ccaef34d61 update interop.md 2024-11-03 23:30:29 +03:00
833f7dbc53 update interop.md 2024-11-03 23:27:10 +03:00
a7feb7b61b update interop.md 2024-11-03 23:25:06 +03:00
4be27816c2 scenery: make render distance come from a define 2024-11-03 23:09:10 +03:00
d794ca862f remove junky UncoloredSpaceTriangle union, hide vertex generation from generic triangle implementation 2024-11-03 23:08:53 +03:00
26c75ffd7c optimize case of sequential shared radius circle drawing by reusing the geometry by just offsetting it 2024-11-03 22:33:18 +03:00
e4da4a8b7f add a TODO 2024-11-03 21:54:55 +03:00
963d549eed CMakeLists.txt: only put -s for gcc 2024-10-29 12:32:21 +03:00
9121da0675 yet another api rework, removal of integer types in public api, optionals at the end, some cleaning 2024-10-29 12:25:24 +03:00
6464d14b3e twn_input.h: remove unbinding 2024-10-28 13:04:49 +03:00
eff9fe6918 twn_util.h: make return structs comply to type naming 2024-10-28 12:39:42 +03:00
d11143ac86 twn_draw.h: new camera api 2024-10-28 12:34:48 +03:00
1d35a3859b /docs/interop.md: valid relative hyperlinks 2024-10-22 20:42:07 +03:00
9da26638c8 rework input to be in line with rendering semantics 2024-10-22 20:32:17 +03:00
a22bcfd97e rework timers, update overlap/intersect and other procedures, some other things i dont remember 2024-10-22 14:45:30 +03:00
a527036436 /docs/interop.md 2024-10-22 13:53:10 +03:00
b390e9db23 audio_set() -> audio_set_parameter(), with string based convention 2024-10-22 13:52:24 +03:00
5a08c01208 fix warnings 2024-10-22 11:06:02 +03:00
eff2d9c5e1 direct header includes, remove redundant ones 2024-10-22 10:39:40 +03:00
8aecc2bd06 fix rects render 2024-10-22 10:05:53 +03:00
1296d41ad7 deferred fog, fix of first frame on double buffered option 2024-10-22 09:47:47 +03:00
48f63fc9df deferred skybox, fixes to use of ARB_depth_clamp extension usage, have TextureKey explicitly in QuadBatch 2024-10-22 09:30:14 +03:00
c49789f1f4 make deferred space triangles work 2024-10-19 20:02:39 +03:00
a7b09b9f39 fix circle rendering over new impl 2024-10-19 19:16:18 +03:00
399b199266 move --gc-sections to shared libraries only 2024-10-18 20:41:34 +03:00
73b6ab047d make /bin/build.sh use clang if it can find it by default 2024-10-18 20:33:46 +03:00
024f17de91 make cmake output less verbose 2024-10-18 20:33:25 +03:00
92de2c00c0 make use of thinlto for release builds 2024-10-18 20:33:03 +03:00
b683594013 proper size of build_vertex_buffer for circle indices 2024-10-18 18:45:17 +03:00
92 changed files with 3871 additions and 2155 deletions

View File

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

15
.editorconfig Normal file
View 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
View File

@ -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
View File

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

View File

@ -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>)
@ -316,8 +344,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})

View File

@ -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

View File

@ -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]

View File

@ -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,

View File

@ -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

View File

@ -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]

View File

@ -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();
} }

View File

@ -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 });
} }

View File

@ -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;
} }

View File

@ -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;

View File

@ -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) {

View File

@ -12,11 +12,11 @@ static void update_tiles(struct World *world) {
for (size_t row = 0; row < world->tilemap_height; ++row) { for (size_t 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,19 +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); if (intersection)
} else { *intersection = rect_overlap(rect, tile_frect);
is_intersecting = overlap_frect(&rect, &tile_frect, intersection);
}
if (is_intersecting) if (is_intersecting)
break; break;
@ -143,46 +135,21 @@ bool world_find_intersect_frect(struct World *world, Rect rect, Rect *intersecti
} }
bool world_find_intersect_rect(struct World *world, Recti rect, Recti *intersection) {
bool is_intersecting = false;
const size_t tile_count = world->tilemap_height * world->tilemap_width;
for (size_t i = 0; i < tile_count; ++i) {
if (world->tiles[i].type == TILE_TYPE_VOID)
continue;
Recti *tile_rect = &world->tiles[i].rect;
if (intersection == NULL) {
Recti temp;
is_intersecting = overlap_rect(&rect, tile_rect, &temp);
} else {
is_intersecting = overlap_rect(&rect, tile_rect, intersection);
}
if (is_intersecting)
break;
}
return is_intersecting;
}
bool world_is_tile_at(struct World *world, float x, float y) { 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);
} }

View File

@ -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);

View File

@ -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

View File

@ -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]

View File

@ -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();
} }

View File

@ -12,63 +12,147 @@
#include <stdlib.h> #include <stdlib.h>
static void ingame_tick(State *state) { #define TERRAIN_FREQUENCY 0.15f
#define TERRAIN_DISTANCE 64
#define HALF_TERRAIN_DISTANCE ((float)TERRAIN_DISTANCE / 2)
#define PLAYER_HEIGHT 0.6f
static float heightmap[TERRAIN_DISTANCE][TERRAIN_DISTANCE];
static void process_fly_mode(State *state) {
SceneIngame *scn = (SceneIngame *)state->scene; SceneIngame *scn = (SceneIngame *)state->scene;
if (input_is_mouse_captured()) { DrawCameraFromPrincipalAxesResult dir_and_up =
const float sensitivity = 0.6f; /* TODO: put this in a better place */ draw_camera_from_principal_axes(scn->pos, (float)M_PI_2 * 0.8f, scn->roll, scn->pitch, scn->yaw);
scn->yaw += (float)ctx.mouse_movement.x * sensitivity;
scn->pitch -= (float)ctx.mouse_movement.y * sensitivity;
scn->pitch = clampf(scn->pitch, -89.0f, 89.0f);
const float yaw_rad = scn->yaw * (float)DEG2RAD; const Vec3 right = m_vec_norm(m_vec_cross(dir_and_up.direction, dir_and_up.up));
const float pitch_rad = scn->pitch * (float)DEG2RAD;
scn->cam.target = m_vec_norm(((Vec3){
cosf(yaw_rad) * cosf(pitch_rad),
sinf(pitch_rad),
sinf(yaw_rad) * cosf(pitch_rad)
}));
}
const Vec3 right = m_vec_norm(m_vec_cross(scn->cam.target, scn->cam.up));
const float speed = 0.04f; /* TODO: put this in a better place */ const float speed = 0.04f; /* TODO: put this in a better place */
if (input_is_action_pressed("player_left")) if (input_action_pressed("player_left"))
scn->cam.pos = vec3_sub(scn->cam.pos, m_vec_scale(right, speed)); scn->pos = vec3_sub(scn->pos, m_vec_scale(right, speed));
if (input_is_action_pressed("player_right")) if (input_action_pressed("player_right"))
scn->cam.pos = vec3_add(scn->cam.pos, m_vec_scale(right, speed)); scn->pos = vec3_add(scn->pos, m_vec_scale(right, speed));
if (input_is_action_pressed("player_forward")) if (input_action_pressed("player_forward"))
scn->cam.pos = vec3_add(scn->cam.pos, m_vec_scale(scn->cam.target, speed)); scn->pos = vec3_add(scn->pos, m_vec_scale(dir_and_up.direction, speed));
if (input_is_action_pressed("player_backward")) if (input_action_pressed("player_backward"))
scn->cam.pos = vec3_sub(scn->cam.pos, m_vec_scale(scn->cam.target, speed)); scn->pos = vec3_sub(scn->pos, m_vec_scale(dir_and_up.direction, speed));
if (input_is_action_pressed("player_jump")) if (input_action_pressed("player_jump"))
scn->cam.pos.y += speed; scn->pos.y += speed;
if (input_is_action_pressed("player_run")) if (input_action_pressed("player_run"))
scn->cam.pos.y -= speed; scn->pos.y -= speed;
}
/* toggle mouse capture with end key */
if (input_is_action_just_pressed("mouse_capture_toggle")) { static float height_at(SceneIngame *scn, Vec2 position) {
input_set_mouse_captured(!input_is_mouse_captured()); float height0, height1, height2, weight0, weight1, weight2;
int const x = (int)(HALF_TERRAIN_DISTANCE + (position.x - scn->pos.x));
int const y = (int)(HALF_TERRAIN_DISTANCE + (position.y - scn->pos.z));
height0 = heightmap[x][y];
height1 = heightmap[x + 1][y + 1];
Vec2 incell = { position.x - floorf(position.x), position.y - floorf(position.y) };
/* who needs barycentric coordinates, am i right? */
weight0 = 1 / sqrtf(powf(incell.x, 2) + powf(incell.y, 2));
weight1 = 1 / sqrtf(powf(1 - incell.x, 2) + powf(1 - incell.y, 2));
/* find which triangle we're directly under */
/* for this manhattan distance is sufficient */
if (incell.x + (1 - incell.y) < (1 - incell.x) + incell.y) {
height2 = heightmap[x][y + 1];
weight2 = 1 / sqrtf(powf(incell.x, 2) + powf(1 - incell.y, 2));
} else {
height2 = heightmap[x + 1][y];
weight2 = 1 / sqrtf(powf(1 - incell.x, 2) + powf(incell.y, 2));
} }
draw_camera(&scn->cam); return (height0 * weight0 + height1 * weight1 + height2 * weight2) / (weight0 + weight1 + weight2);
}
#define TERRAIN_FREQUENCY 0.1f
for (int ly = 64; ly--;) { static void process_ground_mode(State *state) {
for (int lx = 64; lx--;) { SceneIngame *scn = (SceneIngame *)state->scene;
float x = SDL_truncf(scn->cam.pos.x + 32 - (float)lx);
float y = SDL_truncf(scn->cam.pos.z + 32 - (float)ly);
float d0 = stb_perlin_noise3((float)x * TERRAIN_FREQUENCY, (float)y * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6; DrawCameraFromPrincipalAxesResult dir_and_up =
float d1 = stb_perlin_noise3((float)(x + 1) * TERRAIN_FREQUENCY, (float)y * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6; draw_camera_from_principal_axes(scn->pos, (float)M_PI_2 * 0.8f, scn->roll, scn->pitch, scn->yaw);
float d2 = stb_perlin_noise3((float)(x + 1) * TERRAIN_FREQUENCY, (float)(y - 1) * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6;
float d3 = stb_perlin_noise3((float)x * TERRAIN_FREQUENCY, (float)(y - 1) * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6; dir_and_up.direction.y = 0;
dir_and_up.direction = vec3_norm(dir_and_up.direction);
const Vec3 right = m_vec_norm(m_vec_cross(dir_and_up.direction, dir_and_up.up));
const float speed = 0.18f; /* TODO: put this in a better place */
Vec3 target = scn->pos;
/* gravity */
{
float const height = height_at(scn, (Vec2){scn->pos.x, scn->pos.z});
if (target.y > height + PLAYER_HEIGHT)
target.y = target.y - 0.4f;
if (target.y < height + PLAYER_HEIGHT)
target.y = height + PLAYER_HEIGHT;
}
/* movement */
{
Vec3 direction = {0, 0, 0};
if (input_action_pressed("player_left"))
direction = m_vec_sub(direction, m_vec_scale(right, speed));
if (input_action_pressed("player_right"))
direction = m_vec_add(direction, m_vec_scale(right, speed));
if (input_action_pressed("player_forward"))
direction = m_vec_add(direction, m_vec_scale(dir_and_up.direction, speed));
if (input_action_pressed("player_backward"))
direction = m_vec_sub(direction, m_vec_scale(dir_and_up.direction, speed));
target = m_vec_add(target, direction);
}
/* interpolate */
scn->pos.x = (target.x - scn->pos.x) * (0.13f / 0.9f) + scn->pos.x;
scn->pos.y = (target.y - scn->pos.y) * (0.13f / 0.9f) + scn->pos.y;
scn->pos.z = (target.z - scn->pos.z) * (0.13f / 0.9f) + scn->pos.z;
}
static void generate_terrain(SceneIngame *scn) {
for (int ly = 0; ly < TERRAIN_DISTANCE; ly++) {
for (int lx = 0; lx < TERRAIN_DISTANCE; lx++) {
float x = floorf(scn->pos.x - HALF_TERRAIN_DISTANCE + (float)lx);
float y = floorf(scn->pos.z - HALF_TERRAIN_DISTANCE + (float)ly);
float height = stb_perlin_noise3((float)x * TERRAIN_FREQUENCY, (float)y * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 1;
height += stb_perlin_noise3((float)x * TERRAIN_FREQUENCY / 10, (float)y * TERRAIN_FREQUENCY / 10, 0, 0, 0, 0) * 10 - 1;
heightmap[lx][ly] = height;
}
}
}
static void draw_terrain(SceneIngame *scn) {
for (int ly = TERRAIN_DISTANCE - 1; ly > 0; ly--) {
for (int lx = 0; lx < TERRAIN_DISTANCE - 1; lx++) {
int32_t x = (int32_t)(floorf(scn->pos.x - HALF_TERRAIN_DISTANCE + (float)lx));
int32_t y = (int32_t)(floorf(scn->pos.z - HALF_TERRAIN_DISTANCE + (float)ly));
float d0 = heightmap[lx][ly];
float d1 = heightmap[lx + 1][ly];
float d2 = heightmap[lx + 1][ly - 1];
float d3 = heightmap[lx][ly - 1];
draw_triangle("/assets/grass.png", draw_triangle("/assets/grass.png",
(Vec3){ (float)x, d0, (float)y }, (Vec3){ (float)x, d0, (float)y },
@ -76,7 +160,10 @@ static void ingame_tick(State *state) {
(Vec3){ (float)x, d3, (float)y - 1 }, (Vec3){ (float)x, d3, (float)y - 1 },
(Vec2){ 128, 128 }, (Vec2){ 128, 128 },
(Vec2){ 128, 0 }, (Vec2){ 128, 0 },
(Vec2){ 0, 128 }); (Vec2){ 0, 128 },
(Color){255, 255, 255, 255},
(Color){255, 255, 255, 255},
(Color){255, 255, 255, 255});
draw_triangle("/assets/grass.png", draw_triangle("/assets/grass.png",
(Vec3){ (float)x + 1, d1, (float)y }, (Vec3){ (float)x + 1, d1, (float)y },
@ -84,12 +171,61 @@ static void ingame_tick(State *state) {
(Vec3){ (float)x, d3, (float)y - 1 }, (Vec3){ (float)x, d3, (float)y - 1 },
(Vec2){ 128, 0 }, (Vec2){ 128, 0 },
(Vec2){ 0, 0 }, (Vec2){ 0, 0 },
(Vec2){ 0, 128 }); (Vec2){ 0, 128 },
(Color){255, 255, 255, 255},
(Color){255, 255, 255, 255},
(Color){255, 255, 255, 255});
draw_billboard("/assets/grasses/10.png",
(Vec3){ (float)x, d0 + 0.15f, (float)y },
(Vec2){0.3f, 0.3f},
(Color){255, 255, 255, 255}, true);
} }
} }
}
static void ingame_tick(State *state) {
SceneIngame *scn = (SceneIngame *)state->scene;
input_action("player_left", CONTROL_A);
input_action("player_right", CONTROL_D);
input_action("player_forward", CONTROL_W);
input_action("player_backward", CONTROL_S);
input_action("player_jump", CONTROL_SPACE);
input_action("player_run", CONTROL_LSHIFT);
input_action("mouse_capture_toggle", CONTROL_ESCAPE);
input_action("toggle_camera_mode", CONTROL_C);
if (scn->mouse_captured) {
const float sensitivity = 0.4f * (float)DEG2RAD; /* TODO: put this in a better place */
scn->yaw += (float)ctx.mouse_movement.x * sensitivity;
scn->pitch -= (float)ctx.mouse_movement.y * sensitivity;
scn->pitch = clampf(scn->pitch, (float)-M_PI * 0.49f, (float)M_PI * 0.49f);
}
if (input_action_just_pressed("toggle_camera_mode"))
scn->flying_camera = !scn->flying_camera;
if (scn->flying_camera) {
process_fly_mode(state);
} else {
process_ground_mode(state);
}
/* toggle mouse capture with end key */
if (input_action_just_pressed("mouse_capture_toggle"))
scn->mouse_captured = !scn->mouse_captured;
ctx.mouse_capture = scn->mouse_captured;
generate_terrain(scn);
draw_terrain(scn);
draw_skybox("/assets/miramar/miramar_*.tga"); draw_skybox("/assets/miramar/miramar_*.tga");
draw_fog(0.9f, 1.0f, 0.05f, (Color){ 140, 147, 160, 255 });
ctx.fog_color = (Color){ 140, 147, 160, 255 };
ctx.fog_density = 0.03f;
} }
@ -105,13 +241,13 @@ Scene *ingame_scene(State *state) {
new_scene->base.tick = ingame_tick; new_scene->base.tick = ingame_tick;
new_scene->base.end = ingame_end; new_scene->base.end = ingame_end;
new_scene->cam = (Camera){ .pos = { 32, 0, 1 }, .up = { 0, 1, 0 }, .fov = (float)M_PI_2 }; new_scene->mouse_captured = true;
m_audio(m_set(path, "music/mod65.xm"), m_audio(m_set(path, "music/mod65.xm"),
m_opt(channel, "soundtrack"), m_opt(channel, "soundtrack"),
m_opt(repeat, true)); m_opt(repeat, true));
input_set_mouse_captured(true); new_scene->pos = (Vec3){ 0.1f, 0.0, 0.1f };
return (Scene *)new_scene; return (Scene *)new_scene;
} }

View File

@ -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;

View File

@ -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 }
); );

View 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})

View 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"

View 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);
}

View File

@ -0,0 +1,11 @@
#ifndef STATE_H
#define STATE_H
#include "twn_game_api.h"
struct state {
float r;
};
#endif

View File

@ -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

View File

@ -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
View File

@ -0,0 +1 @@
luabind.c

View File

@ -6,12 +6,21 @@ if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug) set(CMAKE_BUILD_TYPE Debug)
endif() endif()
add_subdirectory($ENV{TWNROOT} $ENV{TWNBUILDDIR}) add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/luabind.c
COMMAND ${PYTHON3} ${CMAKE_CURRENT_SOURCE_DIR}/bindgen.py $ENV{TWNROOT}/share/twn_api.json > ${CMAKE_CURRENT_SOURCE_DIR}/luabind.c
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/bindgen.py $ENV{TWNROOT}/share/twn_api.json
)
set(SOURCE_FILES set(SOURCE_FILES
game.c game.c
state.h state.h
${CMAKE_CURRENT_SOURCE_DIR}/luabind.c
lua/src/lapi.c lua/src/lapi.c
lua/src/lapi.h lua/src/lapi.h
lua/src/lauxlib.c lua/src/lauxlib.c
@ -74,4 +83,4 @@ set(SOURCE_FILES
) )
use_townengine(${PROJECT_NAME} "${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR}) use_townengine(${PROJECT_NAME} "${SOURCE_FILES}" ${CMAKE_SOURCE_DIR})

211
apps/twnlua/bindgen.py Executable file
View File

@ -0,0 +1,211 @@
#!/bin/env python3
import sys, json
with open(sys.argv[1], 'r') if sys.argv[1] != "-" else sys.stdin as f:
api_source = f.read()
api = json.loads(api_source)
def default(parameter):
basetype = parameter["type"].rsplit(' *', 1)[0]
if parameter["type"] == "float":
return parameter["default"]
elif parameter["type"] == "bool":
return "true" if parameter["default"] else "false"
elif parameter["type"] == "char *":
if parameter["default"] == {}:
return "NULL"
else: return '"' + parameter["default"] + '"'
elif basetype in api["types"]:
if parameter["type"].endswith(" *"):
if parameter["default"] == {}:
return "NULL"
else:
return "&(%s){\n%s\n }" % \
(parameter["type"], ", \n".join(" .%s = %s" % (n, v) for n, v in parameter["default"].items()))
else:
return "(%s){\n%s\n }" % \
(parameter["type"], ", \n".join(" .%s = %s" % (n, v) for n, v in parameter["default"].items()))
raise BaseException("Unhandled default value of type '%s'" % parameter["type"])
def to_table(typedesc, variable, indent = 0):
binding = ' ' * indent + "lua_createtable(L, 0, %i);\n" % len(typedesc["fields"])
for field in typedesc["fields"]:
if field["type"] == "float" or field["type"] == "uint8_t":
binding += ' ' * indent + "lua_pushnumber(L, (double)(%s));\n" % (variable + ".%s" % field["name"])
elif field["type"] == "bool":
binding += ' ' * indent + "lua_pushboolean(L, (%s));\n" % (variable + ".%s" % field["name"])
elif field["type"] in api["types"]:
binding += to_table(api["types"][field["type"]], variable + ".%s" % field["name"], indent + 4)
else:
raise BaseException("Unhandled return field type '%s'" % (field["type"]))
binding += ' ' * indent + "lua_setfield(L, -2, \"%s\");\n" % field["name"]
return binding
def from_table(typedesc, variable, indent = 0):
binding = ""
for field in typedesc["fields"]:
binding += ' ' * indent + "lua_getfield(L, -1, \"%s\");\n" % field["name"]
if field["type"] == "float" or field["type"] == "uint8_t":
binding += ' ' * indent + "%s = (%s)lua_tonumber(L, -1);\n" % (variable + ".%s" % field["name"], field["type"])
elif field["type"] == "bool":
binding += ' ' * indent + "%s = lua_toboolean(L, -1);\n" % (variable + ".%s" % field["name"])
elif field["type"] in api["types"]:
binding += from_table(api["types"][field["type"]], variable + ".%s" % field["name"], indent + 4)
else:
raise BaseException("Unhandled return field type '%s'" % (field["type"]))
binding += ' ' * indent + "lua_pop(L, 1);\n"
return binding
print('#include "twn_game_api.h"\n')
# TODO: reuse implementation from the engine, this also breaks with statically compiled build
print('#define STB_DS_IMPLEMENTATION')
print('#include <stb_ds.h>')
print('#include <lua.h>')
print('#include <lualib.h>')
print('#include <lauxlib.h>\n')
bindings, used_converters = [], {}
for procedure, procedure_desc in api["procedures"].items():
binding = "static int binding_%s(lua_State *L) {\n" % procedure
binding += " luaL_checktype(L, 1, LUA_TTABLE);\n"
if "params" in procedure_desc:
for parameter in procedure_desc["params"]:
basetype = parameter["type"].rsplit(' *', 1)[0]
if parameter["type"].endswith("*") and not parameter["type"] == "char *":
binding += " %s %s_value;\n" % (basetype, parameter["name"])
binding += " %s %s;\n" % (parameter["type"] if not parameter["type"].endswith("*") else 'const ' + parameter["type"], parameter["name"])
binding += " lua_getfield(L, 1, \"%s\");\n" % parameter["name"]
if "default" in parameter and parameter["type"] != "float":
binding += " if (lua_isnoneornil(L, -1))\n"
binding += " %s = %s;\n" % (parameter["name"], default(parameter))
binding += " else\n "
if parameter["type"] == "float":
if "default" in parameter:
binding += " int is_%s_num;\n" % parameter["name"]
binding += " %s = (float)lua_tonumberx(L, -1, &is_%s_num);\n" % (parameter["name"], parameter["name"]);
binding += " if (!is_%s_num) %s = %s;\n" % (parameter["name"], parameter["name"], default(parameter))
else:
binding += " %s = (float)lua_tonumber(L, -1);\n" % (parameter["name"]);
elif parameter["type"] == "bool":
binding += " %s = lua_toboolean(L, -1);\n" % (parameter["name"]);
elif parameter["type"] == "char *":
binding += " %s = lua_tostring(L, -1);\n" % (parameter["name"]);
elif basetype in api["types"]:
used_converters[basetype] = api["types"][basetype]
if parameter["type"].endswith(" *"):
binding += " { %s_value = to_%s(L); %s = &%s_value; }\n" % (parameter["name"], basetype.lower(), parameter["name"], parameter["name"]);
else:
binding += " %s = to_%s(L);\n" % (parameter["name"], basetype.lower());
else:
raise BaseException("Unhandled parameter type '%s'" % (parameter["type"]))
if "return" in procedure_desc:
if procedure_desc["return"] == "bool":
binding += " lua_pushboolean(L, (int)(%s(%s)));\n" % (procedure, ", ".join(param["name"] for param in procedure_desc["params"]))
elif procedure_desc["return"] == "float":
binding += " lua_pushnumber(L, (double)(%s(%s)));\n" % (procedure, ", ".join(param["name"] for param in procedure_desc["params"]))
elif procedure_desc["return"] == "char *":
binding += " lua_pushstring(L, %s(%s));\n" % (procedure, ", ".join(param["name"] for param in procedure_desc["params"]))
elif type(procedure_desc["return"]) is dict or procedure_desc["return"] in api["types"]:
# TODO: handle enums
type_desc = procedure_desc["return"] if type(procedure_desc["return"]) is dict else api["types"][procedure_desc["return"]]
binding += " %s result = %s(%s);\n" % (type_desc["c_type"], procedure, ", ".join(param["name"] for param in procedure_desc["params"]))
binding += to_table(type_desc, "result", 4)
else:
raise BaseException("Unhandled return type '%s'" % (procedure_desc["return"]))
binding += " return 1;\n}\n"
else:
binding += " %s(%s);\n" % (procedure, ", ".join(param["name"] for param in procedure_desc["params"]))
binding += " return 0;\n}\n"
bindings += [binding]
storages, converters, initializers, deinitializers = [], [], [], []
for typename, typedesc in used_converters.items():
if "no_convert" in typedesc and typedesc["no_convert"]:
continue
converter = "static %s to_%s(lua_State *L) {\n" % (typename, typename.lower())
converter += " %s %s;\n" % (typename, typename.lower());
if "fields" in typedesc:
for field in typedesc["fields"]:
converter += " lua_getfield(L, -1, \"%s\");\n" % (field["name"]);
if field["type"] == "float":
converter += " %s.%s = (float)lua_tonumber(L, -1);\n" % (typename.lower(), field["name"]);
elif field["type"] == "uint8_t":
converter += " %s.%s = (uint8_t)lua_tointeger(L, -1);\n" % (typename.lower(), field["name"]);
else:
raise BaseException("Unhandled converter field type '%s'" % (field["type"]))
converter += " lua_pop(L, 1);\n";
# TODO: wild idea: use compile time built hash table
elif "enums" in typedesc:
storages += ["struct %sHashItem { char *key; %s value; };\nstatic struct %sHashItem *%s_map = NULL;\n" % (typename, typename, typename, typename.lower())]
# TODO: use arena
for enum in typedesc["enums"]:
initializer = " shput(%s_map, \"%s\", %s);" % (typename.lower(), enum, typedesc["enums"][enum])
initializers += [initializer]
deinitializers += [" shfree(%s_map);" % typename.lower()]
converter += " char const *value = lua_tostring(L, -1);\n";
converter += " %s = shget(%s_map, value);\n" % (typename.lower(), typename.lower())
converter += " lua_pop(L, 1);\n";
converter += " return %s;\n}\n" % (typename.lower())
converters += [converter]
print('\n'.join(storages))
print("extern void bindgen_init(void);\n")
print("void bindgen_init(void) {\n" + '\n'.join(initializers) + "\n}\n")
print('\n'.join(converters))
print('\n'.join(bindings))
loader = "extern void bindgen_load_%s(lua_State *L);\n" % api["name"]
loader += "void bindgen_load_%s(lua_State *L) {\n" % api["name"]
loader += " bindgen_init();\n"
for procedure, procedure_desc in api["procedures"].items():
loader += " lua_pushcfunction(L, binding_%s);\n" % procedure
loader += " lua_setglobal(L, \"%s\");\n" % procedure
loader += "}\n"
print(loader)
unloader = "extern void bindgen_unload_%s(lua_State *L);\n" % api["name"]
unloader += "void bindgen_unload_%s(lua_State *L) {\n (void)L;\n" % api["name"]
unloader += '\n'.join(deinitializers)
unloader += "\n}\n"
print(unloader)
# exceptions for the base townengine api
# TODO: is there a way to generalize it? or rather, is there any need to do so?
if api["name"] == "twn":
contexter = "extern void bindgen_build_context(lua_State *L);\n"
contexter += "void bindgen_build_context(lua_State *L) {\n"
contexter += to_table(api["types"]["Context"], "ctx", 4)
contexter += "}\n\n"
contexter += "extern void bindgen_upload_context(lua_State *L);\n"
contexter += "void bindgen_upload_context(lua_State *L) {\n"
contexter += from_table(api["types"]["Context"], "ctx", 4)
contexter += "}"
print(contexter)

View File

@ -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 {
position = offset, string = "it never happened",
font = "/fonts/kenney-pixel.ttf", position = offset,
} 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)

View File

@ -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]

View File

@ -10,6 +10,13 @@
#include <malloc.h> #include <malloc.h>
/* generated by bindgen.py */
void bindgen_load_twn(lua_State *L);
void bindgen_unload_twn(lua_State *L);
void bindgen_build_context(lua_State *L);
void bindgen_upload_context(lua_State *L);
/* require will go through physicsfs exclusively so that scripts can be in the data dir */ /* require will go through physicsfs exclusively so that scripts can be in the data dir */
static int physfs_loader(lua_State *L) { static int physfs_loader(lua_State *L) {
const char *name = luaL_checkstring(L, 1); const char *name = luaL_checkstring(L, 1);
@ -37,198 +44,46 @@ static int physfs_loader(lua_State *L) {
} }
static Rect table_to_rect(lua_State *L, int idx, const char *name) { /* WARN! experimental and will probably be removed */
/* types are checked here to help prevent unexpected results */ /* it is an attempt to ease memory usage problems posed by using lua, partially successful */
Rect rect; static void *custom_alloc(void *ud, void *ptr, size_t osize, size_t nsize) {
int is_num; (void)ud;
lua_getfield(L, idx, "x"); /* small allocations are placed in slots, as there's a big chance they will not need to be resized */
rect.x = (float)lua_tonumberx(L, -1, &is_num); static char slots[1024][128];
if (!is_num) static int16_t free_slots[1024] = { [0] = -1 };
luaL_error(L, "bad field 'x' in '%s' (number expected, got %s)", name, luaL_typename(L, -1)); static size_t free_slot_count = 1024;
lua_pop(L, 1);
lua_getfield(L, idx, "y"); if (free_slots[0] == -1)
rect.y = (float)lua_tonumberx(L, -1, &is_num); for (int i = 0; i < 1024; i++)
if (!is_num) free_slots[i] = (int16_t)i;
luaL_error(L, "bad field 'y' in '%s' (number expected, got %s)", name, luaL_typename(L, -1));
lua_pop(L, 1);
lua_getfield(L, idx, "w"); if (nsize == 0) {
rect.w = (float)lua_tonumberx(L, -1, &is_num); if (ptr && (char *)ptr >= &slots[0][0] && (char *)ptr <= &slots[1024-1][128-1])
if (!is_num) free_slots[free_slot_count++] = (int16_t)(((uintptr_t)ptr - (uintptr_t)slots) / 128);
luaL_error(L, "bad field 'w' in '%s' (number expected, got %s)", name, luaL_typename(L, -1)); else if (osize)
lua_pop(L, 1); SDL_free(ptr);
return NULL;
} else {
if (!ptr && nsize <= 128 && free_slot_count > 0) {
/* use a slot */
return slots[free_slots[--free_slot_count]];
}
lua_getfield(L, idx, "h"); if ((char *)ptr >= &slots[0][0] && (char *)ptr <= &slots[1024-1][128-1]) {
rect.h = (float)lua_tonumberx(L, -1, &is_num); /* still fits */
if (!is_num) if (nsize <= 128)
luaL_error(L, "bad field 'h' in '%s' (number expected, got %s)", name, luaL_typename(L, -1)); return ptr;
lua_pop(L, 1);
return rect; /* move from slot to dynamic memory */
} void *mem = SDL_malloc(nsize);
SDL_memcpy(mem, ptr, osize);
free_slots[free_slot_count++] = (int16_t)(((uintptr_t)ptr - (uintptr_t)slots) / 128);
return mem;
}
return SDL_realloc(ptr, nsize);
static Color table_to_color(lua_State *L, int idx) {
Color color;
lua_getfield(L, idx, "r");
color.r = (uint8_t)lua_tointeger(L, -1);
lua_pop(L, 1);
lua_getfield(L, idx, "g");
color.g = (uint8_t)lua_tointeger(L, -1);
lua_pop(L, 1);
lua_getfield(L, idx, "b");
color.b = (uint8_t)lua_tointeger(L, -1);
lua_pop(L, 1);
lua_getfield(L, idx, "a");
color.a = (uint8_t)lua_tointeger(L, -1);
lua_pop(L, 1);
return color;
}
/* sprite(data [table]) */
/* data should contain the following fields: */
/*
* path [string]
* rect [table]
* texture_region [table], optional
* color [table], optional
* rotation [number], optional
* flip_x [boolean], optional
* flip_y [boolean], optional
* stretch [boolean], optional
*/
static int b_sprite(lua_State *L) {
luaL_checktype(L, 1, LUA_TTABLE);
DrawSpriteArgs args = { 0 };
lua_getfield(L, 1, "path");
const char *field_path = lua_tostring(L, -1);
if (field_path == NULL)
luaL_error(L, "bad field 'path' in 'data' (string expected, got %s)", luaL_typename(L, -1));
args.path = field_path;
lua_getfield(L, 1, "rect");
if (!lua_istable(L, -1))
luaL_error(L, "bad field 'rect' in 'data' (table expected, got %s)", luaL_typename(L, -1));
args.rect = table_to_rect(L, -1, "data.rect");
lua_getfield(L, 1, "texture_region");
if (lua_istable(L, -1)) {
args.texture_region_opt = table_to_rect(L, -1, "data.texture_region");
args.texture_region_opt_set = true;
} }
lua_getfield(L, 1, "color");
if (lua_istable(L, -1)) {
args.color_opt = table_to_color(L, -1);
args.color_opt_set = true;
}
lua_getfield(L, 1, "rotation");
if (lua_isnumber(L, -1)) {
args.rotation_opt = (float)lua_tonumber(L, -1);
args.rotation_opt_set = true;
}
lua_getfield(L, 1, "flip_x");
if (lua_isboolean(L, -1)) {
args.flip_x_opt = lua_toboolean(L, -1);
args.flip_x_opt_set = true;
}
lua_getfield(L, 1, "flip_y");
if (lua_isboolean(L, -1)) {
args.flip_y_opt = lua_toboolean(L, -1);
args.flip_y_opt_set = true;
}
lua_getfield(L, 1, "stretch");
if (lua_isboolean(L, -1)) {
args.stretch_opt = lua_toboolean(L, -1);
args.stretch_opt_set = true;
}
draw_sprite_args(args);
return 0;
}
/* rectangle(data [table]) */
/* data should contain the following fields: */
/*
* rect [table]
* color [table], optional, defaults to white
*/
static int b_rectangle(lua_State *L) {
luaL_checktype(L, 1, LUA_TTABLE);
lua_getfield(L, 1, "rect");
if (!lua_istable(L, -1))
luaL_error(L, "bad field 'rect' in 'data' (table expected, got %s)", luaL_typename(L, -1));
Rect rect = table_to_rect(L, -1, "data.rect");
Color color = { 255, 255, 255, 255 };
lua_getfield(L, 1, "color");
if (lua_istable(L, -1))
color = table_to_color(L, -1);
draw_rectangle(rect, color);
return 0;
}
/* text(data [table]) */
/* data should contain the following fields: */
/*
* string [string]
* position [table]
* height_px [number], optional, defaults to 22
* color [table], optional, defaults to black
* font [string]
*/
static int b_text(lua_State *L) {
luaL_checktype(L, 1, LUA_TTABLE);
lua_getfield(L, 1, "string");
const char *string = lua_tostring(L, -1);
if (string == NULL)
luaL_error(L, "bad field 'string' in 'data' (string expected, got %s)", luaL_typename(L, -1));
lua_getfield(L, 1, "position");
if (!lua_istable(L, -1))
luaL_error(L, "bad field 'position' in 'data' (table expected, got %s)", luaL_typename(L, -1));
lua_getfield(L, -1, "x");
float x = (float)lua_tonumber(L, -1);
lua_getfield(L, -2, "y");
float y = (float)lua_tonumber(L, -1);
lua_getfield(L, 1, "height_px");
int is_num;
int height_px = (int)lua_tointegerx(L, -1, &is_num);
if (!is_num)
height_px = 22;
lua_getfield(L, 1, "color");
Color color = { 0, 0, 0, 255 };
if (lua_istable(L, -1))
color = table_to_color(L, -1);
lua_getfield(L, 1, "font");
const char *font = lua_tostring(L, -1);
if (font == NULL)
luaL_error(L, "bad field 'font' in 'data' (string expected, got %s)", luaL_typename(L, -1));
draw_text(string, (Vec2) { x, y }, height_px, color, font);
return 0;
} }
@ -246,14 +101,15 @@ void game_tick(void) {
} }
state->L = luaL_newstate(); state->L = luaL_newstate();
/* fakey version of luaL_openlibs() that excludes file i/o */ lua_setallocf(state->L, custom_alloc, NULL);
/* fakey version of luaL_openlibs() that excludes file i/o and os stuff */
{ {
static const luaL_Reg loaded_libs[] = { static const luaL_Reg loaded_libs[] = {
{ LUA_GNAME, luaopen_base }, { LUA_GNAME, luaopen_base },
{ LUA_LOADLIBNAME, luaopen_package }, { LUA_LOADLIBNAME, luaopen_package },
{ LUA_COLIBNAME, luaopen_coroutine }, { LUA_COLIBNAME, luaopen_coroutine },
{ LUA_TABLIBNAME, luaopen_table }, { LUA_TABLIBNAME, luaopen_table },
{ LUA_OSLIBNAME, luaopen_os },
{ LUA_STRLIBNAME, luaopen_string }, { LUA_STRLIBNAME, luaopen_string },
{ LUA_MATHLIBNAME, luaopen_math }, { LUA_MATHLIBNAME, luaopen_math },
{ LUA_UTF8LIBNAME, luaopen_utf8 }, { LUA_UTF8LIBNAME, luaopen_utf8 },
@ -269,7 +125,7 @@ void game_tick(void) {
/* package.searchers = { physfs_loader } */ /* package.searchers = { physfs_loader } */
lua_getglobal(state->L, "package"); lua_getglobal(state->L, "package");
lua_newtable(state->L); lua_createtable(state->L, 0, 1);
lua_setfield(state->L, -2, "searchers"); lua_setfield(state->L, -2, "searchers");
lua_getfield(state->L, -1, "searchers"); lua_getfield(state->L, -1, "searchers");
@ -280,9 +136,11 @@ void game_tick(void) {
lua_pop(state->L, 2); lua_pop(state->L, 2);
/* binding */ /* binding */
lua_register(state->L, "sprite", b_sprite); // lua_register(state->L, "sprite", b_sprite);
lua_register(state->L, "rectangle", b_rectangle); // lua_register(state->L, "rectangle", b_rectangle);
lua_register(state->L, "text", b_text); // lua_register(state->L, "text", b_text);
bindgen_load_twn(state->L);
/* now finally get to running the code */ /* now finally get to running the code */
unsigned char *game_buf = NULL; unsigned char *game_buf = NULL;
@ -299,16 +157,29 @@ void game_tick(void) {
State *state = ctx.udata; State *state = ctx.udata;
bindgen_build_context(state->L);
lua_getglobal(state->L, "ctx");
if (!lua_isnoneornil(state->L, -1)) {
lua_getfield(state->L, -1, "udata");
lua_setfield(state->L, -3, "udata");
}
lua_pop(state->L, 1);
lua_setglobal(state->L, "ctx");
lua_getglobal(state->L, "game_tick"); lua_getglobal(state->L, "game_tick");
if (lua_pcall(state->L, 0, 0, 0) != LUA_OK) { if (lua_pcall(state->L, 0, 0, 0) != LUA_OK) {
log_critical("%s", lua_tostring(state->L, -1)); log_critical("%s", lua_tostring(state->L, -1));
lua_pop(state->L, 1); lua_pop(state->L, 1);
} }
lua_getglobal(state->L, "ctx");
bindgen_upload_context(state->L);
} }
void game_end(void) { void game_end(void) {
State *state = ctx.udata; State *state = ctx.udata;
bindgen_unload_twn(state->L);
lua_close(state->L); lua_close(state->L);
free(state); free(state);
} }

View File

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

View File

@ -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

View File

@ -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

View File

@ -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

Binary file not shown.

16
docs/interop.md Normal file
View File

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

View File

@ -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
View File

@ -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]}"))

View File

@ -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);

View File

@ -16,21 +16,25 @@ typedef struct Context {
void *udata; void *udata;
/* which frame is it, starting from 0 at startup */ /* which frame is it, starting from 0 at startup */
uint64_t frame_number; float frame_number;
/* real time spent on one frame (in seconds) */ /* real time spent on one frame (in seconds) */
/* townengine is fixed step based, so you don't have */ /* townengine is fixed step based, so you don't have to use delta */
/* TODO: actually set it */ /* TODO: actually set it */
float frame_duration; float frame_duration;
/* it is disabled by having fog_density approximately equal to zero */
float fog_density;
Color fog_color;
/* resolution is set from config and dictates both logical and drawing space, as they're related */ /* resolution is set from config and dictates both logical and drawing space, as they're related */
/* even if scaling is done, game logic should never change over that */ /* even if scaling is done, game logic should never change over that */
Vec2i resolution; Vec2 resolution;
Vec2i mouse_position; Vec2 mouse_position;
Vec2i mouse_movement; Vec2 mouse_movement;
/* is set on startup, should be used as source of randomness */ /* is set on startup, should be used as source of randomness */
uint64_t random_seed; float random_seed;
/* whether debugging logic should be enabled in user code */ /* whether debugging logic should be enabled in user code */
bool debug; bool debug;
@ -38,6 +42,7 @@ typedef struct Context {
/* is set to true when state is invalidated and needs to be rebuilt */ /* is set to true when state is invalidated and needs to be rebuilt */
/* watch for it and handle properly! */ /* watch for it and handle properly! */
bool initialization_needed; bool initialization_needed;
bool mouse_capture;
} Context; } Context;
/* when included after twn_engine_context there's an 'ctx' defined already */ /* when included after twn_engine_context there's an 'ctx' defined already */

View File

@ -3,20 +3,19 @@
#include "twn_types.h" #include "twn_types.h"
#include "twn_option.h" #include "twn_option.h"
#include "twn_camera.h"
#include "twn_engine_api.h" #include "twn_engine_api.h"
#include <stdbool.h> #include <stdbool.h>
/* pushes a sprite onto the sprite render queue */ /* pushes a sprite onto the sprite render queue */
TWN_API void draw_sprite(char const *path, TWN_API void draw_sprite(char const *texture,
Rect rect, Rect rect,
Rect const *texture_region, /* optional, default: NULL */ Rect const *texture_region, /* optional, default: NULL */
Color color, /* optional, default: all 255 */ Color color, /* optional, default: all 255 */
float rotation, /* optional, default: 0 */ float rotation, /* optional, default: 0 */
bool flip_x, /* optional, default: false */ bool flip_x, /* optional, default: false */
bool flip_y, /* optional, default: false */ bool flip_y, /* optional, default: false */
bool stretch); /* optional, default: false */ bool stretch); /* optional, default: true */
/* pushes a filled rectangle onto the rectangle render queue */ /* pushes a filled rectangle onto the rectangle render queue */
TWN_API void draw_rectangle(Rect rect, Color color); TWN_API void draw_rectangle(Rect rect, Color color);
@ -28,67 +27,81 @@ TWN_API void draw_circle(Vec2 position, float radius, Color color);
/* TODO: have font optional, with something minimal coming embedded */ /* TODO: have font optional, with something minimal coming embedded */
TWN_API void draw_text(char const *string, TWN_API void draw_text(char const *string,
Vec2 position, Vec2 position,
int height_px, /* optional, default: 22 */ float height, /* optional, default: 22 */
Color color, /* optional, default: all 0 */ Color color, /* optional, default: all 0 */
char const *font); char const *font); /* optional, default: NULL */
TWN_API int draw_text_width(char const *string, TWN_API float draw_text_width(char const *string,
int height_px, /* TODO: make optional */ float height, /* optional, default: 22 */
char const *font); char const *font); /* optional, default: NULL */
TWN_API void draw_9slice(char const *texture_path, TWN_API void draw_nine_slice(char const *texture,
int texture_w, Vec2 corners,
int texture_h, Rect rect,
int border_thickness, float border_thickness, /* optional, default: 0 */
Rect rect, Color color); /* optional, default: all 255 */
Color color); /* TODO: make optional */
TWN_API void draw_line(Vec2 start,
Vec2 finish,
float thickness, /* optional, default: 1 */
Color color); /* optional, default: all 255 */
TWN_API void draw_box(Rect rect,
float thickness, /* optional, default: 1 */
Color color); /* optional, default: all 255 */
/* pushes a textured 3d triangle onto the render queue */ /* pushes a textured 3d triangle onto the render queue */
/* vertices are in absolute coordinates, relative to world origin */
/* texture coordinates are in pixels */ /* texture coordinates are in pixels */
TWN_API void draw_triangle(char const *path, TWN_API void draw_triangle(char const *texture,
Vec3 v0, Vec3 v0,
Vec3 v1, Vec3 v1,
Vec3 v2, Vec3 v2,
Vec2 uv0, Vec2 uv0,
Vec2 uv1, Vec2 uv1,
Vec2 uv2); Vec2 uv2,
Color c0, /* optional, default: all 255 */
Color c1, /* optional, default: all 255 */
Color c2); /* optional, default: all 255 */
// TODO: decide whether it's needed to begin with? TWN_API void draw_quad(char const *texture,
// intended usage for it is baked lighting, i would think. Vec3 v0, /* upper-left */
/* pushes a colored textured 3d triangle onto the render queue */ Vec3 v1, /* bottom-left */
// void unfurl_colored_triangle(const char *path, Vec3 v2, /* bottom-right */
// Vec3 v0, Vec3 v3, /* upper-right */
// Vec3 v1, Rect texture_region,
// Vec3 v2, Color color); /* optional, default: all 255 */
// Vec2sh uv0,
// Vec2sh uv1,
// Vec2sh uv2,
// Color c0,
// Color c1,
// Color c2);
// TODO: TWN_API void draw_billboard(const char *texture,
// http://www.lighthouse3d.com/opengl/billboarding/index.php?billCheat2 Vec3 position,
// void unfurl_billboard(const char *path, Vec2 size,
// Vec2 position, Color color, /* optional, default: all 255 */
// Vec2 scaling, bool cylindrical); /* optional, default: false */
// Rect uvs);
/* pushes a camera state to be used for all future unfurl_* commands */ /* sets a perspective 3d camera to be used for all 3d commands */
TWN_API void draw_camera(const Camera *camera); TWN_API void draw_camera(Vec3 position, float fov, Vec3 up, Vec3 direction);
/* same as draw_camera(), but with first person controller in mind */
/* direction and up vectors are inferred from roll, pitch and yaw parameters (in radians) */
/* return value is direction and up vectors, so that you can use them in logic (such as controllers) */
typedef struct DrawCameraFromPrincipalAxesResult {
Vec3 direction;
Vec3 up;
} DrawCameraFromPrincipalAxesResult;
TWN_API DrawCameraFromPrincipalAxesResult draw_camera_from_principal_axes(Vec3 position,
float fov,
float roll,
float pitch,
float yaw);
/* expects '*' masks that will be expanded to 6 names: 'up', 'down', 'east', 'west', 'north' and 'south' */ /* expects '*' masks that will be expanded to 6 names: 'up', 'down', 'east', 'west', 'north' and 'south' */
TWN_API void draw_skybox(const char *paths); TWN_API void draw_skybox(const char *textures);
TWN_API void draw_fog(float start, float end, float density, Color color);
#ifndef TWN_NOT_C #ifndef TWN_NOT_C
typedef struct DrawSpriteArgs { typedef struct DrawSpriteArgs {
char const *path; char const *texture;
Rect rect; Rect rect;
m_option_list( m_option_list(

View File

@ -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")))

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

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

View File

@ -9,13 +9,28 @@
#include <math.h> #include <math.h>
/* aren't macros to prevent double evaluation with side effects */ static inline Vec2 vec2_add(Vec2 a, Vec2 b) {
/* maybe could be inlined? i hope LTO will resolve this */ return (Vec2) { a.x + b.x, a.y + b.y };
static inline Vec2 vec2_from_vec2i(Vec2i vec) { }
return (Vec2) {
.x = (float)vec.x, static inline Vec2 vec2_sub(Vec2 a, Vec2 b) {
.y = (float)vec.y, return (Vec2) { a.x - b.x, a.y - b.y };
}; }
static inline Vec2 vec2_div(Vec2 a, Vec2 b) {
return (Vec2) { a.x / b.x, a.y / b.y };
}
static inline Vec2 vec2_mul(Vec2 a, Vec2 b) {
return (Vec2) { a.x * b.x, a.y * b.y };
}
static inline Vec2 vec2_scale(Vec2 a, float s) {
return (Vec2) { a.x * s, a.y * s };
}
static inline float vec2_length(Vec2 a) {
return sqrtf(a.x * a.x + a.y * a.y);
} }
static inline Vec3 vec3_add(Vec3 a, Vec3 b) { static inline Vec3 vec3_add(Vec3 a, Vec3 b) {
@ -26,12 +41,12 @@ static inline Vec3 vec3_sub(Vec3 a, Vec3 b) {
return (Vec3) { a.x - b.x, a.y - b.y, a.z - b.z }; return (Vec3) { a.x - b.x, a.y - b.y, a.z - b.z };
} }
static inline Vec2 vec2_div(Vec2 a, Vec2 b) { static inline Vec3 vec3_div(Vec3 a, Vec3 b) {
return (Vec2) { a.x / b.x, a.y / b.y }; return (Vec3) { a.x / b.x, a.y / b.y, a.z / b.z };
} }
static inline Vec2 vec2_scale(Vec2 a, float s) { static inline Vec3 vec3_mul(Vec3 a, Vec3 b) {
return (Vec2) { a.x * s, a.y * s }; return (Vec3) { a.x * b.x, a.y * b.y, a.z * b.z };
} }
static inline Vec3 vec3_scale(Vec3 a, float s) { static inline Vec3 vec3_scale(Vec3 a, float s) {
@ -42,6 +57,10 @@ static inline float vec3_dot(Vec3 a, Vec3 b) {
return a.x * b.x + a.y * b.y + a.z * b.z; return a.x * b.x + a.y * b.y + a.z * b.z;
} }
static inline float vec3_length(Vec3 a) {
return sqrtf(a.x * a.x + a.y * a.y + a.z * a.z);
}
static inline Vec3 vec3_cross(Vec3 a, Vec3 b) { static inline Vec3 vec3_cross(Vec3 a, Vec3 b) {
return (Vec3) { return (Vec3) {
a.y * b.z - a.z * b.y, a.y * b.z - a.z * b.y,
@ -83,16 +102,26 @@ static inline Vec3 vec3_rotate(Vec3 v, float angle, Vec3 axis) {
return v; return v;
} }
#define m_vec2_from(p_any_vec2) (_Generic((p_any_vec2), \
Vec2i: vec2_from_vec2i, \ /* TODO: remove. */
)(p_any_vec2)) #define m_vec_add(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \
Vec2: vec2_add, \
Vec3: vec3_add \
)(p_any_vec0, p_any_vec1))
#define m_vec_sub(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \ #define m_vec_sub(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \
Vec2: vec2_sub, \
Vec3: vec3_sub \ Vec3: vec3_sub \
)(p_any_vec0, p_any_vec1)) )(p_any_vec0, p_any_vec1))
#define m_vec_div(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \ #define m_vec_div(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \
Vec2: vec2_div \ Vec2: vec2_div, \
Vec3: vec3_div \
)(p_any_vec0, p_any_vec1))
#define m_vec_mul(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \
Vec2: vec2_mul, \
Vec3: vec3_mul \
)(p_any_vec0, p_any_vec1)) )(p_any_vec0, p_any_vec1))
#define m_vec_scale(p_any_vec, p_any_scalar) (_Generic((p_any_vec), \ #define m_vec_scale(p_any_vec, p_any_scalar) (_Generic((p_any_vec), \

533
share/twn_api.json Normal file
View File

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

View File

@ -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;

View File

@ -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>

View File

@ -0,0 +1,185 @@
#include "twn_draw.h"
#include "twn_draw_c.h"
#include "twn_engine_context_c.h"
#include "twn_textures_c.h"
#include "twn_types.h"
#include "twn_util_c.h"
#include "twn_vec.h"
#include <stb_ds.h>
void draw_billboard(const char *texture,
Vec3 position,
Vec2 size,
Color color,
bool cylindrical)
{
// TODO: order drawing by atlas id as well, so that texture rebinding is not as common
const TextureKey texture_key = textures_get_key(&ctx.texture_cache, texture);
struct MeshBatchItem *batch_p = hmgetp_null(ctx.billboard_batches, texture_key);
if (!batch_p) {
struct MeshBatch item = {0};
hmput(ctx.billboard_batches, texture_key, item);
batch_p = &ctx.billboard_batches[hmlenu(ctx.billboard_batches) - 1]; /* TODO: can last index be used? */
}
struct SpaceBillboard billboard = {
.color = color,
.cylindrical = cylindrical,
.position = position,
.size = size,
};
struct SpaceBillboard *billboards = (struct SpaceBillboard *)(void *)batch_p->value.primitives;
arrpush(billboards, billboard);
batch_p->value.primitives = (uint8_t *)billboards;
}
/* reused for all billboards */
static Vec3 right_plus_up;
static Vec3 right_minus_up;
static Vec3 right_plus_up_cylindrical;
static Vec3 right_minus_up_cylindrical;
/* precalculate (right + up) and (right - up) that are used with all batches */
static void calculate_intermediates(void) {
Vec3 const right = { camera_look_at_matrix.row[0].x, camera_look_at_matrix.row[1].x, camera_look_at_matrix.row[2].x };
Vec3 const up = { camera_look_at_matrix.row[0].y, camera_look_at_matrix.row[1].y, camera_look_at_matrix.row[2].y };
right_plus_up = m_vec_add(right, up);
right_minus_up = m_vec_sub(right, up);
Vec3 const up_cylindrical = { 0, 1, 0 };
right_plus_up_cylindrical = m_vec_add(right, up_cylindrical);
right_minus_up_cylindrical = m_vec_sub(right, up_cylindrical);
}
/* http://www.lighthouse3d.com/opengl/billboarding/index.php?billCheat2 */
void finally_draw_billboard_batch(struct MeshBatch const *batch,
TextureKey texture_key)
{
const size_t primitives_len = arrlenu(batch->primitives);
/* nothing to do */
if (primitives_len == 0)
return;
/* TODO: only do it once per frame */
calculate_intermediates();
const Rect srcrect = textures_get_srcrect(&ctx.texture_cache, texture_key);
const Rect dims = textures_get_dims(&ctx.texture_cache, texture_key);
const float wr = srcrect.w / dims.w;
const float hr = srcrect.h / dims.h;
const float xr = srcrect.x / dims.w;
const float yr = srcrect.y / dims.h;
const Vec2 uv0 = { xr + wr, yr };
const Vec2 uv1 = { xr + wr, yr + hr };
const Vec2 uv2 = { xr, yr + hr };
const Vec2 uv3 = { xr, yr };
for (size_t batch_n = 0; batch_n <= (primitives_len - 1) / QUAD_ELEMENT_BUFFER_LENGTH; batch_n++) {
/* emit vertex data */
VertexBuffer const buffer = get_scratch_vertex_array();
VertexBufferBuilder builder = build_vertex_buffer(
buffer,
sizeof (ElementIndexedBillboard) * MIN(primitives_len - batch_n * QUAD_ELEMENT_BUFFER_LENGTH, QUAD_ELEMENT_BUFFER_LENGTH));
for (size_t i = 0; i < primitives_len; ++i) {
struct SpaceBillboard const billboard = ((SpaceBillboard *)(void *)batch->primitives)[batch_n * QUAD_ELEMENT_BUFFER_LENGTH + i];
/* a = (right + up) * size, b = (right - up) * size*/
Vec3 a, b;
if (billboard.cylindrical) {
a = vec3_mul(right_plus_up_cylindrical, ((Vec3){billboard.size.x, billboard.size.y, billboard.size.x }));
b = vec3_mul(right_minus_up_cylindrical, ((Vec3){billboard.size.x, billboard.size.y, billboard.size.x }));
} else {
a = vec3_mul(right_plus_up, ((Vec3){billboard.size.x, billboard.size.y, billboard.size.x }));
b = vec3_mul(right_minus_up, ((Vec3){billboard.size.x, billboard.size.y, billboard.size.x }));
}
struct ElementIndexedBillboard const payload = {
/* flat shading is assumed, so we can skip setting the duplicates */
.c0 = billboard.color,
// .c1 = billboard.color,
.c2 = billboard.color,
// .c3 = billboard.color,
.uv0 = uv0,
.uv1 = uv1,
.uv2 = uv2,
.uv3 = uv3,
.v0 = vec3_sub(billboard.position, b),
.v1 = vec3_sub(billboard.position, a),
.v2 = vec3_add(billboard.position, b),
.v3 = vec3_add(billboard.position, a),
};
((struct ElementIndexedBillboard *)builder.base)[i] = payload;
}
finish_vertex_builder(&builder);
/* commit to drawing */
DeferredCommandDraw command = {0};
command.vertices = (AttributeArrayPointer) {
.arity = 3,
.type = TWN_FLOAT,
.stride = offsetof(ElementIndexedBillboard, v1),
.offset = offsetof(ElementIndexedBillboard, v0),
.buffer = buffer
};
command.texcoords = (AttributeArrayPointer) {
.arity = 2,
.type = TWN_FLOAT,
.stride = offsetof(ElementIndexedBillboard, v1),
.offset = offsetof(ElementIndexedBillboard, uv0),
.buffer = buffer
};
command.colors = (AttributeArrayPointer) {
.arity = 4,
.type = TWN_UNSIGNED_BYTE,
.stride = offsetof(ElementIndexedBillboard, v1),
.offset = offsetof(ElementIndexedBillboard, c0),
.buffer = buffer
};
command.textured = true;
command.texture_key = texture_key;
command.element_buffer = get_quad_element_buffer();
command.element_count = 6 * (uint32_t)primitives_len;
command.range_end = 6 * (uint32_t)primitives_len;
/* TODO: support alpha blended case, with distance sort */
TextureMode mode = textures_get_mode(&ctx.texture_cache, texture_key);
if (mode == TEXTURE_MODE_GHOSTLY)
mode = TEXTURE_MODE_SEETHROUGH;
command.texture_mode = mode;
command.pipeline = PIPELINE_SPACE;
command.depth_range_high = depth_range_high;
command.depth_range_low = depth_range_low;
DeferredCommand final_command = {
.type = DEFERRED_COMMAND_TYPE_DRAW,
.draw = command
};
arrpush(deferred_commands, final_command);
}
}

View File

@ -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);
} }

View 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

View File

@ -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; for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i) {
finally_draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches[i].value,
ctx.uncolored_mesh_batches[i].key);
}
use_space_pipeline(); for (size_t i = 0; i < hmlenu(ctx.billboard_batches); ++i) {
apply_fog(); finally_draw_billboard_batch(&ctx.billboard_batches[i].value, ctx.billboard_batches[i].key);
}
for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i) {
draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches[i].value,
ctx.uncolored_mesh_batches[i].key);
} }
pop_fog(); render_skybox(); /* after everything else, as to use depth buffer for early z rejection */
} }
@ -353,37 +403,143 @@ void render(void) {
/* fit rendering context onto the resizable screen */ /* fit rendering context onto the resizable screen */
if (ctx.window_size_has_changed) { if (ctx.window_size_has_changed) {
if ((float)ctx.window_dims.x / (float)ctx.window_dims.y > (float)ctx.base_render_width / (float)ctx.base_render_height) { setup_viewport((int)ctx.viewport_rect.x, (int)ctx.viewport_rect.y, (int)ctx.viewport_rect.w, (int)ctx.viewport_rect.h);
float ratio = (float)ctx.window_dims.y / (float)ctx.base_render_height;
int w = (int)((float)ctx.base_render_width * ratio);
setup_viewport(
ctx.window_dims.x / 2 - w / 2,
0,
w,
ctx.window_dims.y
);
} else {
float ratio = (float)ctx.window_dims.x / (float)ctx.base_render_width;
int h = (int)((float)ctx.base_render_height * ratio);
setup_viewport(
0,
ctx.window_dims.y / 2 - h / 2,
ctx.window_dims.x,
h
);
}
} }
start_render_frame(); { start_render_frame(); {
render_space(); render_space();
render_skybox(); /* after space, as to use depth buffer for early z rejection */
render_2d(); render_2d();
} end_render_frame(); } end_render_frame();
} }
void draw_camera(const Camera *const camera) { void draw_camera(Vec3 position, float fov, Vec3 up, Vec3 direction) {
/* TODO: skip recaulculating if it's the same? */ if (fabsf(0.0f - fov) < 0.00001f || fov >= M_PIf)
camera_projection_matrix = camera_perspective(camera); log_warn("Invalid fov given (%f)", (double)fov);
camera_look_at_matrix = camera_look_at(camera);
Camera const camera = {
.fov = fov,
.pos = position,
.target = direction,
.up = up,
};
camera_projection_matrix = camera_perspective(&camera);
camera_look_at_matrix = camera_look_at(&camera);
}
/* TODO: https://stackoverflow.com/questions/62493770/how-to-add-roll-in-camera-class */
DrawCameraFromPrincipalAxesResult draw_camera_from_principal_axes(Vec3 position, float fov, float roll, float pitch, float yaw) {
if (fabsf(0.0f - fov) < 0.00001f || fov >= M_PIf)
log_warn("Invalid fov given (%f)", (double)fov);
(void)roll;
float yawc, yaws, pitchc, pitchs;
sincosf(yaw, &yaws, &yawc);
sincosf(pitch, &pitchs, &pitchc);
Camera const camera = {
.fov = fov,
.pos = position,
.target = m_vec_norm(((Vec3){
yawc * pitchc,
pitchs,
yaws * pitchc,
})),
.up = (Vec3){0, 1, 0},
};
camera_projection_matrix = camera_perspective(&camera);
camera_look_at_matrix = camera_look_at(&camera);
return (DrawCameraFromPrincipalAxesResult) {
.direction = camera.target,
.up = camera.up,
};
}
void set_depth_range(double low, double high) {
depth_range_low = low;
depth_range_high = high;
}
void clear_draw_buffer(void) {
/* TODO: we can optimize a rectangle drawn over whole window to a clear color call*/
DeferredCommand command = {
.type = DEFERRED_COMMAND_TYPE_CLEAR,
.clear = (DeferredCommandClear) {
.clear_color = true,
.clear_depth = true,
.clear_stencil = true,
.color = (Color) { 230, 230, 230, 1 }
}
};
arrpush(deferred_commands, command);
}
void issue_deferred_draw_commands(void) {
for (size_t i = 0; i < arrlenu(deferred_commands); ++i) {
switch (deferred_commands[i].type) {
case DEFERRED_COMMAND_TYPE_CLEAR: {
finally_clear_draw_buffer(deferred_commands[i].clear);
break;
}
case DEFERRED_COMMAND_TYPE_DRAW: {
finally_draw_command(deferred_commands[i].draw);
break;
}
case DEFERRED_COMMAND_TYPE_DRAW_SKYBOX: {
finally_render_skybox(deferred_commands[i].draw_skybox);
break;
}
default:
SDL_assert(false);
}
}
}
/* TODO: Support thickness */
void draw_line(Vec2 start,
Vec2 finish,
float thickness,
Color color)
{
if (fabsf(1.0f - thickness) >= 0.00001f)
log_warn("Thickness isn't yet implemented for line drawing (got %f)", (double)thickness);
LinePrimitive line = {
.start = start,
.finish = finish,
.thickness = thickness,
.color = color,
};
Primitive2D primitive = {
.type = PRIMITIVE_2D_LINE,
.line = line,
};
arrput(ctx.render_queue_2d, primitive);
}
void draw_box(Rect rect,
float thickness,
Color color)
{
draw_line((Vec2){rect.x, rect.y}, (Vec2){rect.x + rect.w, rect.y}, thickness, color);
draw_line((Vec2){rect.x + rect.w, rect.y}, (Vec2){rect.x + rect.w, rect.y + rect.h}, thickness, color);
draw_line((Vec2){rect.x + rect.w, rect.y + rect.h}, (Vec2){rect.x, rect.y + rect.h}, thickness, color);
draw_line((Vec2){rect.x, rect.y + rect.h}, (Vec2){rect.x, rect.y}, thickness, color);
} }

View File

@ -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 */ } UncoloredSpaceTriangle;
} primitive;
/* 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

View File

@ -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();
}

View File

@ -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

View File

@ -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);
}

View File

@ -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,

View File

@ -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;
}
} }

View File

@ -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 *)&current->rect.color != uniform_color) if (*(const uint32_t *)(void const *)&current->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);
@ -85,13 +79,13 @@ void render_rect_batch(const Primitive2D primitives[],
const size_t cur = batch.mode == TEXTURE_MODE_GHOSTLY ? i : batch.size - i - 1; const size_t cur = batch.mode == TEXTURE_MODE_GHOSTLY ? i : batch.size - i - 1;
const RectPrimitive rect = primitives[cur].rect; const RectPrimitive rect = primitives[cur].rect;
Vec2 v0 = { rect.rect.x, rect.rect.y }; Vec2 v0 = { rect.rect.x, rect.rect.y };
Vec2 v1 = { rect.rect.x, rect.rect.y + rect.rect.h }; Vec2 v1 = { rect.rect.x, rect.rect.y + rect.rect.h };
Vec2 v2 = { rect.rect.x + rect.rect.w, rect.rect.y + rect.rect.h }; Vec2 v2 = { rect.rect.x + rect.rect.w, rect.rect.y + rect.rect.h };
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);

View File

@ -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;
} }

View File

@ -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 *)&current->sprite.color != uniform_color) if (*(const uint32_t *)(void const *)&current->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);
} }
} }

View File

@ -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);
} }

View File

@ -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);
} }

View File

@ -0,0 +1,71 @@
#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 sigset_t mask;
static struct sigaction sa;
static struct sigevent sev;
static bool created;
/* stop application */
static void sanity_timer_handler(int sig, siginfo_t *si, void *uc) {
(void)uc; (void)sig; (void)si;
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
"sanity timer", "Your game tick exceeded its allocated time, application is closing",
ctx.window);
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;
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
View 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

View File

@ -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"

View File

@ -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,71 +247,110 @@ void audio_play(const char *path,
float volume, float volume,
float panning) float panning)
{ {
AudioChannelItem *pair = shgetp_null(ctx.audio_channels, channel); if (!ctx.audio_initialized) {
profile_start("audio initialization");
/* create a channel if it doesn't exist */ SDL_AudioSpec request, got;
if (!pair) { SDL_zero(request);
AudioFileType file_type = infer_audio_file_type(path);
request.freq = AUDIO_FREQUENCY;
request.format = AUDIO_F32;
request.channels = 2;
#ifndef TWN_FEATURE_PUSH_AUDIO
request.callback = audio_callback;
#endif
/* TODO: check for errors */
ctx.audio_device = SDL_OpenAudioDevice(NULL, 0, &request, &got, 0);
ctx.audio_stream_format = got.format;
ctx.audio_stream_frequency = got.freq;
ctx.audio_stream_channel_count = got.channels;
/* TODO: relax this */
SDL_assert_always(got.freq == AUDIO_FREQUENCY);
SDL_assert_always(got.format == AUDIO_F32);
SDL_assert_always(got.channels == 2);
SDL_PauseAudioDevice(ctx.audio_device, 0);
profile_end("audio initialization");
ctx.audio_initialized = true;
}
if (channel) {
AudioChannelItem *pair = shgetp_null(ctx.audio_channels, channel);
/* create a channel if it doesn't exist */
if (!pair) {
AudioFileType const file_type = infer_audio_file_type(path);
AudioChannel new_channel = {
.file_type = file_type,
.context = init_audio_context(path, file_type),
.path = path,
.name = channel,
.repeat = repeat,
.volume = volume,
.panning = panning,
};
shput(ctx.audio_channels, channel, new_channel);
pair = shgetp_null(ctx.audio_channels, channel);
}
/* TODO: destroy and create new context when channel is reused for different file */
/* works for both restarts and new audio */
if (strcmp(pair->value.path, path) == 0)
repeat_audio(&pair->value);
} else {
/* audio without channel plays without repeat and ability to change parameters over its course, nor stop it */
AudioFileType const file_type = infer_audio_file_type(path);
AudioChannel new_channel = { 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),
.path = path, .path = path,
.name = channel, .name = NULL,
.repeat = repeat, .repeat = false,
.volume = volume, .volume = volume,
.panning = panning, .panning = panning,
}; };
shput(ctx.audio_channels, channel, new_channel);
pair = shgetp_null(ctx.audio_channels, channel); if (repeat)
log_warn("Cannot repeat audio played on unnamed scratch channel (for %s)", path);
arrpush(ctx.unnamed_audio_channels, new_channel);
} }
/* TODO: destroy and create new context when channel is reused for different file */
/* works for both restarts and new audio */
if (strcmp(pair->value.path, path) == 0)
repeat_audio(&pair->value);
} }
TWN_API void audio_set(const char *channel, AudioParam param, float value) { TWN_API void audio_parameter(const char *channel, const char *param, float value) {
AudioChannelItem *pair = shgetp_null(ctx.audio_channels, channel); AudioChannelItem *pair = shgetp_null(ctx.audio_channels, channel);
if (!pair) { if (!pair) {
log_warn("No channel by the name of %s to set a parameter for", channel); log_warn("No channel by the name of %s to set a parameter for", channel);
return; return;
} }
switch (param) { if (SDL_strncmp(param, "repeat", sizeof "repeat" - 1) == 0) {
case AUDIO_PARAM_REPEAT:
pair->value.repeat = (bool)value; pair->value.repeat = (bool)value;
break;
case AUDIO_PARAM_VOLUME: } else if (SDL_strncmp(param, "volume", sizeof "volume" - 1) == 0) {
if (value > 1.0f) { if (value > 1.0f || value < 0.0f) {
log_warn("Out of range volume for channel %s set", channel); log_warn("Out of range volume for channel %s set", channel);
value = 1.0f; value = clampf(value, 0.0f, 1.0f);
}
if (value < 0.0f) {
log_warn("Out of range volume for channel %s set", channel);
value = 0.0f;
} }
pair->value.volume = value; pair->value.volume = value;
break;
case AUDIO_PARAM_PANNING: } else if (SDL_strncmp(param, "panning", sizeof "panning" - 1) == 0) {
if (value > 1.0f) { if (value > 1.0f || value < -1.0f) {
log_warn("Out of range panning for channel %s set", channel); log_warn("Out of range panning for channel %s set", channel);
value = 1.0f; value = clampf(value, -1.0f, +1.0f);
}
if (value < -1.0f) {
log_warn("Out of range panning for channel %s set", channel);
value = -1.0f;
} }
pair->value.panning = value; pair->value.panning = value;
break;
default: } else
CRY("Audio channel parameter setting failed", "Invalid parameter enum given"); CRY("Audio channel parameter setting failed", "Invalid parameter enum given");
}
} }
/* TODO: handle it more properly in regards to clipping and alike */
/* this assumes float based streams */ /* this assumes float based streams */
static void audio_mixin_streams(const AudioChannel *channel, static void audio_mixin_streams(const AudioChannel *channel,
uint8_t *restrict a, uint8_t *restrict a,
@ -241,37 +363,39 @@ static void audio_mixin_streams(const AudioChannel *channel,
const float left_panning = fminf(fabsf(channel->panning - 1.0f), 1.0f); const float left_panning = fminf(fabsf(channel->panning - 1.0f), 1.0f);
const float right_panning = fminf(fabsf(channel->panning + 1.0f), 1.0f); const float right_panning = fminf(fabsf(channel->panning + 1.0f), 1.0f);
for (size_t s = 0; s < frames; s += 2) { for (size_t s = 0; s < frames; ++s) {
/* left channel */ /* left channel */
sa[s] += (float)(sb[s] * channel->volume * left_panning); sa[s * 2 + 0] += (float)(sb[s * 2 + 0] * channel->volume * left_panning);
sa[s * 2 + 0] *= 1 / (float)M_2_SQRTPI;
/* right channel */ /* right channel */
sa[s + 1] += (float)(sb[s + 1] * channel->volume * right_panning); sa[s * 2 + 1] += (float)(sb[s * 2 + 1] * channel->volume * right_panning);
sa[s * 2 + 1] *= 1 / (float)M_2_SQRTPI;
} }
} }
/* remember: sample is data for all channels where frame is a part of it */ /* remember: frame consists of sample * channel_count */
static void audio_sample_and_mixin_channel(const AudioChannel *channel, static void audio_sample_and_mixin_channel(AudioChannel *channel,
uint8_t *stream, uint8_t *stream,
int len) int len)
{ {
static uint8_t buffer[16384]; static uint8_t buffer[16384]; /* TODO: better make it a growable scratch instead, which will simplify things */
const int float_buffer_frames = sizeof (buffer) / sizeof (float); const size_t float_buffer_frames = sizeof (buffer) / sizeof (float) / 2;
const int stream_frames = len / (int)(sizeof (float)); const size_t stream_frames = len / sizeof (float) / 2;
switch (channel->file_type) { switch (channel->file_type) {
case AUDIO_FILE_TYPE_OGG: { case AUDIO_FILE_TYPE_OGG: {
/* feed stream for needed conversions */ /* feed stream for needed conversions */
for (int i = 0; i < stream_frames; ) { for (size_t i = 0; i < stream_frames; ) {
const int n_frames = (stream_frames - i) > float_buffer_frames ? const size_t n_frames = (stream_frames - i) > float_buffer_frames ?
float_buffer_frames : stream_frames - i; float_buffer_frames : stream_frames - i;
const int samples_per_channel = stb_vorbis_get_samples_float_interleaved( const size_t samples_per_channel = stb_vorbis_get_samples_float_interleaved(
channel->context.vorbis.handle, channel->context.vorbis.handle,
channel->context.vorbis.channel_count, 2,
(float *)buffer, (float *)buffer,
n_frames); (int)n_frames * 2);
/* handle end of file */ /* handle end of file */
if (samples_per_channel == 0) { if (samples_per_channel == 0) {
@ -279,30 +403,61 @@ static void audio_sample_and_mixin_channel(const AudioChannel *channel,
/* seek to start and try sampling some more */ /* seek to start and try sampling some more */
stb_vorbis_seek_start(channel->context.vorbis.handle); stb_vorbis_seek_start(channel->context.vorbis.handle);
continue; continue;
} else } else {
/* leave silence */ /* leave silence */
channel->finished = true;
break; break;
}
} }
/* panning and mixing */ /* panning and mixing */
audio_mixin_streams(channel, audio_mixin_streams(channel,
&stream[i * sizeof(float)], buffer, &stream[i * sizeof(float) * 2], buffer,
samples_per_channel * 2); samples_per_channel);
i += samples_per_channel * 2; i += samples_per_channel;
}
break;
}
case AUDIO_FILE_TYPE_WAV: {
/* feed stream for needed conversions */
for (size_t i = 0; i < stream_frames; ) {
const size_t limit = MIN(stream_frames - i, channel->context.wav.spec.samples - channel->context.wav.position);
/* same format, just feed it directly */
audio_mixin_streams(channel,
&stream[i * sizeof(float) * 2],
&((uint8_t *)channel->context.wav.samples)[channel->context.wav.position * sizeof (float) * 2],
limit);
channel->context.wav.position += limit;
if (channel->context.wav.position >= channel->context.wav.spec.samples) {
if (channel->repeat)
channel->context.wav.position = 0;
else {
/* leave silence */
channel->finished = true;
break;
}
}
i += limit;
} }
break; break;
} }
case AUDIO_FILE_TYPE_XM: { case AUDIO_FILE_TYPE_XM: {
for (int i = 0; i < stream_frames; ) { for (size_t i = 0; i < stream_frames; ) {
const int n_frames = (stream_frames - i) > float_buffer_frames ? const size_t n_frames = (stream_frames - i) > float_buffer_frames ?
float_buffer_frames : stream_frames - i; float_buffer_frames : stream_frames - i;
const int samples_per_channel = xm_generate_samples(channel->context.xm.handle, const size_t samples_per_channel = xm_generate_samples(channel->context.xm.handle,
(float *)buffer, (float *)buffer,
n_frames / 2); n_frames);
/* handle end of file */ /* handle end of file */
if (samples_per_channel == 0) { if (samples_per_channel == 0) {
@ -310,18 +465,20 @@ static void audio_sample_and_mixin_channel(const AudioChannel *channel,
/* seek to start and try sampling some more */ /* seek to start and try sampling some more */
xm_restart(channel->context.xm.handle); xm_restart(channel->context.xm.handle);
continue; continue;
} else } else {
channel->finished = true;
/* leave silence */ /* leave silence */
break; break;
}
} }
/* panning and mixing */ /* panning and mixing */
audio_mixin_streams(channel, audio_mixin_streams(channel,
&stream[i * sizeof(float)], &stream[i * sizeof(float) * 2],
buffer, buffer,
samples_per_channel * 2); samples_per_channel);
i += samples_per_channel * 2; i += samples_per_channel;
} }
break; break;
@ -355,8 +512,23 @@ void audio_callback(void *userdata, uint8_t *stream, int len) {
sanity_check_channel(&ctx.audio_channels[i].value); sanity_check_channel(&ctx.audio_channels[i].value);
audio_sample_and_mixin_channel(&ctx.audio_channels[i].value, stream, len); audio_sample_and_mixin_channel(&ctx.audio_channels[i].value, stream, len);
} }
for (int i = 0; i < arrlen(ctx.unnamed_audio_channels); ++i) {
sanity_check_channel(&ctx.unnamed_audio_channels[i]);
audio_sample_and_mixin_channel(&ctx.unnamed_audio_channels[i], stream, len);
}
/* ditch finished unnamed */
int i = 0;
while (i < arrlen(ctx.unnamed_audio_channels)) {
if (ctx.unnamed_audio_channels[i].finished) {
free_audio_channel(ctx.unnamed_audio_channels[i]);
arrdelswap(ctx.unnamed_audio_channels, i);
} else i++;
}
} }
TWN_API void audio_play_args(PlayAudioArgs args) { TWN_API void audio_play_args(PlayAudioArgs args) {
const char *channel = m_or(args, channel, NULL); const char *channel = m_or(args, channel, NULL);
const bool repeat = m_or(args, repeat, false); const bool repeat = m_or(args, repeat, false);

View File

@ -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;

View File

@ -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);

View File

@ -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 */
@ -12,11 +12,11 @@ typedef struct Camera {
Vec3 pos; /* eye position */ Vec3 pos; /* eye position */
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 */
} Camera; } Camera;
TWN_API Matrix4 camera_look_at(const Camera *camera); Matrix4 camera_look_at(const Camera *camera);
TWN_API Matrix4 camera_perspective(const Camera *const camera); Matrix4 camera_perspective(const Camera *const camera);
#endif #endif

View File

@ -28,8 +28,11 @@ typedef struct EngineContext {
char **argv; char **argv;
/* where the app was run from, used as the root for packs */ /* where the app was run from, used as the root for packs */
char *base_dir; char *base_dir;
char *title;
Vec2i window_dims; Vec2 window_dims;
Rect viewport_rect;
float viewport_scale;
/* configuration */ /* configuration */
toml_table_t *config_table; toml_table_t *config_table;
@ -45,11 +48,13 @@ typedef struct EngineContext {
/* rendering */ /* rendering */
Primitive2D *render_queue_2d; Primitive2D *render_queue_2d;
MeshBatchItem *uncolored_mesh_batches; MeshBatchItem *uncolored_mesh_batches;
MeshBatchItem *billboard_batches;
TextCache text_cache; TextCache text_cache;
TextureCache texture_cache; TextureCache texture_cache;
/* audio */ /* audio */
AudioChannelItem *audio_channels; AudioChannelItem *audio_channels;
AudioChannel *unnamed_audio_channels;
SDL_AudioDeviceID audio_device; SDL_AudioDeviceID audio_device;
int audio_stream_frequency; int audio_stream_frequency;
SDL_AudioFormat audio_stream_format; SDL_AudioFormat audio_stream_format;
@ -64,11 +69,6 @@ typedef struct EngineContext {
int64_t delta_averager_residual; int64_t delta_averager_residual;
int64_t time_averager[4]; int64_t time_averager[4];
/* this should be a multiple of the current ticks per second */
/* use it to simulate low framerate (e.g. at 60 tps, set to 2 for 30 fps) */
/* it can be changed at runtime; any resulting logic anomalies are bugs */
uint32_t update_multiplicity;
SDL_GLContext *gl_context; SDL_GLContext *gl_context;
SDL_Window *window; SDL_Window *window;
uint32_t window_id; uint32_t window_id;
@ -78,6 +78,9 @@ typedef struct EngineContext {
bool resync_flag; bool resync_flag;
bool was_successful; bool was_successful;
bool render_double_buffered; bool render_double_buffered;
/* signals mouse focus, used to disable mouse capture */
bool window_mouse_resident;
bool audio_initialized;
} EngineContext; } EngineContext;
/* TODO: does it need to be marked with TWN_API? */ /* TODO: does it need to be marked with TWN_API? */

View File

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

View File

@ -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

View File

@ -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,20 +207,42 @@ 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)
input_state_update(&ctx.input); update_viewport();
game_object_tick(); input_state_update(&ctx.input);
preserve_persistent_ctx_fields();
ctx.frame_accumulator -= ctx.desired_frametime; profile_start("game tick");
ctx.game.frame_number = (ctx.game.frame_number % ULLONG_MAX) + 1; start_sanity_timer(2000);
ctx.game.initialization_needed = false; 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
static uint8_t audio_buffer[(AUDIO_FREQUENCY / 60) * sizeof (float) * 2];
if (ctx.audio_initialized) {
audio_callback(NULL, audio_buffer, sizeof audio_buffer);
if (SDL_QueueAudio(ctx.audio_device, audio_buffer, sizeof audio_buffer))
CRY_SDL("Error queueing audio: ");
} }
#endif
/* TODO: ctx.game_copy = ctx.game should be placed after it, but it messes with state used in render() */
preserve_persistent_ctx_fields();
ctx.frame_accumulator -= ctx.desired_frametime;
ctx.game.frame_number++;
/* TODO: should we ask for reinitialization in such case? */
if (ctx.game.frame_number > 16777216)
ctx.game.frame_number = 0;
ctx.game.initialization_needed = false;
} }
/* TODO: in some cases machine might want to assume frames will be fed as much as possible */ /* TODO: in some cases machine might want to assume frames will be fed as much as possible */
@ -320,19 +351,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 +433,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,135 +457,61 @@ 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");
{ if (!datum_title.ok)
toml_datum_t datum_title = toml_string_in(about, "title"); datum_title.u.s = SDL_strdup("townengine project");
if (!datum_title.ok) {
CRY("Initialization failed", "Valid about.title expected in configuration file"); ctx.title = datum_title.u.s;
goto fail;
} /* 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) { CRY("Initialization failed", "Valid about.developer expected in configuration file");
CRY("Initialization failed", "Valid about.developer expected in configuration file"); 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 |
SDL_WINDOW_RESIZABLE |
SDL_WINDOW_OPENGL);
SDL_free(datum_title.u.s);
//SDL_free(datum_developer.u.s);
} }
if (ctx.window == NULL) { ctx.game.resolution.x = (float)ctx.base_render_width;
CRY_SDL("Window creation failed."); ctx.game.resolution.y = (float)ctx.base_render_height;
goto fail;
}
ctx.gl_context = SDL_GL_CreateContext(ctx.window); //SDL_free(datum_developer.u.s);
if (!ctx.gl_context) {
CRY_SDL("GL context creation failed.");
goto fail;
}
if (SDL_GL_MakeCurrent(ctx.window, ctx.gl_context)) {
CRY_SDL("GL context binding failed.");
goto fail;
}
if (SDL_GL_SetSwapInterval(-1))
SDL_GL_SetSwapInterval(1);
#ifndef EMSCRIPTEN
if (gladLoadGL() == 0) {
CRY("Init", "GLAD failed");
goto fail;
}
#endif
log_info("OpenGL context: %s\n", glGetString(GL_VERSION));
#ifndef EMSCRIPTEN
glHint(GL_TEXTURE_COMPRESSION_HINT, GL_NICEST);
glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
glHint(GL_FOG_HINT, GL_FASTEST);
#endif
/* might need this to have multiple windows */
ctx.window_id = SDL_GetWindowID(ctx.window);
glViewport(0, 0, (GLsizei)ctx.base_render_width, (GLsizei)ctx.base_render_height);
/* TODO: */ /* TODO: */
// SDL_GetRendererOutputSize(ctx.renderer, &ctx.window_w, &ctx.window_h); // SDL_GetRendererOutputSize(ctx.renderer, &ctx.window_w, &ctx.window_h);
ctx.window_dims.x = (int)ctx.base_render_width; ctx.window_dims.x = (float)ctx.base_render_width;
ctx.window_dims.y = (int)ctx.base_render_height; ctx.window_dims.y = (float)ctx.base_render_height;
/* add a watcher for immediate updates on window size */ /* add a watcher for immediate updates on window size */
SDL_AddEventWatch(event_callback, NULL); SDL_AddEventWatch(event_callback, NULL);
/* audio initialization */
{
SDL_AudioSpec request, got;
SDL_zero(request);
request.freq = AUDIO_FREQUENCY;
request.format = AUDIO_F32;
request.channels = 2;
request.callback = audio_callback;
/* TODO: check for errors */
ctx.audio_device = SDL_OpenAudioDevice(NULL, 0, &request, &got, 0);
ctx.audio_stream_format = got.format;
ctx.audio_stream_frequency = got.freq;
ctx.audio_stream_channel_count = got.channels;
SDL_assert_always(got.format == AUDIO_F32);
SDL_assert_always(got.channels == 2);
SDL_PauseAudioDevice(ctx.audio_device, 0);
}
/* you could change this at runtime if you wanted */
ctx.update_multiplicity = 1;
#ifndef EMSCRIPTEN
/* hook up opengl debugging callback */
if (ctx.game.debug) {
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(opengl_log, NULL);
}
#endif
/* random seeding */ /* random seeding */
/* SDL_GetPerformanceCounter returns some platform-dependent number. */ /* SDL_GetPerformanceCounter returns some platform-dependent number. */
/* it should vary between game instances. i checked! random enough for me. */ /* it should vary between game instances. i checked! random enough for me. */
ctx.game.random_seed = SDL_GetPerformanceCounter(); ctx.game.random_seed = (float)(SDL_GetPerformanceCounter() % 16777216);
srand((unsigned int)ctx.game.random_seed); srand((unsigned int)(SDL_GetPerformanceCounter()));
stbds_rand_seed(ctx.game.random_seed); stbds_rand_seed((size_t)(SDL_GetPerformanceCounter()));
/* main loop machinery */ /* main loop machinery */
toml_datum_t datum_ticks_per_second = toml_int_in(engine, "ticks_per_second"); toml_datum_t datum_ticks_per_second = toml_int_in(engine, "ticks_per_second");
@ -620,9 +579,9 @@ static bool initialize(void) {
if (!datum_font_filtering.ok) { if (!datum_font_filtering.ok) {
ctx.font_filtering = TEXT_FONT_FILTERING_DEFAULT; ctx.font_filtering = TEXT_FONT_FILTERING_DEFAULT;
} else { } else {
if (SDL_strcmp(datum_font_filtering.u.s, "nearest") == 0) { if (SDL_strncmp(datum_font_filtering.u.s, "nearest", sizeof "nearest" - 1) == 0) {
ctx.font_filtering = TEXTURE_FILTER_NEAREAST; ctx.font_filtering = TEXTURE_FILTER_NEAREAST;
} else if (SDL_strcmp(datum_font_filtering.u.s, "linear") == 0) { } else if (SDL_strncmp(datum_font_filtering.u.s, "linear", sizeof "linear" - 1) == 0) {
ctx.font_filtering = TEXTURE_FILTER_LINEAR; ctx.font_filtering = TEXTURE_FILTER_LINEAR;
} else { } else {
ctx.font_filtering = TEXT_FONT_FILTERING_DEFAULT; ctx.font_filtering = TEXT_FONT_FILTERING_DEFAULT;
@ -635,9 +594,6 @@ static bool initialize(void) {
ctx.render_queue_2d = NULL; ctx.render_queue_2d = NULL;
ctx.uncolored_mesh_batches = NULL; ctx.uncolored_mesh_batches = NULL;
textures_cache_init(&ctx.texture_cache, ctx.window);
text_cache_init(&ctx.text_cache);
/* input */ /* input */
toml_datum_t datum_keybind_slots = toml_int_in(engine, "keybind_slots"); toml_datum_t datum_keybind_slots = toml_int_in(engine, "keybind_slots");
if (!datum_keybind_slots.ok) { if (!datum_keybind_slots.ok) {
@ -651,14 +607,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 +679,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 +691,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 +705,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 +739,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 +756,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 +796,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 +810,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();

View File

@ -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>

View File

@ -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) {
stbi_image_free(cache->hash[i].value.data->pixels); if (missing_texture_surface && cache->hash[i].value.data->pixels != missing_texture_surface->pixels)
stbi_image_free(cache->hash[i].value.data->pixels);
else
SDL_free(cache->hash[i].value.data->pixels);
SDL_FreeSurface(cache->hash[i].value.data); SDL_FreeSurface(cache->hash[i].value.data);
} }
shfree(cache->hash); shfree(cache->hash);
@ -334,6 +379,9 @@ static TextureKey textures_load(TextureCache *cache, const char *path) {
return (TextureKey){ (uint16_t)i }; return (TextureKey){ (uint16_t)i };
SDL_Surface *surface = textures_load_surface(path); SDL_Surface *surface = textures_load_surface(path);
if (surface == missing_texture_surface && missing_texture_id != 0)
return (TextureKey){ missing_texture_id };
Texture new_texture = { Texture new_texture = {
.data = surface, .data = surface,
.mode = infer_texture_mode(surface), .mode = infer_texture_mode(surface),
@ -350,13 +398,22 @@ static TextureKey textures_load(TextureCache *cache, const char *path) {
new_texture.loner_texture = create_gpu_texture(TEXTURE_FILTER_NEAREAST, true); new_texture.loner_texture = create_gpu_texture(TEXTURE_FILTER_NEAREAST, true);
upload_texture_from_surface(new_texture.loner_texture, surface); upload_texture_from_surface(new_texture.loner_texture, surface);
new_texture.srcrect = (Rect) { .w = (float)surface->w, .h = (float)surface->h }; new_texture.srcrect = (Rect) { .w = (float)surface->w, .h = (float)surface->h };
} else { } else {
/* will be fully populated as the atlas updates */ /* will be fully populated as the atlas updates */
new_texture.atlas_index = cache->atlas_index; new_texture.atlas_index = cache->atlas_index;
cache->is_dirty = true; cache->is_dirty = true;
} }
shput(cache->hash, path, new_texture); shput(cache->hash, path, new_texture);
return (TextureKey){ (uint16_t)shgeti(cache->hash, path) };
uint16_t const id = (uint16_t)shlenu(cache->hash) - 1;
/* reuse this id for every later missing texture */
if (surface == missing_texture_surface)
missing_texture_id = id;
return (TextureKey){ id };
} }
@ -439,6 +496,8 @@ TextureKey textures_get_key(TextureCache *cache, const char *path) {
} }
} }
/* TODO: this will be bad when dynamic strings are involved */
/* to mitigate that we could free ptr_to_texture each frame */
/* try loading */ /* try loading */
last_texture = textures_load(cache, path); last_texture = textures_load(cache, path);
hmput(ptr_to_texture, path, last_texture); hmput(ptr_to_texture, path, last_texture);
@ -533,7 +592,7 @@ void textures_bind_repeating(const TextureCache *cache, TextureKey key) {
upload_gpu_texture(repeating_texture, upload_gpu_texture(repeating_texture,
texture.data->pixels, texture.data->pixels,
4, texture.data->format->BytesPerPixel,
texture.data->w, texture.data->w,
texture.data->h); texture.data->h);

View File

@ -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"
@ -17,8 +17,8 @@
typedef struct Texture { typedef struct Texture {
Rect srcrect; /* position in atlas */ Rect srcrect; /* position in atlas */
SDL_Surface *data; /* original image data */ SDL_Surface *data; /* original image data */
int atlas_index; int atlas_index;
GPUTexture loner_texture; /* stored directly for loners, == 0 means atlas_index should be used */ GPUTexture loner_texture; /* stored directly for loners, == 0 means atlas_index should be used */
GPUTexture repeating_texture; /* separately allocated Texture, for loners == loner_texture */ GPUTexture repeating_texture; /* separately allocated Texture, for loners == loner_texture */

19
src/twn_types_c.h Normal file
View 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

View File

@ -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);
}

View File

@ -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

View File

@ -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)

View File

@ -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 )

View File

@ -548,7 +548,7 @@ extern void * stbds_shmode_func(size_t elemsize, int mode);
#define stbds_arraddnindex(a,n)(stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), stbds_header(a)->length-(n)) : stbds_arrlen(a)) #define stbds_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)