Compare commits
186 Commits
2286cdefeb
...
main
Author | SHA1 | Date | |
---|---|---|---|
75890b1a71 | |||
73db3e57dc | |||
2975aa2dfb | |||
6726faf719 | |||
183dfa6be5 | |||
e974194af0 | |||
8607aa48ec | |||
f6600dfbda | |||
bdabd04388 | |||
0e075ec334 | |||
b256fc903a | |||
b52ecaeaa0 | |||
37e46e9a7e | |||
a472e6af52 | |||
66b2f04d9d | |||
90f4097070 | |||
829ff4780c | |||
8e15c9ec3c | |||
474ea84a77 | |||
7b8b9416ba | |||
8ed8158ae6 | |||
48e3a4c233 | |||
56530f9864 | |||
f86f3dd41a | |||
adae6be7e5 | |||
cd3033f9c4 | |||
e11e63f273 | |||
75737b738f | |||
ce2c2513aa | |||
36c0af9953 | |||
826622cd58 | |||
78b6a26de9 | |||
5f7b8bac6d | |||
6d6230c6a1 | |||
c07e16490e | |||
f5e55bb997 | |||
1e6e323fe1 | |||
dbf9599fe5 | |||
923cd81571 | |||
733a1786ab | |||
a03e1d885d | |||
67feb5974a | |||
5be4ed4645 | |||
4a41f47a58 | |||
35bb26705a | |||
13bc71a28d | |||
b97a155de4 | |||
5df80addeb | |||
787977b747 | |||
f90b973d86 | |||
32675c012c | |||
a97515e948 | |||
ed8e826b94 | |||
4e5ff9433c | |||
55829a1bef | |||
119bd52c51 | |||
5abd1ced1c | |||
80db96672d | |||
2f6f7852be | |||
307d5552f6 | |||
5911cbd980 | |||
e47b761a2c | |||
844283c2fb | |||
09eac707c3 | |||
5e89710458 | |||
4bc1feb826 | |||
1c3973c6a2 | |||
da5bdb4fae | |||
ed2afec5a7 | |||
6812c7c13d | |||
8c0f43ec34 | |||
23fbd45564 | |||
a36459397e | |||
5f3920fdba | |||
f57525cea6 | |||
6b2901be28 | |||
9f0d15b9f6 | |||
b46331e08d | |||
d2938da8e2 | |||
9134e51817 | |||
d66eda1894 | |||
a88392b9e9 | |||
05f85062e8 | |||
d5aec5e6e1 | |||
62866d33ae | |||
ce7240d423 | |||
7a38f7bcf3 | |||
affaf7f557 | |||
a223506a5f | |||
98d7d76a42 | |||
814269ab0c | |||
d76ea06470 | |||
dc6b298532 | |||
f25e27b102 | |||
dd4fc45be3 | |||
85e47dd677 | |||
a020b92824 | |||
b6347996f9 | |||
66c525a0d4 | |||
70fab28158 | |||
80a4ae3d0e | |||
bd3b090f6f | |||
6eb0730c52 | |||
a231d650f2 | |||
e15975bfaa | |||
b67bc92857 | |||
991196f7c8 | |||
0b89c90ad7 | |||
3d51c8c48f | |||
21d8e2c5a5 | |||
f805bf3f92 | |||
5228fa7e41 | |||
f044a75ffe | |||
723ccf1126 | |||
6bd3afe9b2 | |||
d90bf4cbe2 | |||
48f34f4623 | |||
9c007f34df | |||
a1f4599efd | |||
4c1a8e087a | |||
a2b1f1820a | |||
85ec8d3366 | |||
7eebc7a2d7 | |||
2b26fad983 | |||
47799deb8b | |||
1cd4bfa638 | |||
9beef7686e | |||
cee344c7c1 | |||
18a76649b9
|
|||
88a4876d91
|
|||
835edd737c | |||
9a486fa912
|
|||
d4ce6ab9ec | |||
24b417c287 | |||
5a83381ae1 | |||
793bd850f6 | |||
29d163216c | |||
ffc3badc50 | |||
bedfe0cdfb | |||
927f284fda | |||
2616549f88 | |||
cc4f7f7417 | |||
f81c583319 | |||
0df0a9226f | |||
af1b9caedc | |||
72d1941091 | |||
8a58336d16 | |||
1818532ec9 | |||
e9f8dbebbf | |||
c81f95e571 | |||
5ba11dc584 | |||
f2aded9046 | |||
d6aaef3f68 | |||
322fbf6bbd | |||
5a7d7433d1 | |||
037548436d | |||
5e27845e55 | |||
3bf8d7bedb | |||
85d7d54eed | |||
8c248cb3fb | |||
145b040a0f | |||
559ff9fedc | |||
990135105a | |||
7040d6f218 | |||
cb88b4bcc5 | |||
8110789b3a | |||
0eadeb7e9d | |||
3d10e1782a | |||
d9df3f9b04 | |||
d9d7072c86 | |||
3733b53cc5 | |||
b6b436e1b7 | |||
02b5ac4cc3 | |||
4efe80bb5a | |||
f3a2dc9063 | |||
53c43a8f34 | |||
507bff6ed8 | |||
00636d65a9 | |||
42253fc58a | |||
c6cbf941a2 | |||
277d1b2e10 | |||
46955a19c1 | |||
9ab3e942cd | |||
c7bb317ead | |||
241e72be1a | |||
f4fccc08c4 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -4,12 +4,15 @@
|
|||||||
!*/
|
!*/
|
||||||
!bin/*
|
!bin/*
|
||||||
!Makefile
|
!Makefile
|
||||||
|
!LICENSE
|
||||||
|
!COPYING
|
||||||
|
|
||||||
**/*.exe
|
**/*.exe
|
||||||
**/*.html
|
**/*.html
|
||||||
**/*.js
|
**/*.js
|
||||||
**/*.wasm
|
**/*.wasm
|
||||||
**/*.wasm.map
|
**/*.wasm.map
|
||||||
|
**/*.data
|
||||||
**/*.so
|
**/*.so
|
||||||
**/*.dll
|
**/*.dll
|
||||||
**/*.7z
|
**/*.7z
|
||||||
|
103
CMakeLists.txt
103
CMakeLists.txt
@ -5,6 +5,8 @@ project(townengine LANGUAGES C)
|
|||||||
set(CMAKE_MESSAGE_LOG_LEVEL "WARNING")
|
set(CMAKE_MESSAGE_LOG_LEVEL "WARNING")
|
||||||
set(CMAKE_INSTALL_MESSAGE NEVER)
|
set(CMAKE_INSTALL_MESSAGE NEVER)
|
||||||
|
|
||||||
|
# TODO: test whether webgl 1 is good enough.
|
||||||
|
|
||||||
# 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)
|
||||||
@ -26,19 +28,19 @@ set(TWN_TARGET townengine CACHE INTERNAL "")
|
|||||||
set(TWN_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "")
|
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_USE_AMALGAM "Enable use of twn_amalgam.c as a single compilation unit" ON)
|
||||||
option(TWN_FEATURE_PUSH_AUDIO "Enable frame based audio push for easy realtime audio" ON)
|
option(TWN_FEATURE_DYNLIB_GAME "Enable dynamic library loading support" ON)
|
||||||
option(TWN_USE_AMALGAM "Enable use of twn_amalgam.c as a single compilation unit" ON)
|
set(TWN_OUT_DIR ${CMAKE_SOURCE_DIR} CACHE PATH "Artifact destination")
|
||||||
|
|
||||||
# todo: figure out how to compile for dynamic linking instead
|
# todo: figure out how to compile for dynamic linking instead?
|
||||||
if(EMSCRIPTEN)
|
if(HAIKU OR EMSCRIPTEN)
|
||||||
if(TWN_FEATURE_DYNLIB_GAME)
|
if(TWN_FEATURE_DYNLIB_GAME)
|
||||||
message(WARNING "TWN_FEATURE_DYNLIB_GAME is set, but not supported - it is turned off")
|
message(WARNING "TWN_FEATURE_DYNLIB_GAME is set, but not supported - it is turned off")
|
||||||
set(TWN_FEATURE_DYNLIB_GAME OFF CACHE INTERNAL "")
|
set(TWN_FEATURE_DYNLIB_GAME OFF CACHE INTERNAL "")
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(HAIKU)
|
if(HAIKU OR EMSCRIPTEN)
|
||||||
if(TWN_SANITIZE)
|
if(TWN_SANITIZE)
|
||||||
message(WARNING "TWN_SANITIZE is set, but not supported - it is turned off")
|
message(WARNING "TWN_SANITIZE is set, but not supported - it is turned off")
|
||||||
set(TWN_SANITIZE OFF CACHE INTERNAL "")
|
set(TWN_SANITIZE OFF CACHE INTERNAL "")
|
||||||
@ -64,11 +66,7 @@ set(PHYSFS_ARCHIVE_7Z OFF CACHE INTERNAL "")
|
|||||||
add_subdirectory(third-party/physfs ${CMAKE_CURRENT_BINARY_DIR}/third-party/physfs SYSTEM)
|
add_subdirectory(third-party/physfs ${CMAKE_CURRENT_BINARY_DIR}/third-party/physfs SYSTEM)
|
||||||
add_subdirectory(third-party/libxm ${CMAKE_CURRENT_BINARY_DIR}/third-party/libxm SYSTEM)
|
add_subdirectory(third-party/libxm ${CMAKE_CURRENT_BINARY_DIR}/third-party/libxm SYSTEM)
|
||||||
|
|
||||||
if(EMSCRIPTEN)
|
set(TWN_RENDERING_API OPENGL_15)
|
||||||
set(TWN_RENDERING_API WEBGL1)
|
|
||||||
else()
|
|
||||||
set(TWN_RENDERING_API OPENGL_15)
|
|
||||||
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}
|
||||||
@ -84,7 +82,6 @@ set(TWN_THIRD_PARTY_SOURCE_FILES
|
|||||||
|
|
||||||
set(TWN_NONOPT_SOURCE_FILES
|
set(TWN_NONOPT_SOURCE_FILES
|
||||||
src/twn_stb.c
|
src/twn_stb.c
|
||||||
src/twn_main.c
|
|
||||||
|
|
||||||
src/twn_context.c include/twn_context.h
|
src/twn_context.c include/twn_context.h
|
||||||
src/twn_audio.c include/twn_audio.h
|
src/twn_audio.c include/twn_audio.h
|
||||||
@ -97,6 +94,7 @@ set(TWN_NONOPT_SOURCE_FILES
|
|||||||
src/twn_filewatch.c src/twn_filewatch_c.h
|
src/twn_filewatch.c src/twn_filewatch_c.h
|
||||||
src/twn_filewatch.c src/twn_filewatch_c.h
|
src/twn_filewatch.c src/twn_filewatch_c.h
|
||||||
src/twn_timer.c src/twn_timer_c.h
|
src/twn_timer.c src/twn_timer_c.h
|
||||||
|
src/twn_workers.c src/twn_workers_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_quads.c
|
||||||
@ -107,6 +105,8 @@ set(TWN_NONOPT_SOURCE_FILES
|
|||||||
src/rendering/twn_billboards.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_models.c
|
||||||
|
src/rendering/twn_lines.c
|
||||||
)
|
)
|
||||||
|
|
||||||
set(TWN_SOURCE_FILES
|
set(TWN_SOURCE_FILES
|
||||||
@ -136,12 +136,10 @@ 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>
|
||||||
${SDL2_INCLUDE_DIR}/SDL.h
|
$<$<NOT:$<BOOL:${EMSCRIPTEN}>>:${SDL2_INCLUDE_DIR}/SDL.h>
|
||||||
third-party/physfs/src/physfs.h)
|
third-party/physfs/src/physfs.h)
|
||||||
|
|
||||||
|
|
||||||
@ -153,14 +151,14 @@ function(give_options_without_warnings target)
|
|||||||
-fno-signed-zeros
|
-fno-signed-zeros
|
||||||
-fno-trapping-math
|
-fno-trapping-math
|
||||||
-freciprocal-math
|
-freciprocal-math
|
||||||
|
# TODO: require packaging for web case
|
||||||
$<$<BOOL:${EMSCRIPTEN}>:-sUSE_SDL=2>)
|
$<$<BOOL:${EMSCRIPTEN}>:-sUSE_SDL=2>)
|
||||||
|
|
||||||
set(BUILD_FLAGS_RELEASE
|
set(BUILD_FLAGS_RELEASE
|
||||||
-O3
|
-O3
|
||||||
-flto=$<IF:$<STREQUAL:${CMAKE_C_COMPILER_ID},Clang>,thin,auto>
|
-flto=$<IF:$<STREQUAL:${CMAKE_C_COMPILER_ID},Clang>,thin,auto>
|
||||||
-mavx -mavx2
|
$<$<STREQUAL:${CMAKE_SYSTEM_PROCESSOR},AMD64>:-sse2 -mavx -mavx2>
|
||||||
-fdata-sections
|
$<$<BOOL:${EMSCRIPTEN}>:-msimd128 -mrelaxed-simd>
|
||||||
-ffunction-sections
|
|
||||||
-funroll-loops
|
-funroll-loops
|
||||||
-fomit-frame-pointer
|
-fomit-frame-pointer
|
||||||
$<$<STREQUAL:${CMAKE_C_COMPILER_ID},Gnu>:-s>)
|
$<$<STREQUAL:${CMAKE_C_COMPILER_ID},Gnu>:-s>)
|
||||||
@ -170,8 +168,20 @@ function(give_options_without_warnings target)
|
|||||||
-g3
|
-g3
|
||||||
-gdwarf
|
-gdwarf
|
||||||
-fno-omit-frame-pointer
|
-fno-omit-frame-pointer
|
||||||
$<$<BOOL:${TWN_SANITIZE}>:-fstack-protector-all -fsanitize=undefined -fsanitize=address>
|
$<$<BOOL:${TWN_SANITIZE}>:-fstack-protector-all -fsanitize=undefined -fsanitize=address>)
|
||||||
$<$<BOOL:${EMSCRIPTEN}>:-gsource-map>)
|
|
||||||
|
set(LINK_FLAGS
|
||||||
|
-Bsymbolic-functions
|
||||||
|
|
||||||
|
$<$<BOOL:${EMSCRIPTEN}>:-sLEGACY_GL_EMULATION -sGL_FFP_ONLY -sMIN_WEBGL_VERSION=2 -sMAX_WEBGL_VERSION=2
|
||||||
|
-sENVIRONMENT=web -sDEFAULT_TO_CXX=0>
|
||||||
|
$<$<BOOL:${EMSCRIPTEN}>:--preload-file ${TWN_OUT_DIR}/data@data -sALLOW_MEMORY_GROWTH>
|
||||||
|
$<$<NOT:$<BOOL:${EMSCRIPTEN}>>:-Wl,--as-needed>
|
||||||
|
$<$<BOOL:${LINUX}>:-Wl,--hash-style=gnu>)
|
||||||
|
|
||||||
|
set(LINK_FLAGS_RELEASE
|
||||||
|
$<$<STREQUAL:${CMAKE_C_COMPILER_ID},Clang>:-Wl,--strip-all>
|
||||||
|
${BUILD_FLAGS_RELEASE})
|
||||||
|
|
||||||
if (CMAKE_C_COMPILER_LINKER_ID MATCHES GNU OR CMAKE_C_COMPILER_LINKER_ID MATCHES GNUgold)
|
if (CMAKE_C_COMPILER_LINKER_ID MATCHES GNU OR CMAKE_C_COMPILER_LINKER_ID MATCHES GNUgold)
|
||||||
set(THINLTO_USAGE "-plugin-opt,")
|
set(THINLTO_USAGE "-plugin-opt,")
|
||||||
@ -193,11 +203,9 @@ function(give_options_without_warnings target)
|
|||||||
|
|
||||||
target_link_options(${target} PUBLIC
|
target_link_options(${target} PUBLIC
|
||||||
${BUILD_FLAGS}
|
${BUILD_FLAGS}
|
||||||
# -Wl,--no-undefined # TODO: use later for implementing no-libc
|
${LINK_FLAGS}
|
||||||
$<$<CONFIG:Release>:${BUILD_FLAGS_RELEASE}>
|
$<$<CONFIG:Release>:${LINK_FLAGS_RELEASE}>
|
||||||
$<$<CONFIG:Debug>:${BUILD_FLAGS_DEBUG}>
|
$<$<CONFIG:Debug>:${BUILD_FLAGS_DEBUG}>)
|
||||||
-Bsymbolic-functions
|
|
||||||
$<$<BOOL:${LINUX}>:-Wl,--hash-style=gnu>)
|
|
||||||
|
|
||||||
get_target_property(target_type ${target} TYPE)
|
get_target_property(target_type ${target} TYPE)
|
||||||
if (target_type MATCHES SHARED_LIBRARY)
|
if (target_type MATCHES SHARED_LIBRARY)
|
||||||
@ -206,6 +214,9 @@ function(give_options_without_warnings target)
|
|||||||
|
|
||||||
target_link_options(${target} PUBLIC
|
target_link_options(${target} PUBLIC
|
||||||
$<$<CONFIG:Release>:${BUILD_SHARED_LIBRARY_FLAGS_RELEASE}>)
|
$<$<CONFIG:Release>:${BUILD_SHARED_LIBRARY_FLAGS_RELEASE}>)
|
||||||
|
elseif(CMAKE_BUILD_TYPE MATCHES Release)
|
||||||
|
target_compile_options(${target} PUBLIC
|
||||||
|
$<$<STREQUAL:${CMAKE_C_COMPILER_ID},Clang>:-mllvm=--enable-gvn-hoist>)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_compile_definitions(${target} PRIVATE
|
target_compile_definitions(${target} PRIVATE
|
||||||
@ -236,6 +247,7 @@ function(give_options target)
|
|||||||
-Werror=vla
|
-Werror=vla
|
||||||
-Wno-missing-field-initializers
|
-Wno-missing-field-initializers
|
||||||
-Wunused-result
|
-Wunused-result
|
||||||
|
-Wno-pre-c11-compat
|
||||||
$<$<STREQUAL:${CMAKE_C_COMPILER_ID},Gnu>:-Wcast-align=strict>)
|
$<$<STREQUAL:${CMAKE_C_COMPILER_ID},Gnu>:-Wcast-align=strict>)
|
||||||
|
|
||||||
target_compile_options(${target} PRIVATE
|
target_compile_options(${target} PRIVATE
|
||||||
@ -254,6 +266,7 @@ function(include_deps target)
|
|||||||
third-party/stb
|
third-party/stb
|
||||||
third-party/dmon
|
third-party/dmon
|
||||||
third-party/tomlc99
|
third-party/tomlc99
|
||||||
|
third-party/fast_obj
|
||||||
$<$<NOT:$<BOOL:${EMSCRIPTEN}>>:third-party/glad/include>)
|
$<$<NOT:$<BOOL:${EMSCRIPTEN}>>:third-party/glad/include>)
|
||||||
|
|
||||||
list(TRANSFORM THIRD_PARTY_INCLUDES PREPEND ${TWN_ROOT_DIR}/)
|
list(TRANSFORM THIRD_PARTY_INCLUDES PREPEND ${TWN_ROOT_DIR}/)
|
||||||
@ -267,19 +280,40 @@ endfunction()
|
|||||||
function(link_deps target)
|
function(link_deps target)
|
||||||
target_link_libraries(${target} PUBLIC
|
target_link_libraries(${target} PUBLIC
|
||||||
$<$<NOT:$<BOOL:${EMSCRIPTEN}>>:SDL2::SDL2>
|
$<$<NOT:$<BOOL:${EMSCRIPTEN}>>:SDL2::SDL2>
|
||||||
|
$<$<NOT:$<OR:$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>,$<BOOL:${EMSCRIPTEN}>>>:SDL2::SDL2main>
|
||||||
physfs-static
|
physfs-static
|
||||||
xms)
|
xms)
|
||||||
target_include_directories(${target} PUBLIC ${SDL2_INCLUDE_DIRS})
|
target_include_directories(${target} PUBLIC ${SDL2_INCLUDE_DIRS})
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
|
|
||||||
function(use_townengine target sources output_directory)
|
function(put_townengine output_directory)
|
||||||
|
cmake_path(GET TWN_OUT_DIR STEM LAST_ONLY target)
|
||||||
|
|
||||||
|
add_executable(${target} ${TWN_ROOT_DIR}/src/twn_main.c)
|
||||||
|
target_link_options(${target} PRIVATE $<$<BOOL:${LINUX}>:-Wl,-rpath,$ORIGIN/>)
|
||||||
|
set_target_properties(${TWN_TARGET} PROPERTIES
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY ${output_directory})
|
||||||
|
set_target_properties(${target} PROPERTIES
|
||||||
|
OUTPUT_NAME ${target}
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY ${output_directory}
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY ${output_directory})
|
||||||
|
give_options(${target})
|
||||||
|
include_deps(${target})
|
||||||
|
link_deps(${target})
|
||||||
|
target_link_libraries(${target} PUBLIC ${TWN_TARGET})
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
|
||||||
|
function(use_townengine sources output_directory)
|
||||||
|
cmake_path(GET TWN_OUT_DIR STEM LAST_ONLY target)
|
||||||
|
|
||||||
if(TWN_FEATURE_DYNLIB_GAME)
|
if(TWN_FEATURE_DYNLIB_GAME)
|
||||||
# game shared library, for reloading
|
# game shared library, for reloading
|
||||||
add_library(${target}_game SHARED ${sources})
|
add_library(${target}_game SHARED ${sources})
|
||||||
give_options(${target}_game)
|
give_options(${target}_game)
|
||||||
include_deps(${target}_game)
|
include_deps(${target}_game)
|
||||||
target_link_libraries(${target}_game PUBLIC $<$<NOT:$<BOOL:${EMSCRIPTEN}>>:SDL2::SDL2> ${TWN_TARGET})
|
target_link_libraries(${target}_game PUBLIC ${TWN_TARGET})
|
||||||
set_target_properties(${target}_game PROPERTIES
|
set_target_properties(${target}_game PROPERTIES
|
||||||
OUTPUT_NAME game
|
OUTPUT_NAME game
|
||||||
LIBRARY_OUTPUT_DIRECTORY ${output_directory}
|
LIBRARY_OUTPUT_DIRECTORY ${output_directory}
|
||||||
@ -338,5 +372,18 @@ link_deps(twn_third_parties)
|
|||||||
give_options(${TWN_TARGET})
|
give_options(${TWN_TARGET})
|
||||||
include_deps(${TWN_TARGET})
|
include_deps(${TWN_TARGET})
|
||||||
link_deps(${TWN_TARGET})
|
link_deps(${TWN_TARGET})
|
||||||
target_link_libraries(${TWN_TARGET} PUBLIC twn_third_parties)
|
|
||||||
target_include_directories(${TWN_TARGET} PRIVATE ${TWN_ROOT_DIR}/src)
|
target_include_directories(${TWN_TARGET} PRIVATE ${TWN_ROOT_DIR}/src)
|
||||||
|
target_link_libraries(${TWN_TARGET} PUBLIC
|
||||||
|
twn_third_parties
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/font.o)
|
||||||
|
|
||||||
|
# embed resources
|
||||||
|
# TODO: think of a portable way to compress/decompress them
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/font.o
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E env CMAKE_CURRENT_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR} sh bin/prep-embed.sh
|
||||||
|
DEPENDS share/assets/Dernyns256.ttf)
|
||||||
|
|
||||||
|
add_custom_target(asset-compilation ALL DEPENDS
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/font.o)
|
||||||
|
@ -6,11 +6,11 @@ if(NOT CMAKE_BUILD_TYPE)
|
|||||||
set(CMAKE_BUILD_TYPE Debug)
|
set(CMAKE_BUILD_TYPE Debug)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build)
|
add_subdirectory($ENV{TWNROOT} ${CMAKE_BINARY_DIR}/twn)
|
||||||
|
|
||||||
set(SOURCE_FILES
|
set(SOURCE_FILES
|
||||||
game.c
|
game.c
|
||||||
state.h
|
state.h
|
||||||
)
|
)
|
||||||
|
|
||||||
use_townengine(${PROJECT_NAME} "${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR})
|
use_townengine("${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
[[deps]]
|
[[deps]]
|
||||||
source = "../../../common-data"
|
source = "../../../data"
|
||||||
name = "common-data"
|
name = "common-data"
|
||||||
|
@ -69,8 +69,8 @@ void game_tick(void)
|
|||||||
ctx.udata = calloc(1, sizeof(State));
|
ctx.udata = calloc(1, sizeof(State));
|
||||||
}
|
}
|
||||||
|
|
||||||
input_action("add_a_bit", CONTROL_LEFT_MOUSE);
|
input_action("add_a_bit", "LCLICK");
|
||||||
input_action("add_a_lot", CONTROL_RIGHT_MOUSE);
|
input_action("add_a_lot", "RCLICK");
|
||||||
|
|
||||||
State *state = ctx.udata;
|
State *state = ctx.udata;
|
||||||
|
|
||||||
|
1
apps/demos/crawl/data/assets/LICENSES
Normal file
1
apps/demos/crawl/data/assets/LICENSES
Normal file
@ -0,0 +1 @@
|
|||||||
|
castledoors.png - https://opengameart.org/content/castle-door - CC-BY 3.0
|
BIN
apps/demos/crawl/data/assets/brick.png
(Stored with Git LFS)
Normal file
BIN
apps/demos/crawl/data/assets/brick.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
apps/demos/crawl/data/assets/castledoors.png
(Stored with Git LFS)
Normal file
BIN
apps/demos/crawl/data/assets/castledoors.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
apps/demos/crawl/data/assets/lever.png
(Stored with Git LFS)
Normal file
BIN
apps/demos/crawl/data/assets/lever.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
apps/demos/crawl/data/assets/mossy_rock.png
(Stored with Git LFS)
Normal file
BIN
apps/demos/crawl/data/assets/mossy_rock.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
apps/demos/crawl/data/assets/pebbles.png
(Stored with Git LFS)
Normal file
BIN
apps/demos/crawl/data/assets/pebbles.png
(Stored with Git LFS)
Normal file
Binary file not shown.
37
apps/demos/crawl/data/levels/00.lvl
Normal file
37
apps/demos/crawl/data/levels/00.lvl
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
@map
|
||||||
|
-- Grid of symbols defined by @classes section.
|
||||||
|
#####
|
||||||
|
#.@.#
|
||||||
|
#...#
|
||||||
|
##.######
|
||||||
|
#...X...#
|
||||||
|
#./.#####
|
||||||
|
#####
|
||||||
|
|
||||||
|
|
||||||
|
@classes
|
||||||
|
-- Defines classes under symbols, which could have properties attached.
|
||||||
|
# stone_wall
|
||||||
|
wall_texture : /assets/brick.png
|
||||||
|
solid : 1
|
||||||
|
. stone_floor
|
||||||
|
tile_texture : /assets/mossy_rock.png
|
||||||
|
@ player_spawn
|
||||||
|
unique : 1
|
||||||
|
face : south
|
||||||
|
hold : torch
|
||||||
|
tile_texture : /assets/mossy_rock.png
|
||||||
|
X door
|
||||||
|
open_on_signal : sg_torch0
|
||||||
|
tile_texture : /assets/mossy_rock.png
|
||||||
|
face : horizon
|
||||||
|
face_texture : /assets/castledoors.png
|
||||||
|
/ lever
|
||||||
|
on_interact_emit : sg_torch0
|
||||||
|
tile_texture : /assets/mossy_rock.png
|
||||||
|
face : observer
|
||||||
|
face_texture : /assets/lever.png
|
||||||
|
|
||||||
|
@meta
|
||||||
|
-- Arbitrary sections could be defined with value pairs.
|
||||||
|
description : Test Level! Just two square rooms and a tunnel opened by lever.
|
71
apps/demos/crawl/data/scripts/game.lua
Normal file
71
apps/demos/crawl/data/scripts/game.lua
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
require("level")
|
||||||
|
require("render")
|
||||||
|
|
||||||
|
function lerp(a, b, x)
|
||||||
|
return a + ((b - a) * x)
|
||||||
|
end
|
||||||
|
|
||||||
|
function qlerp(a, b, x)
|
||||||
|
return lerp(a, b, x * x)
|
||||||
|
end
|
||||||
|
|
||||||
|
function game_tick()
|
||||||
|
if ctx.udata == nil then
|
||||||
|
ctx.udata = {
|
||||||
|
level = load_level("levels/00.lvl")
|
||||||
|
}
|
||||||
|
ctx.udata.player = {
|
||||||
|
position = ctx.udata.level.classes.player_spawn.position,
|
||||||
|
position_lerp = ctx.udata.level.classes.player_spawn.position,
|
||||||
|
direction = { x = 1, y = 0, z = 0 },
|
||||||
|
direction_lerp = { x = 1, y = 0, z = 0 },
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
input_action { control = "A", name = "turn_left" }
|
||||||
|
input_action { control = "D", name = "turn_right" }
|
||||||
|
input_action { control = "W", name = "walk_forward" }
|
||||||
|
input_action { control = "S", name = "walk_backward" }
|
||||||
|
|
||||||
|
if input_action_just_released { name = "turn_left" } then
|
||||||
|
ctx.udata.player.direction = { x = ctx.udata.player.direction.z,
|
||||||
|
y = ctx.udata.player.direction.y,
|
||||||
|
z =-ctx.udata.player.direction.x }
|
||||||
|
end
|
||||||
|
|
||||||
|
if input_action_just_released { name = "turn_right" } then
|
||||||
|
ctx.udata.player.direction = { x =-ctx.udata.player.direction.z,
|
||||||
|
y = ctx.udata.player.direction.y,
|
||||||
|
z = ctx.udata.player.direction.x }
|
||||||
|
end
|
||||||
|
|
||||||
|
local move = { x = 0, y = 0 }
|
||||||
|
if input_action_just_released { name = "walk_forward" } then
|
||||||
|
move = { x = move.x + ctx.udata.player.direction.x, y = move.y + ctx.udata.player.direction.z }
|
||||||
|
end
|
||||||
|
if input_action_just_released { name = "walk_backward" } then
|
||||||
|
move = { x = move.x - ctx.udata.player.direction.x, y = move.y - ctx.udata.player.direction.z }
|
||||||
|
end
|
||||||
|
|
||||||
|
if ctx.udata.level.grid[ctx.udata.player.position.y + move.y][ctx.udata.player.position.x + move.x].solid ~= nil then
|
||||||
|
move = { x = 0, y = 0 }
|
||||||
|
end
|
||||||
|
|
||||||
|
ctx.udata.player.position = { x = ctx.udata.player.position.x + move.x, y = ctx.udata.player.position.y + move.y }
|
||||||
|
|
||||||
|
ctx.udata.player.position_lerp.x = qlerp(ctx.udata.player.position_lerp.x, ctx.udata.player.position.x, ctx.frame_duration * 30)
|
||||||
|
ctx.udata.player.position_lerp.y = qlerp(ctx.udata.player.position_lerp.y, ctx.udata.player.position.y, ctx.frame_duration * 30)
|
||||||
|
ctx.udata.player.direction_lerp.x = qlerp(ctx.udata.player.direction_lerp.x, ctx.udata.player.direction.x, ctx.frame_duration * 40)
|
||||||
|
ctx.udata.player.direction_lerp.z = qlerp(ctx.udata.player.direction_lerp.z, ctx.udata.player.direction.z, ctx.frame_duration * 40)
|
||||||
|
|
||||||
|
draw_camera {
|
||||||
|
position = {
|
||||||
|
x = ctx.udata.player.position_lerp.x + 0.5 - ctx.udata.player.direction.x / 2,
|
||||||
|
y = 0.5,
|
||||||
|
z = ctx.udata.player.position_lerp.y + 0.5 - ctx.udata.player.direction.z / 2,
|
||||||
|
},
|
||||||
|
direction = ctx.udata.player.direction_lerp,
|
||||||
|
}
|
||||||
|
|
||||||
|
render_dungeon(ctx.udata.level)
|
||||||
|
end
|
91
apps/demos/crawl/data/scripts/level.lua
Normal file
91
apps/demos/crawl/data/scripts/level.lua
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
|
||||||
|
function load_level(file)
|
||||||
|
local f = file_read { file = file }
|
||||||
|
|
||||||
|
local result = {
|
||||||
|
-- templates to fill the grid with
|
||||||
|
classes = {
|
||||||
|
-- predefined empty tile
|
||||||
|
void = { },
|
||||||
|
},
|
||||||
|
-- symbol to class lookup table
|
||||||
|
glossary = {
|
||||||
|
[" "] = "void",
|
||||||
|
},
|
||||||
|
-- grid consists of expanded classes, of size dimensions
|
||||||
|
grid = {},
|
||||||
|
-- map consists of original rows of symbols that the grid is constructed from
|
||||||
|
map = {},
|
||||||
|
-- maximum extends of the map, unspecified tiles are filled with "void"
|
||||||
|
size = { x = 0, y = 0 },
|
||||||
|
}
|
||||||
|
|
||||||
|
-- iterate over lines
|
||||||
|
local section, subsection = "none", "none"
|
||||||
|
local from = 1
|
||||||
|
local start, limit = string.find(f, "\n", from)
|
||||||
|
while start do
|
||||||
|
local line = string.sub(f, from, start - 1)
|
||||||
|
-- skip over
|
||||||
|
if #line == 0 or line:find("^%-%-%s*") then
|
||||||
|
goto skip
|
||||||
|
-- start new section
|
||||||
|
elseif line:find("^@%g+") then
|
||||||
|
section = line:sub(2); subsection = "none"
|
||||||
|
-- decode map one line at a time
|
||||||
|
elseif section == "map" then
|
||||||
|
if result.size.x < #line then
|
||||||
|
result.size.x = #line
|
||||||
|
end
|
||||||
|
result.map[#result.map + 1] = line
|
||||||
|
-- templates to expand
|
||||||
|
elseif section == "classes" then
|
||||||
|
-- properties
|
||||||
|
if line:find("^ %g+") then
|
||||||
|
local _, _, property, value = line:find("^ (%g+)%s?:%s?(.*)")
|
||||||
|
result.classes[subsection][property] = value
|
||||||
|
goto skip
|
||||||
|
end
|
||||||
|
local symbol, classname = line:sub(1,1), line:sub(3)
|
||||||
|
result.classes[classname] = {
|
||||||
|
symbol = symbol,
|
||||||
|
}
|
||||||
|
result.glossary[symbol] = classname
|
||||||
|
subsection = classname
|
||||||
|
elseif section ~= "none" then
|
||||||
|
local _, _, property, value = line:find("^(%g+)%s?:%s?(.*)")
|
||||||
|
if result[section] == nil then
|
||||||
|
result[section] = {}
|
||||||
|
end
|
||||||
|
result[section][property] = value
|
||||||
|
end
|
||||||
|
::skip::
|
||||||
|
from = limit + 1
|
||||||
|
start, limit = string.find(f, "\n", from)
|
||||||
|
end
|
||||||
|
-- post process, expand map to grid
|
||||||
|
for y = 1, #result.map do
|
||||||
|
result.grid[y] = {}
|
||||||
|
for x = 1, result.size.x do
|
||||||
|
-- past defined for line
|
||||||
|
local symbol
|
||||||
|
if x > #result.map[y] then
|
||||||
|
symbol = " "
|
||||||
|
else
|
||||||
|
symbol = result.map[y]:sub(x,x)
|
||||||
|
end
|
||||||
|
local class = result.classes[result.glossary[symbol]]
|
||||||
|
if class["unique"] ~= nil then
|
||||||
|
class.position = { x = x, y = y }
|
||||||
|
end
|
||||||
|
result.grid[y][x] = class
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
result.size.y = #result.map
|
||||||
|
|
||||||
|
print(result.meta.description)
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
88
apps/demos/crawl/data/scripts/render.lua
Normal file
88
apps/demos/crawl/data/scripts/render.lua
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
-- if this is too wasteful, one could check nerby tiles to see whether faces could be visible
|
||||||
|
-- more robust solution would be to travel the level from observer point of view
|
||||||
|
function render_dungeon(dungeon)
|
||||||
|
for y = 1, dungeon.size.y do
|
||||||
|
for x = 1, dungeon.size.x do
|
||||||
|
if dungeon.grid[y][x].wall_texture ~= nil then
|
||||||
|
draw_quad {
|
||||||
|
texture = dungeon.grid[y][x].wall_texture,
|
||||||
|
v3 = { x = x, y = 1, z = y },
|
||||||
|
v2 = { x = x, y = 0, z = y },
|
||||||
|
v1 = { x = x + 1, y = 0, z = y },
|
||||||
|
v0 = { x = x + 1, y = 1, z = y },
|
||||||
|
texture_region = { w = 128, h = 128 },
|
||||||
|
}
|
||||||
|
draw_quad {
|
||||||
|
texture = dungeon.grid[y][x].wall_texture,
|
||||||
|
v3 = { x = x + 1, y = 1, z = y },
|
||||||
|
v2 = { x = x + 1, y = 0, z = y },
|
||||||
|
v1 = { x = x + 1, y = 0, z = y + 1 },
|
||||||
|
v0 = { x = x + 1, y = 1, z = y + 1 },
|
||||||
|
texture_region = { w = 128, h = 128 },
|
||||||
|
}
|
||||||
|
draw_quad {
|
||||||
|
texture = dungeon.grid[y][x].wall_texture,
|
||||||
|
v3 = { x = x + 1, y = 1, z = y + 1 },
|
||||||
|
v2 = { x = x + 1, y = 0, z = y + 1 },
|
||||||
|
v1 = { x = x, y = 0, z = y + 1 },
|
||||||
|
v0 = { x = x, y = 1, z = y + 1 },
|
||||||
|
texture_region = { w = 128, h = 128 },
|
||||||
|
}
|
||||||
|
draw_quad {
|
||||||
|
texture = dungeon.grid[y][x].wall_texture,
|
||||||
|
v3 = { x = x, y = 1, z = y + 1 },
|
||||||
|
v2 = { x = x, y = 0, z = y + 1 },
|
||||||
|
v1 = { x = x, y = 0, z = y },
|
||||||
|
v0 = { x = x, y = 1, z = y },
|
||||||
|
texture_region = { w = 128, h = 128 },
|
||||||
|
}
|
||||||
|
|
||||||
|
elseif dungeon.grid[y][x].tile_texture ~= nil then
|
||||||
|
draw_quad {
|
||||||
|
texture = dungeon.grid[y][x].tile_texture,
|
||||||
|
v0 = { x = x + 1, y = 0, z = y },
|
||||||
|
v1 = { x = x, y = 0, z = y },
|
||||||
|
v2 = { x = x, y = 0, z = y + 1 },
|
||||||
|
v3 = { x = x + 1, y = 0, z = y + 1},
|
||||||
|
texture_region = { w = 128, h = 128 },
|
||||||
|
}
|
||||||
|
draw_quad {
|
||||||
|
texture = dungeon.grid[y][x].tile_texture,
|
||||||
|
v3 = { x = x + 1, y = 1, z = y },
|
||||||
|
v2 = { x = x, y = 1, z = y },
|
||||||
|
v1 = { x = x, y = 1, z = y + 1 },
|
||||||
|
v0 = { x = x + 1, y = 1, z = y + 1},
|
||||||
|
texture_region = { w = 128, h = 128 },
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
if dungeon.grid[y][x].face_texture ~= nil then
|
||||||
|
if dungeon.grid[y][x].face == "horizon" then
|
||||||
|
draw_quad {
|
||||||
|
texture = dungeon.grid[y][x].face_texture,
|
||||||
|
v3 = { x = x + 1, y = 1, z = y },
|
||||||
|
v2 = { x = x + 1, y = 0, z = y },
|
||||||
|
v1 = { x = x + 1, y = 0, z = y + 1 },
|
||||||
|
v0 = { x = x + 1, y = 1, z = y + 1 },
|
||||||
|
texture_region = { w = 64, h = 64 },
|
||||||
|
}
|
||||||
|
draw_quad {
|
||||||
|
texture = dungeon.grid[y][x].face_texture,
|
||||||
|
v3 = { x = x, y = 1, z = y + 1 },
|
||||||
|
v2 = { x = x, y = 0, z = y + 1 },
|
||||||
|
v1 = { x = x, y = 0, z = y },
|
||||||
|
v0 = { x = x, y = 1, z = y },
|
||||||
|
texture_region = { w = 64, h = 64 },
|
||||||
|
}
|
||||||
|
|
||||||
|
elseif dungeon.grid[y][x].face == "observer" then
|
||||||
|
draw_billboard {
|
||||||
|
texture = dungeon.grid[y][x].face_texture,
|
||||||
|
position = { x = x + 0.5, y = 0.5, z = y + 0.5 },
|
||||||
|
size = { x = 0.5, y = 0.5 },
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
27
apps/demos/crawl/data/twn.toml
Normal file
27
apps/demos/crawl/data/twn.toml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# 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 ]
|
||||||
|
interpreter = "$TWNROOT/apps/twnlua"
|
||||||
|
#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"
|
@ -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{TWNROOT}/build)
|
add_subdirectory($ENV{TWNROOT} ${CMAKE_BINARY_DIR}/twn)
|
||||||
|
|
||||||
set(SOURCE_FILES
|
set(SOURCE_FILES
|
||||||
game.c
|
game.c
|
||||||
@ -20,4 +20,4 @@ set(SOURCE_FILES
|
|||||||
scenes/ingame.c scenes/ingame.h
|
scenes/ingame.c scenes/ingame.h
|
||||||
)
|
)
|
||||||
|
|
||||||
use_townengine(${PROJECT_NAME} "${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR})
|
use_townengine("${SOURCE_FILES}" ${TWN_OUT_DIR})
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
[[deps]]
|
[[deps]]
|
||||||
source = "../../../common-data" # where does it come from, might be an url
|
source = "../../../data" # where does it come from, might be an url
|
||||||
name = "common-data" # should be globally unique
|
name = "common-data" # should be globally unique
|
||||||
|
@ -22,17 +22,13 @@ void game_tick(void) {
|
|||||||
|
|
||||||
State *state = ctx.udata;
|
State *state = ctx.udata;
|
||||||
|
|
||||||
input_action("debug_toggle", CONTROL_BACKSPACE);
|
input_action("debug_toggle", "BACKSPACE");
|
||||||
input_action("debug_dump_atlases", CONTROL_HOME);
|
input_action("debug_dump_atlases", "HOME");
|
||||||
|
|
||||||
if (input_action_just_pressed("debug_toggle")) {
|
if (input_action_just_pressed("debug_toggle")) {
|
||||||
ctx.debug = !ctx.debug;
|
ctx.debug = !ctx.debug;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input_action_just_pressed("debug_dump_atlases")) {
|
|
||||||
textures_dump_atlases();
|
|
||||||
}
|
|
||||||
|
|
||||||
state->scene->tick(state);
|
state->scene->tick(state);
|
||||||
|
|
||||||
/* there's a scene switch pending, we can do it now that the tick is done */
|
/* there's a scene switch pending, we can do it now that the tick is done */
|
||||||
|
@ -11,9 +11,9 @@
|
|||||||
|
|
||||||
|
|
||||||
static void update_timers(Player *player) {
|
static void update_timers(Player *player) {
|
||||||
player->jump_air_timer = timer_tick_frames(player->jump_air_timer);
|
player->jump_air_timer = player->jump_air_timer - 1 <= 0 ? 0 : player->jump_air_timer - 1;
|
||||||
player->jump_coyote_timer = timer_tick_frames(player->jump_coyote_timer);
|
player->jump_coyote_timer = player->jump_coyote_timer - 1 <= 0 ? 0 : player->jump_coyote_timer - 1;
|
||||||
player->jump_buffer_timer = timer_tick_frames(player->jump_buffer_timer);
|
player->jump_buffer_timer = player->jump_buffer_timer - 1 <= 0 ? 0 : player->jump_buffer_timer - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,12 +11,17 @@
|
|||||||
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_left", "A");
|
||||||
input_action("player_right", CONTROL_D);
|
input_action("player_right", "D");
|
||||||
input_action("player_forward", CONTROL_W);
|
input_action("player_forward", "W");
|
||||||
input_action("player_backward", CONTROL_S);
|
input_action("player_backward", "S");
|
||||||
input_action("player_jump", CONTROL_SPACE);
|
input_action("player_jump", "SPACE");
|
||||||
input_action("player_run", CONTROL_LSHIFT);
|
input_action("player_run", "LSHIFT");
|
||||||
|
|
||||||
|
draw_camera_2d((Vec2){ scn->player->rect.x + scn->player->rect.w / 2 - ctx.resolution.x / 2,
|
||||||
|
scn->player->rect.y + scn->player->rect.h / 2 - ctx.resolution.y / 2 },
|
||||||
|
0, 1
|
||||||
|
);
|
||||||
|
|
||||||
world_drawdef(scn->world);
|
world_drawdef(scn->world);
|
||||||
player_calc(scn->player);
|
player_calc(scn->player);
|
||||||
|
@ -13,7 +13,7 @@ static void title_tick(State *state) {
|
|||||||
SceneTitle *scn = (SceneTitle *)state->scene;
|
SceneTitle *scn = (SceneTitle *)state->scene;
|
||||||
(void)scn;
|
(void)scn;
|
||||||
|
|
||||||
input_action("ui_accept", CONTROL_RETURN);
|
input_action("ui_accept", "ENTER");
|
||||||
|
|
||||||
if (input_action_just_pressed("ui_accept")) {
|
if (input_action_just_pressed("ui_accept")) {
|
||||||
switch_to(state, ingame_scene);
|
switch_to(state, ingame_scene);
|
||||||
|
@ -6,15 +6,14 @@ if(NOT CMAKE_BUILD_TYPE)
|
|||||||
set(CMAKE_BUILD_TYPE Debug)
|
set(CMAKE_BUILD_TYPE Debug)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build)
|
add_subdirectory($ENV{TWNROOT} ${CMAKE_BINARY_DIR}/twn)
|
||||||
|
|
||||||
set(SOURCE_FILES
|
set(SOURCE_FILES
|
||||||
game.c
|
game.c
|
||||||
state.h
|
state.h
|
||||||
|
|
||||||
scenes/scene.c scenes/scene.h
|
scenes/scene.c scenes/scene.h
|
||||||
scenes/title.c scenes/title.h
|
|
||||||
scenes/ingame.c scenes/ingame.h
|
scenes/ingame.c scenes/ingame.h
|
||||||
)
|
)
|
||||||
|
|
||||||
use_townengine(${PROJECT_NAME} "${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR})
|
use_townengine("${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
[[deps]]
|
[[deps]]
|
||||||
source = "../../../common-data" # where does it come from, might be an url
|
source = "../../../data" # where does it come from, might be an url
|
||||||
name = "common-data" # should be globally unique
|
name = "common-data" # should be globally unique
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
#include "state.h"
|
#include "state.h"
|
||||||
#include "scenes/scene.h"
|
#include "scenes/scene.h"
|
||||||
#include "scenes/title.h"
|
|
||||||
#include "scenes/ingame.h"
|
#include "scenes/ingame.h"
|
||||||
|
|
||||||
#include "twn_game_api.h"
|
#include "twn_game_api.h"
|
||||||
@ -18,23 +17,19 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
State *state = ctx.udata;
|
State *state = ctx.udata;
|
||||||
|
|
||||||
input_action("debug_toggle", CONTROL_BACKSPACE);
|
input_action("debug_toggle", "BACKSPACE");
|
||||||
input_action("debug_dump_atlases", CONTROL_HOME);
|
input_action("debug_dump_atlases", "HOME");
|
||||||
|
|
||||||
if (input_action_just_pressed("debug_toggle")) {
|
if (input_action_just_pressed("debug_toggle")) {
|
||||||
ctx.debug = !ctx.debug;
|
ctx.debug = !ctx.debug;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input_action_just_pressed("debug_dump_atlases")) {
|
|
||||||
textures_dump_atlases();
|
|
||||||
}
|
|
||||||
|
|
||||||
state->scene->tick(state);
|
state->scene->tick(state);
|
||||||
|
|
||||||
/* there's a scene switch pending, we can do it now that the tick is done */
|
/* there's a scene switch pending, we can do it now that the tick is done */
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#include "ingame.h"
|
#include "ingame.h"
|
||||||
#include "title.h"
|
|
||||||
#include "scene.h"
|
#include "scene.h"
|
||||||
|
|
||||||
#include "twn_game_api.h"
|
#include "twn_game_api.h"
|
||||||
@ -13,21 +12,298 @@
|
|||||||
|
|
||||||
|
|
||||||
#define TERRAIN_FREQUENCY 0.15f
|
#define TERRAIN_FREQUENCY 0.15f
|
||||||
#define TERRAIN_DISTANCE 64
|
#define TERRAIN_RADIUS 128
|
||||||
|
#define GRASS_RADIUS 16
|
||||||
|
#define TERRAIN_DISTANCE (TERRAIN_RADIUS * 2)
|
||||||
#define HALF_TERRAIN_DISTANCE ((float)TERRAIN_DISTANCE / 2)
|
#define HALF_TERRAIN_DISTANCE ((float)TERRAIN_DISTANCE / 2)
|
||||||
#define PLAYER_HEIGHT 0.6f
|
#define PLAYER_HEIGHT 0.6f
|
||||||
|
#define TREE_DENSITY 0.03f
|
||||||
|
|
||||||
|
#define G_CONST 10.0f
|
||||||
|
|
||||||
|
/* TODO: pregenerate grid of levels of detail */
|
||||||
static float heightmap[TERRAIN_DISTANCE][TERRAIN_DISTANCE];
|
static float heightmap[TERRAIN_DISTANCE][TERRAIN_DISTANCE];
|
||||||
|
|
||||||
|
|
||||||
|
/* vehicle sim ! */
|
||||||
|
/* https://www.youtube.com/watch?v=pwbwFdWBkU0 */
|
||||||
|
/* representation is a "jelly" box with spring configuration trying to make it coherent */
|
||||||
|
|
||||||
|
/* == springs == */
|
||||||
|
/* damped spring: F = -kx - cv */
|
||||||
|
/* x = length(p1-p0) - at_rest_length */
|
||||||
|
/* v = dot(v1 - v0, unit(p1-p0)) */
|
||||||
|
/* F = (-kx - cv) * unit(p1-p0) */
|
||||||
|
/* v += (F/m)*t */
|
||||||
|
/* one points gains positive F, other negative F, to come together */
|
||||||
|
|
||||||
|
/* == ground interaction == */
|
||||||
|
/* if point is under terrain, then apply this: */
|
||||||
|
/* x = y difference on point and ground */
|
||||||
|
/* -x(n) = x * normal */
|
||||||
|
/* -v(n) = dot(v, normal) */
|
||||||
|
/* -F = (-kx(n)-cv(n)) * normal */
|
||||||
|
|
||||||
|
/* == friction == */
|
||||||
|
/* v(o)/F(o) are perpendicular to slope (x - x(n)) */
|
||||||
|
/* if v(o) == 0, then */
|
||||||
|
/* -- at rest, static friction overcomes */
|
||||||
|
/* if F(o) <= f(s)*x(n), F(o) = 0 */
|
||||||
|
/* else, F(o) -= f(k) * x(n) */
|
||||||
|
/* else if length(v(o) + (F(o)/m)*t) <= (f(k)*x(n)*t), v(o) = 0 */
|
||||||
|
/* else, F = -unit(v(o)*f(k)*x(n)) */
|
||||||
|
|
||||||
|
#define VEHICLE_MASS 200.0f
|
||||||
|
#define VEHICLE_LENGTH 3.0f
|
||||||
|
#define VEHICLE_WIDTH 1.7f
|
||||||
|
#define VEHICLE_HEIGHT 1.3f
|
||||||
|
/* spring constant */
|
||||||
|
#define VEHICLE_SPRING_K 22000.0f
|
||||||
|
#define VEHICLE_SPRING_K_SHOCK 18000.0f
|
||||||
|
#define VEHICLE_SPRING_GK 70000.0f
|
||||||
|
/* damping constant */
|
||||||
|
#define VEHICLE_SPRING_C 800.0f
|
||||||
|
#define VEHICLE_SPRING_C_SHOCK 500.0f
|
||||||
|
#define VEHICLE_SPRING_GC 100.0f
|
||||||
|
#define VEHICLE_FRICTION_S 1000.0f
|
||||||
|
#define VEHICLE_FRICTION_K 110.0f
|
||||||
|
#define VEHICLE_FRICTION_V 4000.0f
|
||||||
|
|
||||||
|
/* TODO: shock springs, that are more loose, which are used to simulate the wheels */
|
||||||
|
/* initial, ideal corner positions */
|
||||||
|
static const Vec3 vbpi[8] = {
|
||||||
|
[0] = { 0, 0, 0 },
|
||||||
|
[1] = { VEHICLE_LENGTH, 0, 0 },
|
||||||
|
[2] = { VEHICLE_LENGTH, 0, VEHICLE_WIDTH },
|
||||||
|
[3] = { 0, 0, VEHICLE_WIDTH },
|
||||||
|
[4] = { 0, VEHICLE_HEIGHT, 0 },
|
||||||
|
[5] = { VEHICLE_LENGTH, VEHICLE_HEIGHT, 0 },
|
||||||
|
[6] = { VEHICLE_LENGTH, VEHICLE_HEIGHT, VEHICLE_WIDTH },
|
||||||
|
[7] = { 0, VEHICLE_HEIGHT, VEHICLE_WIDTH },
|
||||||
|
};
|
||||||
|
/* corner positions in simulation */
|
||||||
|
static Vec3 vbp[8] = { vbpi[0], vbpi[1], vbpi[2], vbpi[3],
|
||||||
|
vbpi[4], vbpi[5], vbpi[6], vbpi[7], };
|
||||||
|
/* corner velocities */
|
||||||
|
static Vec3 vbv[8];
|
||||||
|
/* springs */
|
||||||
|
static uint8_t vbs[28][2] = {
|
||||||
|
{0, 1}, {4, 5}, {0, 4},
|
||||||
|
{1, 2}, {5, 6}, {1, 5},
|
||||||
|
{2, 3}, {6, 7}, {2, 6},
|
||||||
|
{3, 0}, {7, 4}, {3, 7},
|
||||||
|
|
||||||
|
{0, 2}, {0, 5}, {0, 7},
|
||||||
|
{1, 3}, {1, 6}, {1, 4},
|
||||||
|
{4, 6}, {2, 7}, {2, 5},
|
||||||
|
{5, 7}, {3, 4}, {3, 6},
|
||||||
|
|
||||||
|
{0, 6}, {1, 7}, {2, 4}, {3, 5},
|
||||||
|
};
|
||||||
|
/* ackermann steering geometry */
|
||||||
|
static float vehicle_turning_extend;
|
||||||
|
static float vehicle_turning_speed = 0.12f;
|
||||||
|
static float vehicle_turning_extend_limit = VEHICLE_WIDTH * 1.75f;
|
||||||
|
|
||||||
|
static float height_at(SceneIngame *scn, Vec2 position);
|
||||||
|
static Vec3 normal_at(SceneIngame *scn, Vec2 position);
|
||||||
|
|
||||||
|
|
||||||
|
static inline float clampf(float f, float min, float max) {
|
||||||
|
const float t = f < min ? min : f;
|
||||||
|
return t > max ? max : t;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void draw_vehicle(SceneIngame *scn) {
|
||||||
|
for (size_t i = 0; i < 12; ++i)
|
||||||
|
draw_line_3d(vbp[vbs[i][0]], vbp[vbs[i][1]], 1, (Color){255, 255, 255, 255});
|
||||||
|
for (size_t i = 12; i < 24; ++i)
|
||||||
|
draw_line_3d(vbp[vbs[i][0]], vbp[vbs[i][1]], 1, (Color){200, 200, 200, 255});
|
||||||
|
for (size_t i = 24; i < 28; ++i)
|
||||||
|
draw_line_3d(vbp[vbs[i][0]], vbp[vbs[i][1]], 1, (Color){255, 125, 125, 255});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void process_vehicle(SceneIngame *scn) {
|
||||||
|
/* apply gravity */
|
||||||
|
Vec3 Facc[8] = {0};
|
||||||
|
|
||||||
|
/* steering */
|
||||||
|
bool steered = false;
|
||||||
|
if (input_action_pressed("player_left")) {
|
||||||
|
vehicle_turning_extend -= vehicle_turning_speed;
|
||||||
|
steered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input_action_pressed("player_right")) {
|
||||||
|
vehicle_turning_extend += vehicle_turning_speed;
|
||||||
|
steered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!steered)
|
||||||
|
vehicle_turning_extend -= copysignf(vehicle_turning_speed * 0.9f, vehicle_turning_extend);
|
||||||
|
|
||||||
|
vehicle_turning_extend = clampf(vehicle_turning_extend, -vehicle_turning_extend_limit, vehicle_turning_extend_limit);
|
||||||
|
if (fabsf(vehicle_turning_extend) <= 0.11f)
|
||||||
|
vehicle_turning_extend = 0;
|
||||||
|
|
||||||
|
Vec3 const Fg = { .y = -VEHICLE_MASS * G_CONST };
|
||||||
|
for (size_t i = 0; i < 8; ++i)
|
||||||
|
Facc[i] = vec3_add(Facc[i], Fg);
|
||||||
|
|
||||||
|
/* apply springs */
|
||||||
|
for (size_t i = 0; i < 28; ++i) {
|
||||||
|
Vec3 const p0 = vbp[vbs[i][0]];
|
||||||
|
Vec3 const p1 = vbp[vbs[i][1]];
|
||||||
|
Vec3 const v0 = vbv[vbs[i][0]];
|
||||||
|
Vec3 const v1 = vbv[vbs[i][1]];
|
||||||
|
Vec3 const pd = vec3_sub(p1, p0);
|
||||||
|
Vec3 const pn = vec3_norm(pd);
|
||||||
|
/* TODO: length at rest could be precalculated */
|
||||||
|
float const lar = vec3_length(vec3_sub(vbpi[vbs[i][1]], vbpi[vbs[i][0]]));
|
||||||
|
float const x = vec3_length(pd) - lar;
|
||||||
|
float const v = vec3_dot(vec3_sub(v1, v0), pn);
|
||||||
|
float const spring_k = i == 2 | i == 5 || i == 8 || i == 11 ? VEHICLE_SPRING_K_SHOCK : VEHICLE_SPRING_K;
|
||||||
|
float const spring_c = i == 2 | i == 5 || i == 8 || i == 11 ? VEHICLE_SPRING_C_SHOCK : VEHICLE_SPRING_C;
|
||||||
|
Vec3 const Fs = vec3_scale(pn, -spring_k * x - spring_c * v);
|
||||||
|
Facc[vbs[i][0]] = vec3_sub(Facc[vbs[i][0]], Fs);
|
||||||
|
Facc[vbs[i][1]] = vec3_add(Facc[vbs[i][1]], Fs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* spring and friction against the ground */
|
||||||
|
for (size_t i = 0; i < 8; ++i) {
|
||||||
|
Vec3 const p = vbp[i];
|
||||||
|
Vec3 const v = vbv[i];
|
||||||
|
float const h = height_at(scn, (Vec2){ p.x, p.z });
|
||||||
|
Vec3 const fwd = vec3_norm(vec3_sub(vbp[1], vbp[0]));
|
||||||
|
if (h >= p.y) {
|
||||||
|
/* back wheel processing: acceleration */
|
||||||
|
if (i == 0 || i == 3) {
|
||||||
|
float scale = 0;
|
||||||
|
if (scn->camera_mode == 2 && input_action_pressed("player_forward"))
|
||||||
|
scale += 1;
|
||||||
|
if (scn->camera_mode == 2 && input_action_pressed("player_backward"))
|
||||||
|
scale -= 1;
|
||||||
|
Facc[i] = vec3_add(Facc[i], vec3_scale(fwd, 6500 * scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* normal force, for displacement */
|
||||||
|
Vec3 const n = normal_at(scn, (Vec2){ p.x, p.z });
|
||||||
|
float const xn = (h - p.y) * n.y;
|
||||||
|
float const vn = vec3_dot(v, n);
|
||||||
|
Vec3 const Fn = vec3_scale(n, -VEHICLE_SPRING_GK * xn - VEHICLE_SPRING_GC * vn);
|
||||||
|
Facc[i] = vec3_sub(Facc[i], Fn);
|
||||||
|
|
||||||
|
/* friction force, perpendicular to normal force */
|
||||||
|
/* TODO: is it right? aren't vn+vol should be = |v| */
|
||||||
|
Vec3 const von = vec3_norm(vec3_cross(n, vec3_cross(v, n)));
|
||||||
|
Vec3 const vo = vec3_scale(vec3_scale(von, vec3_length(v) - vn), -1);
|
||||||
|
float const vol = vec3_length(vo);
|
||||||
|
Vec3 const Fon = vec3_norm(vec3_cross(n, vec3_cross(Facc[i], n)));
|
||||||
|
Vec3 Fo = vec3_scale(vec3_scale(Fon, vec3_length(Facc[i]) - vec3_dot(Facc[i], n)), -1);
|
||||||
|
/* portion of total force along the surface */
|
||||||
|
float const Fol = vec3_length(Fo);
|
||||||
|
float const fkxn = VEHICLE_FRICTION_K * xn;
|
||||||
|
/* at rest, might want to start moving */
|
||||||
|
if (fabsf(0.0f - vol) <= 0.0001f) {
|
||||||
|
/* cannot overcome static friction, force along the surface is zeroed */
|
||||||
|
if (Fol <= VEHICLE_FRICTION_S * xn) { Fo = vec3_scale(Fo, -1);}
|
||||||
|
/* resist the force by friction, while starting to move */
|
||||||
|
else { Fo = vec3_sub(Fo, vec3_scale(von, fkxn));}
|
||||||
|
/* not at rest, stop accelerating along the surface */
|
||||||
|
} else if (vol + (Fol / VEHICLE_MASS) * ctx.frame_duration <= fkxn * ctx.frame_duration * 2) {
|
||||||
|
/* ugh ... */
|
||||||
|
vbv[i] = vec3_add(v, vo);
|
||||||
|
/* just apply friction */
|
||||||
|
} else {
|
||||||
|
Fo = vec3_scale(von, -fkxn * 400);
|
||||||
|
}
|
||||||
|
Facc[i] = vec3_add(Facc[i], Fo);
|
||||||
|
|
||||||
|
/* rear wheel friction */
|
||||||
|
if (i == 0 || i == 3) {
|
||||||
|
Vec3 const pn = vec3_cross(fwd, n);
|
||||||
|
Vec3 const Fp = vec3_scale(pn, vec3_dot(v, pn) * -VEHICLE_FRICTION_V);
|
||||||
|
Facc[i] = vec3_add(Facc[i], Fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* front wheel processing */
|
||||||
|
if (i == 1 || i == 2) {
|
||||||
|
/* steering influences "center of turning", which is a point */
|
||||||
|
/* laying on line defined by rear axle */
|
||||||
|
/* front arms are rotated to be perpendicular to center of turning, */
|
||||||
|
/* which then are used to dissipate forces, thus providing control */
|
||||||
|
Vec3 const rear_bar = vec3_sub(vbp[0], vbp[3]);
|
||||||
|
Vec3 const rear_center = vec3_scale(vec3_add(vbp[0], vbp[3]), 0.5);
|
||||||
|
Vec3 a, b, r;
|
||||||
|
if (i == 1) {
|
||||||
|
a = vec3_sub(vbp[3], vbp[2]);
|
||||||
|
b = vec3_sub(rear_center, vbp[2]);
|
||||||
|
r = vbp[2];
|
||||||
|
} else {
|
||||||
|
a = vec3_sub(vbp[0], vbp[1]);
|
||||||
|
b = vec3_sub(rear_center, vbp[1]);
|
||||||
|
r = vbp[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
float const arm_angle = vec3_angle(a, b);
|
||||||
|
Vec3 const turn_center = vec3_add(rear_center, vec3_scale(vec3_norm(rear_bar), vehicle_turning_extend));
|
||||||
|
Vec3 const arm = vec3_sub(r, turn_center);
|
||||||
|
Vec3 const n = vec3_norm(vec3_cross(a, b));
|
||||||
|
Vec3 const wheel = vec3_norm(vec3_rotate(arm, -arm_angle, n));
|
||||||
|
Vec3 const p = vec3_norm(vec3_cross(wheel, n));
|
||||||
|
draw_line_3d(r, vec3_add(r, p), 1, (Color){0,255,255,255});
|
||||||
|
|
||||||
|
Vec3 const Fp = vec3_scale(p, vec3_dot(v, p) * -VEHICLE_FRICTION_V);
|
||||||
|
Facc[i] = vec3_add(Facc[i], Fp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3 vd = vec3_scale(vec3_scale(Facc[i], (1.0f / VEHICLE_MASS)), ctx.frame_duration);
|
||||||
|
vbv[i] = vec3_add(vbv[i], vd);
|
||||||
|
vbp[i] = vec3_add(vbp[i], vec3_scale(vbv[i], ctx.frame_duration));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void process_vehicle_mode(State *state) {
|
||||||
|
SceneIngame *scn = (SceneIngame *)state->scene;
|
||||||
|
|
||||||
|
Vec3 const top_center = vec3_sub(vbp[4], vec3_scale(vec3_sub(vbp[4], vbp[6]), 1.0f / 2.0f));
|
||||||
|
// Vec3 const front_center = vec3_add(vbp[4], vec3_scale(vec3_sub(vbp[4], vbp[7]), 1.0f / 2.0f));
|
||||||
|
// Vec3 const facing_direction = vec3_sub(top_center, front_center);
|
||||||
|
|
||||||
|
float yawc, yaws, pitchc, pitchs;
|
||||||
|
sincosf(scn->yaw + (float)M_PI_2, &yaws, &yawc);
|
||||||
|
sincosf(scn->pitch, &pitchs, &pitchc);
|
||||||
|
|
||||||
|
Vec3 const looking_direction = vec3_norm(((Vec3){
|
||||||
|
yawc * pitchc,
|
||||||
|
pitchs,
|
||||||
|
yaws * pitchc,
|
||||||
|
}));
|
||||||
|
|
||||||
|
Vec3 const orbit = vec3_sub(top_center, vec3_scale(looking_direction, 7.5));
|
||||||
|
|
||||||
|
draw_camera(orbit, looking_direction, (Vec3){0,1,0}, (float)M_PI_2 * 0.8f, 1, TERRAIN_RADIUS * sqrtf(3));
|
||||||
|
|
||||||
|
scn->looking_direction = looking_direction;
|
||||||
|
|
||||||
|
scn->pos = top_center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void process_fly_mode(State *state) {
|
static void process_fly_mode(State *state) {
|
||||||
SceneIngame *scn = (SceneIngame *)state->scene;
|
SceneIngame *scn = (SceneIngame *)state->scene;
|
||||||
|
|
||||||
DrawCameraFromPrincipalAxesResult dir_and_up =
|
DrawCameraFromPrincipalAxesResult dir_and_up =
|
||||||
draw_camera_from_principal_axes(scn->pos, scn->roll, scn->pitch, scn->yaw, (float)M_PI_2 * 0.8f, 1);
|
draw_camera_from_principal_axes(scn->pos, scn->roll, scn->pitch, scn->yaw, (float)M_PI_2 * 0.8f, 1, TERRAIN_RADIUS * sqrtf(3));
|
||||||
|
|
||||||
|
scn->looking_direction = dir_and_up.direction;
|
||||||
|
|
||||||
const Vec3 right = m_vec_norm(m_vec_cross(dir_and_up.direction, dir_and_up.up));
|
const Vec3 right = m_vec_norm(m_vec_cross(dir_and_up.direction, dir_and_up.up));
|
||||||
const float speed = 0.04f; /* TODO: put this in a better place */
|
const float speed = 0.1f; /* TODO: put this in a better place */
|
||||||
if (input_action_pressed("player_left"))
|
if (input_action_pressed("player_left"))
|
||||||
scn->pos = vec3_sub(scn->pos, m_vec_scale(right, speed));
|
scn->pos = vec3_sub(scn->pos, m_vec_scale(right, speed));
|
||||||
|
|
||||||
@ -47,33 +323,39 @@ static void process_fly_mode(State *state) {
|
|||||||
scn->pos.y -= speed;
|
scn->pos.y -= speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* TODO: could be baked in map format */
|
||||||
|
static Vec3 normal_at(SceneIngame *scn, Vec2 position) {
|
||||||
|
int const x = (int)(floorf(position.x - scn->world_center.x));
|
||||||
|
int const y = (int)(floorf(position.y - scn->world_center.y));
|
||||||
|
|
||||||
|
float const height0 = heightmap[x][y];
|
||||||
|
float const height1 = heightmap[x + 1][y];
|
||||||
|
float const height2 = heightmap[x][y + 1];
|
||||||
|
|
||||||
|
Vec3 const a = { .x = 1, .y = height0 - height1, .z = 0 };
|
||||||
|
Vec3 const b = { .x = 0, .y = height0 - height2, .z = -1 };
|
||||||
|
|
||||||
|
return vec3_norm(vec3_cross(a, b));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: don't operate on triangles, instead interpolate on quads */
|
||||||
static float height_at(SceneIngame *scn, Vec2 position) {
|
static float height_at(SceneIngame *scn, Vec2 position) {
|
||||||
float height0, height1, height2, weight0, weight1, weight2;
|
int const x = (int)(floorf(position.x - scn->world_center.x));
|
||||||
|
int const y = (int)(floorf(position.y - scn->world_center.y));
|
||||||
|
|
||||||
int const x = (int)(HALF_TERRAIN_DISTANCE + (position.x - scn->pos.x));
|
float const height0 = heightmap[x][y];
|
||||||
int const y = (int)(HALF_TERRAIN_DISTANCE + (position.y - scn->pos.z));
|
float const height1 = heightmap[x + 1][y];
|
||||||
|
float const height2 = heightmap[x][y + 1];
|
||||||
height0 = heightmap[x][y];
|
float const height3 = heightmap[x + 1][y + 1];
|
||||||
height1 = heightmap[x + 1][y + 1];
|
|
||||||
|
|
||||||
Vec2 incell = { position.x - floorf(position.x), position.y - floorf(position.y) };
|
Vec2 incell = { position.x - floorf(position.x), position.y - floorf(position.y) };
|
||||||
|
|
||||||
/* who needs barycentric coordinates, am i right? */
|
float const weight0 = (1 - incell.x) * (1 - incell.y);
|
||||||
weight0 = 1 / sqrtf(powf(incell.x, 2) + powf(incell.y, 2));
|
float const weight1 = ( incell.x) * (1 - incell.y);
|
||||||
weight1 = 1 / sqrtf(powf(1 - incell.x, 2) + powf(1 - incell.y, 2));
|
float const weight2 = (1 - incell.x) * ( incell.y);
|
||||||
|
float const weight3 = ( incell.x) * ( incell.y);
|
||||||
|
|
||||||
/* find which triangle we're directly under */
|
return (height0 * weight0 + height1 * weight1 + height2 * weight2 + height3 * weight3) / (weight0 + weight1 + weight2 + weight3);
|
||||||
/* 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));
|
|
||||||
}
|
|
||||||
|
|
||||||
return (height0 * weight0 + height1 * weight1 + height2 * weight2) / (weight0 + weight1 + weight2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -81,13 +363,15 @@ static void process_ground_mode(State *state) {
|
|||||||
SceneIngame *scn = (SceneIngame *)state->scene;
|
SceneIngame *scn = (SceneIngame *)state->scene;
|
||||||
|
|
||||||
DrawCameraFromPrincipalAxesResult dir_and_up =
|
DrawCameraFromPrincipalAxesResult dir_and_up =
|
||||||
draw_camera_from_principal_axes(scn->pos, scn->roll, scn->pitch, scn->yaw, (float)M_PI_2 * 0.8f, 1);
|
draw_camera_from_principal_axes(scn->pos, scn->roll, scn->pitch, scn->yaw, (float)M_PI_2 * 0.8f, 1, TERRAIN_RADIUS * sqrtf(3));
|
||||||
|
|
||||||
|
scn->looking_direction = dir_and_up.direction;
|
||||||
|
|
||||||
dir_and_up.direction.y = 0;
|
dir_and_up.direction.y = 0;
|
||||||
dir_and_up.direction = vec3_norm(dir_and_up.direction);
|
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 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 */
|
const float speed = 0.20f; /* TODO: put this in a better place */
|
||||||
|
|
||||||
Vec3 target = scn->pos;
|
Vec3 target = scn->pos;
|
||||||
|
|
||||||
@ -96,7 +380,7 @@ static void process_ground_mode(State *state) {
|
|||||||
float const height = height_at(scn, (Vec2){scn->pos.x, scn->pos.z});
|
float const height = height_at(scn, (Vec2){scn->pos.x, scn->pos.z});
|
||||||
|
|
||||||
if (target.y > height + PLAYER_HEIGHT)
|
if (target.y > height + PLAYER_HEIGHT)
|
||||||
target.y = target.y - 0.4f;
|
target.y = target.y - 0.6f;
|
||||||
|
|
||||||
if (target.y < height + PLAYER_HEIGHT)
|
if (target.y < height + PLAYER_HEIGHT)
|
||||||
target.y = height + PLAYER_HEIGHT;
|
target.y = height + PLAYER_HEIGHT;
|
||||||
@ -135,50 +419,100 @@ static void generate_terrain(SceneIngame *scn) {
|
|||||||
float y = floorf(scn->pos.z - HALF_TERRAIN_DISTANCE + (float)ly);
|
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;
|
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;
|
height += stb_perlin_noise3((float)x * TERRAIN_FREQUENCY / 10, (float)y * TERRAIN_FREQUENCY / 10, 0, 0, 0, 0) * 20 - 1;
|
||||||
|
|
||||||
heightmap[lx][ly] = height;
|
heightmap[lx][ly] = height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scn->world_center = (Vec2){ floorf(scn->pos.x - HALF_TERRAIN_DISTANCE), floorf(scn->pos.z - HALF_TERRAIN_DISTANCE) };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int32_t ceil_sqrt(int32_t const n) {
|
||||||
|
int32_t res = 1;
|
||||||
|
while(res * res < n)
|
||||||
|
res++;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static uint32_t adler32(const void *buf, size_t buflength) {
|
||||||
|
const uint8_t *buffer = (const uint8_t*)buf;
|
||||||
|
|
||||||
|
uint32_t s1 = 1;
|
||||||
|
uint32_t s2 = 0;
|
||||||
|
|
||||||
|
for (size_t n = 0; n < buflength; n++) {
|
||||||
|
s1 = (s1 + buffer[n]) % 65521;
|
||||||
|
s2 = (s2 + s1) % 65521;
|
||||||
|
}
|
||||||
|
return (s2 << 16) | s1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void draw_terrain(SceneIngame *scn) {
|
static void draw_terrain(SceneIngame *scn) {
|
||||||
for (int ly = TERRAIN_DISTANCE - 1; ly > 0; ly--) {
|
/* used to cull invisible tiles over field of view (to horizon) */
|
||||||
for (int lx = 0; lx < TERRAIN_DISTANCE - 1; lx++) {
|
Vec2 const d = vec2_norm((Vec2){ .x = scn->looking_direction.x, .y = scn->looking_direction.z });
|
||||||
int32_t x = (int32_t)(floorf(scn->pos.x - HALF_TERRAIN_DISTANCE + (float)lx));
|
float const c = cosf((float)M_PI_2 * 0.8f * 0.8f);
|
||||||
int32_t y = (int32_t)(floorf(scn->pos.z - HALF_TERRAIN_DISTANCE + (float)ly));
|
|
||||||
|
/* draw terrain in circle */
|
||||||
|
int32_t const rsi = (int32_t)TERRAIN_RADIUS * (int32_t)TERRAIN_RADIUS;
|
||||||
|
for (int32_t iy = -(int32_t)TERRAIN_RADIUS; iy <= (int32_t)TERRAIN_RADIUS - 1; ++iy) {
|
||||||
|
int32_t const dx = ceil_sqrt(rsi - (iy + (iy <= 0)) * (iy + (iy <= 0)));
|
||||||
|
for (int32_t ix = -dx; ix < dx - 1; ++ix) {
|
||||||
|
int32_t lx = ix + TERRAIN_RADIUS;
|
||||||
|
int32_t ly = iy + TERRAIN_RADIUS;
|
||||||
|
|
||||||
|
float x = (float)(floorf(scn->pos.x - HALF_TERRAIN_DISTANCE + (float)lx));
|
||||||
|
float y = (float)(floorf(scn->pos.z - HALF_TERRAIN_DISTANCE + (float)ly));
|
||||||
|
|
||||||
|
/* cull tiles outside of vision */
|
||||||
|
if (vec2_dot(vec2_norm((Vec2){x - scn->pos.x + d.x * 2, y - scn->pos.z + d.y * 2}), d) < c)
|
||||||
|
continue;
|
||||||
|
|
||||||
float d0 = heightmap[lx][ly];
|
float d0 = heightmap[lx][ly];
|
||||||
float d1 = heightmap[lx + 1][ly];
|
float d1 = heightmap[lx + 1][ly];
|
||||||
float d2 = heightmap[lx + 1][ly - 1];
|
float d2 = heightmap[lx + 1][ly - 1];
|
||||||
float d3 = heightmap[lx][ly - 1];
|
float d3 = heightmap[lx][ly - 1];
|
||||||
|
|
||||||
draw_triangle("/assets/grass.png",
|
draw_quad("/assets/grass2.png",
|
||||||
(Vec3){ (float)x, d0, (float)y },
|
(Vec3){ (float)x, d0, (float)y },
|
||||||
(Vec3){ (float)x + 1, d1, (float)y },
|
|
||||||
(Vec3){ (float)x, d3, (float)y - 1 },
|
|
||||||
(Vec2){ 128, 128 },
|
|
||||||
(Vec2){ 128, 0 },
|
|
||||||
(Vec2){ 0, 128 },
|
|
||||||
(Color){255, 255, 255, 255},
|
|
||||||
(Color){255, 255, 255, 255},
|
|
||||||
(Color){255, 255, 255, 255});
|
|
||||||
|
|
||||||
draw_triangle("/assets/grass.png",
|
|
||||||
(Vec3){ (float)x + 1, d1, (float)y },
|
(Vec3){ (float)x + 1, d1, (float)y },
|
||||||
(Vec3){ (float)x + 1, d2, (float)y - 1 },
|
(Vec3){ (float)x + 1, d2, (float)y - 1 },
|
||||||
(Vec3){ (float)x, d3, (float)y - 1 },
|
(Vec3){ (float)x, d3, (float)y - 1 },
|
||||||
(Vec2){ 128, 0 },
|
(Rect){ .w = 128, .h = 128 },
|
||||||
(Vec2){ 0, 0 },
|
|
||||||
(Vec2){ 0, 128 },
|
|
||||||
(Color){255, 255, 255, 255},
|
|
||||||
(Color){255, 255, 255, 255},
|
|
||||||
(Color){255, 255, 255, 255});
|
(Color){255, 255, 255, 255});
|
||||||
|
|
||||||
draw_billboard("/assets/grasses/10.png",
|
if (((float)(adler32(&((Vec2){x, y}), sizeof (Vec2)) % 100) / 100) <= TREE_DENSITY)
|
||||||
(Vec3){ (float)x, d0 + 0.15f, (float)y },
|
draw_billboard("/assets/trreez.png",
|
||||||
(Vec2){0.3f, 0.3f},
|
(Vec3){ (float)x, d0 + 1.95f, (float)y },
|
||||||
|
(Vec2){2.f, 2.f},
|
||||||
|
(Rect){0},
|
||||||
|
(Color){255, 255, 255, 255}, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t const rsi_g = (int32_t)GRASS_RADIUS * (int32_t)GRASS_RADIUS;
|
||||||
|
for (int32_t iy = -(int32_t)GRASS_RADIUS; iy <= (int32_t)GRASS_RADIUS - 1; ++iy) {
|
||||||
|
int32_t const dx = ceil_sqrt(rsi_g - (iy + (iy <= 0)) * (iy + (iy <= 0)));
|
||||||
|
for (int32_t ix = -dx; ix < dx; ++ix) {
|
||||||
|
int32_t lx = ix + TERRAIN_RADIUS;
|
||||||
|
int32_t ly = iy + TERRAIN_RADIUS;
|
||||||
|
|
||||||
|
float x = (float)(floorf(scn->pos.x - HALF_TERRAIN_DISTANCE + (float)lx));
|
||||||
|
float y = (float)(floorf(scn->pos.z - HALF_TERRAIN_DISTANCE + (float)ly));
|
||||||
|
|
||||||
|
float d = heightmap[lx][ly];
|
||||||
|
|
||||||
|
draw_billboard("/assets/grasses/25.png",
|
||||||
|
(Vec3){
|
||||||
|
(float)x + (float)((adler32(&((Vec2){x, y}), sizeof (Vec2))) % 32) / 64.0f,
|
||||||
|
d + 0.2f,
|
||||||
|
(float)y + (float)((adler32(&((Vec2){y, x}), sizeof (Vec2))) % 32) / 64.0f
|
||||||
|
},
|
||||||
|
(Vec2){0.4f, 0.4f},
|
||||||
|
(Rect){0},
|
||||||
(Color){255, 255, 255, 255}, true);
|
(Color){255, 255, 255, 255}, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -188,29 +522,31 @@ static void draw_terrain(SceneIngame *scn) {
|
|||||||
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_left", "A");
|
||||||
input_action("player_right", CONTROL_D);
|
input_action("player_right", "D");
|
||||||
input_action("player_forward", CONTROL_W);
|
input_action("player_forward", "W");
|
||||||
input_action("player_backward", CONTROL_S);
|
input_action("player_backward", "S");
|
||||||
input_action("player_jump", CONTROL_SPACE);
|
input_action("player_jump", "SPACE");
|
||||||
input_action("player_run", CONTROL_LSHIFT);
|
input_action("player_run", "LSHIFT");
|
||||||
input_action("mouse_capture_toggle", CONTROL_ESCAPE);
|
input_action("mouse_capture_toggle", "ESCAPE");
|
||||||
input_action("toggle_camera_mode", CONTROL_C);
|
input_action("toggle_camera_mode", "C");
|
||||||
|
|
||||||
if (scn->mouse_captured) {
|
if (scn->mouse_captured) {
|
||||||
const float sensitivity = 0.4f * (float)DEG2RAD; /* TODO: put this in a better place */
|
const float sensitivity = 0.4f * (float)(M_PI / 180); /* TODO: put this in a better place */
|
||||||
scn->yaw += (float)ctx.mouse_movement.x * sensitivity;
|
scn->yaw += (float)ctx.mouse_movement.x * sensitivity;
|
||||||
scn->pitch -= (float)ctx.mouse_movement.y * sensitivity;
|
scn->pitch -= (float)ctx.mouse_movement.y * sensitivity;
|
||||||
scn->pitch = clampf(scn->pitch, (float)-M_PI * 0.49f, (float)M_PI * 0.49f);
|
scn->pitch = clampf(scn->pitch, (float)-M_PI * 0.49f, (float)M_PI * 0.49f);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input_action_just_pressed("toggle_camera_mode"))
|
if (input_action_just_pressed("toggle_camera_mode"))
|
||||||
scn->flying_camera = !scn->flying_camera;
|
scn->camera_mode = scn->camera_mode == 2 ? 0 : scn->camera_mode + 1;
|
||||||
|
|
||||||
if (scn->flying_camera) {
|
if (scn->camera_mode == 1) {
|
||||||
process_fly_mode(state);
|
process_fly_mode(state);
|
||||||
} else {
|
} else if (scn->camera_mode == 0) {
|
||||||
process_ground_mode(state);
|
process_ground_mode(state);
|
||||||
|
} else if (scn->camera_mode) {
|
||||||
|
process_vehicle_mode(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* toggle mouse capture with end key */
|
/* toggle mouse capture with end key */
|
||||||
@ -220,12 +556,15 @@ static void ingame_tick(State *state) {
|
|||||||
ctx.mouse_capture = scn->mouse_captured;
|
ctx.mouse_capture = scn->mouse_captured;
|
||||||
|
|
||||||
generate_terrain(scn);
|
generate_terrain(scn);
|
||||||
|
process_vehicle(scn);
|
||||||
|
|
||||||
draw_terrain(scn);
|
draw_terrain(scn);
|
||||||
|
draw_vehicle(scn);
|
||||||
|
|
||||||
draw_skybox("/assets/miramar/miramar_*.tga");
|
draw_skybox("/assets/miramar/miramar_*.tga");
|
||||||
|
|
||||||
ctx.fog_color = (Color){ 140, 147, 160, 255 };
|
ctx.fog_color = (Color){ 140, 147, 160, 255 };
|
||||||
ctx.fog_density = 0.03f;
|
ctx.fog_density = 0.015f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -243,7 +582,7 @@ Scene *ingame_scene(State *state) {
|
|||||||
|
|
||||||
new_scene->mouse_captured = true;
|
new_scene->mouse_captured = true;
|
||||||
|
|
||||||
m_audio(m_set(path, "music/mod65.xm"),
|
m_audio(m_set(path, "music/woah.ogg"),
|
||||||
m_opt(channel, "soundtrack"),
|
m_opt(channel, "soundtrack"),
|
||||||
m_opt(repeat, true));
|
m_opt(repeat, true));
|
||||||
|
|
||||||
|
@ -12,13 +12,16 @@
|
|||||||
typedef struct SceneIngame {
|
typedef struct SceneIngame {
|
||||||
Scene base;
|
Scene base;
|
||||||
|
|
||||||
|
Vec3 looking_direction;
|
||||||
|
Vec2 world_center;
|
||||||
|
|
||||||
Vec3 pos;
|
Vec3 pos;
|
||||||
float yaw;
|
float yaw;
|
||||||
float pitch;
|
float pitch;
|
||||||
float roll;
|
float roll;
|
||||||
|
|
||||||
bool mouse_captured;
|
bool mouse_captured;
|
||||||
bool flying_camera;
|
int camera_mode;
|
||||||
} SceneIngame;
|
} SceneIngame;
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,61 +0,0 @@
|
|||||||
#include "title.h"
|
|
||||||
#include "ingame.h"
|
|
||||||
|
|
||||||
#include "twn_game_api.h"
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
|
|
||||||
static void title_tick(State *state) {
|
|
||||||
SceneTitle *scn = (SceneTitle *)state->scene;
|
|
||||||
(void)scn;
|
|
||||||
|
|
||||||
input_action("ui_accept", CONTROL_RETURN);
|
|
||||||
|
|
||||||
if (input_action_just_pressed("ui_accept")) {
|
|
||||||
switch_to(state, ingame_scene);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_sprite("/assets/title.png", ((Rect) {
|
|
||||||
((float)ctx.resolution.x / 2) - ((float)320 / 2), 64, 320, 128 }));
|
|
||||||
|
|
||||||
/* draw the tick count as an example of dynamic text */
|
|
||||||
size_t text_str_len = snprintf(NULL, 0, "%llu", (unsigned long long)state->ctx->frame_number) + 1;
|
|
||||||
char *text_str = malloc(text_str_len);
|
|
||||||
snprintf(text_str, text_str_len, "%llu", (unsigned long long)state->ctx->frame_number);
|
|
||||||
|
|
||||||
const char *font = "/fonts/kenney-pixel.ttf";
|
|
||||||
float text_h = 32;
|
|
||||||
float text_w = draw_text_width(text_str, text_h, font);
|
|
||||||
|
|
||||||
draw_rectangle(
|
|
||||||
(Rect) {
|
|
||||||
.x = 0,
|
|
||||||
.y = 0,
|
|
||||||
.w = (float)text_w,
|
|
||||||
.h = (float)text_h,
|
|
||||||
},
|
|
||||||
(Color) { 0, 0, 0, 255 }
|
|
||||||
);
|
|
||||||
|
|
||||||
draw_text(text_str, (Vec2){ 0, 0 }, text_h, (Color) { 255, 255, 255, 255 }, font);
|
|
||||||
free(text_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void title_end(State *state) {
|
|
||||||
free(state->scene);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Scene *title_scene(State *state) {
|
|
||||||
(void)state;
|
|
||||||
|
|
||||||
SceneTitle *new_scene = calloc(1, sizeof *new_scene);
|
|
||||||
new_scene->base.tick = title_tick;
|
|
||||||
new_scene->base.end = title_end;
|
|
||||||
|
|
||||||
return (Scene *)new_scene;
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
#ifndef TITLE_H
|
|
||||||
#define TITLE_H
|
|
||||||
|
|
||||||
#include "../state.h"
|
|
||||||
#include "scene.h"
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct SceneTitle {
|
|
||||||
Scene base;
|
|
||||||
} SceneTitle;
|
|
||||||
|
|
||||||
|
|
||||||
Scene *title_scene(State *state);
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
@ -6,11 +6,11 @@ if(NOT CMAKE_BUILD_TYPE)
|
|||||||
set(CMAKE_BUILD_TYPE Debug)
|
set(CMAKE_BUILD_TYPE Debug)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build)
|
add_subdirectory($ENV{TWNROOT} ${CMAKE_BINARY_DIR}/twn)
|
||||||
|
|
||||||
set(SOURCE_FILES
|
set(SOURCE_FILES
|
||||||
game.c
|
game.c
|
||||||
state.h
|
state.h
|
||||||
)
|
)
|
||||||
|
|
||||||
use_townengine(${PROJECT_NAME} "${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR})
|
use_townengine("${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
@ -113,14 +113,16 @@ void game_tick(void) {
|
|||||||
|
|
||||||
Vec2 const mouse_snap = {floorf(ctx.mouse_position.x / 8) * 8, floorf(ctx.mouse_position.y / 8) * 8};
|
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("up", "LCLICK");
|
||||||
input_action("down", CONTROL_RIGHT_MOUSE);
|
input_action("down", "RCLICK");
|
||||||
|
|
||||||
if (input_action_just_pressed("up"))
|
if (input_action_pressed("up"))
|
||||||
state->r += 1;
|
state->r += 1;
|
||||||
if (input_action_just_pressed("down"))
|
if (input_action_pressed("down") && state->r > 2)
|
||||||
state->r -= 1;
|
state->r -= 1;
|
||||||
|
|
||||||
|
draw_circle(mouse_snap, state->r * 8, (Color){125, 125, 125, 125});
|
||||||
|
|
||||||
int32_t const rsi = (int32_t)state->r * (int32_t)state->r;
|
int32_t const rsi = (int32_t)state->r * (int32_t)state->r;
|
||||||
int32_t acc = 1;
|
int32_t acc = 1;
|
||||||
for (int32_t iy = (int32_t)state->r - 1; iy >= 0; --iy) {
|
for (int32_t iy = (int32_t)state->r - 1; iy >= 0; --iy) {
|
||||||
@ -133,9 +135,8 @@ void game_tick(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_circle(mouse_snap, state->r * 8, (Color){125, 125, 125, 125});
|
/* uncomment to see performance difference between variants */
|
||||||
|
// benchmark(state);
|
||||||
benchmark(state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ if(NOT CMAKE_BUILD_TYPE)
|
|||||||
set(CMAKE_BUILD_TYPE Debug)
|
set(CMAKE_BUILD_TYPE Debug)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build)
|
add_subdirectory($ENV{TWNROOT} ${CMAKE_BINARY_DIR}/twn)
|
||||||
|
|
||||||
set(SOURCE_FILES
|
set(SOURCE_FILES
|
||||||
game.c
|
game.c
|
||||||
@ -13,4 +13,4 @@ set(SOURCE_FILES
|
|||||||
)
|
)
|
||||||
|
|
||||||
cmake_path(GET CMAKE_SOURCE_DIR STEM LAST_ONLY GAME_PROJECT_NAME)
|
cmake_path(GET CMAKE_SOURCE_DIR STEM LAST_ONLY GAME_PROJECT_NAME)
|
||||||
use_townengine(${GAME_PROJECT_NAME} "${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR})
|
use_townengine("${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
@ -14,6 +14,7 @@ dev_id = "you"
|
|||||||
# Game runtime details
|
# Game runtime details
|
||||||
[game]
|
[game]
|
||||||
resolution = [ 640, 480 ]
|
resolution = [ 640, 480 ]
|
||||||
|
background_color = [ 255, 125, 0, 255 ]
|
||||||
#debug = true
|
#debug = true
|
||||||
|
|
||||||
# Engine tweaks. You probably don't need to change these
|
# Engine tweaks. You probably don't need to change these
|
||||||
|
13
apps/templates/lua/.gitignore
vendored
13
apps/templates/lua/.gitignore
vendored
@ -3,10 +3,15 @@
|
|||||||
!*.*
|
!*.*
|
||||||
!*/
|
!*/
|
||||||
|
|
||||||
*.so
|
**/*.so
|
||||||
*.dll
|
**/*.dll
|
||||||
*.exe
|
**/*.exe
|
||||||
*.trace
|
**/*.trace
|
||||||
|
**/*.js
|
||||||
|
**/*.wasm
|
||||||
|
**/*.wasm.map
|
||||||
|
**/*.data
|
||||||
|
**/*.html
|
||||||
|
|
||||||
data/scripts/twnapi.lua
|
data/scripts/twnapi.lua
|
||||||
build/
|
build/
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
-- called every frame, with constant delta time
|
-- called every frame, with constant delta time
|
||||||
function game_tick()
|
function game_tick()
|
||||||
-- ctx.initialization_needed is true first frame and every time dynamic reload is performed
|
-- ctx.udata persists on code reload
|
||||||
if ctx.initialization_needed then
|
if ctx.udata == nil then
|
||||||
-- ctx.udata persists on reload
|
|
||||||
ctx.udata = {}
|
ctx.udata = {}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,53 +0,0 @@
|
|||||||
---@type { frame_number: number, frame_duration: number, fog_density: number, fog_color: { r: number, g: number, b: number, a: number }, resolution: { x: number, y: number }, mouse_position: { x: number, y: number }, mouse_movement: { x: number, y: number }, random_seed: number, debug: boolean, initialization_needed: boolean, mouse_capture: boolean, udata: table }
|
|
||||||
ctx = nil
|
|
||||||
---@alias Control '"A"'|'"B"'|'"C"'|'"D"'|'"E"'|'"F"'|'"G"'|'"H"'|'"I"'|'"J"'|'"K"'|'"L"'|'"M"'|'"N"'|'"O"'|'"P"'|'"Q"'|'"R"'|'"S"'|'"T"'|'"U"'|'"V"'|'"W"'|'"X"'|'"Y"'|'"Z"'|'"1"'|'"2"'|'"3"'|'"4"'|'"5"'|'"6"'|'"7"'|'"8"'|'"9"'|'"0"'|'"RETURN"'|'"ESCAPE"'|'"BACKSPACE"'|'"TAB"'|'"SPACE"'|'"MINUS"'|'"EQUALS"'|'"LEFTBRACKET"'|'"RIGHTBRACKET"'|'"BACKSLASH"'|'"NONUSHASH"'|'"SEMICOLON"'|'"APOSTROPHE"'|'"GRAVE"'|'"COMMA"'|'"PERIOD"'|'"SLASH"'|'"CAPSLOCK"'|'"F1"'|'"F2"'|'"F3"'|'"F4"'|'"F5"'|'"F6"'|'"F7"'|'"F8"'|'"F9"'|'"F10"'|'"F11"'|'"F12"'|'"PRINTSCREEN"'|'"SCROLLLOCK"'|'"PAUSE"'|'"INSERT"'|'"HOME"'|'"PAGEUP"'|'"DELETE"'|'"END"'|'"PAGEDOWN"'|'"RIGHT"'|'"LEFT"'|'"DOWN"'|'"UP"'|'"NUMLOCKCLEAR"'|'"KP_DIVIDE"'|'"KP_MULTIPLY"'|'"KP_MINUS"'|'"KP_PLUS"'|'"KP_ENTER"'|'"KP_1"'|'"KP_2"'|'"KP_3"'|'"KP_4"'|'"KP_5"'|'"KP_6"'|'"KP_7"'|'"KP_8"'|'"KP_9"'|'"KP_0"'|'"KP_PERIOD"'|'"NONUSBACKSLASH"'|'"APPLICATION"'|'"POWER"'|'"KP_EQUALS"'|'"F13"'|'"F14"'|'"F15"'|'"F16"'|'"F17"'|'"F18"'|'"F19"'|'"F20"'|'"F21"'|'"F22"'|'"F23"'|'"F24"'|'"EXECUTE"'|'"HELP"'|'"MENU"'|'"SELECT"'|'"STOP"'|'"AGAIN"'|'"UNDO"'|'"CUT"'|'"COPY"'|'"PASTE"'|'"FIND"'|'"MUTE"'|'"VOLUMEUP"'|'"VOLUMEDOWN"'|'"KP_COMMA"'|'"KP_EQUALSAS400"'|'"INTERNATIONAL1"'|'"INTERNATIONAL2"'|'"INTERNATIONAL3"'|'"INTERNATIONAL4"'|'"INTERNATIONAL5"'|'"INTERNATIONAL6"'|'"INTERNATIONAL7"'|'"INTERNATIONAL8"'|'"INTERNATIONAL9"'|'"LANG1"'|'"LANG2"'|'"LANG3"'|'"LANG4"'|'"LANG5"'|'"LANG6"'|'"LANG7"'|'"LANG8"'|'"LANG9"'|'"ALTERASE"'|'"SYSREQ"'|'"CANCEL"'|'"CLEAR"'|'"PRIOR"'|'"RETURN2"'|'"SEPARATOR"'|'"OUT"'|'"OPER"'|'"CLEARAGAIN"'|'"CRSEL"'|'"EXSEL"'|'"KP_00"'|'"KP_000"'|'"THOUSANDSSEPARATOR"'|'"DECIMALSEPARATOR"'|'"CURRENCYUNIT"'|'"CURRENCYSUBUNIT"'|'"KP_LEFTPAREN"'|'"KP_RIGHTPAREN"'|'"KP_LEFTBRACE"'|'"KP_RIGHTBRACE"'|'"KP_TAB"'|'"KP_BACKSPACE"'|'"KP_A"'|'"KP_B"'|'"KP_C"'|'"KP_D"'|'"KP_E"'|'"KP_F"'|'"KP_XOR"'|'"KP_POWER"'|'"KP_PERCENT"'|'"KP_LESS"'|'"KP_GREATER"'|'"KP_AMPERSAND"'|'"KP_DBLAMPERSAND"'|'"KP_VERTICALBAR"'|'"KP_DBLVERTICALBAR"'|'"KP_COLON"'|'"KP_HASH"'|'"KP_SPACE"'|'"KP_AT"'|'"KP_EXCLAM"'|'"KP_MEMSTORE"'|'"KP_MEMRECALL"'|'"KP_MEMCLEAR"'|'"KP_MEMADD"'|'"KP_MEMSUBTRACT"'|'"KP_MEMMULTIPLY"'|'"KP_MEMDIVIDE"'|'"KP_PLUSMINUS"'|'"KP_CLEAR"'|'"KP_CLEARENTRY"'|'"KP_BINARY"'|'"KP_OCTAL"'|'"KP_DECIMAL"'|'"KP_HEXADECIMAL"'|'"LCTRL"'|'"LSHIFT"'|'"LALT"'|'"LGUI"'|'"RCTRL"'|'"RSHIFT"'|'"RALT"'|'"RGUI"'|'"MODE"'|'"KBDILLUMTOGGLE"'|'"KBDILLUMDOWN"'|'"KBDILLUMUP"'|'"EJECT"'|'"SLEEP"'|'"APP1"'|'"APP2"'|'"AUDIOREWIND"'|'"AUDIOFASTFORWARD"'|'"SOFTLEFT"'|'"SOFTRIGHT"'|'"CALL"'|'"ENDCALL"'|'"LEFT_MOUSE"'|'"RIGHT_MOUSE"'|'"MIDDLE_MOUSE"'|'"X1"'|'"X2"'
|
|
||||||
---@param args { name: string, control: Control }
|
|
||||||
function input_action(args) end
|
|
||||||
---@param args { name: string }
|
|
||||||
function input_action_pressed(args) end
|
|
||||||
---@param args { name: string }
|
|
||||||
function input_action_just_pressed(args) end
|
|
||||||
---@param args { name: string }
|
|
||||||
function input_action_just_released(args) end
|
|
||||||
---@param args { name: string }
|
|
||||||
function input_action_position(args) end
|
|
||||||
---@param args { texture: string, rect: { x: number, y: number, w: number, h: number }, texture_region: { x: number, y: number, w: number, h: number }?, color: { r: number, g: number, b: number, a: number }?, rotation: number?, flip_x: boolean?, flip_y: boolean?, stretch: boolean? }
|
|
||||||
function draw_sprite(args) end
|
|
||||||
---@param args { rect: { x: number, y: number, w: number, h: number }, color: { r: number, g: number, b: number, a: number }? }
|
|
||||||
function draw_rectangle(args) end
|
|
||||||
---@param args { position: { x: number, y: number }, radius: number, color: { r: number, g: number, b: number, a: number }? }
|
|
||||||
function draw_circle(args) end
|
|
||||||
---@param args { string: string, position: { x: number, y: number }, height: number?, color: { r: number, g: number, b: number, a: number }?, font: string? }
|
|
||||||
function draw_text(args) end
|
|
||||||
---@param args { string: string, height: number?, font: string? }
|
|
||||||
function draw_text_width(args) end
|
|
||||||
---@param args { texture: string, corners: { x: number, y: number }, rect: { x: number, y: number, w: number, h: number }, border_thickness: number?, color: { r: number, g: number, b: number, a: number }? }
|
|
||||||
function draw_nine_slice(args) end
|
|
||||||
---@param args { start: { x: number, y: number }, finish: { x: number, y: number }, thickness: number?, color: { r: number, g: number, b: number, a: number }? }
|
|
||||||
function draw_line(args) end
|
|
||||||
---@param args { texture: string, v0: { x: number, y: number, z: number }, v1: { x: number, y: number, z: number }, v2: { x: number, y: number, z: number }, uv0: { x: number, y: number }, uv1: { x: number, y: number }, uv2: { x: number, y: number }, c0: { r: number, g: number, b: number, a: number }?, c1: { r: number, g: number, b: number, a: number }?, c2: { r: number, g: number, b: number, a: number }? }
|
|
||||||
function draw_triangle(args) end
|
|
||||||
---@param args { texture: string, v0: { x: number, y: number, z: number }, v1: { x: number, y: number, z: number }, v2: { x: number, y: number, z: number }, v3: { x: number, y: number, z: number }, texture_region: { x: number, y: number, w: number, h: number }, color: { r: number, g: number, b: number, a: number }? }
|
|
||||||
function draw_quad(args) end
|
|
||||||
---@param args { texture: string, position: { x: number, y: number, z: number }, size: { x: number, y: number }, color: { r: number, g: number, b: number, a: number }?, cylindrical: boolean? }
|
|
||||||
function draw_billboard(args) end
|
|
||||||
---@param args { position: { x: number, y: number, z: number }, direction: { x: number, y: number, z: number }?, up: { x: number, y: number, z: number }?, fov: number?, zoom: number? }
|
|
||||||
function draw_camera(args) end
|
|
||||||
---@param args { position: { x: number, y: number, z: number }, roll: number?, pitch: number?, yaw: number?, fov: number?, zoom: number? }
|
|
||||||
function draw_camera_from_principal_axes(args) end
|
|
||||||
---@param args { textures: string? }
|
|
||||||
function draw_skybox(args) end
|
|
||||||
---@param args { audio: string, channel: string?, loops: boolean?, volume: number?, panning: number? }
|
|
||||||
function audio_play(args) end
|
|
||||||
---@param args { channel: string, parameter: string, value: number }
|
|
||||||
function audio_parameter(args) end
|
|
||||||
---@param args { value: { x: number, y: number }, identity: string }
|
|
||||||
function log_vec2(args) end
|
|
||||||
---@param args { value: { x: number, y: number, z: number }, identity: string }
|
|
||||||
function log_vec3(args) end
|
|
||||||
---@param args { value: { x: number, y: number, w: number, h: number }, identity: string }
|
|
||||||
function log_rect(args) end
|
|
||||||
---@param args { profile: string }
|
|
||||||
function profile_start(args) end
|
|
||||||
---@param args { profile: string }
|
|
||||||
function profile_end(args) end
|
|
19
apps/templates/zig/.gitignore
vendored
Normal file
19
apps/templates/zig/.gitignore
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# ignore executables
|
||||||
|
*
|
||||||
|
!*.*
|
||||||
|
!*/
|
||||||
|
|
||||||
|
**/*.so
|
||||||
|
**/*.dll
|
||||||
|
**/*.exe
|
||||||
|
**/*.trace
|
||||||
|
**/*.js
|
||||||
|
**/*.wasm
|
||||||
|
**/*.wasm.map
|
||||||
|
**/*.data
|
||||||
|
**/*.html
|
||||||
|
|
||||||
|
data/scripts/twnapi.lua
|
||||||
|
build/
|
||||||
|
.zig-cache/
|
||||||
|
zig-out/
|
26
apps/templates/zig/CMakeLists.txt
Normal file
26
apps/templates/zig/CMakeLists.txt
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.21)
|
||||||
|
project(twngame LANGUAGES C)
|
||||||
|
|
||||||
|
if(NOT CMAKE_BUILD_TYPE)
|
||||||
|
set(CMAKE_BUILD_TYPE Debug)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_subdirectory($ENV{TWNROOT} ${CMAKE_BINARY_DIR}/twn)
|
||||||
|
|
||||||
|
cmake_path(GET CMAKE_SOURCE_DIR STEM LAST_ONLY GAME_PROJECT_NAME)
|
||||||
|
put_townengine(${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
||||||
|
file(GLOB_RECURSE zig-sources ${CMAKE_CURRENT_SOURCE_DIR}/src/*.zig)
|
||||||
|
|
||||||
|
# TODO: support static build
|
||||||
|
# TODO: propagate release switches
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/libgame.so
|
||||||
|
COMMAND env zig build
|
||||||
|
DEPENDS ${TWN_TARGET} ${zig-sources}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_target(
|
||||||
|
zig-step ALL
|
||||||
|
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/libgame.so
|
||||||
|
)
|
58
apps/templates/zig/build.zig
Normal file
58
apps/templates/zig/build.zig
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
// Although this function looks imperative, note that its job is to
|
||||||
|
// declaratively construct a build graph that will be executed by an external
|
||||||
|
// runner.
|
||||||
|
pub fn build(b: *std.Build) void {
|
||||||
|
// Standard target options allows the person running `zig build` to choose
|
||||||
|
// what target to build for. Here we do not override the defaults, which
|
||||||
|
// means any target is allowed, and the default is native. Other options
|
||||||
|
// for restricting supported target set are available.
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
|
||||||
|
// Standard optimization options allow the person running `zig build` to select
|
||||||
|
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
|
||||||
|
// set a preferred release mode, allowing the user to decide how to optimize.
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
// This creates a "module", which represents a collection of source files alongside
|
||||||
|
// some compilation options, such as optimization mode and linked system libraries.
|
||||||
|
// Every executable or library we compile will be based on one or more modules.
|
||||||
|
const lib_mod = b.createModule(.{
|
||||||
|
// `root_source_file` is the Zig "entry point" of the module. If a module
|
||||||
|
// only contains e.g. external object files, you can make this `null`.
|
||||||
|
// In this case the main source file is merely a path, however, in more
|
||||||
|
// complicated build scripts, this could be a generated file.
|
||||||
|
.root_source_file = b.path("src/root.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.link_libc = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
lib_mod.addIncludePath(b.path("../../../"));
|
||||||
|
lib_mod.addIncludePath(b.path("../../../include/"));
|
||||||
|
lib_mod.addLibraryPath(b.path("./"));
|
||||||
|
|
||||||
|
// Now, we will create a static library based on the module we created above.
|
||||||
|
// This creates a `std.Build.Step.Compile`, which is the build step responsible
|
||||||
|
// for actually invoking the compiler.
|
||||||
|
const lib = b.addLibrary(.{
|
||||||
|
.linkage = .dynamic,
|
||||||
|
.name = "game",
|
||||||
|
.root_module = lib_mod,
|
||||||
|
});
|
||||||
|
|
||||||
|
lib.linkSystemLibrary("townengine");
|
||||||
|
|
||||||
|
// This declares intent for the library to be installed into the standard
|
||||||
|
// location when the user invokes the "install" step (the default step when
|
||||||
|
// running `zig build`).
|
||||||
|
const install_artifact = b.addInstallArtifact(lib, .{
|
||||||
|
.dest_dir = .{
|
||||||
|
.override = .{
|
||||||
|
.custom = "../",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
b.getInstallStep().dependOn(&install_artifact.step);
|
||||||
|
}
|
27
apps/templates/zig/data/twn.toml
Normal file
27
apps/templates/zig/data/twn.toml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# 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 = "Zig Awesomeness"
|
||||||
|
developer = "notwanp"
|
||||||
|
app_id = "yourzigthing"
|
||||||
|
dev_id = "definatelynotwanp"
|
||||||
|
|
||||||
|
# Game runtime details
|
||||||
|
[game]
|
||||||
|
resolution = [ 640, 480 ]
|
||||||
|
background_color = [ 255, 125, 0, 255 ]
|
||||||
|
#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"
|
24
apps/templates/zig/src/root.zig
Normal file
24
apps/templates/zig/src/root.zig
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const c = @cImport({
|
||||||
|
@cInclude("twn_game_api.h");
|
||||||
|
});
|
||||||
|
|
||||||
|
export fn game_tick() void {
|
||||||
|
tick() catch |err| {
|
||||||
|
std.log.err(@errorName(err));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export fn game_end() void {
|
||||||
|
end() catch |err| {
|
||||||
|
std.log.err(@errorName(err));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tick() !void {
|
||||||
|
if (c.ctx.initialization_needed) {
|
||||||
|
std.debug.print("lmao\n", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end() !void {}
|
16
apps/tools/twndel/CMakeLists.txt
Normal file
16
apps/tools/twndel/CMakeLists.txt
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.21)
|
||||||
|
project(twndel LANGUAGES C)
|
||||||
|
|
||||||
|
if(NOT CMAKE_BUILD_TYPE)
|
||||||
|
set(CMAKE_BUILD_TYPE Debug)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_subdirectory($ENV{TWNROOT} ${CMAKE_BINARY_DIR}/twn)
|
||||||
|
|
||||||
|
set(SOURCE_FILES
|
||||||
|
tool.c
|
||||||
|
state.h
|
||||||
|
)
|
||||||
|
|
||||||
|
cmake_path(GET CMAKE_SOURCE_DIR STEM LAST_ONLY GAME_PROJECT_NAME)
|
||||||
|
use_townengine("${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR})
|
BIN
apps/tools/twndel/data/bong.ogg
(Stored with Git LFS)
Normal file
BIN
apps/tools/twndel/data/bong.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
apps/tools/twndel/data/camera.png
(Stored with Git LFS)
Normal file
BIN
apps/tools/twndel/data/camera.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
apps/tools/twndel/data/center.png
(Stored with Git LFS)
Normal file
BIN
apps/tools/twndel/data/center.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
apps/tools/twndel/data/click.wav
Normal file
BIN
apps/tools/twndel/data/click.wav
Normal file
Binary file not shown.
BIN
apps/tools/twndel/data/drop.ogg
(Stored with Git LFS)
Normal file
BIN
apps/tools/twndel/data/drop.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
apps/tools/twndel/data/grab.png
(Stored with Git LFS)
Normal file
BIN
apps/tools/twndel/data/grab.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
apps/tools/twndel/data/point.png
(Stored with Git LFS)
Normal file
BIN
apps/tools/twndel/data/point.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
apps/tools/twndel/data/pop.wav
Normal file
BIN
apps/tools/twndel/data/pop.wav
Normal file
Binary file not shown.
BIN
apps/tools/twndel/data/rip.wav
Normal file
BIN
apps/tools/twndel/data/rip.wav
Normal file
Binary file not shown.
BIN
apps/tools/twndel/data/selectin.ogg
(Stored with Git LFS)
Normal file
BIN
apps/tools/twndel/data/selectin.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
apps/tools/twndel/data/selectout.ogg
(Stored with Git LFS)
Normal file
BIN
apps/tools/twndel/data/selectout.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
apps/tools/twndel/data/snip.wav
Normal file
BIN
apps/tools/twndel/data/snip.wav
Normal file
Binary file not shown.
BIN
apps/tools/twndel/data/spooncap.wav
Normal file
BIN
apps/tools/twndel/data/spooncap.wav
Normal file
Binary file not shown.
BIN
apps/tools/twndel/data/switch.ogg
(Stored with Git LFS)
Normal file
BIN
apps/tools/twndel/data/switch.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
12
apps/tools/twndel/data/twn.toml
Normal file
12
apps/tools/twndel/data/twn.toml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[about]
|
||||||
|
title = "Townengine Modeling Tool"
|
||||||
|
developer = "twnteam"
|
||||||
|
app_id = "twndel"
|
||||||
|
dev_id = "twnteam"
|
||||||
|
|
||||||
|
# Game runtime details
|
||||||
|
[game]
|
||||||
|
resolution = [ 640, 480 ]
|
||||||
|
background_color = [ 255, 125, 0, 255 ]
|
||||||
|
|
||||||
|
[engine]
|
BIN
apps/tools/twndel/data/x.png
(Stored with Git LFS)
Normal file
BIN
apps/tools/twndel/data/x.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
apps/tools/twndel/data/y.png
(Stored with Git LFS)
Normal file
BIN
apps/tools/twndel/data/y.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
apps/tools/twndel/data/z.png
(Stored with Git LFS)
Normal file
BIN
apps/tools/twndel/data/z.png
(Stored with Git LFS)
Normal file
Binary file not shown.
118
apps/tools/twndel/state.h
Normal file
118
apps/tools/twndel/state.h
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
#ifndef STATE_H
|
||||||
|
#define STATE_H
|
||||||
|
|
||||||
|
#include "twn_game_api.h"
|
||||||
|
|
||||||
|
|
||||||
|
#define POINTS_PER_METER 128
|
||||||
|
#define UNDO_STACK_SIZE 32
|
||||||
|
#define CAMERA_FOV ((float)M_PI_2 * 0.75f)
|
||||||
|
|
||||||
|
#define POINT_LIMIT 65534
|
||||||
|
#define FACE_LIMIT 2048
|
||||||
|
#define OBJECT_LIMIT 16
|
||||||
|
#define TEXTURE_LIMIT 32
|
||||||
|
|
||||||
|
#define INVALID_POINT (POINT_LIMIT+1)
|
||||||
|
#define INVALID_FACE (FACE_LIMIT+1)
|
||||||
|
#define INVALID_OBJECT (OBJECT_LIMIT+1)
|
||||||
|
#define INVALID_TEXTURE (TEXTURE_LIMIT+1)
|
||||||
|
|
||||||
|
#define CAMERA_ROTATION_SPEED 0.04f
|
||||||
|
#define CAMERA_TRANSLATION_SPEED 0.04f
|
||||||
|
#define SELECTION_SPHERE_RADIUS 32
|
||||||
|
/* should be an odd number */
|
||||||
|
#define SNAP_LINES_SHOW 7
|
||||||
|
#define SNAP_LINES_WIDTH 1.0f
|
||||||
|
#define SNAP_LINES_COLOR ((Color){200,200,200,150})
|
||||||
|
|
||||||
|
typedef struct Operation {
|
||||||
|
enum {
|
||||||
|
OPERATION_MOVE_POINT,
|
||||||
|
OPERATION_SET_TEXTURE,
|
||||||
|
OPERATION_TRIANGULATE,
|
||||||
|
} kind;
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
uint16_t point;
|
||||||
|
int16_t delta_x;
|
||||||
|
int16_t delta_y;
|
||||||
|
int16_t delta_z;
|
||||||
|
uint8_t object;
|
||||||
|
} move_point;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
uint16_t face;
|
||||||
|
int16_t delta_texture;
|
||||||
|
uint8_t object;
|
||||||
|
} set_texture;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
uint16_t old_face;
|
||||||
|
uint16_t new_face;
|
||||||
|
uint8_t object;
|
||||||
|
} triangulate;
|
||||||
|
} data;
|
||||||
|
|
||||||
|
bool chained;
|
||||||
|
} Operation;
|
||||||
|
|
||||||
|
typedef struct Point {
|
||||||
|
int16_t x, y, z;
|
||||||
|
} Point;
|
||||||
|
|
||||||
|
/* TODO: store topology in terms on edge connections? might be bad, as it's stateful */
|
||||||
|
/* triangles have p3 = INVALID_POINT */
|
||||||
|
/* lines have p2, p3 = INVALID_POINT */
|
||||||
|
/* billboards have p1, p2, p3 = INVALID_POINT */
|
||||||
|
typedef struct Face {
|
||||||
|
uint16_t p[4];
|
||||||
|
/* texture origin, as point on face plane, in absolute coordinates */
|
||||||
|
int16_t tex_x, tex_y;
|
||||||
|
uint8_t texture;
|
||||||
|
uint8_t tex_scale;
|
||||||
|
} Face;
|
||||||
|
|
||||||
|
typedef struct Object {
|
||||||
|
char *name;
|
||||||
|
bool is_invisible;
|
||||||
|
Point position;
|
||||||
|
char *textures[TEXTURE_LIMIT + 1];
|
||||||
|
uint8_t textures_sz;
|
||||||
|
Point rotation;
|
||||||
|
Face faces[FACE_LIMIT];
|
||||||
|
uint16_t faces_sz;
|
||||||
|
} Object;
|
||||||
|
|
||||||
|
typedef struct State {
|
||||||
|
Operation op_stack[UNDO_STACK_SIZE];
|
||||||
|
uint32_t op_stack_ptr;
|
||||||
|
bool op_active;
|
||||||
|
|
||||||
|
Vec3 active_center;
|
||||||
|
Vec3 camera_position;
|
||||||
|
Vec3 camera_direction;
|
||||||
|
float camera_zoom;
|
||||||
|
bool camera_is_orthographic;
|
||||||
|
/* defaults to wireframe */
|
||||||
|
bool solid_display_mode;
|
||||||
|
/* positions skipped */
|
||||||
|
uint8_t grid_snap_granularity;
|
||||||
|
|
||||||
|
Point points[POINT_LIMIT];
|
||||||
|
uint16_t points_sz;
|
||||||
|
|
||||||
|
Object objects[OBJECT_LIMIT];
|
||||||
|
uint8_t objects_sz;
|
||||||
|
|
||||||
|
/* which axes are blocked and which are not */
|
||||||
|
/* order: x, y, z */
|
||||||
|
bool axis_mask[3];
|
||||||
|
char *current_texture;
|
||||||
|
|
||||||
|
uint8_t current_hovered_obj;
|
||||||
|
uint16_t current_hovered_face;
|
||||||
|
} State;
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
972
apps/tools/twndel/tool.c
Normal file
972
apps/tools/twndel/tool.c
Normal file
@ -0,0 +1,972 @@
|
|||||||
|
#include "twn_game_api.h"
|
||||||
|
#include "state.h"
|
||||||
|
#include "twn_vec.h"
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
|
/* planned features: */
|
||||||
|
/* grid-based, bounded space (65536 / POINTS_PER_METER meters), which allows for efficient storage */
|
||||||
|
/* 65534 point limit, 16 object limit, 2048 faces per object, 32 textures per object */
|
||||||
|
/* triangles and quads only */
|
||||||
|
/* support for billboards and flat two sided quads */
|
||||||
|
/* texture painting */
|
||||||
|
/* bones with mesh animations, snapping to grid, with no weights */
|
||||||
|
/* billboard render to specified angles */
|
||||||
|
/* 1 point light primitive lighting */
|
||||||
|
/* live edited textures are capped at 128x128 */
|
||||||
|
|
||||||
|
/* assumptions: */
|
||||||
|
/* up is always (0,1,0) */
|
||||||
|
/* preallocations everywhere */
|
||||||
|
|
||||||
|
static State state;
|
||||||
|
static bool init;
|
||||||
|
|
||||||
|
|
||||||
|
static uint8_t new_object(const char *name) {
|
||||||
|
if (state.objects_sz >= OBJECT_LIMIT)
|
||||||
|
return INVALID_OBJECT;
|
||||||
|
state.objects_sz++;
|
||||||
|
state.objects[state.objects_sz-1].name = SDL_strdup(name);
|
||||||
|
return state.objects_sz-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static uint16_t new_point(int16_t x, int16_t y, int16_t z) {
|
||||||
|
if (state.points_sz >= POINT_LIMIT)
|
||||||
|
return INVALID_POINT;
|
||||||
|
state.points_sz++;
|
||||||
|
state.points[state.points_sz-1] = (Point){x, y, z};
|
||||||
|
return state.points_sz-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static uint16_t push_face(uint8_t object,
|
||||||
|
uint16_t p0, uint16_t p1, uint16_t p2, uint16_t p3,
|
||||||
|
uint8_t texture, uint8_t tex_scale, int16_t tex_x, int16_t tex_y)
|
||||||
|
{
|
||||||
|
Object *o = &state.objects[object];
|
||||||
|
o->faces_sz++;
|
||||||
|
o->faces[o->faces_sz-1] = (Face) {
|
||||||
|
.p = {p0, p1, p2, p3},
|
||||||
|
.texture = texture,
|
||||||
|
.tex_scale = tex_scale,
|
||||||
|
.tex_x = tex_x,
|
||||||
|
.tex_y = tex_y,
|
||||||
|
};
|
||||||
|
return o->faces_sz-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static uint8_t push_texture(uint8_t object, char *texture) {
|
||||||
|
Object *o = &state.objects[object];
|
||||||
|
|
||||||
|
/* check whether it's already here */
|
||||||
|
for (uint8_t i = 0; i < o->textures_sz; ++i)
|
||||||
|
if (SDL_strcmp(o->textures[i], texture) == 0)
|
||||||
|
return i;
|
||||||
|
|
||||||
|
o->textures_sz++;
|
||||||
|
o->textures[o->textures_sz-1] = SDL_strdup(texture);
|
||||||
|
return o->textures_sz-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* TODO: use tombstones instead? it would be easier to maintain, by a lot */
|
||||||
|
/* note: make sure nothing depends on none */
|
||||||
|
static void pop_face(uint8_t object, uint16_t face) {
|
||||||
|
Object *o = &state.objects[object];
|
||||||
|
if (face != o->faces_sz-1)
|
||||||
|
o->faces[face] = o->faces[o->faces_sz-1];
|
||||||
|
o->faces_sz--;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static void push_operation(Operation operation, bool active) {
|
||||||
|
state.op_stack_ptr++;
|
||||||
|
uint8_t op = state.op_stack_ptr % UNDO_STACK_SIZE;
|
||||||
|
state.op_stack[op] = operation;
|
||||||
|
state.op_active = active;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void extend_operation(Operation operation) {
|
||||||
|
uint8_t op = state.op_stack_ptr % UNDO_STACK_SIZE;
|
||||||
|
Operation ext = state.op_stack[op];
|
||||||
|
ext.chained = true;
|
||||||
|
state.op_stack[op] = operation;
|
||||||
|
push_operation(ext, state.op_active);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static inline Vec3 point_to_vec3(uint8_t object, uint16_t point) {
|
||||||
|
Object *o = &state.objects[object];
|
||||||
|
return (Vec3){ (o->position.x + state.points[point].x) / (float)POINTS_PER_METER,
|
||||||
|
(o->position.y + state.points[point].y) / (float)POINTS_PER_METER,
|
||||||
|
(o->position.z + state.points[point].z) / (float)POINTS_PER_METER };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* TODO: something is wrong, figure out how to introduce rotation to this. */
|
||||||
|
static inline Vec2 project_texture_coordinate(Vec2 origin, Vec3 plane, Vec3 point, float scale) {
|
||||||
|
Vec3 right = vec3_norm(vec3_cross(plane, fabsf(0.0f - plane.y) < 1e-9f ? (Vec3){0,1,0} : (Vec3){1,0,0}));
|
||||||
|
Vec3 up = vec3_norm(vec3_cross(right, plane));
|
||||||
|
Vec2 pp = { vec3_dot(point, right), vec3_dot(point, up) };
|
||||||
|
return vec2_scale(vec2_sub(origin, pp), scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void render_object(uint8_t object) {
|
||||||
|
Object *o = &state.objects[object];
|
||||||
|
|
||||||
|
if (o->is_invisible)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (uint16_t fi = 0; fi < o->faces_sz; ++fi) {
|
||||||
|
Face *f = &o->faces[fi];
|
||||||
|
|
||||||
|
if (f->p[2] != INVALID_POINT) {
|
||||||
|
Vec3 p0 = point_to_vec3(object, f->p[0]);
|
||||||
|
Vec3 p1 = point_to_vec3(object, f->p[1]);
|
||||||
|
Vec3 p2 = point_to_vec3(object, f->p[2]);
|
||||||
|
|
||||||
|
if (state.solid_display_mode) {
|
||||||
|
Vec3 n = vec3_norm(vec3_cross(vec3_sub(p1, p0), vec3_sub(p2, p0)));
|
||||||
|
Vec2 to = { f->tex_x / (float)POINTS_PER_METER, f->tex_y / (float)POINTS_PER_METER, };
|
||||||
|
|
||||||
|
Vec2 tul = project_texture_coordinate(to, n, p0, (float)f->tex_scale);
|
||||||
|
Vec2 tdl = project_texture_coordinate(to, n, p1, (float)f->tex_scale);
|
||||||
|
Vec2 tdr = project_texture_coordinate(to, n, p2, (float)f->tex_scale);
|
||||||
|
|
||||||
|
draw_triangle(o->textures[f->texture],
|
||||||
|
p0, p1, p2,
|
||||||
|
tul, tdl, tdr,
|
||||||
|
(Color){255,255,255,255},
|
||||||
|
(Color){255,255,255,255},
|
||||||
|
(Color){255,255,255,255});
|
||||||
|
|
||||||
|
if (f->p[3] != INVALID_POINT) {
|
||||||
|
Vec3 p3 = point_to_vec3(object, f->p[3]);
|
||||||
|
Vec2 tur = project_texture_coordinate(to, n, p3, (float)f->tex_scale);
|
||||||
|
draw_triangle(o->textures[f->texture],
|
||||||
|
p2, p3, p0,
|
||||||
|
tdr, tur, tul,
|
||||||
|
(Color){255,255,255,255},
|
||||||
|
(Color){255,255,255,255},
|
||||||
|
(Color){255,255,255,255});
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
draw_line_3d(p0, p1, 1, (Color){255,255,255,255});
|
||||||
|
draw_line_3d(p1, p2, 1, (Color){255,255,255,255});
|
||||||
|
|
||||||
|
if (f->p[3] == INVALID_POINT)
|
||||||
|
draw_line_3d(p2, p0, 1, (Color){255,255,255,255});
|
||||||
|
else {
|
||||||
|
Vec3 p3 = point_to_vec3(object, f->p[3]);
|
||||||
|
draw_line_3d(p2, p3, 1, (Color){255,255,255,255});
|
||||||
|
draw_line_3d(p3, p0, 1, (Color){255,255,255,255});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else
|
||||||
|
SDL_assert_always(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static uint8_t new_cube(Point pos, Point size) {
|
||||||
|
uint8_t object = new_object("cube");
|
||||||
|
|
||||||
|
uint16_t p0 = new_point(pos.x - size.x / 2, pos.y - size.y / 2, pos.z - size.z / 2);
|
||||||
|
uint16_t p1 = new_point(pos.x - size.x / 2, pos.y + size.y / 2, pos.z - size.z / 2);
|
||||||
|
uint16_t p2 = new_point(pos.x + size.x / 2, pos.y + size.y / 2, pos.z - size.z / 2);
|
||||||
|
uint16_t p3 = new_point(pos.x + size.x / 2, pos.y - size.y / 2, pos.z - size.z / 2);
|
||||||
|
uint16_t p4 = new_point(pos.x - size.x / 2, pos.y - size.y / 2, pos.z + size.z / 2);
|
||||||
|
uint16_t p5 = new_point(pos.x - size.x / 2, pos.y + size.y / 2, pos.z + size.z / 2);
|
||||||
|
uint16_t p6 = new_point(pos.x + size.x / 2, pos.y + size.y / 2, pos.z + size.z / 2);
|
||||||
|
uint16_t p7 = new_point(pos.x + size.x / 2, pos.y - size.y / 2, pos.z + size.z / 2);
|
||||||
|
|
||||||
|
uint8_t tex = push_texture(object, "/data/placeholder.png");
|
||||||
|
push_face(object, p2, p3, p0, p1, tex, 128, pos.x - size.x / 2, pos.y - size.y / 2);
|
||||||
|
push_face(object, p5, p4, p7, p6, tex, 128, pos.x - size.x / 2, pos.y - size.y / 2);
|
||||||
|
push_face(object, p1, p0, p4, p5, tex, 128, pos.x - size.x / 2, pos.y - size.y / 2);
|
||||||
|
push_face(object, p6, p7, p3, p2, tex, 128, pos.x - size.x / 2, pos.y - size.y / 2);
|
||||||
|
push_face(object, p2, p1, p5, p6, tex, 128, pos.x - size.x / 2, pos.y - size.y / 2);
|
||||||
|
push_face(object, p0, p3, p7, p4, tex, 128, pos.x - size.x / 2, pos.y - size.y / 2);
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void process_camera_rotation(void) {
|
||||||
|
float horizontal_rotation = 0;
|
||||||
|
float vertical_rotation = 0;
|
||||||
|
|
||||||
|
if (input_action_pressed("camera_rotate_left"))
|
||||||
|
horizontal_rotation -= CAMERA_ROTATION_SPEED;
|
||||||
|
if (input_action_pressed("camera_rotate_right"))
|
||||||
|
horizontal_rotation += CAMERA_ROTATION_SPEED;
|
||||||
|
if (input_action_pressed("camera_rotate_up"))
|
||||||
|
vertical_rotation -= CAMERA_ROTATION_SPEED;
|
||||||
|
if (input_action_pressed("camera_rotate_down"))
|
||||||
|
vertical_rotation += CAMERA_ROTATION_SPEED;
|
||||||
|
|
||||||
|
Vec3 front = vec3_cross(state.camera_direction, (Vec3){0,1,0});
|
||||||
|
Vec3 local_position = vec3_sub(state.active_center, state.camera_position);
|
||||||
|
Vec3 new_local_position = vec3_rotate(local_position, horizontal_rotation, (Vec3){0,1,0});
|
||||||
|
|
||||||
|
state.camera_direction = vec3_rotate(state.camera_direction, horizontal_rotation, (Vec3){0,1,0});
|
||||||
|
Vec3 new_rot = vec3_rotate(state.camera_direction, vertical_rotation, front);
|
||||||
|
|
||||||
|
/* only apply if it's in limits */
|
||||||
|
float d = vec3_dot(new_rot, (Vec3){0,-1,0});
|
||||||
|
if (fabsf(d) <= 0.999f) {
|
||||||
|
new_local_position = vec3_rotate(new_local_position, vertical_rotation, front);
|
||||||
|
state.camera_direction = new_rot;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.camera_position = vec3_sub(state.active_center, new_local_position);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void process_camera_translation(void) {
|
||||||
|
Vec3 right = vec3_norm(vec3_cross(state.camera_direction, (Vec3){0,1,0}));
|
||||||
|
Vec3 up = vec3_norm(vec3_cross(state.camera_direction, right));
|
||||||
|
Vec3 was = state.camera_position;
|
||||||
|
|
||||||
|
if (input_action_pressed("camera_rotate_left"))
|
||||||
|
state.camera_position = vec3_sub(state.camera_position, vec3_scale(right, CAMERA_TRANSLATION_SPEED));
|
||||||
|
if (input_action_pressed("camera_rotate_right"))
|
||||||
|
state.camera_position = vec3_add(state.camera_position, vec3_scale(right, CAMERA_TRANSLATION_SPEED));
|
||||||
|
if (input_action_pressed("camera_rotate_up"))
|
||||||
|
state.camera_position = vec3_sub(state.camera_position, vec3_scale(up, CAMERA_TRANSLATION_SPEED));
|
||||||
|
if (input_action_pressed("camera_rotate_down"))
|
||||||
|
state.camera_position = vec3_add(state.camera_position, vec3_scale(up, CAMERA_TRANSLATION_SPEED));
|
||||||
|
|
||||||
|
state.active_center = vec3_add(state.active_center, vec3_sub(state.camera_position, was));
|
||||||
|
|
||||||
|
draw_billboard("/data/camera.png",
|
||||||
|
vec3_add(state.camera_position, vec3_scale(state.camera_direction, vec3_length(state.camera_position))),
|
||||||
|
(Vec2){0.2f,0.2f},
|
||||||
|
(Rect){0},
|
||||||
|
(Color){255,255,255,255},
|
||||||
|
false);
|
||||||
|
|
||||||
|
/* show relation to origin */
|
||||||
|
draw_billboard("/data/center.png",
|
||||||
|
(Vec3){0},
|
||||||
|
(Vec2){0.1f,0.1f},
|
||||||
|
(Rect){0},
|
||||||
|
(Color){255,255,255,255},
|
||||||
|
false);
|
||||||
|
|
||||||
|
draw_line_3d((Vec3){0}, state.active_center, 1, (Color){255,255,255,255});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void process_camera_movement(void) {
|
||||||
|
input_action("camera_rotate_left", "A");
|
||||||
|
input_action("camera_rotate_right", "D");
|
||||||
|
input_action("camera_rotate_up", "W");
|
||||||
|
input_action("camera_rotate_down", "S");
|
||||||
|
input_action("camera_lock_rotation", "SPACE");
|
||||||
|
|
||||||
|
if (input_action_pressed("camera_lock_rotation")) {
|
||||||
|
process_camera_translation();
|
||||||
|
} else {
|
||||||
|
process_camera_rotation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static inline DrawCameraUnprojectResult unproject_point(Vec2 point) {
|
||||||
|
return draw_camera_unproject(
|
||||||
|
point,
|
||||||
|
state.camera_position,
|
||||||
|
state.camera_direction,
|
||||||
|
(Vec3){0, 1, 0},
|
||||||
|
state.camera_is_orthographic ? 0 : CAMERA_FOV,
|
||||||
|
state.camera_zoom,
|
||||||
|
100 );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool find_closest_point(uint8_t* object_result, uint16_t *point_result) {
|
||||||
|
DrawCameraUnprojectResult pos_and_ray = unproject_point(ctx.mouse_position);
|
||||||
|
|
||||||
|
/* step over every selectable object and find points closest to the view ray */
|
||||||
|
/* by constructing triangles and finding their height, from perpendicular */
|
||||||
|
uint16_t closest_point = INVALID_POINT;
|
||||||
|
uint8_t closest_obj = INVALID_OBJECT;
|
||||||
|
float closest_distance = INFINITY;
|
||||||
|
|
||||||
|
for (uint8_t obj = 0; obj < state.objects_sz; ++obj) {
|
||||||
|
Object *o = &state.objects[obj];
|
||||||
|
|
||||||
|
if (o->is_invisible)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* TODO: is it possible to skip repeated points? does it matter? */
|
||||||
|
/* as we limit the point could we could actually have bool array preallocated for this */
|
||||||
|
for (uint16_t fi = 0; fi < o->faces_sz; ++fi) {
|
||||||
|
Face *f = &o->faces[fi];
|
||||||
|
for (uint16_t pi = 0; pi < 4; ++pi) {
|
||||||
|
if (f->p[pi] == INVALID_POINT) break;
|
||||||
|
Vec3 p = point_to_vec3(obj, f->p[pi]);
|
||||||
|
Vec3 d = vec3_sub(pos_and_ray.position, p);
|
||||||
|
Vec3 b = vec3_cross(d, pos_and_ray.direction);
|
||||||
|
float ray_dist = vec3_length(b);
|
||||||
|
if (ray_dist > ((float)SELECTION_SPHERE_RADIUS / POINTS_PER_METER))
|
||||||
|
continue;
|
||||||
|
float dist = vec3_length(vec3_sub(pos_and_ray.position, vec3_add(p, b)));
|
||||||
|
if (dist < closest_distance) {
|
||||||
|
closest_distance = dist;
|
||||||
|
closest_obj = obj;
|
||||||
|
closest_point = f->p[pi];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closest_point == INVALID_POINT)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (object_result)
|
||||||
|
*object_result = closest_obj;
|
||||||
|
if (point_result)
|
||||||
|
*point_result = closest_point;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* o = vector origin */
|
||||||
|
/* v = vector direction, normalized */
|
||||||
|
/* p = any point on plane */
|
||||||
|
/* n = normal of a plane */
|
||||||
|
static bool vector_plane_intersection(Vec3 o, Vec3 v, Vec3 p, Vec3 n, Vec3 *out) {
|
||||||
|
float dot = vec3_dot(n, v);
|
||||||
|
if (fabsf(dot) > FLT_EPSILON) {
|
||||||
|
Vec3 w = vec3_sub(o, p);
|
||||||
|
float fac = -vec3_dot(n, w) / dot;
|
||||||
|
*out = vec3_add(o, vec3_scale(v, fac));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/* vector and plane are perpendicular, assume that it lies exactly on it */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool find_closest_face(uint8_t* object_result, uint16_t *face_result) {
|
||||||
|
DrawCameraUnprojectResult cam = unproject_point(ctx.mouse_position);
|
||||||
|
|
||||||
|
uint8_t closest_obj = INVALID_OBJECT;
|
||||||
|
uint16_t closest_face = INVALID_FACE;
|
||||||
|
float closest_distance = INFINITY;
|
||||||
|
|
||||||
|
for (uint8_t oi = 0; oi < state.objects_sz; ++oi) {
|
||||||
|
Object *o = &state.objects[oi];
|
||||||
|
for (uint16_t fi = 0; fi < o->faces_sz; ++fi) {
|
||||||
|
Face *f = &o->faces[fi];
|
||||||
|
|
||||||
|
if (f->p[1] == INVALID_POINT) continue;
|
||||||
|
|
||||||
|
Vec3 p0 = point_to_vec3(oi, f->p[0]);
|
||||||
|
Vec3 p1 = point_to_vec3(oi, f->p[1]);
|
||||||
|
Vec3 p2 = point_to_vec3(oi, f->p[2]);
|
||||||
|
|
||||||
|
Vec3 n = vec3_norm(vec3_cross(vec3_sub(p1, p0), vec3_sub(p2, p0)));
|
||||||
|
|
||||||
|
/* culling */
|
||||||
|
if (vec3_dot(state.camera_direction, n) >= 0) continue;
|
||||||
|
|
||||||
|
Vec3 i;
|
||||||
|
if (!vector_plane_intersection(cam.position, cam.direction, p0, n, &i))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float dist = vec3_length(vec3_sub(p0, i));
|
||||||
|
if (dist >= closest_distance) continue;
|
||||||
|
|
||||||
|
/* left normals are used to determine whether point lies to the left for all forming lines */
|
||||||
|
Vec3 ln0 = vec3_norm(vec3_cross(n, vec3_sub(p1, p0)));
|
||||||
|
if (vec3_dot(ln0, vec3_sub(p0, i)) >= 0) continue;
|
||||||
|
|
||||||
|
Vec3 ln1 = vec3_norm(vec3_cross(n, vec3_sub(p2, p1)));
|
||||||
|
if (vec3_dot(ln1, vec3_sub(p1, i)) >= 0) continue;
|
||||||
|
|
||||||
|
if (f->p[3] == INVALID_POINT) {
|
||||||
|
/* triangle */
|
||||||
|
Vec3 ln2 = vec3_norm(vec3_cross(n, vec3_sub(p0, p2)));
|
||||||
|
if (vec3_dot(ln2, vec3_sub(p2, i)) >= 0) continue;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
/* quad */
|
||||||
|
Vec3 p3 = point_to_vec3(oi, f->p[3]);
|
||||||
|
Vec3 ln2 = vec3_norm(vec3_cross(n, vec3_sub(p3, p2)));
|
||||||
|
if (vec3_dot(ln2, vec3_sub(p2, i)) >= 0) continue;
|
||||||
|
|
||||||
|
Vec3 ln3 = vec3_norm(vec3_cross(n, vec3_sub(p0, p3)));
|
||||||
|
if (vec3_dot(ln3, vec3_sub(p3, i)) >= 0) continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
closest_distance = dist;
|
||||||
|
closest_face = fi;
|
||||||
|
closest_obj = oi;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closest_face == INVALID_FACE)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (object_result)
|
||||||
|
*object_result = closest_obj;
|
||||||
|
if (face_result)
|
||||||
|
*face_result = closest_face;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void show_snap_lines(Vec3 p) {
|
||||||
|
float step = 1.0f / ((float)POINTS_PER_METER / state.grid_snap_granularity);
|
||||||
|
int const lines_per_side = (SNAP_LINES_SHOW - 1) / 2;
|
||||||
|
|
||||||
|
for (int l = -lines_per_side; l <= lines_per_side; ++l) {
|
||||||
|
if (!state.axis_mask[0]) {
|
||||||
|
Vec3 c = vec3_add(p, vec3_scale((Vec3){1,0,0}, step * (float)l));
|
||||||
|
draw_line_3d(
|
||||||
|
vec3_add(c, vec3_scale((Vec3){0,0,1}, SNAP_LINES_WIDTH)),
|
||||||
|
vec3_add(c, vec3_scale((Vec3){0,0,1}, -SNAP_LINES_WIDTH)),
|
||||||
|
1,
|
||||||
|
SNAP_LINES_COLOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.axis_mask[1]) {
|
||||||
|
Vec3 axis = fabsf(vec3_dot(state.camera_direction, (Vec3){0,0,1})) >= 0.5f ? (Vec3){1,0,0} : (Vec3){0,0,1};
|
||||||
|
Vec3 c = vec3_add(p, vec3_scale((Vec3){0,1,0}, step * (float)l));
|
||||||
|
draw_line_3d(
|
||||||
|
vec3_add(c, vec3_scale(axis, SNAP_LINES_WIDTH)),
|
||||||
|
vec3_add(c, vec3_scale(axis, -SNAP_LINES_WIDTH)),
|
||||||
|
1,
|
||||||
|
SNAP_LINES_COLOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.axis_mask[2]) {
|
||||||
|
Vec3 c = vec3_add(p, vec3_scale((Vec3){0,0,1}, step * (float)l));
|
||||||
|
draw_line_3d(
|
||||||
|
vec3_add(c, vec3_scale((Vec3){1,0,0}, SNAP_LINES_WIDTH)),
|
||||||
|
vec3_add(c, vec3_scale((Vec3){1,0,0}, -SNAP_LINES_WIDTH)),
|
||||||
|
1,
|
||||||
|
SNAP_LINES_COLOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool process_operation_move_point(Operation *op) {
|
||||||
|
/* finish dragging around */
|
||||||
|
/* TODO: dont keep empty ops on stack? */
|
||||||
|
if (input_action_just_released("select")) {
|
||||||
|
state.op_active = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float size = sinf(ctx.frame_number / 10) * 0.05f + 0.05f;
|
||||||
|
draw_billboard("/data/grab.png",
|
||||||
|
point_to_vec3(op->data.move_point.object, op->data.move_point.point),
|
||||||
|
(Vec2){size, size},
|
||||||
|
(Rect){0},
|
||||||
|
(Color){255,255,255,255},
|
||||||
|
false);
|
||||||
|
|
||||||
|
DrawCameraUnprojectResult cam = unproject_point(ctx.mouse_position);
|
||||||
|
Vec3 p = point_to_vec3(op->data.move_point.object, op->data.move_point.point);
|
||||||
|
bool point_moved = false;
|
||||||
|
|
||||||
|
/* figure out which planes are angled acutely against the viewing direction */
|
||||||
|
bool y_closer_than_horizon = fabsf(vec3_dot(state.camera_direction, (Vec3){0,1,0})) >= 0.5f;
|
||||||
|
|
||||||
|
Vec3 plane_normal_x = y_closer_than_horizon ? (Vec3){0,1,0} : (Vec3){0,0,1};
|
||||||
|
Vec3 plane_normal_z = y_closer_than_horizon ? (Vec3){0,1,0} : (Vec3){1,0,0};
|
||||||
|
|
||||||
|
/* show snapping in lines */
|
||||||
|
show_snap_lines(p);
|
||||||
|
|
||||||
|
Vec3 s;
|
||||||
|
if (!state.axis_mask[0]) {
|
||||||
|
if (vector_plane_intersection(cam.position, cam.direction, p, plane_normal_x, &s)) {
|
||||||
|
int16_t xch = (int16_t)(roundf(vec3_dot(vec3_sub(s, p), (Vec3){1,0,0}) * (float)POINTS_PER_METER));
|
||||||
|
xch -= xch % state.grid_snap_granularity;
|
||||||
|
state.points[op->data.move_point.point].x += xch;
|
||||||
|
op->data.move_point.delta_x += xch;
|
||||||
|
if (xch != 0) point_moved = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.axis_mask[1]) {
|
||||||
|
if (vector_plane_intersection(cam.position, cam.direction, p, (Vec3){0,0,1}, &s)) {
|
||||||
|
int16_t ych = (int16_t)(roundf(vec3_dot(vec3_sub(s, p), (Vec3){0,1,0}) * (float)POINTS_PER_METER));
|
||||||
|
ych -= ych % state.grid_snap_granularity;
|
||||||
|
state.points[op->data.move_point.point].y += ych;
|
||||||
|
op->data.move_point.delta_y += ych;
|
||||||
|
if (ych != 0) point_moved = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.axis_mask[2]) {
|
||||||
|
if (vector_plane_intersection(cam.position, cam.direction, p, plane_normal_z, &s)) {
|
||||||
|
int16_t zch = (int16_t)(roundf(vec3_dot(vec3_sub(s, p), (Vec3){0,0,1}) * (float)POINTS_PER_METER));
|
||||||
|
zch -= zch % state.grid_snap_granularity;
|
||||||
|
state.points[op->data.move_point.point].z += zch;
|
||||||
|
op->data.move_point.delta_z += zch;
|
||||||
|
if (zch != 0) point_moved = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (point_moved) {
|
||||||
|
audio_play("/data/bong.ogg", NULL, false, 0.12f, 0.0f);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void reverse_operation_move_point(Operation *op) {
|
||||||
|
SDL_assert(op->kind == OPERATION_MOVE_POINT);
|
||||||
|
state.points[op->data.move_point.point].x -= op->data.move_point.delta_x;
|
||||||
|
state.points[op->data.move_point.point].y -= op->data.move_point.delta_y;
|
||||||
|
state.points[op->data.move_point.point].z -= op->data.move_point.delta_z;
|
||||||
|
audio_play("/data/drop.ogg", NULL, false, 0.4f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void reverse_operation_set_texture(Operation *op) {
|
||||||
|
SDL_assert(op->kind == OPERATION_SET_TEXTURE);
|
||||||
|
Object *o = &state.objects[op->data.set_texture.object];
|
||||||
|
o->faces[op->data.set_texture.face].texture += op->data.set_texture.delta_texture;
|
||||||
|
audio_play("/data/drop.ogg", NULL, false, 0.4f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void reverse_triangulation(Operation *op) {
|
||||||
|
SDL_assert(op->kind == OPERATION_TRIANGULATE);
|
||||||
|
Object *o = &state.objects[op->data.set_texture.object];
|
||||||
|
Face *fn = &o->faces[op->data.triangulate.new_face];
|
||||||
|
Face *fo = &o->faces[op->data.triangulate.old_face];
|
||||||
|
fo->p[3] = fo->p[2];
|
||||||
|
fo->p[2] = fn->p[1];
|
||||||
|
pop_face(op->data.set_texture.object, op->data.triangulate.new_face);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* TODO: reverse of this */
|
||||||
|
static void try_subdividing_from_moving(uint8_t object, uint16_t point) {
|
||||||
|
Object *o = &state.objects[object];
|
||||||
|
bool not_first = false;
|
||||||
|
|
||||||
|
for (uint16_t fi = 0; fi < o->faces_sz; ++fi) {
|
||||||
|
Face *f = &o->faces[fi];
|
||||||
|
if (f->p[3] == INVALID_POINT) continue;
|
||||||
|
for (uint16_t pi = 0; pi < 4; ++pi) {
|
||||||
|
if (f->p[pi] == point) {
|
||||||
|
Face new0 = *f;
|
||||||
|
new0.p[0] = f->p[pi];
|
||||||
|
new0.p[1] = f->p[(pi + 1) % 4];
|
||||||
|
new0.p[2] = f->p[(pi + 3) % 4];
|
||||||
|
new0.p[3] = INVALID_POINT;
|
||||||
|
|
||||||
|
uint16_t newf = push_face(
|
||||||
|
object,
|
||||||
|
f->p[(pi + 1) % 4],
|
||||||
|
f->p[(pi + 2) % 4],
|
||||||
|
f->p[(pi + 3) % 4],
|
||||||
|
INVALID_POINT,
|
||||||
|
f->texture,
|
||||||
|
f->tex_scale,
|
||||||
|
f->tex_x,
|
||||||
|
f->tex_y);
|
||||||
|
|
||||||
|
*f = new0;
|
||||||
|
|
||||||
|
extend_operation((Operation){
|
||||||
|
.kind = OPERATION_TRIANGULATE,
|
||||||
|
.data = { .triangulate = { .new_face = newf, .old_face = fi, .object = object} },
|
||||||
|
.chained = not_first,
|
||||||
|
});
|
||||||
|
|
||||||
|
not_first = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void process_undo(void) {
|
||||||
|
if (state.op_active)
|
||||||
|
state.op_active = false;
|
||||||
|
|
||||||
|
/* TODO: checks and defined limit */
|
||||||
|
Operation *op = &state.op_stack[state.op_stack_ptr % UNDO_STACK_SIZE];
|
||||||
|
state.op_stack_ptr--;
|
||||||
|
|
||||||
|
switch (op->kind) {
|
||||||
|
case OPERATION_MOVE_POINT: {
|
||||||
|
reverse_operation_move_point(op);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OPERATION_SET_TEXTURE: {
|
||||||
|
reverse_operation_set_texture(op);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OPERATION_TRIANGULATE: {
|
||||||
|
reverse_triangulation(op);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
(void)0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* pop another if they're chained together */
|
||||||
|
if (op->chained) process_undo();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void process_operations(void) {
|
||||||
|
if (input_action_just_pressed("undo"))
|
||||||
|
process_undo();
|
||||||
|
|
||||||
|
if (!state.op_active) {
|
||||||
|
/* point dragging */
|
||||||
|
if (!state.current_texture) {
|
||||||
|
uint16_t point_select; uint8_t obj_select;
|
||||||
|
if (find_closest_point(&obj_select, &point_select)) {
|
||||||
|
draw_billboard("/data/point.png",
|
||||||
|
point_to_vec3(obj_select, point_select),
|
||||||
|
(Vec2){0.05f, 0.05f},
|
||||||
|
(Rect){0},
|
||||||
|
(Color){255,255,255,255},
|
||||||
|
false);
|
||||||
|
|
||||||
|
if (input_action_just_pressed("select"))
|
||||||
|
push_operation((Operation){
|
||||||
|
.kind = OPERATION_MOVE_POINT,
|
||||||
|
.data = { .move_point = { .point = point_select, .object = obj_select } },
|
||||||
|
}, true );
|
||||||
|
}
|
||||||
|
|
||||||
|
/* texture setting */
|
||||||
|
} else {
|
||||||
|
uint8_t obj_select; uint16_t face_select;
|
||||||
|
if (find_closest_face(&obj_select, &face_select)) {
|
||||||
|
state.current_hovered_face = face_select;
|
||||||
|
state.current_hovered_obj = obj_select;
|
||||||
|
|
||||||
|
if (input_action_just_pressed("rotate")) {
|
||||||
|
Face *f = &state.objects[obj_select].faces[face_select];
|
||||||
|
int16_t tex_x = f->tex_x;
|
||||||
|
int16_t tex_y = f->tex_y;
|
||||||
|
f->tex_x = -tex_y;
|
||||||
|
f->tex_y = tex_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input_action_pressed("select") && state.current_texture) {
|
||||||
|
uint8_t new_tex = push_texture(obj_select, state.current_texture);
|
||||||
|
uint8_t cur_tex = state.objects[obj_select].faces[face_select].texture;
|
||||||
|
|
||||||
|
if (new_tex != cur_tex) {
|
||||||
|
state.objects[obj_select].faces[face_select].texture = new_tex;
|
||||||
|
audio_play("/data/bong.ogg", NULL, false, 0.5f, 0.0f);
|
||||||
|
|
||||||
|
push_operation((Operation){
|
||||||
|
.kind = OPERATION_SET_TEXTURE,
|
||||||
|
.data = { .set_texture = {
|
||||||
|
.face = face_select,
|
||||||
|
.object = obj_select,
|
||||||
|
.delta_texture = cur_tex - new_tex,
|
||||||
|
}},
|
||||||
|
}, false );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.op_active) {
|
||||||
|
Operation *op = &state.op_stack[state.op_stack_ptr % UNDO_STACK_SIZE];
|
||||||
|
switch (op->kind) {
|
||||||
|
case OPERATION_MOVE_POINT: {
|
||||||
|
bool update = process_operation_move_point(op);
|
||||||
|
|
||||||
|
if (update)
|
||||||
|
try_subdividing_from_moving(op->data.move_point.object, op->data.move_point.point);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OPERATION_SET_TEXTURE:
|
||||||
|
case OPERATION_TRIANGULATE:
|
||||||
|
default:
|
||||||
|
(void)0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void draw_axes(void) {
|
||||||
|
/* axis helpers */
|
||||||
|
/* idea: double selection of axes for diagonal edits */
|
||||||
|
draw_line_3d(state.active_center, (Vec3){256,0,0}, 1, state.axis_mask[0] ? (Color){0,0,0,75} : (Color){255,0,0,125});
|
||||||
|
draw_billboard("/data/x.png",
|
||||||
|
vec3_add(state.active_center, (Vec3){2,0,0}),
|
||||||
|
(Vec2){0.1f, 0.1f},
|
||||||
|
(Rect){0},
|
||||||
|
state.axis_mask[0] ? (Color){0,0,0,255} : (Color){255,0,0,255},
|
||||||
|
false);
|
||||||
|
|
||||||
|
draw_line_3d(state.active_center, (Vec3){0,256,0}, 1, state.axis_mask[1] ? (Color){0,0,0,75} : (Color){75,125,25,125});
|
||||||
|
draw_billboard("/data/y.png",
|
||||||
|
vec3_add(state.active_center, (Vec3){0,1.5f,0}),
|
||||||
|
(Vec2){0.1f, 0.1f},
|
||||||
|
(Rect){0},
|
||||||
|
state.axis_mask[1] ? (Color){0,0,0,255} : (Color){75,125,25,255},
|
||||||
|
false);
|
||||||
|
|
||||||
|
draw_line_3d(state.active_center, (Vec3){0,0,256}, 1, state.axis_mask[2] ? (Color){0,0,0,75} : (Color){0,0,255,125});
|
||||||
|
draw_billboard("/data/z.png",
|
||||||
|
vec3_add(state.active_center, (Vec3){0,0,2}),
|
||||||
|
(Vec2){0.1f, 0.1f},
|
||||||
|
(Rect){0},
|
||||||
|
state.axis_mask[2] ? (Color){0,0,0,255} : (Color){0,0,255,255},
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void draw_hovered_face_border(void) {
|
||||||
|
if (state.current_hovered_obj == INVALID_OBJECT || state.current_hovered_face == INVALID_FACE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Object *o = &state.objects[state.current_hovered_obj];
|
||||||
|
Face *f = &o->faces[state.current_hovered_face];
|
||||||
|
|
||||||
|
if (f->p[3] != INVALID_POINT) {
|
||||||
|
Vec3 p0 = point_to_vec3(state.current_hovered_obj, f->p[0]);
|
||||||
|
Vec3 p1 = point_to_vec3(state.current_hovered_obj, f->p[1]);
|
||||||
|
Vec3 p2 = point_to_vec3(state.current_hovered_obj, f->p[2]);
|
||||||
|
Vec3 p3 = point_to_vec3(state.current_hovered_obj, f->p[3]);
|
||||||
|
Vec3 center = vec3_scale(vec3_add(p2, p0), 0.5);
|
||||||
|
float size = sinf(ctx.frame_number / 10) * 0.05f + 0.1f;
|
||||||
|
Vec3 c0 = vec3_add(p0, vec3_scale(vec3_sub(p0, center), size));
|
||||||
|
Vec3 c1 = vec3_add(p1, vec3_scale(vec3_sub(p1, center), size));
|
||||||
|
Vec3 c2 = vec3_add(p2, vec3_scale(vec3_sub(p2, center), size));
|
||||||
|
Vec3 c3 = vec3_add(p3, vec3_scale(vec3_sub(p3, center), size));
|
||||||
|
draw_line_3d(c0, c1, 1, (Color){255,255,255,255});
|
||||||
|
draw_line_3d(c1, c2, 1, (Color){255,255,255,255});
|
||||||
|
draw_line_3d(c2, c3, 1, (Color){255,255,255,255});
|
||||||
|
draw_line_3d(c3, c0, 1, (Color){255,255,255,255});
|
||||||
|
} else {
|
||||||
|
Vec3 p0 = point_to_vec3(state.current_hovered_obj, f->p[0]);
|
||||||
|
Vec3 p1 = point_to_vec3(state.current_hovered_obj, f->p[1]);
|
||||||
|
Vec3 p2 = point_to_vec3(state.current_hovered_obj, f->p[2]);
|
||||||
|
Vec3 center = vec3_scale(vec3_add(p0, vec3_add(p1, p2)), 0.33f);
|
||||||
|
float size = sinf(ctx.frame_number / 10) * 0.05f + 0.1f;
|
||||||
|
Vec3 c0 = vec3_add(p0, vec3_scale(vec3_sub(p0, center), size));
|
||||||
|
Vec3 c1 = vec3_add(p1, vec3_scale(vec3_sub(p1, center), size));
|
||||||
|
Vec3 c2 = vec3_add(p2, vec3_scale(vec3_sub(p2, center), size));
|
||||||
|
draw_line_3d(c0, c1, 1, (Color){255,255,255,255});
|
||||||
|
draw_line_3d(c1, c2, 1, (Color){255,255,255,255});
|
||||||
|
draw_line_3d(c2, c0, 1, (Color){255,255,255,255});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void display_texture_selection(void) {
|
||||||
|
String list = file_read("/data/assets/", ":images");
|
||||||
|
if (!list.data)
|
||||||
|
return;
|
||||||
|
|
||||||
|
draw_rectangle((Rect){0, 480 - 50, 640, 480}, (Color){0,0,0,126});
|
||||||
|
|
||||||
|
char *selected = NULL;
|
||||||
|
char *saveptr = NULL;
|
||||||
|
int count = 0;
|
||||||
|
char const *part = SDL_strtokr(list.data, "\n", &saveptr);
|
||||||
|
do {
|
||||||
|
Rect box = (Rect){(float)count * 50, 480 - 48, 48, 48};
|
||||||
|
draw_sprite(part,
|
||||||
|
box,
|
||||||
|
(Rect){0,0,64,64},
|
||||||
|
(Color){255,255,255,255},
|
||||||
|
0, false, false, true);
|
||||||
|
count++;
|
||||||
|
|
||||||
|
if (state.current_texture && SDL_strcmp(part, state.current_texture) == 0)
|
||||||
|
draw_box(box, 1, (Color){255,255,255,255});
|
||||||
|
|
||||||
|
if (rect_intersects(box, (Rect){ctx.mouse_position.x, ctx.mouse_position.y, 1, 1}))
|
||||||
|
selected = SDL_strdup(part);
|
||||||
|
|
||||||
|
} while ((part = SDL_strtokr(NULL, "\n", &saveptr)));
|
||||||
|
|
||||||
|
if (selected) {
|
||||||
|
draw_text(selected, (Vec2){0, 480 - 48 - 24}, 24, (Color){255,255,255,255}, NULL);
|
||||||
|
if (input_action_just_pressed("select")) {
|
||||||
|
if (state.current_texture) SDL_free(state.current_texture);
|
||||||
|
state.current_texture = selected;
|
||||||
|
} else
|
||||||
|
SDL_free(selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void process_camera_inputs(void) {
|
||||||
|
if (input_action_just_pressed("toggle_display_mode")) {
|
||||||
|
audio_play("/data/click.wav", NULL, false, 0.7f, 0.0f);
|
||||||
|
state.solid_display_mode = !state.solid_display_mode;
|
||||||
|
if (state.current_texture) {
|
||||||
|
SDL_free(state.current_texture);
|
||||||
|
state.current_texture = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input_action_just_pressed("toggle_projection")) {
|
||||||
|
audio_play("/data/pop.wav", NULL, false, 0.8f, 0.0f);
|
||||||
|
state.camera_is_orthographic = !state.camera_is_orthographic;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input_action_just_pressed("toggle_x_axis")) {
|
||||||
|
state.axis_mask[0] = 0; state.axis_mask[1] = 1; state.axis_mask[2] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input_action_just_pressed("toggle_y_axis")) {
|
||||||
|
state.axis_mask[0] = 1; state.axis_mask[1] = 0; state.axis_mask[2] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input_action_just_pressed("toggle_z_axis")) {
|
||||||
|
state.axis_mask[0] = 1; state.axis_mask[1] = 1; state.axis_mask[2] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: determine bounding box for this? */
|
||||||
|
/* TODO: no idea whether it's all correct in terms of directions. */
|
||||||
|
if (input_action_just_pressed("camera_front")) {
|
||||||
|
audio_play("/data/pop.wav", NULL, false, 0.8f, 0.0f);
|
||||||
|
state.camera_is_orthographic = true;
|
||||||
|
state.camera_direction = (Vec3){0,0,-1};
|
||||||
|
state.camera_position = vec3_add(state.active_center, (Vec3){0,0,2});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input_action_just_pressed("camera_back")) {
|
||||||
|
audio_play("/data/pop.wav", NULL, false, 0.8f, 0.0f);
|
||||||
|
state.camera_is_orthographic = true;
|
||||||
|
state.camera_direction = (Vec3){0,0,1};
|
||||||
|
state.camera_position = vec3_add(state.active_center, (Vec3){0,0,-2});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input_action_just_pressed("camera_left")) {
|
||||||
|
audio_play("/data/pop.wav", NULL, false, 0.8f, 0.0f);
|
||||||
|
state.camera_is_orthographic = true;
|
||||||
|
state.camera_direction = (Vec3){1,0,0};
|
||||||
|
state.camera_position = vec3_add(state.active_center, (Vec3){-2,0,0});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input_action_just_pressed("camera_right")) {
|
||||||
|
audio_play("/data/pop.wav", NULL, false, 0.8f, 0.0f);
|
||||||
|
state.camera_is_orthographic = true;
|
||||||
|
state.camera_direction = (Vec3){-1,0,0};
|
||||||
|
state.camera_position = vec3_add(state.active_center, (Vec3){2,0,0});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: broken */
|
||||||
|
if (input_action_just_pressed("camera_above")) {
|
||||||
|
audio_play("/data/pop.wav", NULL, false, 0.8f, 0.0f);
|
||||||
|
state.camera_is_orthographic = true;
|
||||||
|
state.camera_direction = (Vec3){0,-1,0};
|
||||||
|
state.camera_position = vec3_add(state.active_center, (Vec3){0,2,0});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: broken */
|
||||||
|
if (input_action_just_pressed("camera_below")) {
|
||||||
|
audio_play("/data/pop.wav", NULL, false, 0.8f, 0.0f);
|
||||||
|
state.camera_is_orthographic = true;
|
||||||
|
state.camera_direction = (Vec3){0,1,0};
|
||||||
|
state.camera_position = vec3_add(state.active_center, (Vec3){0,-2,0});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void game_tick(void) {
|
||||||
|
if (!init) {
|
||||||
|
/* default state */
|
||||||
|
new_cube((Point){0,0,0}, (Point){POINTS_PER_METER,POINTS_PER_METER,POINTS_PER_METER});
|
||||||
|
state.camera_position = (Vec3){2,1,2};
|
||||||
|
state.camera_direction = vec3_norm(((Vec3){-2,-1,-2}));
|
||||||
|
state.camera_zoom = 0.5f;
|
||||||
|
state.grid_snap_granularity = 16;
|
||||||
|
state.axis_mask[1] = 1; state.axis_mask[2] = 1;
|
||||||
|
init = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.current_hovered_face = INVALID_FACE;
|
||||||
|
state.current_hovered_obj = INVALID_OBJECT;
|
||||||
|
|
||||||
|
input_action("toggle_display_mode", "Q");
|
||||||
|
input_action("toggle_projection", "TAB");
|
||||||
|
|
||||||
|
input_action("toggle_x_axis", "Z");
|
||||||
|
input_action("toggle_y_axis", "X");
|
||||||
|
input_action("toggle_z_axis", "C");
|
||||||
|
|
||||||
|
input_action("select", "LCLICK");
|
||||||
|
input_action("rotate", "R");
|
||||||
|
input_action("undo", "F");
|
||||||
|
|
||||||
|
input_action("camera_front", "KP0");
|
||||||
|
input_action("camera_back", "KP1");
|
||||||
|
input_action("camera_left", "KP2");
|
||||||
|
input_action("camera_right", "KP3");
|
||||||
|
input_action("camera_above", "KP4");
|
||||||
|
input_action("camera_below", "KP5");
|
||||||
|
|
||||||
|
process_camera_inputs();
|
||||||
|
process_camera_movement();
|
||||||
|
process_operations();
|
||||||
|
|
||||||
|
/* helpres */
|
||||||
|
draw_axes();
|
||||||
|
draw_hovered_face_border();
|
||||||
|
|
||||||
|
for (uint8_t obj = 0; obj < state.objects_sz; ++obj)
|
||||||
|
render_object(obj);
|
||||||
|
|
||||||
|
if (state.solid_display_mode)
|
||||||
|
display_texture_selection();
|
||||||
|
|
||||||
|
draw_text("twndel\x03", (Vec2){0, 2}, 32, (Color){255,255,255,200}, NULL);
|
||||||
|
draw_camera(
|
||||||
|
state.camera_position,
|
||||||
|
state.camera_direction,
|
||||||
|
(Vec3){0, 1, 0},
|
||||||
|
state.camera_is_orthographic ? 0 : CAMERA_FOV,
|
||||||
|
state.camera_zoom,
|
||||||
|
100
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void game_end(void) {
|
||||||
|
for (uint8_t obj = 0; obj < state.objects_sz; ++obj) {
|
||||||
|
Object *o = &state.objects[obj];
|
||||||
|
for (uint8_t t = 0; t < o->textures_sz; ++t)
|
||||||
|
SDL_free(o->textures[t]);
|
||||||
|
SDL_free(o->name);
|
||||||
|
}
|
||||||
|
if (state.current_texture) {
|
||||||
|
SDL_free(state.current_texture);
|
||||||
|
state.current_texture = NULL;
|
||||||
|
}
|
||||||
|
}
|
1
apps/twnlua/.gitignore
vendored
1
apps/twnlua/.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
luabind.c
|
luabind.c
|
||||||
|
data/scripts/twnapi.lua
|
||||||
|
@ -1,43 +1,36 @@
|
|||||||
cmake_minimum_required(VERSION 3.21)
|
cmake_minimum_required(VERSION 3.30)
|
||||||
cmake_policy(SET CMP0171 NEW)
|
|
||||||
project(twnlua LANGUAGES C)
|
project(twnlua LANGUAGES C)
|
||||||
|
|
||||||
|
find_package(Python3 COMPONENTS Interpreter)
|
||||||
|
|
||||||
if(NOT CMAKE_BUILD_TYPE)
|
if(NOT CMAKE_BUILD_TYPE)
|
||||||
set(CMAKE_BUILD_TYPE Debug)
|
set(CMAKE_BUILD_TYPE Debug)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build)
|
add_subdirectory($ENV{TWNROOT} ${CMAKE_BINARY_DIR}/twn)
|
||||||
|
|
||||||
set(FLAGS
|
|
||||||
$<$<NOT:$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>>:--no-dynlib-game>
|
|
||||||
)
|
|
||||||
|
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/luabind.c
|
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/game.c
|
||||||
COMMAND ${PYTHON3} ${CMAKE_CURRENT_SOURCE_DIR}/bindgen.py $ENV{TWNROOT}/share/twn_api.json ${FLAGS} > ${CMAKE_CURRENT_SOURCE_DIR}/luabind.c
|
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/bindgen.py $ENV{TWNROOT}/share/twn_api.json ${FLAGS} > ${CMAKE_CURRENT_SOURCE_DIR}/luabind.c
|
||||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/bindgen.py $ENV{TWNROOT}/share/twn_api.json
|
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/bindgen.py $ENV{TWNROOT}/share/twn_api.json
|
||||||
CODEGEN
|
|
||||||
)
|
)
|
||||||
|
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
OUTPUT ${CMAKE_SOURCE_DIR}/data/scripts/twnapi.lua
|
OUTPUT ${TWN_OUT_DIR}/data/scripts/twnapi.lua
|
||||||
COMMAND ${PYTHON3} ${CMAKE_CURRENT_SOURCE_DIR}/docgen.py $ENV{TWNROOT}/share/twn_api.json > ${CMAKE_SOURCE_DIR}/data/scripts/twnapi.lua
|
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/docgen.py $ENV{TWNROOT}/share/twn_api.json > ${TWN_OUT_DIR}/data/scripts/twnapi.lua
|
||||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/docgen.py $ENV{TWNROOT}/share/twn_api.json
|
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/docgen.py $ENV{TWNROOT}/share/twn_api.json
|
||||||
)
|
)
|
||||||
|
|
||||||
add_custom_target(
|
add_custom_target(
|
||||||
twnlua_docgen ALL
|
twnlua_docgen ALL
|
||||||
DEPENDS ${CMAKE_SOURCE_DIR}/data/scripts/twnapi.lua
|
DEPENDS ${TWN_OUT_DIR}/data/scripts/twnapi.lua
|
||||||
)
|
)
|
||||||
|
|
||||||
add_compile_definitions(LUA_32BITS)
|
add_compile_definitions(LUA_32BITS=1)
|
||||||
|
|
||||||
set(SOURCE_FILES
|
set(SOURCE_FILES
|
||||||
game.c
|
game.c
|
||||||
state.h
|
|
||||||
minilua.c
|
minilua.c
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/luabind.c
|
|
||||||
)
|
)
|
||||||
|
|
||||||
cmake_path(GET CMAKE_SOURCE_DIR STEM LAST_ONLY GAME_PROJECT_NAME)
|
use_townengine("${SOURCE_FILES}" ${TWN_OUT_DIR})
|
||||||
use_townengine(${GAME_PROJECT_NAME} "${SOURCE_FILES}" ${CMAKE_SOURCE_DIR})
|
|
||||||
|
@ -36,7 +36,7 @@ def to_table(typedesc, variable, indent = 0):
|
|||||||
binding = ' ' * indent + "lua_createtable(L, 0, %i);\n" % len(typedesc["fields"])
|
binding = ' ' * indent + "lua_createtable(L, 0, %i);\n" % len(typedesc["fields"])
|
||||||
for field in typedesc["fields"]:
|
for field in typedesc["fields"]:
|
||||||
if field["type"] == "float" or field["type"] == "uint8_t":
|
if field["type"] == "float" or field["type"] == "uint8_t":
|
||||||
binding += ' ' * indent + "lua_pushnumber(L, (double)(%s));\n" % (variable + ".%s" % field["name"])
|
binding += ' ' * indent + "lua_pushnumber(L, (float)(%s));\n" % (variable + ".%s" % field["name"])
|
||||||
elif field["type"] == "bool":
|
elif field["type"] == "bool":
|
||||||
binding += ' ' * indent + "lua_pushboolean(L, (%s));\n" % (variable + ".%s" % field["name"])
|
binding += ' ' * indent + "lua_pushboolean(L, (%s));\n" % (variable + ".%s" % field["name"])
|
||||||
elif field["type"] in api["types"]:
|
elif field["type"] in api["types"]:
|
||||||
@ -64,10 +64,7 @@ def from_table(typedesc, variable, indent = 0):
|
|||||||
|
|
||||||
|
|
||||||
print('#include "twn_game_api.h"\n')
|
print('#include "twn_game_api.h"\n')
|
||||||
if not "--no-dynlib-game" in sys.argv:
|
print('/* assumed to be included from game.c, where minilua.h is already present */\n')
|
||||||
print('#define STB_DS_IMPLEMENTATION')
|
|
||||||
print('#include <stb_ds.h>')
|
|
||||||
print('#include "minilua.h"\n')
|
|
||||||
|
|
||||||
bindings, used_converters = [], {}
|
bindings, used_converters = [], {}
|
||||||
for procedure, procedure_desc in api["procedures"].items():
|
for procedure, procedure_desc in api["procedures"].items():
|
||||||
@ -112,11 +109,13 @@ for procedure, procedure_desc in api["procedures"].items():
|
|||||||
if procedure_desc["return"] == "bool":
|
if procedure_desc["return"] == "bool":
|
||||||
binding += " lua_pushboolean(L, (int)(%s(%s)));\n" % (procedure, ", ".join(param["name"] for param in procedure_desc["params"]))
|
binding += " lua_pushboolean(L, (int)(%s(%s)));\n" % (procedure, ", ".join(param["name"] for param in procedure_desc["params"]))
|
||||||
elif procedure_desc["return"] == "float":
|
elif procedure_desc["return"] == "float":
|
||||||
binding += " lua_pushnumber(L, (double)(%s(%s)));\n" % (procedure, ", ".join(param["name"] for param in procedure_desc["params"]))
|
binding += " lua_pushnumber(L, (float)(%s(%s)));\n" % (procedure, ", ".join(param["name"] for param in procedure_desc["params"]))
|
||||||
elif procedure_desc["return"] == "char *":
|
elif procedure_desc["return"] == "char *":
|
||||||
binding += " lua_pushstring(L, %s(%s));\n" % (procedure, ", ".join(param["name"] for param in procedure_desc["params"]))
|
binding += " lua_pushstring(L, %s(%s));\n" % (procedure, ", ".join(param["name"] for param in procedure_desc["params"]))
|
||||||
|
elif procedure_desc["return"] == "String":
|
||||||
|
binding += " String result = %s(%s);\n" % (procedure, ", ".join(param["name"] for param in procedure_desc["params"]))
|
||||||
|
binding += " lua_pushlstring(L, result.data, (int)result.length);\n"
|
||||||
elif type(procedure_desc["return"]) is dict or procedure_desc["return"] in api["types"]:
|
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"]]
|
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 += " %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)
|
binding += to_table(type_desc, "result", 4)
|
||||||
@ -150,35 +149,17 @@ for typename, typedesc in used_converters.items():
|
|||||||
raise BaseException("Unhandled converter field type '%s'" % (field["type"]))
|
raise BaseException("Unhandled converter field type '%s'" % (field["type"]))
|
||||||
converter += " lua_pop(L, %i);\n" % len(typedesc["fields"]);
|
converter += " lua_pop(L, %i);\n" % len(typedesc["fields"]);
|
||||||
|
|
||||||
# TODO: wild idea: use compile time built hash table
|
|
||||||
elif "enums" in typedesc:
|
|
||||||
storages += ["struct %sHashItem { char *key; %s value; };\nstatic struct %sHashItem *%s_map = NULL;\n" % (typename, typename, typename, typename.lower())]
|
|
||||||
|
|
||||||
# TODO: use arena
|
|
||||||
for enum in typedesc["enums"]:
|
|
||||||
initializer = " shput(%s_map, \"%s\", %s);" % (typename.lower(), enum, typedesc["enums"][enum])
|
|
||||||
initializers += [initializer]
|
|
||||||
|
|
||||||
deinitializers += [" shfree(%s_map);" % typename.lower()]
|
|
||||||
|
|
||||||
converter += " char const *value = lua_tostring(L, -1);\n";
|
|
||||||
converter += " %s = shget(%s_map, value);\n" % (typename.lower(), typename.lower())
|
|
||||||
converter += " lua_pop(L, 1);\n";
|
|
||||||
|
|
||||||
converter += " return %s;\n}\n" % (typename.lower())
|
converter += " return %s;\n}\n" % (typename.lower())
|
||||||
converters += [converter]
|
converters += [converter]
|
||||||
|
|
||||||
|
|
||||||
print('\n'.join(storages))
|
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(converters))
|
||||||
print('\n'.join(bindings))
|
print('\n'.join(bindings))
|
||||||
|
|
||||||
|
|
||||||
loader = "extern void bindgen_load_%s(lua_State *L);\n" % api["name"]
|
loader = "extern void bindgen_load_%s(lua_State *L);\n" % api["name"]
|
||||||
loader += "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():
|
for procedure, procedure_desc in api["procedures"].items():
|
||||||
loader += " lua_pushcfunction(L, binding_%s);\n" % procedure
|
loader += " lua_pushcfunction(L, binding_%s);\n" % procedure
|
||||||
loader += " lua_setglobal(L, \"%s\");\n" % procedure
|
loader += " lua_setglobal(L, \"%s\");\n" % procedure
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
[[deps]]
|
[[deps]]
|
||||||
source = "../../common-data" # where does it come from, might be an url
|
source = "../../data" # where does it come from, might be an url
|
||||||
name = "common-data" # should be globally unique
|
name = "common-data" # should be globally unique
|
||||||
|
@ -4,16 +4,39 @@ offset = { x = 0, y = 0 }
|
|||||||
angle = 0
|
angle = 0
|
||||||
|
|
||||||
function game_tick()
|
function game_tick()
|
||||||
|
if ctx.udata == nil then
|
||||||
|
ctx.udata = {
|
||||||
|
frame_count = 0,
|
||||||
|
nest = {
|
||||||
|
frame_count = 0,
|
||||||
|
},
|
||||||
|
arr = { [0] = 0 },
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
draw_text {
|
||||||
|
string = tostring(ctx.udata.frame_count),
|
||||||
|
position = { x = 0, y = 0 },
|
||||||
|
font = "/fonts/kenney-pixel.ttf",
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_text {
|
||||||
|
string = tostring(ctx.udata.nest.frame_count),
|
||||||
|
position = { x = 0, y = 14 },
|
||||||
|
font = "/fonts/kenney-pixel.ttf",
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_text {
|
||||||
|
string = tostring(ctx.udata.arr[0]),
|
||||||
|
position = { x = 0, y = 28 },
|
||||||
|
font = "/fonts/kenney-pixel.ttf",
|
||||||
|
}
|
||||||
|
|
||||||
input_action {
|
input_action {
|
||||||
name = "press",
|
name = "press",
|
||||||
control = "A"
|
control = "A"
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_rectangle {
|
|
||||||
rect = { x = 0, y = 0, w = 640, h = 360 },
|
|
||||||
color = { r = 127, g = 0, b = 127, a = 255 },
|
|
||||||
}
|
|
||||||
|
|
||||||
draw_sprite {
|
draw_sprite {
|
||||||
texture = "/assets/title.png",
|
texture = "/assets/title.png",
|
||||||
rect = {
|
rect = {
|
||||||
@ -32,6 +55,10 @@ function game_tick()
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
ctx.udata.frame_count = ctx.udata.frame_count + 1
|
||||||
|
ctx.udata.nest.frame_count = ctx.udata.nest.frame_count + 1
|
||||||
|
ctx.udata.arr[0] = ctx.udata.arr[0] + 1
|
||||||
|
|
||||||
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)
|
||||||
angle = angle + 0.1
|
angle = angle + 0.1
|
||||||
|
@ -6,5 +6,6 @@ dev_id = "somebody"
|
|||||||
|
|
||||||
[game]
|
[game]
|
||||||
resolution = [ 640, 360 ]
|
resolution = [ 640, 360 ]
|
||||||
|
background_color = [ 127, 0, 127, 255 ]
|
||||||
|
|
||||||
[engine]
|
[engine]
|
||||||
|
@ -17,8 +17,6 @@ def to_lua_type_annot(typedesc):
|
|||||||
return "number"
|
return "number"
|
||||||
elif basetype == "bool":
|
elif basetype == "bool":
|
||||||
return "boolean"
|
return "boolean"
|
||||||
elif basetype == "Control":
|
|
||||||
return "Control"
|
|
||||||
elif basetype == "Vec2":
|
elif basetype == "Vec2":
|
||||||
return r"{ x: number, y: number }"
|
return r"{ x: number, y: number }"
|
||||||
elif basetype == "Vec3":
|
elif basetype == "Vec3":
|
||||||
@ -31,9 +29,10 @@ def to_lua_type_annot(typedesc):
|
|||||||
return "unknown"
|
return "unknown"
|
||||||
# raise BaseException("Unhandled type for annotation: %s" % typedesc)
|
# raise BaseException("Unhandled type for annotation: %s" % typedesc)
|
||||||
|
|
||||||
print("error(\"townengine lua api file is not supposed to be imported!\")")
|
print("---@meta twn")
|
||||||
|
print("---@diagnostic disable")
|
||||||
|
|
||||||
type_annotations, enum_annotations = {}, {}
|
type_annotations = {}
|
||||||
type_annotations["ctx"] = r"{ %s, udata: table }" % \
|
type_annotations["ctx"] = r"{ %s, udata: table }" % \
|
||||||
', '.join("%s: %s" % (f["name"], to_lua_type_annot(f["type"])) for f in api["types"]["Context"]["fields"])
|
', '.join("%s: %s" % (f["name"], to_lua_type_annot(f["type"])) for f in api["types"]["Context"]["fields"])
|
||||||
|
|
||||||
@ -41,12 +40,6 @@ for annot in type_annotations:
|
|||||||
print("---@type " + type_annotations[annot])
|
print("---@type " + type_annotations[annot])
|
||||||
print(r"%s = nil" % annot)
|
print(r"%s = nil" % annot)
|
||||||
|
|
||||||
enum_annotations["Control"] = \
|
|
||||||
'|'.join('\'"%s"\'' % e for e in api["types"]["Control"]["enums"])
|
|
||||||
|
|
||||||
for annot in enum_annotations:
|
|
||||||
print("---@alias %s %s" % (annot, enum_annotations[annot]))
|
|
||||||
|
|
||||||
procedure_annotations = {}
|
procedure_annotations = {}
|
||||||
for procedure, procedure_desc in api["procedures"].items():
|
for procedure, procedure_desc in api["procedures"].items():
|
||||||
procedure_annotations[procedure] = {}
|
procedure_annotations[procedure] = {}
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
#include "twn_game_api.h"
|
#include "twn_game_api.h"
|
||||||
#include "state.h"
|
|
||||||
|
|
||||||
|
/* TODO: actually move it back it its own file, it doesn't give any compilation benefits */
|
||||||
#include "minilua.h"
|
#include "minilua.h"
|
||||||
|
|
||||||
|
#include "state.h"
|
||||||
|
#include "luabind.c"
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
|
#define UDATA_NESTING_LIMIT 128
|
||||||
|
|
||||||
|
|
||||||
/* generated by bindgen.py */
|
/* generated by bindgen.py */
|
||||||
void bindgen_load_twn(lua_State *L);
|
void bindgen_load_twn(lua_State *L);
|
||||||
@ -18,6 +24,12 @@ void bindgen_upload_context(lua_State *L);
|
|||||||
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);
|
||||||
|
|
||||||
|
static const char *name_breaker = NULL;
|
||||||
|
if (name_breaker && SDL_strcmp(name, name_breaker) == 0) {
|
||||||
|
log_string(name_breaker, "Recursive load on itself from lua module");
|
||||||
|
return 0;
|
||||||
|
} name_breaker = name;
|
||||||
|
|
||||||
/* replace dots with path slashes */
|
/* replace dots with path slashes */
|
||||||
char *path_copy = SDL_strdup(name);
|
char *path_copy = SDL_strdup(name);
|
||||||
char *ch = NULL;
|
char *ch = NULL;
|
||||||
@ -28,25 +40,28 @@ static int physfs_loader(lua_State *L) {
|
|||||||
SDL_asprintf(&final_path, "/scripts/%s.lua", path_copy);
|
SDL_asprintf(&final_path, "/scripts/%s.lua", path_copy);
|
||||||
SDL_free(path_copy);
|
SDL_free(path_copy);
|
||||||
|
|
||||||
if (!file_exists(final_path)) {
|
if (SDL_strcmp(file_read(final_path, ":exists").data, "yes") == 0) {
|
||||||
char *error_message = NULL;
|
char *error_message = NULL;
|
||||||
SDL_asprintf(&error_message, "could not find module %s in filesystem", name);
|
SDL_asprintf(&error_message, "could not find module %s in filesystem", name);
|
||||||
lua_pushstring(L, error_message);
|
lua_pushstring(L, error_message);
|
||||||
free(error_message);
|
SDL_free(error_message);
|
||||||
|
|
||||||
free(final_path);
|
SDL_free(final_path);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char *buf = NULL;
|
char *final_path_binary = NULL;
|
||||||
int64_t buf_size = file_to_bytes(final_path, &buf);
|
SDL_asprintf(&final_path_binary, "%s%s", final_path, ":binary");
|
||||||
free(final_path);
|
|
||||||
|
String file = file_read(final_path, ":binary");
|
||||||
|
SDL_free(final_path);
|
||||||
|
|
||||||
/* TODO: use reader interface for streaming instead */
|
/* TODO: use reader interface for streaming instead */
|
||||||
int const result = luaL_loadbuffer(L, (char *)buf, buf_size, name) == LUA_OK;
|
int const result = luaL_loadbuffer(L, file.data, (size_t)file.length, name);
|
||||||
free(buf);
|
if (result != LUA_OK)
|
||||||
|
log_string(lua_tostring(L, -1), NULL);
|
||||||
|
|
||||||
return result;
|
return result == LUA_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -93,21 +108,76 @@ static void *custom_alloc(void *ud, void *ptr, size_t osize, size_t nsize) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void exchange_lua_states(lua_State *from, lua_State *to, int level, int index) {
|
||||||
|
if (level >= UDATA_NESTING_LIMIT) {
|
||||||
|
log_string("ctx.udata nesting limit is reached", NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: use arrays for optimized paths */
|
||||||
|
/* TODO: preallocate table records */
|
||||||
|
switch (lua_type(from, index)) {
|
||||||
|
case LUA_TTABLE:
|
||||||
|
lua_newtable(to);
|
||||||
|
lua_pushnil(from); /* first key */
|
||||||
|
while (lua_next(from, index - 1) != 0) {
|
||||||
|
/* 'key' at index -2 and 'value' at index -1 */
|
||||||
|
exchange_lua_states(from, to, level + 1, -2);
|
||||||
|
exchange_lua_states(from, to, level + 1, -1);
|
||||||
|
lua_settable(to, index - 2);
|
||||||
|
/* removes 'value'; keeps 'key' for next iteration */
|
||||||
|
lua_pop(from, 1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case LUA_TNUMBER:
|
||||||
|
lua_pushnumber(to, lua_tonumber(from, index));
|
||||||
|
break;
|
||||||
|
case LUA_TBOOLEAN:
|
||||||
|
lua_pushboolean(to, lua_toboolean(from, index));
|
||||||
|
break;
|
||||||
|
case LUA_TSTRING:
|
||||||
|
lua_pushstring(to, lua_tostring(from, index));
|
||||||
|
break;
|
||||||
|
case LUA_TNIL:
|
||||||
|
lua_pushnil(to);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* TODO: provide a path and type of it for better diagnostic */
|
||||||
|
log_string("Unserializable udata found and is ignored", NULL);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void game_tick(void) {
|
void game_tick(void) {
|
||||||
if (ctx.initialization_needed) {
|
if (ctx.initialization_needed) {
|
||||||
if (!ctx.udata)
|
if (!ctx.udata)
|
||||||
ctx.udata = calloc(1, sizeof (State));
|
ctx.udata = SDL_calloc(1, sizeof (State));
|
||||||
|
|
||||||
State *state = ctx.udata;
|
State *state = ctx.udata;
|
||||||
|
|
||||||
/* let's init lua */
|
/* let's init lua */
|
||||||
/* state existed already */
|
lua_State *new_state = lua_newstate(custom_alloc, NULL);
|
||||||
|
lua_setallocf(new_state, custom_alloc, NULL);
|
||||||
|
|
||||||
|
/* state existed already, copy its udata over */
|
||||||
if (state->L != NULL) {
|
if (state->L != NULL) {
|
||||||
|
lua_getglobal(state->L, "ctx");
|
||||||
|
lua_getfield(state->L, -1, "udata");
|
||||||
|
SDL_assert(!lua_isnoneornil(state->L, -1));
|
||||||
|
SDL_assert(!lua_isnoneornil(state->L, -2));
|
||||||
|
if (!lua_isnoneornil(state->L, -1)) {
|
||||||
|
lua_newtable(new_state);
|
||||||
|
exchange_lua_states(state->L, new_state, 0, -1);
|
||||||
|
lua_setfield(new_state, -2, "udata");
|
||||||
|
lua_setglobal(new_state, "ctx");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* bye :) */
|
||||||
lua_close(state->L);
|
lua_close(state->L);
|
||||||
}
|
}
|
||||||
state->L = luaL_newstate();
|
|
||||||
|
|
||||||
lua_setallocf(state->L, custom_alloc, NULL);
|
state->L = new_state;
|
||||||
|
|
||||||
/* fakey version of luaL_openlibs() that excludes file i/o and os stuff */
|
/* fakey version of luaL_openlibs() that excludes file i/o and os stuff */
|
||||||
{
|
{
|
||||||
@ -145,24 +215,21 @@ void game_tick(void) {
|
|||||||
bindgen_load_twn(state->L);
|
bindgen_load_twn(state->L);
|
||||||
|
|
||||||
/* now finally get to running the code */
|
/* now finally get to running the code */
|
||||||
unsigned char *game_buf = NULL;
|
String file = file_read("/scripts/game.lua", ":binary");
|
||||||
size_t game_buf_size = file_to_bytes("/scripts/game.lua", &game_buf);
|
|
||||||
/* TODO: use reader interface for streaming instead */
|
/* TODO: use reader interface for streaming instead */
|
||||||
if (luaL_loadbuffer(state->L, (char *)game_buf, game_buf_size, "game.lua") == LUA_OK) {
|
if (luaL_loadbuffer(state->L, file.data, (size_t)file.length, "game.lua") == LUA_OK) {
|
||||||
if (lua_pcall(state->L, 0, 0, 0) != LUA_OK) {
|
if (lua_pcall(state->L, 0, 0, 0) != LUA_OK) {
|
||||||
log_critical("%s", lua_tostring(state->L, -1));
|
log_string(luaL_tolstring(state->L, -1, NULL), "Error executing /scripts/game.lua entry");
|
||||||
lua_pop(state->L, 1);
|
lua_pop(state->L, 1);
|
||||||
} else
|
} else
|
||||||
state->loaded_successfully = true;
|
state->loaded_successfully = true;
|
||||||
} else {
|
} else {
|
||||||
/* got some sort of error, it should be pushed on top of the stack */
|
/* got some sort of error, it should be pushed on top of the stack */
|
||||||
SDL_assert(lua_isstring(state->L, -1));
|
SDL_assert(lua_isstring(state->L, -1));
|
||||||
log_critical("Error loading /scripts/game.lua entry: %s", luaL_tolstring(state->L, -1, NULL));
|
log_string(luaL_tolstring(state->L, -1, NULL), "Error loading /scripts/game.lua entry");
|
||||||
lua_pop(state->L, 1);
|
lua_pop(state->L, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
free(game_buf);
|
|
||||||
|
|
||||||
/* from this point we have access to everything defined in lua */
|
/* from this point we have access to everything defined in lua */
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,7 +247,7 @@ void game_tick(void) {
|
|||||||
|
|
||||||
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_string(luaL_tolstring(state->L, -1, NULL), "Error executing game_tick()");
|
||||||
lua_pop(state->L, 1);
|
lua_pop(state->L, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,5 +261,5 @@ void game_end(void) {
|
|||||||
State *state = ctx.udata;
|
State *state = ctx.udata;
|
||||||
bindgen_unload_twn(state->L);
|
bindgen_unload_twn(state->L);
|
||||||
lua_close(state->L);
|
lua_close(state->L);
|
||||||
free(state);
|
SDL_free(state);
|
||||||
}
|
}
|
||||||
|
@ -210,8 +210,9 @@ extern "C" {
|
|||||||
/*
|
/*
|
||||||
@@ LUA_32BITS enables Lua with 32-bit integers and 32-bit floats.
|
@@ LUA_32BITS enables Lua with 32-bit integers and 32-bit floats.
|
||||||
*/
|
*/
|
||||||
|
#if !defined(LUA_32BITS)
|
||||||
#define LUA_32BITS 0
|
#define LUA_32BITS 0
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ LUA_C89_NUMBERS ensures that Lua uses the largest types available for
|
@@ LUA_C89_NUMBERS ensures that Lua uses the largest types available for
|
||||||
@ -382,7 +383,8 @@ extern "C" {
|
|||||||
|
|
||||||
#else /* }{ */
|
#else /* }{ */
|
||||||
|
|
||||||
#define LUA_API extern
|
/* TWN: Don't export anything, there's no need. */
|
||||||
|
#define LUA_API LUAI_FUNC
|
||||||
|
|
||||||
#endif /* } */
|
#endif /* } */
|
||||||
|
|
||||||
@ -28631,696 +28633,6 @@ LUALIB_API void luaL_openlibs (lua_State *L) {
|
|||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef LUA_MAKE_LUA
|
|
||||||
/*
|
|
||||||
** $Id: lua.c $
|
|
||||||
** Lua stand-alone interpreter
|
|
||||||
** See Copyright Notice in lua.h
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define lua_c
|
|
||||||
|
|
||||||
/*#include "lprefix.h"*/
|
|
||||||
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <signal.h>
|
|
||||||
|
|
||||||
/*#include "lua.h"*/
|
|
||||||
|
|
||||||
/*#include "lauxlib.h"*/
|
|
||||||
/*#include "lualib.h"*/
|
|
||||||
|
|
||||||
|
|
||||||
#if !defined(LUA_PROGNAME)
|
|
||||||
#define LUA_PROGNAME "lua"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !defined(LUA_INIT_VAR)
|
|
||||||
#define LUA_INIT_VAR "LUA_INIT"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define LUA_INITVARVERSION LUA_INIT_VAR LUA_VERSUFFIX
|
|
||||||
|
|
||||||
|
|
||||||
static lua_State *globalL = NULL;
|
|
||||||
|
|
||||||
static const char *progname = LUA_PROGNAME;
|
|
||||||
|
|
||||||
|
|
||||||
#if defined(LUA_USE_POSIX) /* { */
|
|
||||||
|
|
||||||
/*
|
|
||||||
** Use 'sigaction' when available.
|
|
||||||
*/
|
|
||||||
static void setsignal (int sig, void (*handler)(int)) {
|
|
||||||
struct sigaction sa;
|
|
||||||
sa.sa_handler = handler;
|
|
||||||
sa.sa_flags = 0;
|
|
||||||
sigemptyset(&sa.sa_mask); /* do not mask any signal */
|
|
||||||
sigaction(sig, &sa, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
#else /* }{ */
|
|
||||||
|
|
||||||
#define setsignal signal
|
|
||||||
|
|
||||||
#endif /* } */
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
** Hook set by signal function to stop the interpreter.
|
|
||||||
*/
|
|
||||||
static void lstop (lua_State *L, lua_Debug *ar) {
|
|
||||||
(void)ar; /* unused arg. */
|
|
||||||
lua_sethook(L, NULL, 0, 0); /* reset hook */
|
|
||||||
luaL_error(L, "interrupted!");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
** Function to be called at a C signal. Because a C signal cannot
|
|
||||||
** just change a Lua state (as there is no proper synchronization),
|
|
||||||
** this function only sets a hook that, when called, will stop the
|
|
||||||
** interpreter.
|
|
||||||
*/
|
|
||||||
static void laction (int i) {
|
|
||||||
int flag = LUA_MASKCALL | LUA_MASKRET | LUA_MASKLINE | LUA_MASKCOUNT;
|
|
||||||
setsignal(i, SIG_DFL); /* if another SIGINT happens, terminate process */
|
|
||||||
lua_sethook(globalL, lstop, flag, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void print_usage (const char *badoption) {
|
|
||||||
lua_writestringerror("%s: ", progname);
|
|
||||||
if (badoption[1] == 'e' || badoption[1] == 'l')
|
|
||||||
lua_writestringerror("'%s' needs argument\n", badoption);
|
|
||||||
else
|
|
||||||
lua_writestringerror("unrecognized option '%s'\n", badoption);
|
|
||||||
lua_writestringerror(
|
|
||||||
"usage: %s [options] [script [args]]\n"
|
|
||||||
"Available options are:\n"
|
|
||||||
" -e stat execute string 'stat'\n"
|
|
||||||
" -i enter interactive mode after executing 'script'\n"
|
|
||||||
" -l mod require library 'mod' into global 'mod'\n"
|
|
||||||
" -l g=mod require library 'mod' into global 'g'\n"
|
|
||||||
" -v show version information\n"
|
|
||||||
" -E ignore environment variables\n"
|
|
||||||
" -W turn warnings on\n"
|
|
||||||
" -- stop handling options\n"
|
|
||||||
" - stop handling options and execute stdin\n"
|
|
||||||
,
|
|
||||||
progname);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
** Prints an error message, adding the program name in front of it
|
|
||||||
** (if present)
|
|
||||||
*/
|
|
||||||
static void l_message (const char *pname, const char *msg) {
|
|
||||||
if (pname) lua_writestringerror("%s: ", pname);
|
|
||||||
lua_writestringerror("%s\n", msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
** Check whether 'status' is not OK and, if so, prints the error
|
|
||||||
** message on the top of the stack.
|
|
||||||
*/
|
|
||||||
static int report (lua_State *L, int status) {
|
|
||||||
if (status != LUA_OK) {
|
|
||||||
const char *msg = lua_tostring(L, -1);
|
|
||||||
if (msg == NULL)
|
|
||||||
msg = "(error message not a string)";
|
|
||||||
l_message(progname, msg);
|
|
||||||
lua_pop(L, 1); /* remove message */
|
|
||||||
}
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
** Message handler used to run all chunks
|
|
||||||
*/
|
|
||||||
static int msghandler (lua_State *L) {
|
|
||||||
const char *msg = lua_tostring(L, 1);
|
|
||||||
if (msg == NULL) { /* is error object not a string? */
|
|
||||||
if (luaL_callmeta(L, 1, "__tostring") && /* does it have a metamethod */
|
|
||||||
lua_type(L, -1) == LUA_TSTRING) /* that produces a string? */
|
|
||||||
return 1; /* that is the message */
|
|
||||||
else
|
|
||||||
msg = lua_pushfstring(L, "(error object is a %s value)",
|
|
||||||
luaL_typename(L, 1));
|
|
||||||
}
|
|
||||||
luaL_traceback(L, L, msg, 1); /* append a standard traceback */
|
|
||||||
return 1; /* return the traceback */
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
** Interface to 'lua_pcall', which sets appropriate message function
|
|
||||||
** and C-signal handler. Used to run all chunks.
|
|
||||||
*/
|
|
||||||
static int docall (lua_State *L, int narg, int nres) {
|
|
||||||
int status;
|
|
||||||
int base = lua_gettop(L) - narg; /* function index */
|
|
||||||
lua_pushcfunction(L, msghandler); /* push message handler */
|
|
||||||
lua_insert(L, base); /* put it under function and args */
|
|
||||||
globalL = L; /* to be available to 'laction' */
|
|
||||||
setsignal(SIGINT, laction); /* set C-signal handler */
|
|
||||||
status = lua_pcall(L, narg, nres, base);
|
|
||||||
setsignal(SIGINT, SIG_DFL); /* reset C-signal handler */
|
|
||||||
lua_remove(L, base); /* remove message handler from the stack */
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void print_version (void) {
|
|
||||||
lua_writestring(LUA_COPYRIGHT, strlen(LUA_COPYRIGHT));
|
|
||||||
lua_writeline();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
** Create the 'arg' table, which stores all arguments from the
|
|
||||||
** command line ('argv'). It should be aligned so that, at index 0,
|
|
||||||
** it has 'argv[script]', which is the script name. The arguments
|
|
||||||
** to the script (everything after 'script') go to positive indices;
|
|
||||||
** other arguments (before the script name) go to negative indices.
|
|
||||||
** If there is no script name, assume interpreter's name as base.
|
|
||||||
** (If there is no interpreter's name either, 'script' is -1, so
|
|
||||||
** table sizes are zero.)
|
|
||||||
*/
|
|
||||||
static void createargtable (lua_State *L, char **argv, int argc, int script) {
|
|
||||||
int i, narg;
|
|
||||||
narg = argc - (script + 1); /* number of positive indices */
|
|
||||||
lua_createtable(L, narg, script + 1);
|
|
||||||
for (i = 0; i < argc; i++) {
|
|
||||||
lua_pushstring(L, argv[i]);
|
|
||||||
lua_rawseti(L, -2, i - script);
|
|
||||||
}
|
|
||||||
lua_setglobal(L, "arg");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int dochunk (lua_State *L, int status) {
|
|
||||||
if (status == LUA_OK) status = docall(L, 0, 0);
|
|
||||||
return report(L, status);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int dofile (lua_State *L, const char *name) {
|
|
||||||
return dochunk(L, luaL_loadfile(L, name));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int dostring (lua_State *L, const char *s, const char *name) {
|
|
||||||
return dochunk(L, luaL_loadbuffer(L, s, strlen(s), name));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
** Receives 'globname[=modname]' and runs 'globname = require(modname)'.
|
|
||||||
** If there is no explicit modname and globname contains a '-', cut
|
|
||||||
** the suffix after '-' (the "version") to make the global name.
|
|
||||||
*/
|
|
||||||
static int dolibrary (lua_State *L, char *globname) {
|
|
||||||
int status;
|
|
||||||
char *suffix = NULL;
|
|
||||||
char *modname = strchr(globname, '=');
|
|
||||||
if (modname == NULL) { /* no explicit name? */
|
|
||||||
modname = globname; /* module name is equal to global name */
|
|
||||||
suffix = strchr(modname, *LUA_IGMARK); /* look for a suffix mark */
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
*modname = '\0'; /* global name ends here */
|
|
||||||
modname++; /* module name starts after the '=' */
|
|
||||||
}
|
|
||||||
lua_getglobal(L, "require");
|
|
||||||
lua_pushstring(L, modname);
|
|
||||||
status = docall(L, 1, 1); /* call 'require(modname)' */
|
|
||||||
if (status == LUA_OK) {
|
|
||||||
if (suffix != NULL) /* is there a suffix mark? */
|
|
||||||
*suffix = '\0'; /* remove suffix from global name */
|
|
||||||
lua_setglobal(L, globname); /* globname = require(modname) */
|
|
||||||
}
|
|
||||||
return report(L, status);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
** Push on the stack the contents of table 'arg' from 1 to #arg
|
|
||||||
*/
|
|
||||||
static int pushargs (lua_State *L) {
|
|
||||||
int i, n;
|
|
||||||
if (lua_getglobal(L, "arg") != LUA_TTABLE)
|
|
||||||
luaL_error(L, "'arg' is not a table");
|
|
||||||
n = (int)luaL_len(L, -1);
|
|
||||||
luaL_checkstack(L, n + 3, "too many arguments to script");
|
|
||||||
for (i = 1; i <= n; i++)
|
|
||||||
lua_rawgeti(L, -i, i);
|
|
||||||
lua_remove(L, -i); /* remove table from the stack */
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int handle_script (lua_State *L, char **argv) {
|
|
||||||
int status;
|
|
||||||
const char *fname = argv[0];
|
|
||||||
if (strcmp(fname, "-") == 0 && strcmp(argv[-1], "--") != 0)
|
|
||||||
fname = NULL; /* stdin */
|
|
||||||
status = luaL_loadfile(L, fname);
|
|
||||||
if (status == LUA_OK) {
|
|
||||||
int n = pushargs(L); /* push arguments to script */
|
|
||||||
status = docall(L, n, LUA_MULTRET);
|
|
||||||
}
|
|
||||||
return report(L, status);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* bits of various argument indicators in 'args' */
|
|
||||||
#define has_error 1 /* bad option */
|
|
||||||
#define has_i 2 /* -i */
|
|
||||||
#define has_v 4 /* -v */
|
|
||||||
#define has_e 8 /* -e */
|
|
||||||
#define has_E 16 /* -E */
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
** Traverses all arguments from 'argv', returning a mask with those
|
|
||||||
** needed before running any Lua code or an error code if it finds any
|
|
||||||
** invalid argument. In case of error, 'first' is the index of the bad
|
|
||||||
** argument. Otherwise, 'first' is -1 if there is no program name,
|
|
||||||
** 0 if there is no script name, or the index of the script name.
|
|
||||||
*/
|
|
||||||
static int collectargs (char **argv, int *first) {
|
|
||||||
int args = 0;
|
|
||||||
int i;
|
|
||||||
if (argv[0] != NULL) { /* is there a program name? */
|
|
||||||
if (argv[0][0]) /* not empty? */
|
|
||||||
progname = argv[0]; /* save it */
|
|
||||||
}
|
|
||||||
else { /* no program name */
|
|
||||||
*first = -1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
for (i = 1; argv[i] != NULL; i++) { /* handle arguments */
|
|
||||||
*first = i;
|
|
||||||
if (argv[i][0] != '-') /* not an option? */
|
|
||||||
return args; /* stop handling options */
|
|
||||||
switch (argv[i][1]) { /* else check option */
|
|
||||||
case '-': /* '--' */
|
|
||||||
if (argv[i][2] != '\0') /* extra characters after '--'? */
|
|
||||||
return has_error; /* invalid option */
|
|
||||||
*first = i + 1;
|
|
||||||
return args;
|
|
||||||
case '\0': /* '-' */
|
|
||||||
return args; /* script "name" is '-' */
|
|
||||||
case 'E':
|
|
||||||
if (argv[i][2] != '\0') /* extra characters? */
|
|
||||||
return has_error; /* invalid option */
|
|
||||||
args |= has_E;
|
|
||||||
break;
|
|
||||||
case 'W':
|
|
||||||
if (argv[i][2] != '\0') /* extra characters? */
|
|
||||||
return has_error; /* invalid option */
|
|
||||||
break;
|
|
||||||
case 'i':
|
|
||||||
args |= has_i; /* (-i implies -v) *//* FALLTHROUGH */
|
|
||||||
case 'v':
|
|
||||||
if (argv[i][2] != '\0') /* extra characters? */
|
|
||||||
return has_error; /* invalid option */
|
|
||||||
args |= has_v;
|
|
||||||
break;
|
|
||||||
case 'e':
|
|
||||||
args |= has_e; /* FALLTHROUGH */
|
|
||||||
case 'l': /* both options need an argument */
|
|
||||||
if (argv[i][2] == '\0') { /* no concatenated argument? */
|
|
||||||
i++; /* try next 'argv' */
|
|
||||||
if (argv[i] == NULL || argv[i][0] == '-')
|
|
||||||
return has_error; /* no next argument or it is another option */
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default: /* invalid option */
|
|
||||||
return has_error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*first = 0; /* no script name */
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
** Processes options 'e' and 'l', which involve running Lua code, and
|
|
||||||
** 'W', which also affects the state.
|
|
||||||
** Returns 0 if some code raises an error.
|
|
||||||
*/
|
|
||||||
static int runargs (lua_State *L, char **argv, int n) {
|
|
||||||
int i;
|
|
||||||
for (i = 1; i < n; i++) {
|
|
||||||
int option = argv[i][1];
|
|
||||||
lua_assert(argv[i][0] == '-'); /* already checked */
|
|
||||||
switch (option) {
|
|
||||||
case 'e': case 'l': {
|
|
||||||
int status;
|
|
||||||
char *extra = argv[i] + 2; /* both options need an argument */
|
|
||||||
if (*extra == '\0') extra = argv[++i];
|
|
||||||
lua_assert(extra != NULL);
|
|
||||||
status = (option == 'e')
|
|
||||||
? dostring(L, extra, "=(command line)")
|
|
||||||
: dolibrary(L, extra);
|
|
||||||
if (status != LUA_OK) return 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'W':
|
|
||||||
lua_warning(L, "@on", 0); /* warnings on */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int handle_luainit (lua_State *L) {
|
|
||||||
const char *name = "=" LUA_INITVARVERSION;
|
|
||||||
const char *init = getenv(name + 1);
|
|
||||||
if (init == NULL) {
|
|
||||||
name = "=" LUA_INIT_VAR;
|
|
||||||
init = getenv(name + 1); /* try alternative name */
|
|
||||||
}
|
|
||||||
if (init == NULL) return LUA_OK;
|
|
||||||
else if (init[0] == '@')
|
|
||||||
return dofile(L, init+1);
|
|
||||||
else
|
|
||||||
return dostring(L, init, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
** {==================================================================
|
|
||||||
** Read-Eval-Print Loop (REPL)
|
|
||||||
** ===================================================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
#if !defined(LUA_PROMPT)
|
|
||||||
#define LUA_PROMPT "> "
|
|
||||||
#define LUA_PROMPT2 ">> "
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !defined(LUA_MAXINPUT)
|
|
||||||
#define LUA_MAXINPUT 512
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
** lua_stdin_is_tty detects whether the standard input is a 'tty' (that
|
|
||||||
** is, whether we're running lua interactively).
|
|
||||||
*/
|
|
||||||
#if !defined(lua_stdin_is_tty) /* { */
|
|
||||||
|
|
||||||
#if defined(LUA_USE_POSIX) /* { */
|
|
||||||
|
|
||||||
#include <unistd.h>
|
|
||||||
#define lua_stdin_is_tty() isatty(0)
|
|
||||||
|
|
||||||
#elif defined(LUA_USE_WINDOWS) /* }{ */
|
|
||||||
|
|
||||||
#include <io.h>
|
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
#define lua_stdin_is_tty() _isatty(_fileno(stdin))
|
|
||||||
|
|
||||||
#else /* }{ */
|
|
||||||
|
|
||||||
/* ISO C definition */
|
|
||||||
#define lua_stdin_is_tty() 1 /* assume stdin is a tty */
|
|
||||||
|
|
||||||
#endif /* } */
|
|
||||||
|
|
||||||
#endif /* } */
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
** lua_readline defines how to show a prompt and then read a line from
|
|
||||||
** the standard input.
|
|
||||||
** lua_saveline defines how to "save" a read line in a "history".
|
|
||||||
** lua_freeline defines how to free a line read by lua_readline.
|
|
||||||
*/
|
|
||||||
#if !defined(lua_readline) /* { */
|
|
||||||
|
|
||||||
#if defined(LUA_USE_READLINE) /* { */
|
|
||||||
|
|
||||||
#include <readline/readline.h>
|
|
||||||
#include <readline/history.h>
|
|
||||||
#define lua_initreadline(L) ((void)L, rl_readline_name="lua")
|
|
||||||
#define lua_readline(L,b,p) ((void)L, ((b)=readline(p)) != NULL)
|
|
||||||
#define lua_saveline(L,line) ((void)L, add_history(line))
|
|
||||||
#define lua_freeline(L,b) ((void)L, free(b))
|
|
||||||
|
|
||||||
#else /* }{ */
|
|
||||||
|
|
||||||
#define lua_initreadline(L) ((void)L)
|
|
||||||
#define lua_readline(L,b,p) \
|
|
||||||
((void)L, fputs(p, stdout), fflush(stdout), /* show prompt */ \
|
|
||||||
fgets(b, LUA_MAXINPUT, stdin) != NULL) /* get line */
|
|
||||||
#define lua_saveline(L,line) { (void)L; (void)line; }
|
|
||||||
#define lua_freeline(L,b) { (void)L; (void)b; }
|
|
||||||
|
|
||||||
#endif /* } */
|
|
||||||
|
|
||||||
#endif /* } */
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
** Return the string to be used as a prompt by the interpreter. Leave
|
|
||||||
** the string (or nil, if using the default value) on the stack, to keep
|
|
||||||
** it anchored.
|
|
||||||
*/
|
|
||||||
static const char *get_prompt (lua_State *L, int firstline) {
|
|
||||||
if (lua_getglobal(L, firstline ? "_PROMPT" : "_PROMPT2") == LUA_TNIL)
|
|
||||||
return (firstline ? LUA_PROMPT : LUA_PROMPT2); /* use the default */
|
|
||||||
else { /* apply 'tostring' over the value */
|
|
||||||
const char *p = luaL_tolstring(L, -1, NULL);
|
|
||||||
lua_remove(L, -2); /* remove original value */
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* mark in error messages for incomplete statements */
|
|
||||||
#define EOFMARK "<eof>"
|
|
||||||
#define marklen (sizeof(EOFMARK)/sizeof(char) - 1)
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
** Check whether 'status' signals a syntax error and the error
|
|
||||||
** message at the top of the stack ends with the above mark for
|
|
||||||
** incomplete statements.
|
|
||||||
*/
|
|
||||||
static int incomplete (lua_State *L, int status) {
|
|
||||||
if (status == LUA_ERRSYNTAX) {
|
|
||||||
size_t lmsg;
|
|
||||||
const char *msg = lua_tolstring(L, -1, &lmsg);
|
|
||||||
if (lmsg >= marklen && strcmp(msg + lmsg - marklen, EOFMARK) == 0) {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0; /* else... */
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
** Prompt the user, read a line, and push it into the Lua stack.
|
|
||||||
*/
|
|
||||||
static int pushline (lua_State *L, int firstline) {
|
|
||||||
char buffer[LUA_MAXINPUT];
|
|
||||||
char *b = buffer;
|
|
||||||
size_t l;
|
|
||||||
const char *prmt = get_prompt(L, firstline);
|
|
||||||
int readstatus = lua_readline(L, b, prmt);
|
|
||||||
if (readstatus == 0)
|
|
||||||
return 0; /* no input (prompt will be popped by caller) */
|
|
||||||
lua_pop(L, 1); /* remove prompt */
|
|
||||||
l = strlen(b);
|
|
||||||
if (l > 0 && b[l-1] == '\n') /* line ends with newline? */
|
|
||||||
b[--l] = '\0'; /* remove it */
|
|
||||||
if (firstline && b[0] == '=') /* for compatibility with 5.2, ... */
|
|
||||||
lua_pushfstring(L, "return %s", b + 1); /* change '=' to 'return' */
|
|
||||||
else
|
|
||||||
lua_pushlstring(L, b, l);
|
|
||||||
lua_freeline(L, b);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
** Try to compile line on the stack as 'return <line>;'; on return, stack
|
|
||||||
** has either compiled chunk or original line (if compilation failed).
|
|
||||||
*/
|
|
||||||
static int addreturn (lua_State *L) {
|
|
||||||
const char *line = lua_tostring(L, -1); /* original line */
|
|
||||||
const char *retline = lua_pushfstring(L, "return %s;", line);
|
|
||||||
int status = luaL_loadbuffer(L, retline, strlen(retline), "=stdin");
|
|
||||||
if (status == LUA_OK) {
|
|
||||||
lua_remove(L, -2); /* remove modified line */
|
|
||||||
if (line[0] != '\0') /* non empty? */
|
|
||||||
lua_saveline(L, line); /* keep history */
|
|
||||||
}
|
|
||||||
else
|
|
||||||
lua_pop(L, 2); /* pop result from 'luaL_loadbuffer' and modified line */
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
** Read multiple lines until a complete Lua statement
|
|
||||||
*/
|
|
||||||
static int multiline (lua_State *L) {
|
|
||||||
for (;;) { /* repeat until gets a complete statement */
|
|
||||||
size_t len;
|
|
||||||
const char *line = lua_tolstring(L, 1, &len); /* get what it has */
|
|
||||||
int status = luaL_loadbuffer(L, line, len, "=stdin"); /* try it */
|
|
||||||
if (!incomplete(L, status) || !pushline(L, 0)) {
|
|
||||||
lua_saveline(L, line); /* keep history */
|
|
||||||
return status; /* cannot or should not try to add continuation line */
|
|
||||||
}
|
|
||||||
lua_pushliteral(L, "\n"); /* add newline... */
|
|
||||||
lua_insert(L, -2); /* ...between the two lines */
|
|
||||||
lua_concat(L, 3); /* join them */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
** Read a line and try to load (compile) it first as an expression (by
|
|
||||||
** adding "return " in front of it) and second as a statement. Return
|
|
||||||
** the final status of load/call with the resulting function (if any)
|
|
||||||
** in the top of the stack.
|
|
||||||
*/
|
|
||||||
static int loadline (lua_State *L) {
|
|
||||||
int status;
|
|
||||||
lua_settop(L, 0);
|
|
||||||
if (!pushline(L, 1))
|
|
||||||
return -1; /* no input */
|
|
||||||
if ((status = addreturn(L)) != LUA_OK) /* 'return ...' did not work? */
|
|
||||||
status = multiline(L); /* try as command, maybe with continuation lines */
|
|
||||||
lua_remove(L, 1); /* remove line from the stack */
|
|
||||||
lua_assert(lua_gettop(L) == 1);
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
** Prints (calling the Lua 'print' function) any values on the stack
|
|
||||||
*/
|
|
||||||
static void l_print (lua_State *L) {
|
|
||||||
int n = lua_gettop(L);
|
|
||||||
if (n > 0) { /* any result to be printed? */
|
|
||||||
luaL_checkstack(L, LUA_MINSTACK, "too many results to print");
|
|
||||||
lua_getglobal(L, "print");
|
|
||||||
lua_insert(L, 1);
|
|
||||||
if (lua_pcall(L, n, 0, 0) != LUA_OK)
|
|
||||||
l_message(progname, lua_pushfstring(L, "error calling 'print' (%s)",
|
|
||||||
lua_tostring(L, -1)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
** Do the REPL: repeatedly read (load) a line, evaluate (call) it, and
|
|
||||||
** print any results.
|
|
||||||
*/
|
|
||||||
static void doREPL (lua_State *L) {
|
|
||||||
int status;
|
|
||||||
const char *oldprogname = progname;
|
|
||||||
progname = NULL; /* no 'progname' on errors in interactive mode */
|
|
||||||
lua_initreadline(L);
|
|
||||||
while ((status = loadline(L)) != -1) {
|
|
||||||
if (status == LUA_OK)
|
|
||||||
status = docall(L, 0, LUA_MULTRET);
|
|
||||||
if (status == LUA_OK) l_print(L);
|
|
||||||
else report(L, status);
|
|
||||||
}
|
|
||||||
lua_settop(L, 0); /* clear stack */
|
|
||||||
lua_writeline();
|
|
||||||
progname = oldprogname;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* }================================================================== */
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
** Main body of stand-alone interpreter (to be called in protected mode).
|
|
||||||
** Reads the options and handles them all.
|
|
||||||
*/
|
|
||||||
static int pmain (lua_State *L) {
|
|
||||||
int argc = (int)lua_tointeger(L, 1);
|
|
||||||
char **argv = (char **)lua_touserdata(L, 2);
|
|
||||||
int script;
|
|
||||||
int args = collectargs(argv, &script);
|
|
||||||
int optlim = (script > 0) ? script : argc; /* first argv not an option */
|
|
||||||
luaL_checkversion(L); /* check that interpreter has correct version */
|
|
||||||
if (args == has_error) { /* bad arg? */
|
|
||||||
print_usage(argv[script]); /* 'script' has index of bad arg. */
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (args & has_v) /* option '-v'? */
|
|
||||||
print_version();
|
|
||||||
if (args & has_E) { /* option '-E'? */
|
|
||||||
lua_pushboolean(L, 1); /* signal for libraries to ignore env. vars. */
|
|
||||||
lua_setfield(L, LUA_REGISTRYINDEX, "LUA_NOENV");
|
|
||||||
}
|
|
||||||
luaL_openlibs(L); /* open standard libraries */
|
|
||||||
createargtable(L, argv, argc, script); /* create table 'arg' */
|
|
||||||
lua_gc(L, LUA_GCRESTART); /* start GC... */
|
|
||||||
lua_gc(L, LUA_GCGEN, 0, 0); /* ...in generational mode */
|
|
||||||
if (!(args & has_E)) { /* no option '-E'? */
|
|
||||||
if (handle_luainit(L) != LUA_OK) /* run LUA_INIT */
|
|
||||||
return 0; /* error running LUA_INIT */
|
|
||||||
}
|
|
||||||
if (!runargs(L, argv, optlim)) /* execute arguments -e and -l */
|
|
||||||
return 0; /* something failed */
|
|
||||||
if (script > 0) { /* execute main script (if there is one) */
|
|
||||||
if (handle_script(L, argv + script) != LUA_OK)
|
|
||||||
return 0; /* interrupt in case of error */
|
|
||||||
}
|
|
||||||
if (args & has_i) /* -i option? */
|
|
||||||
doREPL(L); /* do read-eval-print loop */
|
|
||||||
else if (script < 1 && !(args & (has_e | has_v))) { /* no active option? */
|
|
||||||
if (lua_stdin_is_tty()) { /* running in interactive mode? */
|
|
||||||
print_version();
|
|
||||||
doREPL(L); /* do read-eval-print loop */
|
|
||||||
}
|
|
||||||
else dofile(L, NULL); /* executes stdin as a file */
|
|
||||||
}
|
|
||||||
lua_pushboolean(L, 1); /* signal no errors */
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int main (int argc, char **argv) {
|
|
||||||
int status, result;
|
|
||||||
lua_State *L = luaL_newstate(); /* create state */
|
|
||||||
if (L == NULL) {
|
|
||||||
l_message(argv[0], "cannot create state: not enough memory");
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
lua_gc(L, LUA_GCSTOP); /* stop GC while building state */
|
|
||||||
lua_pushcfunction(L, &pmain); /* to call 'pmain' in protected mode */
|
|
||||||
lua_pushinteger(L, argc); /* 1st argument */
|
|
||||||
lua_pushlightuserdata(L, argv); /* 2nd argument */
|
|
||||||
status = lua_pcall(L, 2, 1, 0); /* do the call */
|
|
||||||
result = lua_toboolean(L, -1); /* get result */
|
|
||||||
report(L, status);
|
|
||||||
lua_close(L);
|
|
||||||
return (result && status == LUA_OK) ? EXIT_SUCCESS : EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* LUA_MAKE_LUA */
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
MIT License
|
MIT License
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
#ifndef STATE_H
|
#ifndef STATE_H
|
||||||
#define STATE_H
|
#define STATE_H
|
||||||
|
|
||||||
#include <lua.h>
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
typedef struct State {
|
typedef struct State {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#!/bin/env sh
|
#!/bin/env sh
|
||||||
# single header api generator with clang
|
# single header api generator with clang
|
||||||
|
|
||||||
set +e
|
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
|
||||||
|
8
bin/prep-embed.sh
Normal file
8
bin/prep-embed.sh
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/env sh
|
||||||
|
set -e
|
||||||
|
# packages embedded resources with objcopy, so that it is more portable
|
||||||
|
# ld.lld on windows doesn't recognize --format binary, sadly
|
||||||
|
|
||||||
|
objdump=$(objdump -i)
|
||||||
|
bdfname=$(echo "$objdump" | sed -n 2p)
|
||||||
|
objcopy -I binary -O "$bdfname" share/assets/Dernyns256.ttf "$CMAKE_CURRENT_BINARY_DIR/font.o"
|
31
bin/twn
31
bin/twn
@ -1,21 +1,34 @@
|
|||||||
#!/bin/env sh
|
#!/bin/env sh
|
||||||
# townengine tooling interface
|
# townengine tooling interface
|
||||||
|
|
||||||
set +e
|
set -e
|
||||||
|
|
||||||
exe="$(basename $PWD)"
|
exe="$(basename $PWD)"
|
||||||
toolpath="$(dirname -- "${BASH_SOURCE[0]}")"
|
toolpath="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
|
||||||
export TWNROOT=$(realpath "$toolpath"/../)
|
export TWNROOT=$(realpath "$toolpath"/../)
|
||||||
|
|
||||||
case "$1" in
|
case "$1" in
|
||||||
build ) "$toolpath"/twnbuild "${@:2}"
|
build ) "$toolpath"/twnbuild "${@:2}"
|
||||||
;;
|
;;
|
||||||
|
|
||||||
run ) $0 build && ./$exe "${@:2}"
|
run ) $0 build "${@:2}"
|
||||||
|
if [[ "$*" == *"--target=web"* ]]; then
|
||||||
|
if [ "$OS" = "Windows_NT" ]; then
|
||||||
|
explorer "http://0.0.0.0:8000/$exe.html" & python3 -m http.server
|
||||||
|
else
|
||||||
|
xdg-open "http://0.0.0.0:8000/$exe.html" & python3 -m http.server
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
./$exe
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
|
|
||||||
gdb ) unset DEBUGINFOD_URLS
|
gdb ) unset DEBUGINFOD_URLS
|
||||||
$0 build && gdb --se=libgame.so -ex run --args "$(basename $PWD)" "${@:2}"
|
$0 build --debug && gdb --se=libgame.so -ex run --args "$(basename $PWD)" --no-sanity-timer "${@:2}"
|
||||||
|
;;
|
||||||
|
|
||||||
|
init ) cp -r "$TWNROOT/apps/templates/$2" "$3"
|
||||||
|
ln -s "$TWNROOT/bin/twn" "$3/twn"
|
||||||
;;
|
;;
|
||||||
|
|
||||||
apitrace ) case "$2" in
|
apitrace ) case "$2" in
|
||||||
@ -37,6 +50,16 @@ case "$1" in
|
|||||||
api-gen ) "$toolpath"/gen_api_header.sh
|
api-gen ) "$toolpath"/gen_api_header.sh
|
||||||
;;
|
;;
|
||||||
|
|
||||||
|
wiki ) if [ "$OS" = "Windows_NT" ]; then
|
||||||
|
explorer "file://""$(cygpath -w "$(command realpath $TWNROOT/docs/wiki/index.html)")"
|
||||||
|
else
|
||||||
|
xdg-open "file://$TWNROOT/docs/wiki/index.html"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
devcompl ) (cd "$TWNROOT" && "$toolpath"/twnbuild "--build_dir=$TWNROOT/build" "${@:2}")
|
||||||
|
;;
|
||||||
|
|
||||||
* ) echo "Unknown command."
|
* ) echo "Unknown command."
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
56
bin/twnbuild
56
bin/twnbuild
@ -1,36 +1,64 @@
|
|||||||
#!/bin/env python3
|
#!/bin/env python3
|
||||||
|
|
||||||
from subprocess import getoutput, run
|
from subprocess import getoutput, run
|
||||||
|
from os import getcwd
|
||||||
from os.path import expandvars
|
from os.path import expandvars
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from sys import argv
|
from sys import argv
|
||||||
|
from functools import reduce
|
||||||
import tomllib
|
import tomllib
|
||||||
|
|
||||||
|
#TODO: support for default pack override
|
||||||
|
#TODO: automatic full rebuild on git head change (such as new commits)
|
||||||
|
|
||||||
has_ninja = getoutput("command -v ninja") != ""
|
has_ninja = getoutput("command -v ninja") != ""
|
||||||
has_clang = getoutput("command -v clang") != ""
|
has_clang = getoutput("command -v clang") != ""
|
||||||
|
|
||||||
#TODO: support for default pack override
|
target_web = "--target=web" in argv
|
||||||
|
|
||||||
|
#TODO: infer what "native" means for current env
|
||||||
|
build_dir_arg = reduce(lambda c, n: c if c.startswith("--build_dir=") else n, argv + [""], "")
|
||||||
|
build_dir = "build/web" if target_web else build_dir_arg.split("=")[1] if build_dir_arg else "build/native"
|
||||||
|
build_dir += "" if build_dir_arg else "/release" if "--release" in argv else "/debug"
|
||||||
|
|
||||||
|
cmake = ["emcmake", "cmake"] if target_web else ["cmake"]
|
||||||
|
# cmake configuration command
|
||||||
|
command = []
|
||||||
|
|
||||||
cmake = ["cmake"]
|
|
||||||
# check whether clang is around (it's just better)
|
# check whether clang is around (it's just better)
|
||||||
if has_clang:
|
if has_clang and not target_web:
|
||||||
cmake += ["-DCMAKE_C_COMPILER=clang"]
|
command += ["-DCMAKE_C_COMPILER=clang"]
|
||||||
# check whether ninja is around (you better start running)
|
# check whether ninja is around (you better start running)
|
||||||
if has_ninja:
|
if has_ninja:
|
||||||
cmake += ["-G", "Ninja"]
|
command += ["-G", "Ninja"]
|
||||||
cmake += ["-B", "build"]
|
|
||||||
# TODO: have it --fast instead, where separate --no-debug would mean stripping the debug info
|
command += ["-B", build_dir]
|
||||||
|
|
||||||
|
# TODO: have it --fast=1 instead, where separate --debug=0 would mean stripping the debug info
|
||||||
if "--release" in argv:
|
if "--release" in argv:
|
||||||
cmake += ["-DCMAKE_BUILD_TYPE=Release"]
|
command += ["-DCMAKE_BUILD_TYPE=Release"]
|
||||||
|
elif "--debug" in argv:
|
||||||
|
command += ["-DCMAKE_BUILD_TYPE=Debug"]
|
||||||
|
|
||||||
|
if "--unified=1" in argv:
|
||||||
|
command += ["-DTWN_FEATURE_DYNLIB_GAME=ON"]
|
||||||
|
elif "--unified=0" in argv:
|
||||||
|
command += ["-DTWN_FEATURE_DYNLIB_GAME=OFF"]
|
||||||
|
|
||||||
|
if "--sanitize=1" in argv:
|
||||||
|
command += ["-DTWN_SANITIZE=ON"]
|
||||||
|
elif "--sanitize=0" in argv:
|
||||||
|
command += ["-DTWN_SANITIZE=OFF"]
|
||||||
|
|
||||||
|
command += [f"-DTWN_OUT_DIR={getcwd()}"]
|
||||||
# pass arbitrary arguments over
|
# pass arbitrary arguments over
|
||||||
if "--" in argv:
|
if "--" in argv:
|
||||||
cmake += argv[argv.find("--"):]
|
command += argv[argv.index("--")+1:]
|
||||||
|
|
||||||
# if no root cmake file is present, infer it from `twn.toml:game.interpreter`
|
# if no root cmake file is present, infer it from `twn.toml:game.interpreter`
|
||||||
if not Path("CMakeLists.txt").is_file():
|
if not Path("CMakeLists.txt").is_file():
|
||||||
with open("data/twn.toml", "rb") as f:
|
config = tomllib.loads(Path("data/twn.toml").read_text())
|
||||||
config = tomllib.load(f)
|
command += ["-S", expandvars(config["game"]["interpreter"])]
|
||||||
cmake += ["-S", expandvars(config["game"]["interpreter"])]
|
|
||||||
|
|
||||||
run(cmake, check=True)
|
run(cmake + command, check=True)
|
||||||
run(["cmake", "--build", "build", "--parallel"], check=True)
|
run(["cmake"] + ["--build", build_dir, "--parallel"], check=True)
|
||||||
|
BIN
data/assets/dirt/1.png
(Stored with Git LFS)
Normal file
BIN
data/assets/dirt/1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
data/assets/dirt/2.png
(Stored with Git LFS)
Normal file
BIN
data/assets/dirt/2.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
data/assets/grass2.png
(Stored with Git LFS)
Normal file
BIN
data/assets/grass2.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
data/assets/grasses/25.png
(Stored with Git LFS)
Normal file
BIN
data/assets/grasses/25.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
data/assets/trreez.png
(Stored with Git LFS)
Normal file
BIN
data/assets/trreez.png
(Stored with Git LFS)
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user