Compare commits
425 Commits
9c01264fd0
...
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 | |||
2286cdefeb | |||
00ada15dbc | |||
96b6b7e70b | |||
ccfdfd8a35 | |||
7284bb726a | |||
87b33c2e0c | |||
732a3579b0 | |||
2f629433aa | |||
6d58e964bc | |||
0014458dbb | |||
9112630330 | |||
108810d68a | |||
2c94efb796 | |||
11ec35bc8a | |||
2120f6876c | |||
dfd888a80a | |||
695784301d | |||
98d19495a2 | |||
0929aa9be4 | |||
898c11bbdf | |||
aeabb17f86 | |||
24e8dc052d | |||
3f264ca0ad | |||
a7557fceb4 | |||
bd89c4b938 | |||
7074e7499a | |||
2e29cfcfe2 | |||
74d7190c62 | |||
4b2a22bf3c | |||
630c6fb5d4 | |||
d7c744a6ca | |||
f8d7aa8a07 | |||
ea2bbf5de0 | |||
637343db5b | |||
fedf1b5ef3 | |||
732b61e207 | |||
6cb166522e | |||
916e433753 | |||
458b44d0b0 | |||
8de4a1f09b | |||
ac93d114c9 | |||
37b0f4e3a6 | |||
fad11041bc | |||
bbd654a569 | |||
7c33107585 | |||
f625dde8d1 | |||
166ae43981 | |||
6ef3cf1a3a | |||
d84e5f8610 | |||
6a87119c70 | |||
791ab628ca | |||
20394eed6c | |||
a54657e7be | |||
3bdee30e7b | |||
0e4abaae3c | |||
cf8227d7d3 | |||
f365cff590 | |||
77ff5c7f25 | |||
bf3eb50b55 | |||
6a029b7e79 | |||
fad46137a0 | |||
7a403c766e | |||
b0e718876a | |||
785b507330 | |||
d884a9026f | |||
9ddc5c4a66 | |||
16bd49b42e | |||
dd158dee01 | |||
ab3c032313 | |||
34a3de73c6 | |||
597168c282 | |||
37cd8cf2cf | |||
cb5f207761 | |||
6e421543c4 | |||
c97d9b2568 | |||
e281ba593c | |||
a20be2c523 | |||
b20e7202fe | |||
045d2764fa | |||
53917b05b7 | |||
0dc0a18019 | |||
2df5616410 | |||
3f9906a918 | |||
8a5d639f95 | |||
40aef0a1f9 | |||
2de536210b | |||
449d4d3c32 | |||
8233f31269 | |||
45b8b21ec3 | |||
4659cf2aef | |||
db530ca3a0 | |||
f0dfd5627a | |||
0da1e413aa | |||
8c165974c7 | |||
1ba33bdc26 | |||
760515c551 | |||
9d0a2cab81 | |||
d5b42fa242 | |||
851ab80292 | |||
688d71953a | |||
63abf3d374 | |||
80c77424e2 | |||
82d4f21a4b | |||
3990f78a74 | |||
f0ad9b9a8a | |||
ea0af5159f | |||
5059802d09 | |||
b7cb37c06a | |||
664f123a85 | |||
2351d4114c | |||
86bf16b680 | |||
dbe6217e24 | |||
b037d7a0b9 | |||
e984e95fa8 | |||
4ed3764c1d | |||
6d19d2d819 | |||
5bce3e5238 | |||
6298394957 | |||
eefd53a630 | |||
87ae1a7312 | |||
3052bb693a | |||
5f6c8dd8e6 | |||
c694dfff82 | |||
b6ca9bedb4 | |||
8d67e44009 | |||
192907a0db | |||
e8b02570a2 | |||
46e077ba63 | |||
41d0e24780 | |||
777a06a002 | |||
313108092b | |||
83e2dc5468 | |||
951d9c76c8 | |||
f3848d2d52 | |||
8c401eda75 | |||
5c89c55b3e | |||
5b05386bb0 | |||
b0549612a9 | |||
6463ac3dd7 | |||
e914cad0dd | |||
a9d9936cb7 | |||
4dd028aeae | |||
d7a119a592 | |||
cb6c1df0be | |||
3bfa86066e | |||
7c0bf39f12 | |||
4f2b8ccd01 | |||
472a0657f3 | |||
0f368e2700 | |||
33471b4c46 | |||
edcb7fc39c | |||
f9a8448782 | |||
6d5732cc2b | |||
62d738cbbe | |||
f4a3298906 | |||
8ec5a96333 | |||
4277852fc5 | |||
dc2535358e | |||
190eb1f107 | |||
e06c879869 | |||
e7ed72dfc0 | |||
c4c097f050 | |||
1d34c91106 | |||
0d81236331 | |||
f9bb6412b7 | |||
b18f6f1d87 | |||
2f94e17852 | |||
26a2bf293f | |||
4b0d584b7e | |||
19215d5795 | |||
1c97053675 | |||
ee7fc42fbc | |||
cd9c65212d | |||
ccaef34d61 | |||
833f7dbc53 | |||
a7feb7b61b | |||
4be27816c2 | |||
d794ca862f | |||
26c75ffd7c | |||
e4da4a8b7f | |||
963d549eed | |||
9121da0675 | |||
6464d14b3e | |||
eff9fe6918 | |||
d11143ac86 | |||
1d35a3859b | |||
9da26638c8 | |||
a22bcfd97e | |||
a527036436 | |||
b390e9db23 | |||
5a08c01208 | |||
eff2d9c5e1 | |||
8aecc2bd06 | |||
1296d41ad7 | |||
48f63fc9df | |||
c49789f1f4 | |||
a7b09b9f39 | |||
399b199266 | |||
73b6ab047d | |||
024f17de91 | |||
92de2c00c0 | |||
b683594013 | |||
7e409fc14a | |||
aa3cab87d2 | |||
1dc0dea762 | |||
7f56ed8421 | |||
119b706638 | |||
f2bbc1863e | |||
768daf1f54 | |||
139394c6de | |||
446402c2e0 | |||
f7a718003e | |||
f087bf1f7f | |||
19bf88d44e | |||
3535a185df | |||
d34516c4ee | |||
b295c5920c | |||
f7f27119e1 | |||
ffab6a3924 | |||
82bad550e5 | |||
19b9812b3e | |||
c8a65f2894 | |||
f0d3f6778c | |||
da98c0941b | |||
d884cd45d9 | |||
d2422735e6 | |||
ed93072371 | |||
9329d3c2be | |||
ef5d609f4a | |||
64433cbe18 | |||
f96d521af2 | |||
1a7322dccf | |||
e70366f82f | |||
7886650339 | |||
3a57833ac1 | |||
667b599c19 | |||
cfc9ac9583 | |||
b566cf20b5 | |||
4ac87b3021 |
15
.editorconfig
Normal file
15
.editorconfig
Normal file
@ -0,0 +1,15 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{c,h,py}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[CMakeListst.txt]
|
||||
indent_style = space
|
||||
indent_size = 8
|
2
.gitattributes
vendored
2
.gitattributes
vendored
@ -2,3 +2,5 @@
|
||||
*.ogg filter=lfs diff=lfs merge=lfs -text
|
||||
*.xm filter=lfs diff=lfs merge=lfs -text
|
||||
*.tga filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
text=auto eol=lf
|
||||
|
8
.gitignore
vendored
8
.gitignore
vendored
@ -2,13 +2,17 @@
|
||||
*
|
||||
!*.*
|
||||
!*/
|
||||
!bin/*
|
||||
!Makefile
|
||||
!LICENSE
|
||||
!COPYING
|
||||
|
||||
**/*.exe
|
||||
**/*.html
|
||||
**/*.js
|
||||
**/*.wasm
|
||||
**/*.wasm.map
|
||||
**/*.data
|
||||
**/*.so
|
||||
**/*.dll
|
||||
**/*.7z
|
||||
@ -22,8 +26,8 @@
|
||||
.vscode/
|
||||
.idea/
|
||||
.cache/
|
||||
.build/
|
||||
.build-web/
|
||||
build/
|
||||
build-web/
|
||||
build/
|
||||
out/
|
||||
|
||||
|
230
CMakeLists.txt
230
CMakeLists.txt
@ -2,6 +2,11 @@ cmake_minimum_required(VERSION 3.21)
|
||||
|
||||
project(townengine LANGUAGES C)
|
||||
|
||||
set(CMAKE_MESSAGE_LOG_LEVEL "WARNING")
|
||||
set(CMAKE_INSTALL_MESSAGE NEVER)
|
||||
|
||||
# TODO: test whether webgl 1 is good enough.
|
||||
|
||||
# SDL dependencies
|
||||
# for whatever reason Emscripten has SDL2 config, but not actual SDL2 port by default
|
||||
if(NOT EMSCRIPTEN)
|
||||
@ -9,11 +14,11 @@ if(NOT EMSCRIPTEN)
|
||||
endif()
|
||||
|
||||
# CMake actually has no default configuration and it's toolchain file dependent, set debug if not specified
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
if(NOT DEFINED CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Debug)
|
||||
endif()
|
||||
|
||||
if(NOT TWN_SANITIZE AND CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
if(NOT DEFINED TWN_SANITIZE AND CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
set(TWN_SANITIZE ON)
|
||||
endif()
|
||||
|
||||
@ -23,17 +28,19 @@ set(TWN_TARGET townengine CACHE INTERNAL "")
|
||||
set(TWN_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "")
|
||||
|
||||
# feature configuration, set them with -DFEATURE=ON/OFF in cli
|
||||
option(TWN_FEATURE_DYNLIB_GAME "Enable dynamic library loading support" ON)
|
||||
option(TWN_USE_AMALGAM "Enable use of twn_amalgam.c as a single compilation unit" ON)
|
||||
option(TWN_FEATURE_DYNLIB_GAME "Enable dynamic library loading support" ON)
|
||||
set(TWN_OUT_DIR ${CMAKE_SOURCE_DIR} CACHE PATH "Artifact destination")
|
||||
|
||||
# todo: figure out how to compile for dynamic linking instead
|
||||
if(EMSCRIPTEN)
|
||||
# todo: figure out how to compile for dynamic linking instead?
|
||||
if(HAIKU OR EMSCRIPTEN)
|
||||
if(TWN_FEATURE_DYNLIB_GAME)
|
||||
message(WARNING "TWN_FEATURE_DYNLIB_GAME is set, but not supported - it is turned off")
|
||||
set(TWN_FEATURE_DYNLIB_GAME OFF CACHE INTERNAL "")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(HAIKU)
|
||||
if(HAIKU OR EMSCRIPTEN)
|
||||
if(TWN_SANITIZE)
|
||||
message(WARNING "TWN_SANITIZE is set, but not supported - it is turned off")
|
||||
set(TWN_SANITIZE OFF CACHE INTERNAL "")
|
||||
@ -41,45 +48,30 @@ if(HAIKU)
|
||||
endif()
|
||||
|
||||
# add -fPIC globally so that it's linked well
|
||||
add_compile_options($<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:-fPIC>)
|
||||
add_compile_options($<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:-fPIC>
|
||||
-fvisibility=hidden)
|
||||
|
||||
set(PHYSFS_BUILD_SHARED FALSE)
|
||||
set(PHYSFS_DISABLE_INSTALL TRUE)
|
||||
set(PHYSFS_TARGETNAME_UNINSTALL "physfs_uninstall")
|
||||
set(PHYSFS_ARCHIVE_GRP OFF)
|
||||
set(PHYSFS_ARCHIVE_WAD OFF)
|
||||
set(PHYSFS_ARCHIVE_HOG OFF)
|
||||
set(PHYSFS_ARCHIVE_MVL OFF)
|
||||
set(PHYSFS_ARCHIVE_QPAK OFF)
|
||||
set(PHYSFS_ARCHIVE_SLB OFF)
|
||||
set(PHYSFS_ARCHIVE_ISO9660 OFF)
|
||||
set(PHYSFS_ARCHIVE_VDF OFF)
|
||||
set(PHYSFS_BUILD_SHARED FALSE CACHE INTERNAL "")
|
||||
set(PHYSFS_DISABLE_INSTALL TRUE CACHE INTERNAL "")
|
||||
set(PHYSFS_TARGETNAME_UNINSTALL "physfs_uninstall" CACHE INTERNAL "")
|
||||
set(PHYSFS_ARCHIVE_GRP OFF CACHE BOOL "")
|
||||
set(PHYSFS_ARCHIVE_WAD OFF CACHE INTERNAL "")
|
||||
set(PHYSFS_ARCHIVE_HOG OFF CACHE INTERNAL "")
|
||||
set(PHYSFS_ARCHIVE_MVL OFF CACHE INTERNAL "")
|
||||
set(PHYSFS_ARCHIVE_QPAK OFF CACHE INTERNAL "")
|
||||
set(PHYSFS_ARCHIVE_SLB OFF CACHE INTERNAL "")
|
||||
set(PHYSFS_ARCHIVE_ISO9660 OFF CACHE INTERNAL "")
|
||||
set(PHYSFS_ARCHIVE_VDF OFF CACHE INTERNAL "")
|
||||
set(PHYSFS_ARCHIVE_7Z OFF CACHE INTERNAL "")
|
||||
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)
|
||||
|
||||
|
||||
if(LINUX)
|
||||
set(SYSTEM_SOURCE_FILES
|
||||
src/system/linux/twn_elf.c
|
||||
$<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:src/game_object/twn_linux_game_object.c>)
|
||||
elseif(WIN32)
|
||||
set(SYSTEM_SOURCE_FILES
|
||||
$<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:src/game_object/twn_win32_game_object.c>)
|
||||
else()
|
||||
set(SYSTEM_SOURCE_FILES)
|
||||
endif()
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
set(TWN_RENDERING_API WEBGL1)
|
||||
else()
|
||||
set(TWN_RENDERING_API OPENGL_15)
|
||||
endif()
|
||||
set(TWN_RENDERING_API OPENGL_15)
|
||||
|
||||
if(TWN_RENDERING_API MATCHES OPENGL_15)
|
||||
set(SYSTEM_SOURCE_FILES ${SYSTEM_SOURCE_FILES}
|
||||
src/rendering/twn_gl_any_rendering.c
|
||||
src/rendering/twn_gl_15_rendering.c
|
||||
src/rendering/twn_gl_15_gpu_texture.c)
|
||||
src/rendering/twn_gl_15_rendering.c)
|
||||
endif()
|
||||
|
||||
set(TWN_THIRD_PARTY_SOURCE_FILES
|
||||
@ -88,31 +80,47 @@ set(TWN_THIRD_PARTY_SOURCE_FILES
|
||||
third-party/tomlc99/toml.c
|
||||
$<$<NOT:$<BOOL:${EMSCRIPTEN}>>:third-party/glad/src/glad.c>)
|
||||
|
||||
set(TWN_SOURCE_FILES
|
||||
src/twn_loop.c
|
||||
src/twn_main.c
|
||||
src/twn_context.c include/twn_context.h
|
||||
src/twn_audio.c include/twn_audio.h
|
||||
src/twn_util.c include/twn_util.h
|
||||
src/twn_input.c include/twn_input.h
|
||||
src/twn_camera.c include/twn_camera.h
|
||||
src/twn_textures.c src/twn_textures_c.h
|
||||
set(TWN_NONOPT_SOURCE_FILES
|
||||
src/twn_stb.c
|
||||
|
||||
src/twn_context.c include/twn_context.h
|
||||
src/twn_audio.c include/twn_audio.h
|
||||
src/twn_util.c include/twn_util.h
|
||||
src/twn_input.c include/twn_input.h
|
||||
|
||||
src/twn_loop.c src/twn_loop_c.h
|
||||
src/twn_camera.c src/twn_camera_c.h
|
||||
src/twn_textures.c src/twn_textures_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_workers.c src/twn_workers_c.h
|
||||
|
||||
src/rendering/twn_draw.c src/rendering/twn_draw_c.h
|
||||
src/rendering/twn_quads.c
|
||||
src/rendering/twn_sprites.c
|
||||
src/rendering/twn_rects.c
|
||||
src/rendering/twn_text.c
|
||||
src/rendering/twn_triangles.c
|
||||
src/rendering/twn_billboards.c
|
||||
src/rendering/twn_circles.c
|
||||
src/rendering/twn_skybox.c
|
||||
src/rendering/twn_fog.c
|
||||
src/rendering/twn_models.c
|
||||
src/rendering/twn_lines.c
|
||||
)
|
||||
|
||||
set(TWN_SOURCE_FILES
|
||||
$<IF:$<BOOL:${TWN_USE_AMALGAM}>,src/twn_amalgam.c src/twn_stb.c,${TWN_NONOPT_SOURCE_FILES}>
|
||||
|
||||
# for dynamic load based solution main is compiled in a separate target
|
||||
$<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:src/game_object/twn_dynamic_game_object.c>
|
||||
$<$<NOT:$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>>:src/twn_main.c
|
||||
src/game_object/twn_static_game_object.c>
|
||||
|
||||
${SYSTEM_SOURCE_FILES})
|
||||
|
||||
list(TRANSFORM TWN_SOURCE_FILES PREPEND ${TWN_ROOT_DIR}/)
|
||||
source_group(TREE ${TWN_ROOT_DIR} FILES ${TWN_NONOPT_SOURCE_FILES})
|
||||
|
||||
add_library(twn_third_parties STATIC ${TWN_THIRD_PARTY_SOURCE_FILES})
|
||||
|
||||
@ -123,8 +131,6 @@ else()
|
||||
add_library(${TWN_TARGET} STATIC ${TWN_SOURCE_FILES} ${twn_third_parties})
|
||||
endif()
|
||||
|
||||
source_group(TREE ${TWN_ROOT_DIR} FILES ${TWN_SOURCE_FILES})
|
||||
|
||||
set_target_properties(${TWN_TARGET} PROPERTIES
|
||||
C_STANDARD 11
|
||||
C_STANDARD_REQUIRED ON
|
||||
@ -133,7 +139,8 @@ set_target_properties(${TWN_TARGET} PROPERTIES
|
||||
# precompile commonly used not-so-small headers
|
||||
target_precompile_headers(${TWN_TARGET} PRIVATE
|
||||
$<$<NOT:$<BOOL:${EMSCRIPTEN}>>:third-party/glad/include/glad/glad.h>
|
||||
third-party/stb/stb_ds.h)
|
||||
$<$<NOT:$<BOOL:${EMSCRIPTEN}>>:${SDL2_INCLUDE_DIR}/SDL.h>
|
||||
third-party/physfs/src/physfs.h)
|
||||
|
||||
|
||||
function(give_options_without_warnings target)
|
||||
@ -144,44 +151,77 @@ function(give_options_without_warnings target)
|
||||
-fno-signed-zeros
|
||||
-fno-trapping-math
|
||||
-freciprocal-math
|
||||
# TODO: require packaging for web case
|
||||
$<$<BOOL:${EMSCRIPTEN}>:-sUSE_SDL=2>)
|
||||
|
||||
set(BUILD_FLAGS_RELEASE
|
||||
-O3
|
||||
-flto=auto
|
||||
-mavx -mavx2
|
||||
-Wl,--gc-sections
|
||||
-fdata-sections
|
||||
-ffunction-sections
|
||||
-flto=$<IF:$<STREQUAL:${CMAKE_C_COMPILER_ID},Clang>,thin,auto>
|
||||
$<$<STREQUAL:${CMAKE_SYSTEM_PROCESSOR},AMD64>:-sse2 -mavx -mavx2>
|
||||
$<$<BOOL:${EMSCRIPTEN}>:-msimd128 -mrelaxed-simd>
|
||||
-funroll-loops
|
||||
-fomit-frame-pointer
|
||||
-s)
|
||||
$<$<STREQUAL:${CMAKE_C_COMPILER_ID},Gnu>:-s>)
|
||||
|
||||
set(BUILD_FLAGS_DEBUG
|
||||
-O0
|
||||
-g3
|
||||
-gdwarf
|
||||
-fno-omit-frame-pointer
|
||||
$<$<BOOL:${TWN_SANITIZE}>:-fstack-protector-all -fsanitize=undefined -fsanitize=address>
|
||||
$<$<BOOL:${EMSCRIPTEN}>:-gsource-map>)
|
||||
$<$<BOOL:${TWN_SANITIZE}>:-fstack-protector-all -fsanitize=undefined -fsanitize=address>)
|
||||
|
||||
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)
|
||||
set(THINLTO_USAGE "-plugin-opt,")
|
||||
endif()
|
||||
if (CMAKE_C_COMPILER_LINKER_ID MATCHES LLD)
|
||||
set(THINLTO_USAGE "--thinlto-")
|
||||
endif()
|
||||
|
||||
if (THINLTO_USAGE)
|
||||
set(BUILD_SHARED_LIBRARY_FLAGS_RELEASE
|
||||
$<$<STREQUAL:${CMAKE_C_COMPILER_ID},Clang>:-Wl,${THINLTO_USAGE}cache-dir=${CMAKE_CURRENT_BINARY_DIR}/linker-cache/>
|
||||
$<$<STREQUAL:${CMAKE_C_COMPILER_ID},Clang>:-Wl,${THINLTO_USAGE}cache-policy=prune_after=30m>)
|
||||
endif()
|
||||
|
||||
target_compile_options(${target} PUBLIC
|
||||
${BUILD_FLAGS}
|
||||
$<$<CONFIG:Release>:${BUILD_FLAGS_RELEASE}>
|
||||
$<$<CONFIG:Debug>:${BUILD_FLAGS_DEBUG}>
|
||||
$<$<BOOL:${LINUX}>:-Wl,-rpath,$ORIGIN/>)
|
||||
$<$<CONFIG:Debug>:${BUILD_FLAGS_DEBUG}>)
|
||||
|
||||
target_link_options(${target} PUBLIC
|
||||
${BUILD_FLAGS}
|
||||
# -Wl,--no-undefined # TODO: use later for implementing no-libc
|
||||
$<$<CONFIG:Release>:${BUILD_FLAGS_RELEASE}>
|
||||
$<$<CONFIG:Debug>:${BUILD_FLAGS_DEBUG}>
|
||||
$<$<BOOL:${LINUX}>:-Wl,-rpath,$ORIGIN/>
|
||||
-Bsymbolic-functions
|
||||
-Wl,--hash-style=gnu)
|
||||
${LINK_FLAGS}
|
||||
$<$<CONFIG:Release>:${LINK_FLAGS_RELEASE}>
|
||||
$<$<CONFIG:Debug>:${BUILD_FLAGS_DEBUG}>)
|
||||
|
||||
get_target_property(target_type ${target} TYPE)
|
||||
if (target_type MATCHES SHARED_LIBRARY)
|
||||
target_compile_options(${target} PUBLIC
|
||||
$<$<CONFIG:Release>:${BUILD_SHARED_LIBRARY_FLAGS_RELEASE}>)
|
||||
|
||||
target_link_options(${target} PUBLIC
|
||||
$<$<CONFIG:Release>:${BUILD_SHARED_LIBRARY_FLAGS_RELEASE}>)
|
||||
elseif(CMAKE_BUILD_TYPE MATCHES Release)
|
||||
target_compile_options(${target} PUBLIC
|
||||
$<$<STREQUAL:${CMAKE_C_COMPILER_ID},Clang>:-mllvm=--enable-gvn-hoist>)
|
||||
endif()
|
||||
|
||||
target_compile_definitions(${target} PRIVATE
|
||||
$<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:TWN_FEATURE_DYNLIB_GAME>)
|
||||
$<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:TWN_FEATURE_DYNLIB_GAME>
|
||||
_GNU_SOURCE)
|
||||
endfunction()
|
||||
|
||||
|
||||
@ -193,7 +233,9 @@ function(give_options target)
|
||||
-Wno-padded
|
||||
-Wno-declaration-after-statement
|
||||
-Wno-unsafe-buffer-usage
|
||||
-Wno-unused-command-line-argument)
|
||||
-Wno-unused-command-line-argument
|
||||
-Wno-covered-switch-default
|
||||
-Wno-disabled-macro-expansion)
|
||||
|
||||
set(WARNING_FLAGS
|
||||
-Wall
|
||||
@ -205,6 +247,7 @@ function(give_options target)
|
||||
-Werror=vla
|
||||
-Wno-missing-field-initializers
|
||||
-Wunused-result
|
||||
-Wno-pre-c11-compat
|
||||
$<$<STREQUAL:${CMAKE_C_COMPILER_ID},Gnu>:-Wcast-align=strict>)
|
||||
|
||||
target_compile_options(${target} PRIVATE
|
||||
@ -221,8 +264,9 @@ function(include_deps target)
|
||||
third-party/physfs/extras
|
||||
third-party/libxm/include
|
||||
third-party/stb
|
||||
third-party/x-watcher
|
||||
third-party/dmon
|
||||
third-party/tomlc99
|
||||
third-party/fast_obj
|
||||
$<$<NOT:$<BOOL:${EMSCRIPTEN}>>:third-party/glad/include>)
|
||||
|
||||
list(TRANSFORM THIRD_PARTY_INCLUDES PREPEND ${TWN_ROOT_DIR}/)
|
||||
@ -236,19 +280,40 @@ endfunction()
|
||||
function(link_deps target)
|
||||
target_link_libraries(${target} PUBLIC
|
||||
$<$<NOT:$<BOOL:${EMSCRIPTEN}>>:SDL2::SDL2>
|
||||
$<$<NOT:$<OR:$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>,$<BOOL:${EMSCRIPTEN}>>>:SDL2::SDL2main>
|
||||
physfs-static
|
||||
xms)
|
||||
target_include_directories(${target} PUBLIC ${SDL2_INCLUDE_DIRS})
|
||||
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)
|
||||
# game shared library, for reloading
|
||||
add_library(${target}_game SHARED ${sources})
|
||||
give_options(${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
|
||||
OUTPUT_NAME game
|
||||
LIBRARY_OUTPUT_DIRECTORY ${output_directory}
|
||||
@ -257,6 +322,8 @@ function(use_townengine target sources output_directory)
|
||||
# launcher binary, loads game and engine shared library
|
||||
add_executable(${target} ${TWN_ROOT_DIR}/src/twn_main.c)
|
||||
|
||||
target_link_options(${target} PRIVATE $<$<BOOL:${LINUX}>:-Wl,-rpath,$ORIGIN/>)
|
||||
|
||||
# todo: copy instead?
|
||||
# put libtownengine.so alongside the binary
|
||||
set_target_properties(${TWN_TARGET} PROPERTIES
|
||||
@ -267,6 +334,7 @@ function(use_townengine target sources output_directory)
|
||||
endif()
|
||||
|
||||
set_target_properties(${target} PROPERTIES
|
||||
OUTPUT_NAME ${target}
|
||||
LIBRARY_OUTPUT_DIRECTORY ${output_directory}
|
||||
RUNTIME_OUTPUT_DIRECTORY ${output_directory})
|
||||
|
||||
@ -275,7 +343,7 @@ function(use_townengine target sources output_directory)
|
||||
endif()
|
||||
|
||||
target_compile_options(${target} PRIVATE
|
||||
$<$<BOOL:${EMSCRIPTEN}>:--shell-file ${TWN_ROOT_DIR}/shell_minimal.html>)
|
||||
$<$<BOOL:${EMSCRIPTEN}>:--shell-file ${TWN_ROOT_DIR}/src/shell_minimal.html>)
|
||||
|
||||
# system libraries
|
||||
find_library(MATH_LIBRARY m)
|
||||
@ -304,10 +372,18 @@ link_deps(twn_third_parties)
|
||||
give_options(${TWN_TARGET})
|
||||
include_deps(${TWN_TARGET})
|
||||
link_deps(${TWN_TARGET})
|
||||
target_link_libraries(${TWN_TARGET} PUBLIC twn_third_parties)
|
||||
target_include_directories(${TWN_TARGET} PRIVATE ${TWN_ROOT_DIR}/src)
|
||||
target_link_libraries(${TWN_TARGET} PUBLIC
|
||||
twn_third_parties
|
||||
${CMAKE_CURRENT_BINARY_DIR}/font.o)
|
||||
|
||||
# move compie_commands.json into root directory so that it plays nicer with code editors without any configuration
|
||||
add_custom_target(copy-compile-commands ALL
|
||||
${CMAKE_COMMAND} -E copy_if_different ${CMAKE_BINARY_DIR}/compile_commands.json
|
||||
${TWN_ROOT_DIR})
|
||||
# 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)
|
||||
endif()
|
||||
|
||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNBUILDDIR})
|
||||
add_subdirectory($ENV{TWNROOT} ${CMAKE_BINARY_DIR}/twn)
|
||||
|
||||
set(SOURCE_FILES
|
||||
game.c
|
||||
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]]
|
||||
source = "../../../common-data"
|
||||
source = "../../../data"
|
||||
name = "common-data"
|
||||
|
@ -5,7 +5,6 @@ app_id = "bunnymark"
|
||||
dev_id = "morshy"
|
||||
|
||||
[game]
|
||||
base_render_width = 640
|
||||
base_render_height = 480
|
||||
resolution = [ 640, 480 ]
|
||||
|
||||
[engine]
|
||||
|
@ -13,40 +13,44 @@
|
||||
#define RIGHT_CLICK_ADD 500
|
||||
|
||||
|
||||
void handle_input(void)
|
||||
static void handle_input(void)
|
||||
{
|
||||
State *state = ctx.udata;
|
||||
|
||||
if (ctx.mouse_window_position.y <= 60)
|
||||
if (ctx.mouse_position.y <= 60)
|
||||
return;
|
||||
|
||||
if (input_is_action_pressed("add_a_bit"))
|
||||
if (input_action_pressed("add_a_bit"))
|
||||
{ // Left click
|
||||
for (int i = 0; i < LEFT_CLICK_ADD; i++)
|
||||
{
|
||||
if (state->bunniesCount < MAX_BUNNIES)
|
||||
{
|
||||
state->bunnies[state->bunniesCount].position = input_get_action_position("add_a_bit");
|
||||
state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0;
|
||||
state->bunnies[state->bunniesCount].speed.y = (float)(rand() % 500 - 250) / 60.0;
|
||||
state->bunnies[state->bunniesCount].position = input_action_position("add_a_bit");
|
||||
state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0f;
|
||||
state->bunnies[state->bunniesCount].speed.y = (float)(rand() % 500 - 250) / 60.0f;
|
||||
state->bunnies[state->bunniesCount].color =
|
||||
(Color){rand() % 190 + 50, rand() % 160 + 80, rand() % 140 + 100, 255};
|
||||
(Color){(uint8_t)(state->bunniesCount % 190 + 50),
|
||||
(uint8_t)((state->bunniesCount + 120) % 160 + 80),
|
||||
(uint8_t)((state->bunniesCount + 65) % 140 + 100), 255};
|
||||
state->bunniesCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (input_is_action_pressed("add_a_lot"))
|
||||
if (input_action_pressed("add_a_lot"))
|
||||
{ // Right click
|
||||
for (int i = 0; i < RIGHT_CLICK_ADD; i++)
|
||||
{
|
||||
if (state->bunniesCount < MAX_BUNNIES)
|
||||
{
|
||||
state->bunnies[state->bunniesCount].position = input_get_action_position("add_a_lot");
|
||||
state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0;
|
||||
state->bunnies[state->bunniesCount].speed.y = (float)(rand() % 500 - 250) / 60.0;
|
||||
state->bunnies[state->bunniesCount].position = input_action_position("add_a_lot");
|
||||
state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0f;
|
||||
state->bunnies[state->bunniesCount].speed.y = (float)(rand() % 500 - 250) / 60.0f;
|
||||
state->bunnies[state->bunniesCount].color =
|
||||
(Color){rand() % 190 + 50, rand() % 160 + 80, rand() % 140 + 100, 255};
|
||||
(Color){(uint8_t)(state->bunniesCount % 190 + 50),
|
||||
(uint8_t)((state->bunniesCount + 120) % 160 + 80),
|
||||
(uint8_t)((state->bunniesCount + 65) % 140 + 100), 255};
|
||||
state->bunniesCount++;
|
||||
}
|
||||
}
|
||||
@ -62,43 +66,37 @@ void game_tick(void)
|
||||
{ // First tick, initalizing data
|
||||
// Allocating State struct to store data there
|
||||
if (!ctx.udata)
|
||||
ctx.udata = ccalloc(1, sizeof(State));
|
||||
|
||||
input_add_action("add_a_bit");
|
||||
input_bind_action_control("add_a_bit", CONTROL_LEFT_MOUSE);
|
||||
|
||||
input_add_action("add_a_lot");
|
||||
input_bind_action_control("add_a_lot", CONTROL_RIGHT_MOUSE);
|
||||
ctx.udata = calloc(1, sizeof(State));
|
||||
}
|
||||
|
||||
State *state = ctx.udata;
|
||||
input_action("add_a_bit", "LCLICK");
|
||||
input_action("add_a_lot", "RCLICK");
|
||||
|
||||
const double delta =
|
||||
(double)(ctx.delta_time) / 1000.0; // Receiving floating point delta value (diving by 1000 based on vibe)
|
||||
State *state = ctx.udata;
|
||||
|
||||
for (int i = 0; i < state->bunniesCount; i++)
|
||||
{
|
||||
state->bunnies[i].position.x += state->bunnies[i].speed.x;
|
||||
state->bunnies[i].position.y += state->bunnies[i].speed.y;
|
||||
|
||||
if (((state->bunnies[i].position.x + BUNNY_W / 2) > ctx.window_w) ||
|
||||
((state->bunnies[i].position.x + BUNNY_W / 2) < 0))
|
||||
if (((state->bunnies[i].position.x + (float)BUNNY_W / 2) > (float)ctx.resolution.x) ||
|
||||
((state->bunnies[i].position.x + (float)BUNNY_W / 2) < 0))
|
||||
state->bunnies[i].speed.x *= -1;
|
||||
if (((state->bunnies[i].position.y + BUNNY_H / 2) > ctx.window_h) ||
|
||||
((state->bunnies[i].position.y + BUNNY_H / 2 - 60) < 0))
|
||||
if (((state->bunnies[i].position.y + (float)BUNNY_H / 2) > (float)ctx.resolution.y) ||
|
||||
((state->bunnies[i].position.y + (float)BUNNY_H / 2 - 60) < 0))
|
||||
state->bunnies[i].speed.y *= -1;
|
||||
}
|
||||
|
||||
handle_input();
|
||||
|
||||
// Clear window with Gray color (set the background color this way)
|
||||
draw_rectangle((Rect){0, 0, ctx.base_draw_w, ctx.base_draw_h}, GRAY);
|
||||
draw_rectangle((Rect){0, 0, (float)ctx.resolution.x, (float)ctx.resolution.y}, GRAY);
|
||||
|
||||
for (int i = 0; i < state->bunniesCount; i++)
|
||||
{ // Draw each bunny based on their position and color, also scale accordingly
|
||||
m_sprite(m_set(path, "wabbit_alpha.png"),
|
||||
m_set(rect, ((Rect){.x = (int)state->bunnies[i].position.x,
|
||||
.y = (int)state->bunnies[i].position.y,
|
||||
m_sprite(m_set(texture, "wabbit_alpha.png"),
|
||||
m_set(rect, ((Rect){.x = state->bunnies[i].position.x,
|
||||
.y = state->bunnies[i].position.y,
|
||||
.w = BUNNY_W * SPRITE_SCALE,
|
||||
.h = BUNNY_H * SPRITE_SCALE})),
|
||||
m_opt(color, (state->bunnies[i].color)), m_opt(stretch, true), );
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
#include "twn_game_api.h"
|
||||
|
||||
#define MAX_BUNNIES 100000 // 100K bunnies limit
|
||||
#define MAX_BUNNIES 500000 // 100K bunnies limit
|
||||
#define BUNNY_W 26
|
||||
#define BUNNY_H 37
|
||||
#define SPRITE_SCALE 1
|
||||
|
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)
|
||||
endif()
|
||||
|
||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNBUILDDIR})
|
||||
add_subdirectory($ENV{TWNROOT} ${CMAKE_BINARY_DIR}/twn)
|
||||
|
||||
set(SOURCE_FILES
|
||||
game.c
|
||||
@ -20,4 +20,4 @@ set(SOURCE_FILES
|
||||
scenes/ingame.c scenes/ingame.h
|
||||
)
|
||||
|
||||
use_townengine(${PROJECT_NAME} "${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
use_townengine("${SOURCE_FILES}" ${TWN_OUT_DIR})
|
3
apps/demos/platformer/data/packs/data.toml
Normal file
3
apps/demos/platformer/data/packs/data.toml
Normal file
@ -0,0 +1,3 @@
|
||||
[[deps]]
|
||||
source = "../../../data" # where does it come from, might be an url
|
||||
name = "common-data" # should be globally unique
|
@ -5,7 +5,6 @@ app_id = "platformer-demo"
|
||||
dev_id = "townengine-team"
|
||||
|
||||
[game]
|
||||
base_render_width = 640
|
||||
base_render_height = 360
|
||||
resolution = [ 640, 360 ]
|
||||
|
||||
[engine]
|
48
apps/demos/platformer/game.c
Normal file
48
apps/demos/platformer/game.c
Normal file
@ -0,0 +1,48 @@
|
||||
#include "state.h"
|
||||
#include "scenes/scene.h"
|
||||
#include "scenes/title.h"
|
||||
|
||||
#include "twn_game_api.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <malloc.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
void game_tick(void) {
|
||||
if (ctx.initialization_needed) {
|
||||
if (!ctx.udata) {
|
||||
ctx.udata = calloc(1, sizeof (State));
|
||||
|
||||
State *state = ctx.udata;
|
||||
state->ctx = &ctx;
|
||||
state->scene = title_scene(state);
|
||||
}
|
||||
}
|
||||
|
||||
State *state = ctx.udata;
|
||||
|
||||
input_action("debug_toggle", "BACKSPACE");
|
||||
input_action("debug_dump_atlases", "HOME");
|
||||
|
||||
if (input_action_just_pressed("debug_toggle")) {
|
||||
ctx.debug = !ctx.debug;
|
||||
}
|
||||
|
||||
state->scene->tick(state);
|
||||
|
||||
/* there's a scene switch pending, we can do it now that the tick is done */
|
||||
if (state->next_scene != NULL) {
|
||||
state->scene->end(state);
|
||||
state->scene = state->next_scene;
|
||||
state->is_scene_switching = false;
|
||||
state->next_scene = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void game_end(void) {
|
||||
State *state = ctx.udata;
|
||||
state->scene->end(state);
|
||||
free(state);
|
||||
}
|
@ -11,28 +11,28 @@
|
||||
|
||||
|
||||
static void update_timers(Player *player) {
|
||||
tick_timer(&player->jump_air_timer);
|
||||
tick_timer(&player->jump_coyote_timer);
|
||||
tick_timer(&player->jump_buffer_timer);
|
||||
player->jump_air_timer = player->jump_air_timer - 1 <= 0 ? 0 : player->jump_air_timer - 1;
|
||||
player->jump_coyote_timer = player->jump_coyote_timer - 1 <= 0 ? 0 : player->jump_coyote_timer - 1;
|
||||
player->jump_buffer_timer = player->jump_buffer_timer - 1 <= 0 ? 0 : player->jump_buffer_timer - 1;
|
||||
}
|
||||
|
||||
|
||||
static void input_move(Player *player) {
|
||||
/* apply horizontal damping when the player stops moving */
|
||||
/* in other words, make it decelerate to a standstill */
|
||||
if (!input_is_action_pressed("player_left") &&
|
||||
!input_is_action_pressed("player_right"))
|
||||
if (!input_action_pressed("player_left") &&
|
||||
!input_action_pressed("player_right"))
|
||||
{
|
||||
player->dx *= player->horizontal_damping;
|
||||
}
|
||||
|
||||
int input_dir = 0;
|
||||
if (input_is_action_pressed("player_left"))
|
||||
if (input_action_pressed("player_left"))
|
||||
input_dir = -1;
|
||||
if (input_is_action_pressed("player_right"))
|
||||
if (input_action_pressed("player_right"))
|
||||
input_dir = 1;
|
||||
if (input_is_action_pressed("player_left") &&
|
||||
input_is_action_pressed("player_right"))
|
||||
if (input_action_pressed("player_left") &&
|
||||
input_action_pressed("player_right"))
|
||||
input_dir = 0;
|
||||
|
||||
player->dx += (float)input_dir * player->run_horizontal_speed;
|
||||
@ -56,7 +56,7 @@ static void jump(Player *player) {
|
||||
static void input_jump(Player *player) {
|
||||
player->current_gravity_multiplier = player->jump_default_multiplier;
|
||||
|
||||
if (input_is_action_just_pressed("player_jump")) {
|
||||
if (input_action_just_pressed("player_jump")) {
|
||||
player->jump_air_timer = 0;
|
||||
player->jump_buffer_timer = player->jump_buffer_ticks;
|
||||
|
||||
@ -65,7 +65,7 @@ static void input_jump(Player *player) {
|
||||
}
|
||||
}
|
||||
|
||||
if (input_is_action_pressed("player_jump")) {
|
||||
if (input_action_pressed("player_jump")) {
|
||||
if (player->action != PLAYER_ACTION_GROUND && player->jump_air_timer > 0) {
|
||||
player->current_gravity_multiplier = player->jump_boosted_multiplier;
|
||||
player->dy += player->jump_force_increase;
|
||||
@ -147,7 +147,7 @@ static bool corner_correct(Player *player, Rect collision) {
|
||||
|
||||
static void calc_collisions_x(Player *player) {
|
||||
Rect collision;
|
||||
bool is_colliding = world_find_intersect_frect(player->world, player->collider_x, &collision);
|
||||
bool is_colliding = world_find_rect_intersects(player->world, player->collider_x, &collision);
|
||||
if (!is_colliding) return;
|
||||
|
||||
float player_center_x = player->collider_x.x + (player->collider_x.w / 2);
|
||||
@ -164,7 +164,7 @@ static void calc_collisions_x(Player *player) {
|
||||
|
||||
static void calc_collisions_y(Player *player) {
|
||||
Rect collision;
|
||||
bool is_colliding = world_find_intersect_frect(player->world, player->collider_y, &collision);
|
||||
bool is_colliding = world_find_rect_intersects(player->world, player->collider_y, &collision);
|
||||
if (!is_colliding) return;
|
||||
|
||||
float player_center_y = player->collider_y.y + (player->collider_y.h / 2);
|
||||
@ -203,7 +203,7 @@ static void calc_collisions_y(Player *player) {
|
||||
|
||||
|
||||
Player *player_create(World *world) {
|
||||
Player *player = cmalloc(sizeof *player);
|
||||
Player *player = malloc(sizeof *player);
|
||||
|
||||
*player = (Player) {
|
||||
.world = world,
|
||||
@ -255,6 +255,10 @@ static void drawdef(Player *player) {
|
||||
draw_circle((Vec2) { 256, 128 },
|
||||
24,
|
||||
(Color) { 255, 0, 0, 255 });
|
||||
|
||||
draw_circle((Vec2) { 304, 128 },
|
||||
24,
|
||||
(Color) { 255, 0, 0, 255 });
|
||||
}
|
||||
|
||||
|
50
apps/demos/platformer/scenes/ingame.c
Normal file
50
apps/demos/platformer/scenes/ingame.c
Normal file
@ -0,0 +1,50 @@
|
||||
#include "ingame.h"
|
||||
#include "title.h"
|
||||
#include "scene.h"
|
||||
|
||||
#include "twn_game_api.h"
|
||||
#include "twn_vec.h"
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
|
||||
static void ingame_tick(State *state) {
|
||||
SceneIngame *scn = (SceneIngame *)state->scene;
|
||||
|
||||
input_action("player_left", "A");
|
||||
input_action("player_right", "D");
|
||||
input_action("player_forward", "W");
|
||||
input_action("player_backward", "S");
|
||||
input_action("player_jump", "SPACE");
|
||||
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);
|
||||
player_calc(scn->player);
|
||||
}
|
||||
|
||||
|
||||
static void ingame_end(State *state) {
|
||||
SceneIngame *scn = (SceneIngame *)state->scene;
|
||||
player_destroy(scn->player);
|
||||
world_destroy(scn->world);
|
||||
free(state->scene);
|
||||
}
|
||||
|
||||
|
||||
Scene *ingame_scene(State *state) {
|
||||
(void)state;
|
||||
|
||||
SceneIngame *new_scene = calloc(1, sizeof *new_scene);
|
||||
new_scene->base.tick = ingame_tick;
|
||||
new_scene->base.end = ingame_end;
|
||||
|
||||
new_scene->world = world_create();
|
||||
new_scene->player = player_create(new_scene->world);
|
||||
|
||||
return (Scene *)new_scene;
|
||||
}
|
@ -15,8 +15,6 @@ typedef struct SceneIngame {
|
||||
World *world;
|
||||
Player *player;
|
||||
|
||||
Camera cam;
|
||||
|
||||
/* TODO: put this in a better place */
|
||||
float yaw;
|
||||
float pitch;
|
@ -5,33 +5,32 @@
|
||||
|
||||
#include "twn_game_api.h"
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
static void title_tick(State *state) {
|
||||
SceneTitle *scn = (SceneTitle *)state->scene;
|
||||
(void)scn;
|
||||
|
||||
if (input_is_action_just_pressed("ui_accept")) {
|
||||
input_action("ui_accept", "ENTER");
|
||||
|
||||
if (input_action_just_pressed("ui_accept")) {
|
||||
switch_to(state, ingame_scene);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
m_sprite("/assets/title.png", ((Rect) {
|
||||
((float)ctx.base_draw_w / 2) - ((float)320 / 2), 64, 320, 128 }));
|
||||
|
||||
((float)ctx.resolution.x / 2) - ((float)320 / 2), 64, 320, 128 }));
|
||||
|
||||
/* draw the tick count as an example of dynamic text */
|
||||
size_t text_str_len = snprintf(NULL, 0, "%lu", state->ctx->tick_count) + 1;
|
||||
char *text_str = cmalloc(text_str_len);
|
||||
snprintf(text_str, text_str_len, "%lu", state->ctx->tick_count);
|
||||
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";
|
||||
int text_h = 32;
|
||||
int text_w = draw_text_width(text_str, text_h, font);
|
||||
float text_h = 32;
|
||||
float text_w = draw_text_width(text_str, text_h, font);
|
||||
|
||||
draw_rectangle(
|
||||
(Rect) {
|
||||
@ -64,7 +63,7 @@ static void title_end(State *state) {
|
||||
Scene *title_scene(State *state) {
|
||||
(void)state;
|
||||
|
||||
SceneTitle *new_scene = ccalloc(1, sizeof *new_scene);
|
||||
SceneTitle *new_scene = calloc(1, sizeof *new_scene);
|
||||
new_scene->base.tick = title_tick;
|
||||
new_scene->base.end = title_end;
|
||||
|
@ -12,11 +12,11 @@ static void update_tiles(struct World *world) {
|
||||
for (size_t row = 0; row < world->tilemap_height; ++row) {
|
||||
for (size_t col = 0; col < world->tilemap_width; ++col) {
|
||||
world->tiles[(row * world->tilemap_width) + col] = (struct Tile) {
|
||||
.rect = (Recti) {
|
||||
.x = (int)col * world->tile_size,
|
||||
.y = (int)row * world->tile_size,
|
||||
.w = world->tile_size,
|
||||
.h = world->tile_size,
|
||||
.rect = (Rect) {
|
||||
.x = (float)(col * world->tile_size),
|
||||
.y = (float)(row * world->tile_size),
|
||||
.w = (float)world->tile_size,
|
||||
.h = (float)world->tile_size,
|
||||
},
|
||||
.type = world->tilemap[row][col],
|
||||
};
|
||||
@ -25,10 +25,10 @@ static void update_tiles(struct World *world) {
|
||||
}
|
||||
|
||||
|
||||
static Vec2i to_grid_location(struct World *world, float x, float y) {
|
||||
return (Vec2i) {
|
||||
.x = (int)floor(x / (float)world->tile_size),
|
||||
.y = (int)floor(y / (float)world->tile_size),
|
||||
static Vec2 to_grid_location(struct World *world, float x, float y) {
|
||||
return (Vec2) {
|
||||
.x = floor(x / (float)world->tile_size),
|
||||
.y = floor(y / (float)world->tile_size),
|
||||
};
|
||||
}
|
||||
|
||||
@ -39,14 +39,13 @@ static void drawdef_debug(struct World *world) {
|
||||
for (size_t i = 0; i < world->tilemap_height * world->tilemap_width; ++i) {
|
||||
if (world->tiles[i].type == TILE_TYPE_VOID) continue;
|
||||
|
||||
draw_rectangle(to_frect(world->tiles[i].rect),
|
||||
(Color) { 255, 0, 255, 128 });
|
||||
draw_rectangle(world->tiles[i].rect, (Color) { 255, 0, 255, 128 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct World *world_create(void) {
|
||||
struct World *world = cmalloc(sizeof *world);
|
||||
struct World *world = malloc(sizeof *world);
|
||||
|
||||
*world = (struct World) {
|
||||
.tiles = NULL,
|
||||
@ -59,9 +58,9 @@ struct World *world_create(void) {
|
||||
/* create the tilemap */
|
||||
/* it simply stores what's in each tile as a 2d array */
|
||||
/* on its own, it's entirely unrelated to drawing or logic */
|
||||
world->tilemap = cmalloc(sizeof *world->tilemap * world->tilemap_height);
|
||||
world->tilemap = malloc(sizeof *world->tilemap * world->tilemap_height);
|
||||
for (size_t i = 0; i < world->tilemap_height; ++i) {
|
||||
world->tilemap[i] = cmalloc(sizeof **world->tilemap * world->tilemap_width);
|
||||
world->tilemap[i] = malloc(sizeof **world->tilemap * world->tilemap_width);
|
||||
|
||||
for (size_t j = 0; j < world->tilemap_width; ++j) {
|
||||
world->tilemap[i][j] = TILE_TYPE_VOID;
|
||||
@ -82,7 +81,7 @@ struct World *world_create(void) {
|
||||
/* the tiles array contains data meant to be used by other logic */
|
||||
/* most importantly, it is used to draw the tiles */
|
||||
const size_t tile_count = world->tilemap_height * world->tilemap_width;
|
||||
world->tiles = cmalloc(sizeof *world->tiles * tile_count);
|
||||
world->tiles = malloc(sizeof *world->tiles * tile_count);
|
||||
update_tiles(world);
|
||||
|
||||
return world;
|
||||
@ -106,14 +105,14 @@ void world_drawdef(struct World *world) {
|
||||
if (world->tiles[i].type == TILE_TYPE_VOID)
|
||||
continue;
|
||||
|
||||
m_sprite("/assets/white.png", to_frect(world->tiles[i].rect));
|
||||
m_sprite("/assets/white.png", world->tiles[i].rect);
|
||||
}
|
||||
|
||||
drawdef_debug(world);
|
||||
}
|
||||
|
||||
|
||||
bool world_find_intersect_frect(struct World *world, Rect rect, Rect *intersection) {
|
||||
bool world_find_rect_intersects(struct World *world, Rect rect, Rect *intersection) {
|
||||
bool is_intersecting = false;
|
||||
|
||||
const size_t tile_count = world->tilemap_height * world->tilemap_width;
|
||||
@ -121,44 +120,12 @@ bool world_find_intersect_frect(struct World *world, Rect rect, Rect *intersecti
|
||||
if (world->tiles[i].type == TILE_TYPE_VOID)
|
||||
continue;
|
||||
|
||||
Rect tile_frect = {
|
||||
.x = (float)(world->tiles[i].rect.x),
|
||||
.y = (float)(world->tiles[i].rect.y),
|
||||
.w = (float)(world->tiles[i].rect.w),
|
||||
.h = (float)(world->tiles[i].rect.h),
|
||||
};
|
||||
Rect const tile_frect = world->tiles[i].rect;
|
||||
|
||||
if (intersection == NULL) {
|
||||
Rect temp;
|
||||
is_intersecting = overlap_frect(&rect, &tile_frect, &temp);
|
||||
} else {
|
||||
is_intersecting = overlap_frect(&rect, &tile_frect, intersection);
|
||||
}
|
||||
is_intersecting = rect_intersects(rect, tile_frect);
|
||||
|
||||
if (is_intersecting)
|
||||
break;
|
||||
}
|
||||
|
||||
return is_intersecting;
|
||||
}
|
||||
|
||||
|
||||
bool world_find_intersect_rect(struct World *world, Recti rect, Recti *intersection) {
|
||||
bool is_intersecting = false;
|
||||
|
||||
const size_t tile_count = world->tilemap_height * world->tilemap_width;
|
||||
for (size_t i = 0; i < tile_count; ++i) {
|
||||
if (world->tiles[i].type == TILE_TYPE_VOID)
|
||||
continue;
|
||||
|
||||
Recti *tile_rect = &world->tiles[i].rect;
|
||||
|
||||
if (intersection == NULL) {
|
||||
Recti temp;
|
||||
is_intersecting = overlap_rect(&rect, tile_rect, &temp);
|
||||
} else {
|
||||
is_intersecting = overlap_rect(&rect, tile_rect, intersection);
|
||||
}
|
||||
if (intersection)
|
||||
*intersection = rect_overlap(rect, tile_frect);
|
||||
|
||||
if (is_intersecting)
|
||||
break;
|
||||
@ -169,20 +136,20 @@ bool world_find_intersect_rect(struct World *world, Recti rect, Recti *intersect
|
||||
|
||||
|
||||
bool world_is_tile_at(struct World *world, float x, float y) {
|
||||
Vec2i position_in_grid = to_grid_location(world, x, y);
|
||||
return world->tilemap[position_in_grid.y][position_in_grid.x] != TILE_TYPE_VOID;
|
||||
Vec2 position_in_grid = to_grid_location(world, x, y);
|
||||
return world->tilemap[(int32_t)position_in_grid.y][(int32_t)position_in_grid.x] != TILE_TYPE_VOID;
|
||||
}
|
||||
|
||||
|
||||
void world_place_tile(struct World *world, float x, float y) {
|
||||
Vec2i position_in_grid = to_grid_location(world, x, y);
|
||||
world->tilemap[position_in_grid.y][position_in_grid.x] = TILE_TYPE_SOLID;
|
||||
Vec2 position_in_grid = to_grid_location(world, x, y);
|
||||
world->tilemap[(int32_t)position_in_grid.y][(int32_t)position_in_grid.x] = TILE_TYPE_SOLID;
|
||||
update_tiles(world);
|
||||
}
|
||||
|
||||
|
||||
void world_remove_tile(struct World *world, float x, float y) {
|
||||
Vec2i position_in_grid = to_grid_location(world, x, y);
|
||||
world->tilemap[position_in_grid.y][position_in_grid.x] = TILE_TYPE_VOID;
|
||||
Vec2 position_in_grid = to_grid_location(world, x, y);
|
||||
world->tilemap[(int32_t)position_in_grid.y][(int32_t)position_in_grid.x] = TILE_TYPE_VOID;
|
||||
update_tiles(world);
|
||||
}
|
@ -14,7 +14,7 @@ typedef enum TileType {
|
||||
|
||||
|
||||
typedef struct Tile {
|
||||
Recti rect;
|
||||
Rect rect;
|
||||
TileType type;
|
||||
} Tile;
|
||||
|
||||
@ -34,8 +34,7 @@ typedef struct World {
|
||||
World *world_create(void);
|
||||
void world_destroy(World *world);
|
||||
void world_drawdef(World *world);
|
||||
bool world_find_intersect_frect(World *world, Rect rect, Rect *intersection);
|
||||
bool world_find_intersect_rect(World *world, Recti rect, Recti *intersection);
|
||||
bool world_find_rect_intersects(World *world, Rect rect, Rect *intersection);
|
||||
bool world_is_tile_at(World *world, float x, float y);
|
||||
void world_place_tile(World *world, float x, float y);
|
||||
void world_remove_tile(World *world, float x, float y);
|
@ -6,15 +6,14 @@ if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Debug)
|
||||
endif()
|
||||
|
||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNBUILDDIR})
|
||||
add_subdirectory($ENV{TWNROOT} ${CMAKE_BINARY_DIR}/twn)
|
||||
|
||||
set(SOURCE_FILES
|
||||
game.c
|
||||
state.h
|
||||
|
||||
scenes/scene.c scenes/scene.h
|
||||
scenes/title.c scenes/title.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})
|
3
apps/demos/scenery/data/packs/data.toml
Normal file
3
apps/demos/scenery/data/packs/data.toml
Normal file
@ -0,0 +1,3 @@
|
||||
[[deps]]
|
||||
source = "../../../data" # where does it come from, might be an url
|
||||
name = "common-data" # should be globally unique
|
@ -1,11 +1,10 @@
|
||||
[about]
|
||||
title = "Serene Scenery"
|
||||
developer = "Townengine Team"
|
||||
app_id = "platformer-demo"
|
||||
app_id = "scenery-demo"
|
||||
dev_id = "townengine-team"
|
||||
|
||||
[game]
|
||||
base_render_width = 640
|
||||
base_render_height = 360
|
||||
resolution = [ 640, 360 ]
|
||||
|
||||
[engine]
|
49
apps/demos/scenery/game.c
Normal file
49
apps/demos/scenery/game.c
Normal file
@ -0,0 +1,49 @@
|
||||
#include "state.h"
|
||||
#include "scenes/scene.h"
|
||||
#include "scenes/ingame.h"
|
||||
|
||||
#include "twn_game_api.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <malloc.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
void game_tick(void) {
|
||||
if (ctx.initialization_needed) {
|
||||
if (!ctx.udata) {
|
||||
ctx.udata = calloc(1, sizeof (State));
|
||||
|
||||
State *state = ctx.udata;
|
||||
state->ctx = &ctx;
|
||||
state->scene = ingame_scene(state);
|
||||
}
|
||||
}
|
||||
|
||||
State *state = ctx.udata;
|
||||
|
||||
input_action("debug_toggle", "BACKSPACE");
|
||||
input_action("debug_dump_atlases", "HOME");
|
||||
|
||||
if (input_action_just_pressed("debug_toggle")) {
|
||||
ctx.debug = !ctx.debug;
|
||||
}
|
||||
|
||||
state->scene->tick(state);
|
||||
|
||||
/* there's a scene switch pending, we can do it now that the tick is done */
|
||||
if (state->next_scene != NULL) {
|
||||
state->scene->end(state);
|
||||
state->scene = state->next_scene;
|
||||
state->is_scene_switching = false;
|
||||
state->next_scene = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void game_end(void) {
|
||||
State *state = ctx.udata;
|
||||
state->scene->end(state);
|
||||
free(state);
|
||||
}
|
592
apps/demos/scenery/scenes/ingame.c
Normal file
592
apps/demos/scenery/scenes/ingame.c
Normal file
@ -0,0 +1,592 @@
|
||||
#include "ingame.h"
|
||||
#include "scene.h"
|
||||
|
||||
#include "twn_game_api.h"
|
||||
#include "twn_vec.h"
|
||||
|
||||
#define STB_PERLIN_IMPLEMENTATION
|
||||
#include <stb_perlin.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
#define TERRAIN_FREQUENCY 0.15f
|
||||
#define TERRAIN_RADIUS 128
|
||||
#define GRASS_RADIUS 16
|
||||
#define TERRAIN_DISTANCE (TERRAIN_RADIUS * 2)
|
||||
#define HALF_TERRAIN_DISTANCE ((float)TERRAIN_DISTANCE / 2)
|
||||
#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];
|
||||
|
||||
|
||||
/* 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) {
|
||||
SceneIngame *scn = (SceneIngame *)state->scene;
|
||||
|
||||
DrawCameraFromPrincipalAxesResult dir_and_up =
|
||||
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 float speed = 0.1f; /* TODO: put this in a better place */
|
||||
if (input_action_pressed("player_left"))
|
||||
scn->pos = vec3_sub(scn->pos, m_vec_scale(right, speed));
|
||||
|
||||
if (input_action_pressed("player_right"))
|
||||
scn->pos = vec3_add(scn->pos, m_vec_scale(right, speed));
|
||||
|
||||
if (input_action_pressed("player_forward"))
|
||||
scn->pos = vec3_add(scn->pos, m_vec_scale(dir_and_up.direction, speed));
|
||||
|
||||
if (input_action_pressed("player_backward"))
|
||||
scn->pos = vec3_sub(scn->pos, m_vec_scale(dir_and_up.direction, speed));
|
||||
|
||||
if (input_action_pressed("player_jump"))
|
||||
scn->pos.y += speed;
|
||||
|
||||
if (input_action_pressed("player_run"))
|
||||
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) {
|
||||
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];
|
||||
float const height3 = heightmap[x + 1][y + 1];
|
||||
|
||||
Vec2 incell = { position.x - floorf(position.x), position.y - floorf(position.y) };
|
||||
|
||||
float const weight0 = (1 - incell.x) * (1 - incell.y);
|
||||
float const weight1 = ( incell.x) * (1 - incell.y);
|
||||
float const weight2 = (1 - incell.x) * ( incell.y);
|
||||
float const weight3 = ( incell.x) * ( incell.y);
|
||||
|
||||
return (height0 * weight0 + height1 * weight1 + height2 * weight2 + height3 * weight3) / (weight0 + weight1 + weight2 + weight3);
|
||||
}
|
||||
|
||||
|
||||
static void process_ground_mode(State *state) {
|
||||
SceneIngame *scn = (SceneIngame *)state->scene;
|
||||
|
||||
DrawCameraFromPrincipalAxesResult dir_and_up =
|
||||
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 = vec3_norm(dir_and_up.direction);
|
||||
|
||||
const Vec3 right = m_vec_norm(m_vec_cross(dir_and_up.direction, dir_and_up.up));
|
||||
const float speed = 0.20f; /* TODO: put this in a better place */
|
||||
|
||||
Vec3 target = scn->pos;
|
||||
|
||||
/* gravity */
|
||||
{
|
||||
float const height = height_at(scn, (Vec2){scn->pos.x, scn->pos.z});
|
||||
|
||||
if (target.y > height + PLAYER_HEIGHT)
|
||||
target.y = target.y - 0.6f;
|
||||
|
||||
if (target.y < height + PLAYER_HEIGHT)
|
||||
target.y = height + PLAYER_HEIGHT;
|
||||
}
|
||||
|
||||
/* movement */
|
||||
{
|
||||
Vec3 direction = {0, 0, 0};
|
||||
|
||||
if (input_action_pressed("player_left"))
|
||||
direction = m_vec_sub(direction, m_vec_scale(right, speed));
|
||||
|
||||
if (input_action_pressed("player_right"))
|
||||
direction = m_vec_add(direction, m_vec_scale(right, speed));
|
||||
|
||||
if (input_action_pressed("player_forward"))
|
||||
direction = m_vec_add(direction, m_vec_scale(dir_and_up.direction, speed));
|
||||
|
||||
if (input_action_pressed("player_backward"))
|
||||
direction = m_vec_sub(direction, m_vec_scale(dir_and_up.direction, speed));
|
||||
|
||||
target = m_vec_add(target, direction);
|
||||
}
|
||||
|
||||
/* interpolate */
|
||||
scn->pos.x = (target.x - scn->pos.x) * (0.13f / 0.9f) + scn->pos.x;
|
||||
scn->pos.y = (target.y - scn->pos.y) * (0.13f / 0.9f) + scn->pos.y;
|
||||
scn->pos.z = (target.z - scn->pos.z) * (0.13f / 0.9f) + scn->pos.z;
|
||||
}
|
||||
|
||||
|
||||
static void generate_terrain(SceneIngame *scn) {
|
||||
for (int ly = 0; ly < TERRAIN_DISTANCE; ly++) {
|
||||
for (int lx = 0; lx < TERRAIN_DISTANCE; lx++) {
|
||||
float x = floorf(scn->pos.x - HALF_TERRAIN_DISTANCE + (float)lx);
|
||||
float y = floorf(scn->pos.z - HALF_TERRAIN_DISTANCE + (float)ly);
|
||||
|
||||
float height = stb_perlin_noise3((float)x * TERRAIN_FREQUENCY, (float)y * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 1;
|
||||
height += stb_perlin_noise3((float)x * TERRAIN_FREQUENCY / 10, (float)y * TERRAIN_FREQUENCY / 10, 0, 0, 0, 0) * 20 - 1;
|
||||
|
||||
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) {
|
||||
/* used to cull invisible tiles over field of view (to horizon) */
|
||||
Vec2 const d = vec2_norm((Vec2){ .x = scn->looking_direction.x, .y = scn->looking_direction.z });
|
||||
float const c = cosf((float)M_PI_2 * 0.8f * 0.8f);
|
||||
|
||||
/* 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 d1 = heightmap[lx + 1][ly];
|
||||
float d2 = heightmap[lx + 1][ly - 1];
|
||||
float d3 = heightmap[lx][ly - 1];
|
||||
|
||||
draw_quad("/assets/grass2.png",
|
||||
(Vec3){ (float)x, d0, (float)y },
|
||||
(Vec3){ (float)x + 1, d1, (float)y },
|
||||
(Vec3){ (float)x + 1, d2, (float)y - 1 },
|
||||
(Vec3){ (float)x, d3, (float)y - 1 },
|
||||
(Rect){ .w = 128, .h = 128 },
|
||||
(Color){255, 255, 255, 255});
|
||||
|
||||
if (((float)(adler32(&((Vec2){x, y}), sizeof (Vec2)) % 100) / 100) <= TREE_DENSITY)
|
||||
draw_billboard("/assets/trreez.png",
|
||||
(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void ingame_tick(State *state) {
|
||||
SceneIngame *scn = (SceneIngame *)state->scene;
|
||||
|
||||
input_action("player_left", "A");
|
||||
input_action("player_right", "D");
|
||||
input_action("player_forward", "W");
|
||||
input_action("player_backward", "S");
|
||||
input_action("player_jump", "SPACE");
|
||||
input_action("player_run", "LSHIFT");
|
||||
input_action("mouse_capture_toggle", "ESCAPE");
|
||||
input_action("toggle_camera_mode", "C");
|
||||
|
||||
if (scn->mouse_captured) {
|
||||
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->pitch -= (float)ctx.mouse_movement.y * sensitivity;
|
||||
scn->pitch = clampf(scn->pitch, (float)-M_PI * 0.49f, (float)M_PI * 0.49f);
|
||||
}
|
||||
|
||||
if (input_action_just_pressed("toggle_camera_mode"))
|
||||
scn->camera_mode = scn->camera_mode == 2 ? 0 : scn->camera_mode + 1;
|
||||
|
||||
if (scn->camera_mode == 1) {
|
||||
process_fly_mode(state);
|
||||
} else if (scn->camera_mode == 0) {
|
||||
process_ground_mode(state);
|
||||
} else if (scn->camera_mode) {
|
||||
process_vehicle_mode(state);
|
||||
}
|
||||
|
||||
/* toggle mouse capture with end key */
|
||||
if (input_action_just_pressed("mouse_capture_toggle"))
|
||||
scn->mouse_captured = !scn->mouse_captured;
|
||||
|
||||
ctx.mouse_capture = scn->mouse_captured;
|
||||
|
||||
generate_terrain(scn);
|
||||
process_vehicle(scn);
|
||||
|
||||
draw_terrain(scn);
|
||||
draw_vehicle(scn);
|
||||
|
||||
draw_skybox("/assets/miramar/miramar_*.tga");
|
||||
|
||||
ctx.fog_color = (Color){ 140, 147, 160, 255 };
|
||||
ctx.fog_density = 0.015f;
|
||||
}
|
||||
|
||||
|
||||
static void ingame_end(State *state) {
|
||||
free(state->scene);
|
||||
}
|
||||
|
||||
|
||||
Scene *ingame_scene(State *state) {
|
||||
(void)state;
|
||||
|
||||
SceneIngame *new_scene = calloc(1, sizeof *new_scene);
|
||||
new_scene->base.tick = ingame_tick;
|
||||
new_scene->base.end = ingame_end;
|
||||
|
||||
new_scene->mouse_captured = true;
|
||||
|
||||
m_audio(m_set(path, "music/woah.ogg"),
|
||||
m_opt(channel, "soundtrack"),
|
||||
m_opt(repeat, true));
|
||||
|
||||
new_scene->pos = (Vec3){ 0.1f, 0.0, 0.1f };
|
||||
|
||||
return (Scene *)new_scene;
|
||||
}
|
@ -6,16 +6,22 @@
|
||||
#include "../state.h"
|
||||
#include "scene.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
typedef struct SceneIngame {
|
||||
Scene base;
|
||||
|
||||
Camera cam;
|
||||
Vec3 looking_direction;
|
||||
Vec2 world_center;
|
||||
|
||||
/* TODO: put this in a better place */
|
||||
Vec3 pos;
|
||||
float yaw;
|
||||
float pitch;
|
||||
float roll;
|
||||
|
||||
bool mouse_captured;
|
||||
int camera_mode;
|
||||
} SceneIngame;
|
||||
|
||||
|
@ -1,16 +1,16 @@
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
|
||||
project(template LANGUAGES C)
|
||||
project(circle-raster LANGUAGES C)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Debug)
|
||||
endif()
|
||||
|
||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNBUILDDIR})
|
||||
add_subdirectory($ENV{TWNROOT} ${CMAKE_BINARY_DIR}/twn)
|
||||
|
||||
set(SOURCE_FILES
|
||||
game.c
|
||||
state.h
|
||||
)
|
||||
|
||||
use_townengine(${PROJECT_NAME} "${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
use_townengine("${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR})
|
@ -13,8 +13,7 @@ dev_id = "you"
|
||||
|
||||
# Game runtime details
|
||||
[game]
|
||||
base_render_width = 640
|
||||
base_render_height = 480
|
||||
resolution = [ 640, 480 ]
|
||||
#debug = true
|
||||
|
||||
# Engine tweaks. You probably don't need to change these
|
147
apps/examples/circle-raster/game.c
Normal file
147
apps/examples/circle-raster/game.c
Normal file
@ -0,0 +1,147 @@
|
||||
#include "twn_game_api.h"
|
||||
#include "state.h"
|
||||
|
||||
#include <malloc.h>
|
||||
#include <math.h>
|
||||
|
||||
|
||||
/* Emits `x` and `y` for every intersecting cell */
|
||||
/* We snap position to the nearest corner, which means there's no aliasing */
|
||||
/* It works great for integer radii */
|
||||
#define m_iter_circle_pixels(p_center_x, p_center_y, p_radius) \
|
||||
for (float y = (p_center_y + ceilf(p_radius)) - 1; y > (p_center_y - ceilf(p_radius)) - 1; --y) \
|
||||
for (float x = p_center_x - ceilf(sqrtf(p_radius * p_radius - (y - p_center_y + (y <= p_center_y)) * (y - p_center_y + (y <= p_center_y)))); x < p_center_x + ceilf(sqrtf(p_radius * p_radius - (y - p_center_y + (y <= p_center_y)) * (y - p_center_y + (y <= p_center_y)))); ++x)
|
||||
|
||||
|
||||
static int32_t ceil_sqrt(int32_t const n) {
|
||||
int32_t res = 1;
|
||||
#pragma clang loop unroll_count(8)
|
||||
while(res * res < n)
|
||||
res++;
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
static void benchmark(struct state *state) {
|
||||
volatile float x, y;
|
||||
|
||||
profile_start("float");
|
||||
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
float const rs = state->r * state->r;
|
||||
float const cr = ceilf(state->r);
|
||||
for (float iy = -cr; iy <= cr - 1; ++iy) {
|
||||
float const dx = ceilf(sqrtf(rs - (iy + (iy <= 0)) * (iy + (iy <= 0))));
|
||||
for (float ix = -dx; ix < dx; ++ix) {
|
||||
x = ix;
|
||||
y = iy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
profile_end("float");
|
||||
|
||||
profile_start("int32_t");
|
||||
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
int32_t const rsi = (int32_t)state->r * (int32_t)state->r;
|
||||
for (int32_t iy = -(int32_t)state->r; iy <= (int32_t)state->r - 1; ++iy) {
|
||||
int32_t const dx = ceil_sqrt(rsi - (iy + (iy <= 0)) * (iy + (iy <= 0)));
|
||||
for (int32_t ix = -dx; ix < dx; ++ix) {
|
||||
x = (float)ix;
|
||||
y = (float)iy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
profile_end("int32_t");
|
||||
|
||||
profile_start("int32_t acc");
|
||||
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
int32_t const rsi = (int32_t)state->r * (int32_t)state->r;
|
||||
int32_t acc = 1;
|
||||
for (int32_t iy = (int32_t)state->r - 1; iy >= 0; --iy) {
|
||||
while (acc * acc < rsi - iy * iy) acc++;
|
||||
for (int32_t ix = -acc; ix < acc; ++ix) {
|
||||
/* lower portion */
|
||||
x = (float)ix;
|
||||
y = (float)iy;
|
||||
/* upper portion */
|
||||
x = (float)ix;
|
||||
y = (float)-iy - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
profile_end("int32_t acc");
|
||||
|
||||
profile_start("int32_t acc precalc");
|
||||
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
int32_t const rsi = (int32_t)state->r * (int32_t)state->r;
|
||||
int32_t acc = (int32_t)(sqrtf(state->r * state->r - (state->r - 1) * (state->r - 1)));
|
||||
for (int32_t iy = (int32_t)state->r - 1; iy >= 0; --iy) {
|
||||
while (acc * acc < rsi - iy * iy) acc++;
|
||||
for (int32_t ix = -acc; ix < acc; ++ix) {
|
||||
/* lower portion */
|
||||
x = (float)ix;
|
||||
y = (float)iy;
|
||||
/* upper portion */
|
||||
x = (float)ix;
|
||||
y = (float)(int32_t)(~(uint32_t)iy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
profile_end("int32_t acc precalc");
|
||||
|
||||
(void)x; (void)y;
|
||||
}
|
||||
|
||||
|
||||
void game_tick(void) {
|
||||
if (ctx.initialization_needed) {
|
||||
if (!ctx.udata) {
|
||||
ctx.udata = calloc(1, sizeof (struct state));
|
||||
struct state *state = ctx.udata;
|
||||
state->r = 24;
|
||||
}
|
||||
}
|
||||
|
||||
struct state *state = ctx.udata;
|
||||
|
||||
Vec2 const mouse_snap = {floorf(ctx.mouse_position.x / 8) * 8, floorf(ctx.mouse_position.y / 8) * 8};
|
||||
|
||||
input_action("up", "LCLICK");
|
||||
input_action("down", "RCLICK");
|
||||
|
||||
if (input_action_pressed("up"))
|
||||
state->r += 1;
|
||||
if (input_action_pressed("down") && state->r > 2)
|
||||
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 acc = 1;
|
||||
for (int32_t iy = (int32_t)state->r - 1; iy >= 0; --iy) {
|
||||
while (acc * acc < rsi - iy * iy) acc++;
|
||||
for (int32_t ix = -acc; ix < acc; ++ix) {
|
||||
/* lower portion */
|
||||
draw_box((Rect){mouse_snap.x + (float)ix * 8, mouse_snap.y + (float)iy * 8, 8, 8}, 1, (Color){125, 125, 0, 255});
|
||||
/* upper portion */
|
||||
draw_box((Rect){mouse_snap.x + (float)ix * 8, mouse_snap.y + (float)(-iy - 1) * 8, 8, 8}, 1, (Color){125, 125, 0, 255});
|
||||
}
|
||||
}
|
||||
|
||||
/* uncomment to see performance difference between variants */
|
||||
// benchmark(state);
|
||||
}
|
||||
|
||||
|
||||
void game_end(void) {
|
||||
/* do your deinitialization here */
|
||||
struct state *state = ctx.udata;
|
||||
free(state);
|
||||
}
|
11
apps/examples/circle-raster/state.h
Normal file
11
apps/examples/circle-raster/state.h
Normal file
@ -0,0 +1,11 @@
|
||||
#ifndef STATE_H
|
||||
#define STATE_H
|
||||
|
||||
#include "twn_game_api.h"
|
||||
|
||||
struct state {
|
||||
float r;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
@ -1,3 +0,0 @@
|
||||
[[deps]]
|
||||
source = "../../common-data" # where does it come from, might be an url
|
||||
name = "common-data" # should be globally unique
|
@ -1,79 +0,0 @@
|
||||
#include "state.h"
|
||||
#include "scenes/scene.h"
|
||||
#include "scenes/title.h"
|
||||
|
||||
#include "twn_game_api.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <malloc.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
void game_tick(void) {
|
||||
if (ctx.initialization_needed) {
|
||||
if (!ctx.udata) {
|
||||
ctx.udata = ccalloc(1, sizeof (State));
|
||||
|
||||
State *state = ctx.udata;
|
||||
state->ctx = &ctx;
|
||||
state->scene = title_scene(state);
|
||||
}
|
||||
|
||||
input_add_action("debug_toggle");
|
||||
input_bind_action_control("debug_toggle", CONTROL_BACKSPACE);
|
||||
|
||||
input_add_action("debug_dump_atlases");
|
||||
input_bind_action_control("debug_dump_atlases", CONTROL_HOME);
|
||||
|
||||
input_add_action("player_left");
|
||||
input_bind_action_control("player_left", CONTROL_A);
|
||||
|
||||
input_add_action("player_right");
|
||||
input_bind_action_control("player_right", CONTROL_D);
|
||||
|
||||
input_add_action("player_forward");
|
||||
input_bind_action_control("player_forward", CONTROL_W);
|
||||
|
||||
input_add_action("player_backward");
|
||||
input_bind_action_control("player_backward", CONTROL_S);
|
||||
|
||||
input_add_action("player_jump");
|
||||
input_bind_action_control("player_jump", CONTROL_SPACE);
|
||||
|
||||
input_add_action("player_run");
|
||||
input_bind_action_control("player_run", CONTROL_LSHIFT);
|
||||
|
||||
input_add_action("ui_accept");
|
||||
input_bind_action_control("ui_accept", CONTROL_RETURN);
|
||||
|
||||
input_add_action("mouse_capture_toggle");
|
||||
input_bind_action_control("mouse_capture_toggle", CONTROL_ESCAPE);
|
||||
}
|
||||
|
||||
State *state = ctx.udata;
|
||||
|
||||
if (input_is_action_just_pressed("debug_toggle")) {
|
||||
ctx.debug = !ctx.debug;
|
||||
}
|
||||
|
||||
if (input_is_action_just_pressed("debug_dump_atlases")) {
|
||||
textures_dump_atlases();
|
||||
}
|
||||
|
||||
state->scene->tick(state);
|
||||
|
||||
/* there's a scene switch pending, we can do it now that the tick is done */
|
||||
if (state->next_scene != NULL) {
|
||||
state->scene->end(state);
|
||||
state->scene = state->next_scene;
|
||||
state->is_scene_switching = false;
|
||||
state->next_scene = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void game_end(void) {
|
||||
State *state = ctx.udata;
|
||||
state->scene->end(state);
|
||||
free(state);
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
#include "ingame.h"
|
||||
#include "title.h"
|
||||
#include "scene.h"
|
||||
|
||||
#include "twn_game_api.h"
|
||||
#include "twn_vec.h"
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
|
||||
static void ingame_tick(State *state) {
|
||||
SceneIngame *scn = (SceneIngame *)state->scene;
|
||||
|
||||
world_drawdef(scn->world);
|
||||
player_calc(scn->player);
|
||||
|
||||
const Vec3 right = m_vec_norm(m_vec_cross(scn->cam.target, scn->cam.up));
|
||||
const float speed = 0.04f; /* TODO: put this in a better place */
|
||||
if (input_is_action_pressed("player_left"))
|
||||
scn->cam.pos = vec3_sub(scn->cam.pos, m_vec_scale(right, speed));
|
||||
|
||||
if (input_is_action_pressed("player_right"))
|
||||
scn->cam.pos = vec3_add(scn->cam.pos, m_vec_scale(right, speed));
|
||||
|
||||
if (input_is_action_pressed("player_forward"))
|
||||
scn->cam.pos = vec3_add(scn->cam.pos, m_vec_scale(scn->cam.target, speed));
|
||||
|
||||
if (input_is_action_pressed("player_backward"))
|
||||
scn->cam.pos = vec3_sub(scn->cam.pos, m_vec_scale(scn->cam.target, speed));
|
||||
|
||||
if (input_is_action_pressed("player_jump"))
|
||||
scn->cam.pos.y += speed;
|
||||
|
||||
if (input_is_action_pressed("player_run"))
|
||||
scn->cam.pos.y -= speed;
|
||||
}
|
||||
|
||||
|
||||
static void ingame_end(State *state) {
|
||||
SceneIngame *scn = (SceneIngame *)state->scene;
|
||||
player_destroy(scn->player);
|
||||
world_destroy(scn->world);
|
||||
free(state->scene);
|
||||
}
|
||||
|
||||
|
||||
Scene *ingame_scene(State *state) {
|
||||
(void)state;
|
||||
|
||||
SceneIngame *new_scene = ccalloc(1, sizeof *new_scene);
|
||||
new_scene->base.tick = ingame_tick;
|
||||
new_scene->base.end = ingame_end;
|
||||
|
||||
new_scene->world = world_create();
|
||||
new_scene->player = player_create(new_scene->world);
|
||||
|
||||
new_scene->cam = (Camera){ .pos = { 32, 0, 1 }, .up = { 0, 1, 0 }, .fov = (float)M_PI_2 };
|
||||
|
||||
return (Scene *)new_scene;
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
[[deps]]
|
||||
source = "../../common-data" # where does it come from, might be an url
|
||||
name = "common-data" # should be globally unique
|
@ -1,80 +0,0 @@
|
||||
#include "state.h"
|
||||
#include "scenes/scene.h"
|
||||
#include "scenes/title.h"
|
||||
|
||||
#include "twn_game_api.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <malloc.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
void game_tick(void) {
|
||||
if (ctx.initialization_needed) {
|
||||
if (!ctx.udata) {
|
||||
ctx.udata = ccalloc(1, sizeof (State));
|
||||
|
||||
State *state = ctx.udata;
|
||||
state->ctx = &ctx;
|
||||
state->scene = title_scene(state);
|
||||
}
|
||||
|
||||
input_add_action("debug_toggle");
|
||||
input_bind_action_control("debug_toggle", CONTROL_BACKSPACE);
|
||||
|
||||
input_add_action("debug_dump_atlases");
|
||||
input_bind_action_control("debug_dump_atlases", CONTROL_HOME);
|
||||
|
||||
input_add_action("player_left");
|
||||
input_bind_action_control("player_left", CONTROL_A);
|
||||
|
||||
input_add_action("player_right");
|
||||
input_bind_action_control("player_right", CONTROL_D);
|
||||
|
||||
input_add_action("player_forward");
|
||||
input_bind_action_control("player_forward", CONTROL_W);
|
||||
|
||||
input_add_action("player_backward");
|
||||
input_bind_action_control("player_backward", CONTROL_S);
|
||||
|
||||
input_add_action("player_jump");
|
||||
input_bind_action_control("player_jump", CONTROL_SPACE);
|
||||
|
||||
input_add_action("player_run");
|
||||
input_bind_action_control("player_run", CONTROL_LSHIFT);
|
||||
|
||||
input_add_action("ui_accept");
|
||||
input_bind_action_control("ui_accept", CONTROL_RETURN);
|
||||
|
||||
input_add_action("mouse_capture_toggle");
|
||||
input_bind_action_control("mouse_capture_toggle", CONTROL_ESCAPE);
|
||||
}
|
||||
|
||||
State *state = ctx.udata;
|
||||
|
||||
if (input_is_action_just_pressed("debug_toggle")) {
|
||||
ctx.debug = !ctx.debug;
|
||||
}
|
||||
|
||||
if (input_is_action_just_pressed("debug_dump_atlases")) {
|
||||
textures_dump_atlases();
|
||||
}
|
||||
|
||||
state->scene->tick(state);
|
||||
|
||||
/* there's a scene switch pending, we can do it now that the tick is done */
|
||||
if (state->next_scene != NULL) {
|
||||
state->scene->end(state);
|
||||
state->scene = state->next_scene;
|
||||
state->is_scene_switching = false;
|
||||
state->next_scene = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void game_end(void) {
|
||||
State *state = ctx.udata;
|
||||
state->scene->end(state);
|
||||
free(state);
|
||||
}
|
@ -1,117 +0,0 @@
|
||||
#include "ingame.h"
|
||||
#include "title.h"
|
||||
#include "scene.h"
|
||||
|
||||
#include "twn_game_api.h"
|
||||
#include "twn_vec.h"
|
||||
|
||||
#define STB_PERLIN_IMPLEMENTATION
|
||||
#include <stb_perlin.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
static void ingame_tick(State *state) {
|
||||
SceneIngame *scn = (SceneIngame *)state->scene;
|
||||
|
||||
if (input_is_mouse_captured()) {
|
||||
const float sensitivity = 0.6f; /* TODO: put this in a better place */
|
||||
scn->yaw += (float)ctx.mouse_relative_position.x * sensitivity;
|
||||
scn->pitch -= (float)ctx.mouse_relative_position.y * sensitivity;
|
||||
scn->pitch = clampf(scn->pitch, -89.0f, 89.0f);
|
||||
|
||||
const float yaw_rad = scn->yaw * (float)DEG2RAD;
|
||||
const float pitch_rad = scn->pitch * (float)DEG2RAD;
|
||||
|
||||
scn->cam.target = m_vec_norm(((Vec3){
|
||||
cosf(yaw_rad) * cosf(pitch_rad),
|
||||
sinf(pitch_rad),
|
||||
sinf(yaw_rad) * cosf(pitch_rad)
|
||||
}));
|
||||
}
|
||||
|
||||
const Vec3 right = m_vec_norm(m_vec_cross(scn->cam.target, scn->cam.up));
|
||||
const float speed = 0.04f; /* TODO: put this in a better place */
|
||||
if (input_is_action_pressed("player_left"))
|
||||
scn->cam.pos = vec3_sub(scn->cam.pos, m_vec_scale(right, speed));
|
||||
|
||||
if (input_is_action_pressed("player_right"))
|
||||
scn->cam.pos = vec3_add(scn->cam.pos, m_vec_scale(right, speed));
|
||||
|
||||
if (input_is_action_pressed("player_forward"))
|
||||
scn->cam.pos = vec3_add(scn->cam.pos, m_vec_scale(scn->cam.target, speed));
|
||||
|
||||
if (input_is_action_pressed("player_backward"))
|
||||
scn->cam.pos = vec3_sub(scn->cam.pos, m_vec_scale(scn->cam.target, speed));
|
||||
|
||||
if (input_is_action_pressed("player_jump"))
|
||||
scn->cam.pos.y += speed;
|
||||
|
||||
if (input_is_action_pressed("player_run"))
|
||||
scn->cam.pos.y -= speed;
|
||||
|
||||
/* toggle mouse capture with end key */
|
||||
if (input_is_action_just_pressed("mouse_capture_toggle")) {
|
||||
input_set_mouse_captured(!input_is_mouse_captured());
|
||||
}
|
||||
|
||||
draw_camera(&scn->cam);
|
||||
|
||||
#define TERRAIN_FREQUENCY 0.1f
|
||||
|
||||
for (int ly = 64; ly--;) {
|
||||
for (int lx = 64; lx--;) {
|
||||
float x = SDL_truncf(scn->cam.pos.x + 32 - lx);
|
||||
float y = SDL_truncf(scn->cam.pos.z + 32 - ly);
|
||||
|
||||
float d0 = stb_perlin_noise3((float)x * TERRAIN_FREQUENCY, (float)y * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6;
|
||||
float d1 = stb_perlin_noise3((float)(x + 1) * TERRAIN_FREQUENCY, (float)y * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6;
|
||||
float d2 = stb_perlin_noise3((float)(x + 1) * TERRAIN_FREQUENCY, (float)(y - 1) * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6;
|
||||
float d3 = stb_perlin_noise3((float)x * TERRAIN_FREQUENCY, (float)(y - 1) * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6;
|
||||
|
||||
draw_triangle("/assets/grass.png",
|
||||
(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 });
|
||||
|
||||
draw_triangle("/assets/grass.png",
|
||||
(Vec3){ (float)x + 1, d1, (float)y },
|
||||
(Vec3){ (float)x + 1, d2, (float)y - 1 },
|
||||
(Vec3){ (float)x, d3, (float)y - 1 },
|
||||
(Vec2){ 128, 0 },
|
||||
(Vec2){ 0, 0 },
|
||||
(Vec2){ 0, 128 });
|
||||
}
|
||||
}
|
||||
|
||||
draw_skybox("/assets/miramar/miramar_*.tga");
|
||||
draw_fog(0.9, 1.0, 0.05, (Color){ 140, 147, 160, 255 });
|
||||
}
|
||||
|
||||
|
||||
static void ingame_end(State *state) {
|
||||
free(state->scene);
|
||||
}
|
||||
|
||||
|
||||
Scene *ingame_scene(State *state) {
|
||||
(void)state;
|
||||
|
||||
SceneIngame *new_scene = ccalloc(1, sizeof *new_scene);
|
||||
new_scene->base.tick = ingame_tick;
|
||||
new_scene->base.end = ingame_end;
|
||||
|
||||
new_scene->cam = (Camera){ .pos = { 32, 0, 1 }, .up = { 0, 1, 0 }, .fov = (float)M_PI_2 };
|
||||
|
||||
m_audio(m_set(path, "music/mod65.xm"),
|
||||
m_opt(channel, "soundtrack"),
|
||||
m_opt(repeat, true));
|
||||
|
||||
input_set_mouse_captured(true);
|
||||
|
||||
return (Scene *)new_scene;
|
||||
}
|
@ -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;
|
||||
|
||||
if (input_is_action_just_pressed("ui_accept")) {
|
||||
switch_to(state, ingame_scene);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
m_sprite("/assets/title.png", ((Rect) {
|
||||
((float)ctx.base_draw_w / 2) - ((float)320 / 2), 64, 320, 128 }));
|
||||
|
||||
|
||||
/* draw the tick count as an example of dynamic text */
|
||||
size_t text_str_len = snprintf(NULL, 0, "%lu", state->ctx->tick_count) + 1;
|
||||
char *text_str = cmalloc(text_str_len);
|
||||
snprintf(text_str, text_str_len, "%lu", state->ctx->tick_count);
|
||||
|
||||
const char *font = "/fonts/kenney-pixel.ttf";
|
||||
int text_h = 32;
|
||||
int 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 = ccalloc(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
|
16
apps/templates/c/CMakeLists.txt
Normal file
16
apps/templates/c/CMakeLists.txt
Normal file
@ -0,0 +1,16 @@
|
||||
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)
|
||||
|
||||
set(SOURCE_FILES
|
||||
game.c
|
||||
state.h
|
||||
)
|
||||
|
||||
cmake_path(GET CMAKE_SOURCE_DIR STEM LAST_ONLY GAME_PROJECT_NAME)
|
||||
use_townengine("${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR})
|
27
apps/templates/c/data/twn.toml
Normal file
27
apps/templates/c/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 ]
|
||||
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"
|
@ -1,7 +1,7 @@
|
||||
#include "twn_game_api.h"
|
||||
#include "state.h"
|
||||
|
||||
#include <malloc.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
void game_tick(void) {
|
||||
@ -10,7 +10,7 @@ void game_tick(void) {
|
||||
if (ctx.initialization_needed) {
|
||||
/* application data could be stored in ctx.udata and retrieved anywhere */
|
||||
if (!ctx.udata)
|
||||
ctx.udata = ccalloc(1, sizeof (struct state));
|
||||
ctx.udata = calloc(1, sizeof (struct state));
|
||||
}
|
||||
|
||||
/* a lot of data is accessible from `ctx`, look into `townengine/context.h` for more */
|
17
apps/templates/lua/.gitignore
vendored
Normal file
17
apps/templates/lua/.gitignore
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
# ignore executables
|
||||
*
|
||||
!*.*
|
||||
!*/
|
||||
|
||||
**/*.so
|
||||
**/*.dll
|
||||
**/*.exe
|
||||
**/*.trace
|
||||
**/*.js
|
||||
**/*.wasm
|
||||
**/*.wasm.map
|
||||
**/*.data
|
||||
**/*.html
|
||||
|
||||
data/scripts/twnapi.lua
|
||||
build/
|
7
apps/templates/lua/data/scripts/game.lua
Normal file
7
apps/templates/lua/data/scripts/game.lua
Normal file
@ -0,0 +1,7 @@
|
||||
-- called every frame, with constant delta time
|
||||
function game_tick()
|
||||
-- ctx.udata persists on code reload
|
||||
if ctx.udata == nil then
|
||||
ctx.udata = {}
|
||||
end
|
||||
end
|
27
apps/templates/lua/data/twn.toml
Normal file
27
apps/templates/lua/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"
|
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;
|
||||
}
|
||||
}
|
2
apps/twnlua/.gitignore
vendored
Normal file
2
apps/twnlua/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
luabind.c
|
||||
data/scripts/twnapi.lua
|
@ -1,77 +1,36 @@
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
|
||||
cmake_minimum_required(VERSION 3.30)
|
||||
project(twnlua LANGUAGES C)
|
||||
|
||||
find_package(Python3 COMPONENTS Interpreter)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Debug)
|
||||
endif()
|
||||
|
||||
add_subdirectory($ENV{TWNROOT} $ENV{TWNBUILDDIR})
|
||||
add_subdirectory($ENV{TWNROOT} ${CMAKE_BINARY_DIR}/twn)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/game.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
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${TWN_OUT_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
|
||||
)
|
||||
|
||||
add_custom_target(
|
||||
twnlua_docgen ALL
|
||||
DEPENDS ${TWN_OUT_DIR}/data/scripts/twnapi.lua
|
||||
)
|
||||
|
||||
add_compile_definitions(LUA_32BITS=1)
|
||||
|
||||
set(SOURCE_FILES
|
||||
game.c
|
||||
state.h
|
||||
|
||||
lua/src/lapi.c
|
||||
lua/src/lapi.h
|
||||
lua/src/lauxlib.c
|
||||
lua/src/lauxlib.h
|
||||
lua/src/lbaselib.c
|
||||
lua/src/lcode.c
|
||||
lua/src/lcode.h
|
||||
lua/src/lcorolib.c
|
||||
lua/src/lctype.c
|
||||
lua/src/lctype.h
|
||||
lua/src/ldblib.c
|
||||
lua/src/ldebug.c
|
||||
lua/src/ldebug.h
|
||||
lua/src/ldo.c
|
||||
lua/src/ldo.h
|
||||
lua/src/ldump.c
|
||||
lua/src/lfunc.c
|
||||
lua/src/lfunc.h
|
||||
lua/src/lgc.c
|
||||
lua/src/lgc.h
|
||||
lua/src/linit.c
|
||||
lua/src/liolib.c
|
||||
lua/src/ljumptab.h
|
||||
lua/src/llex.c
|
||||
lua/src/llex.h
|
||||
lua/src/llimits.h
|
||||
lua/src/lmathlib.c
|
||||
lua/src/lmem.c
|
||||
lua/src/lmem.h
|
||||
lua/src/loadlib.c
|
||||
lua/src/lobject.c
|
||||
lua/src/lobject.h
|
||||
lua/src/lopcodes.c
|
||||
lua/src/lopcodes.h
|
||||
lua/src/lopnames.h
|
||||
lua/src/loslib.c
|
||||
lua/src/lparser.c
|
||||
lua/src/lparser.h
|
||||
lua/src/lprefix.h
|
||||
lua/src/lstate.c
|
||||
lua/src/lstate.h
|
||||
lua/src/lstring.c
|
||||
lua/src/lstring.h
|
||||
lua/src/lstrlib.c
|
||||
lua/src/ltable.c
|
||||
lua/src/ltable.h
|
||||
lua/src/ltablib.c
|
||||
lua/src/ltm.c
|
||||
lua/src/ltm.h
|
||||
lua/src/lua.h
|
||||
lua/src/luaconf.h
|
||||
lua/src/lualib.h
|
||||
lua/src/lundump.c
|
||||
lua/src/lundump.h
|
||||
lua/src/lutf8lib.c
|
||||
lua/src/lvm.c
|
||||
lua/src/lvm.h
|
||||
lua/src/lzio.c
|
||||
lua/src/lzio.h
|
||||
minilua.c
|
||||
)
|
||||
|
||||
|
||||
use_townengine(${PROJECT_NAME} "${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
use_townengine("${SOURCE_FILES}" ${TWN_OUT_DIR})
|
||||
|
62
apps/twnlua/README.md
Normal file
62
apps/twnlua/README.md
Normal file
@ -0,0 +1,62 @@
|
||||
# MiniLua
|
||||
|
||||
This is Lua contained in a single header to be bundled in C/C++ applications with ease.
|
||||
[Lua](https://www.lua.org/) is a powerful, efficient, lightweight, embeddable scripting language.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```c
|
||||
#define LUA_IMPL
|
||||
#include "minilua.h"
|
||||
|
||||
int main() {
|
||||
lua_State *L = luaL_newstate();
|
||||
if(L == NULL)
|
||||
return -1;
|
||||
luaL_openlibs(L);
|
||||
luaL_loadstring(L, "print 'hello world'");
|
||||
lua_call(L, 0, 0);
|
||||
lua_close(L);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Copy `minilua.h` into your C or C++ project, include it anywhere you want to use Lua API.
|
||||
Then do the following in *one* C file to implement Lua:
|
||||
```c
|
||||
#define LUA_IMPL
|
||||
#include "minilua.h"
|
||||
```
|
||||
|
||||
By default it detects the system platform to use, however you can explicitly define one.
|
||||
|
||||
Note that almost no modification was made in the Lua implementation code,
|
||||
thus there are some C variable names that may collide with your code,
|
||||
therefore it is best to declare the Lua implementation in dedicated C file.
|
||||
|
||||
Optionally provide the following defines:
|
||||
- `LUA_MAKE_LUA` - implement the Lua command line REPL
|
||||
|
||||
## Documentation
|
||||
|
||||
For documentation on how to use Lua read its [official manual](https://www.lua.org/manual/).
|
||||
|
||||
## Updates
|
||||
|
||||
- **25-Jul-2024**: Updated to Lua 5.4.7.
|
||||
- **13-Nov-2023**: Updated to Lua 5.4.6.
|
||||
- **28-Jan-2022**: Updated to Lua 5.4.4.
|
||||
- **31-Mar-2021**: Updated to Lua 5.4.3.
|
||||
- **03-Dec-2020**: Updated to Lua 5.4.2.
|
||||
- **27-Nov-2020**: Library created, using Lua 5.4.2-rc1.
|
||||
|
||||
## Notes
|
||||
|
||||
This library tries to keep up with latest official Lua release.
|
||||
The header is generated using the bash script `gen.sh` all modifications done is there.
|
||||
|
||||
## License
|
||||
|
||||
Same license as Lua, the MIT license, see LICENSE.txt for information.
|
191
apps/twnlua/bindgen.py
Executable file
191
apps/twnlua/bindgen.py
Executable file
@ -0,0 +1,191 @@
|
||||
#!/bin/env python3
|
||||
|
||||
import sys, json
|
||||
|
||||
with open(sys.argv[1], 'r') if sys.argv[1] != "-" else sys.stdin as f:
|
||||
api_source = f.read()
|
||||
|
||||
api = json.loads(api_source)
|
||||
|
||||
|
||||
def default(parameter):
|
||||
basetype = parameter["type"].rsplit(' *', 1)[0]
|
||||
if parameter["type"] == "float":
|
||||
s = str(parameter["default"])
|
||||
return s + 'f' if '.' in s else s + '.f'
|
||||
elif parameter["type"] == "bool":
|
||||
return "true" if parameter["default"] else "false"
|
||||
elif parameter["type"] == "char *":
|
||||
if parameter["default"] == {}:
|
||||
return "NULL"
|
||||
else: return '"' + parameter["default"] + '"'
|
||||
elif basetype in api["types"]:
|
||||
if parameter["type"].endswith(" *"):
|
||||
if parameter["default"] == {}:
|
||||
return "NULL"
|
||||
else:
|
||||
return "&(%s){\n%s\n }" % \
|
||||
(parameter["type"], ", \n".join(" .%s = %s" % (n, v) for n, v in parameter["default"].items()))
|
||||
else:
|
||||
return "(%s){\n%s\n }" % \
|
||||
(parameter["type"], ", \n".join(" .%s = %s" % (n, v) for n, v in parameter["default"].items()))
|
||||
raise BaseException("Unhandled default value of type '%s'" % parameter["type"])
|
||||
|
||||
|
||||
def to_table(typedesc, variable, indent = 0):
|
||||
binding = ' ' * indent + "lua_createtable(L, 0, %i);\n" % len(typedesc["fields"])
|
||||
for field in typedesc["fields"]:
|
||||
if field["type"] == "float" or field["type"] == "uint8_t":
|
||||
binding += ' ' * indent + "lua_pushnumber(L, (float)(%s));\n" % (variable + ".%s" % field["name"])
|
||||
elif field["type"] == "bool":
|
||||
binding += ' ' * indent + "lua_pushboolean(L, (%s));\n" % (variable + ".%s" % field["name"])
|
||||
elif field["type"] in api["types"]:
|
||||
binding += to_table(api["types"][field["type"]], variable + ".%s" % field["name"], indent + 4)
|
||||
else:
|
||||
raise BaseException("Unhandled return field type '%s'" % (field["type"]))
|
||||
binding += ' ' * indent + "lua_setfield(L, -2, \"%s\");\n" % field["name"]
|
||||
return binding
|
||||
|
||||
|
||||
def from_table(typedesc, variable, indent = 0):
|
||||
binding = ""
|
||||
for field in typedesc["fields"]:
|
||||
binding += ' ' * indent + "lua_getfield(L, -1, \"%s\");\n" % field["name"]
|
||||
if field["type"] == "float" or field["type"] == "uint8_t":
|
||||
binding += ' ' * indent + "%s = (%s)(lua_tonumber(L, -1));\n" % (variable + ".%s" % field["name"], field["type"])
|
||||
elif field["type"] == "bool":
|
||||
binding += ' ' * indent + "%s = lua_toboolean(L, -1);\n" % (variable + ".%s" % field["name"])
|
||||
elif field["type"] in api["types"]:
|
||||
binding += from_table(api["types"][field["type"]], variable + ".%s" % field["name"], indent + 4)
|
||||
else:
|
||||
raise BaseException("Unhandled return field type '%s'" % (field["type"]))
|
||||
binding += ' ' * indent + "lua_pop(L, 1);\n"
|
||||
return binding
|
||||
|
||||
|
||||
print('#include "twn_game_api.h"\n')
|
||||
print('/* assumed to be included from game.c, where minilua.h is already present */\n')
|
||||
|
||||
bindings, used_converters = [], {}
|
||||
for procedure, procedure_desc in api["procedures"].items():
|
||||
binding = "static int binding_%s(lua_State *L) {\n" % procedure
|
||||
binding += " luaL_checktype(L, 1, LUA_TTABLE);\n"
|
||||
|
||||
if "params" in procedure_desc:
|
||||
for parameter in procedure_desc["params"]:
|
||||
basetype = parameter["type"].rsplit(' *', 1)[0]
|
||||
|
||||
if parameter["type"].endswith("*") and not parameter["type"] == "char *":
|
||||
binding += " %s %s_value;\n" % (basetype, parameter["name"])
|
||||
binding += " %s %s;\n" % (parameter["type"] if not parameter["type"].endswith("*") else 'const ' + parameter["type"], parameter["name"])
|
||||
binding += " lua_getfield(L, 1, \"%s\");\n" % parameter["name"]
|
||||
|
||||
if "default" in parameter and not parameter["type"] in ("float", "bool"):
|
||||
binding += " if (lua_isnoneornil(L, -1))\n"
|
||||
binding += " %s = %s;\n" % (parameter["name"], default(parameter))
|
||||
binding += " else\n "
|
||||
|
||||
if parameter["type"] == "float":
|
||||
if "default" in parameter:
|
||||
binding += " int is_%s_num;\n" % parameter["name"]
|
||||
binding += " %s = (float)(lua_tonumberx(L, -1, &is_%s_num));\n" % (parameter["name"], parameter["name"]);
|
||||
binding += " if (!is_%s_num) %s = %s;\n" % (parameter["name"], parameter["name"], default(parameter))
|
||||
else:
|
||||
binding += " %s = (float)(lua_tonumber(L, -1));\n" % (parameter["name"]);
|
||||
elif parameter["type"] == "bool":
|
||||
binding += " %s = lua_toboolean(L, -1);\n" % (parameter["name"]);
|
||||
elif parameter["type"] == "char *":
|
||||
binding += " %s = lua_tostring(L, -1);\n" % (parameter["name"]);
|
||||
elif basetype in api["types"]:
|
||||
used_converters[basetype] = api["types"][basetype]
|
||||
if parameter["type"].endswith(" *"):
|
||||
binding += " { %s_value = to_%s(L); %s = &%s_value; }\n" % (parameter["name"], basetype.lower(), parameter["name"], parameter["name"]);
|
||||
else:
|
||||
binding += " %s = to_%s(L);\n" % (parameter["name"], basetype.lower());
|
||||
else:
|
||||
raise BaseException("Unhandled parameter type '%s'" % (parameter["type"]))
|
||||
|
||||
if "return" in procedure_desc:
|
||||
if procedure_desc["return"] == "bool":
|
||||
binding += " lua_pushboolean(L, (int)(%s(%s)));\n" % (procedure, ", ".join(param["name"] for param in procedure_desc["params"]))
|
||||
elif procedure_desc["return"] == "float":
|
||||
binding += " lua_pushnumber(L, (float)(%s(%s)));\n" % (procedure, ", ".join(param["name"] for param in procedure_desc["params"]))
|
||||
elif procedure_desc["return"] == "char *":
|
||||
binding += " lua_pushstring(L, %s(%s));\n" % (procedure, ", ".join(param["name"] for param in procedure_desc["params"]))
|
||||
elif 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"]:
|
||||
type_desc = procedure_desc["return"] if type(procedure_desc["return"]) is dict else api["types"][procedure_desc["return"]]
|
||||
binding += " %s result = %s(%s);\n" % (type_desc["c_type"], procedure, ", ".join(param["name"] for param in procedure_desc["params"]))
|
||||
binding += to_table(type_desc, "result", 4)
|
||||
else:
|
||||
raise BaseException("Unhandled return type '%s'" % (procedure_desc["return"]))
|
||||
binding += " return 1;\n}\n"
|
||||
else:
|
||||
binding += " %s(%s);\n" % (procedure, ", ".join(param["name"] for param in procedure_desc["params"]))
|
||||
binding += " return 0;\n}\n"
|
||||
|
||||
bindings += [binding]
|
||||
|
||||
|
||||
storages, converters, initializers, deinitializers = [], [], [], []
|
||||
|
||||
for typename, typedesc in used_converters.items():
|
||||
if "no_convert" in typedesc and typedesc["no_convert"]:
|
||||
continue
|
||||
|
||||
converter = "static %s to_%s(lua_State *L) {\n" % (typename, typename.lower())
|
||||
converter += " %s %s;\n" % (typename, typename.lower());
|
||||
|
||||
if "fields" in typedesc:
|
||||
for idx, field in enumerate(typedesc["fields"]):
|
||||
converter += " lua_getfield(L, -%i, \"%s\");\n" % (idx + 1, field["name"]);
|
||||
if field["type"] == "float":
|
||||
converter += " %s.%s = (float)lua_tonumber(L, -1);\n" % (typename.lower(), field["name"]);
|
||||
elif field["type"] == "uint8_t":
|
||||
converter += " %s.%s = (uint8_t)lua_tointeger(L, -1);\n" % (typename.lower(), field["name"]);
|
||||
else:
|
||||
raise BaseException("Unhandled converter field type '%s'" % (field["type"]))
|
||||
converter += " lua_pop(L, %i);\n" % len(typedesc["fields"]);
|
||||
|
||||
converter += " return %s;\n}\n" % (typename.lower())
|
||||
converters += [converter]
|
||||
|
||||
|
||||
print('\n'.join(storages))
|
||||
print('\n'.join(converters))
|
||||
print('\n'.join(bindings))
|
||||
|
||||
|
||||
loader = "extern void bindgen_load_%s(lua_State *L);\n" % api["name"]
|
||||
loader += "void bindgen_load_%s(lua_State *L) {\n" % api["name"]
|
||||
for procedure, procedure_desc in api["procedures"].items():
|
||||
loader += " lua_pushcfunction(L, binding_%s);\n" % procedure
|
||||
loader += " lua_setglobal(L, \"%s\");\n" % procedure
|
||||
|
||||
loader += "}\n"
|
||||
print(loader)
|
||||
|
||||
|
||||
unloader = "extern void bindgen_unload_%s(lua_State *L);\n" % api["name"]
|
||||
unloader += "void bindgen_unload_%s(lua_State *L) {\n (void)L;\n" % api["name"]
|
||||
unloader += '\n'.join(deinitializers)
|
||||
unloader += "\n}\n"
|
||||
print(unloader)
|
||||
|
||||
|
||||
# exceptions for the base townengine api
|
||||
# TODO: is there a way to generalize it? or rather, is there any need to do so?
|
||||
if api["name"] == "twn":
|
||||
contexter = "extern void bindgen_build_context(lua_State *L);\n"
|
||||
contexter += "void bindgen_build_context(lua_State *L) {\n"
|
||||
contexter += to_table(api["types"]["Context"], "ctx", 4)
|
||||
contexter += "}\n\n"
|
||||
|
||||
contexter += "extern void bindgen_upload_context(lua_State *L);\n"
|
||||
contexter += "void bindgen_upload_context(lua_State *L) {\n"
|
||||
contexter += from_table(api["types"]["Context"], "ctx", 4)
|
||||
contexter += "}"
|
||||
|
||||
print(contexter)
|
@ -1,3 +1,3 @@
|
||||
[[deps]]
|
||||
source = "../../common-data" # where does it come from, might be an url
|
||||
name = "common-data" # should be globally unique
|
||||
source = "../../data" # where does it come from, might be an url
|
||||
name = "common-data" # should be globally unique
|
||||
|
@ -4,13 +4,41 @@ offset = { x = 0, y = 0 }
|
||||
angle = 0
|
||||
|
||||
function game_tick()
|
||||
rectangle {
|
||||
rect = { x = 0, y = 0, w = 640, h = 360 },
|
||||
color = { r = 127, g = 0, b = 127, a = 255 },
|
||||
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",
|
||||
}
|
||||
|
||||
sprite {
|
||||
path = "/assets/title.png",
|
||||
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 {
|
||||
name = "press",
|
||||
control = "A"
|
||||
}
|
||||
|
||||
draw_sprite {
|
||||
texture = "/assets/title.png",
|
||||
rect = {
|
||||
x = 320 - (320 / 2),
|
||||
y = 180 - (128 / 2),
|
||||
@ -19,11 +47,17 @@ function game_tick()
|
||||
},
|
||||
}
|
||||
|
||||
text {
|
||||
string = "IT KEEPS HAPPENING",
|
||||
position = offset,
|
||||
font = "/fonts/kenney-pixel.ttf",
|
||||
}
|
||||
if input_action_pressed { name = "press" } then
|
||||
draw_text {
|
||||
string = "it never happened",
|
||||
position = offset,
|
||||
font = "/fonts/kenney-pixel.ttf",
|
||||
}
|
||||
end
|
||||
|
||||
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.y = ORIGIN.y + (math.sin(angle) * RADIUS)
|
||||
|
@ -5,7 +5,7 @@ app_id = "twnlua"
|
||||
dev_id = "somebody"
|
||||
|
||||
[game]
|
||||
base_render_width = 640
|
||||
base_render_height = 360
|
||||
resolution = [ 640, 360 ]
|
||||
background_color = [ 127, 0, 127, 255 ]
|
||||
|
||||
[engine]
|
||||
|
55
apps/twnlua/docgen.py
Executable file
55
apps/twnlua/docgen.py
Executable file
@ -0,0 +1,55 @@
|
||||
#!/bin/env python3
|
||||
|
||||
import sys, json
|
||||
|
||||
with open(sys.argv[1], 'r') if sys.argv[1] != "-" else sys.stdin as f:
|
||||
api_source = f.read()
|
||||
|
||||
api = json.loads(api_source)
|
||||
|
||||
def to_lua_type_annot(typedesc):
|
||||
if type(typedesc) is dict:
|
||||
return r'{ %s }' % ','.join('%s: %s' % (f["name"], to_lua_type_annot(f["type"])) for f in typedesc["fields"])
|
||||
basetype = typedesc.rsplit(' *', 1)[0]
|
||||
if typedesc == "char *":
|
||||
return "string"
|
||||
elif basetype == "float":
|
||||
return "number"
|
||||
elif basetype == "bool":
|
||||
return "boolean"
|
||||
elif basetype == "Vec2":
|
||||
return r"{ x: number, y: number }"
|
||||
elif basetype == "Vec3":
|
||||
return r"{ x: number, y: number, z: number }"
|
||||
elif basetype == "Color":
|
||||
return r"{ r: number, g: number, b: number, a: number }"
|
||||
elif basetype == "Rect":
|
||||
return r"{ x: number, y: number, w: number, h: number }"
|
||||
else:
|
||||
return "unknown"
|
||||
# raise BaseException("Unhandled type for annotation: %s" % typedesc)
|
||||
|
||||
print("---@meta twn")
|
||||
print("---@diagnostic disable")
|
||||
|
||||
type_annotations = {}
|
||||
type_annotations["ctx"] = r"{ %s, udata: table }" % \
|
||||
', '.join("%s: %s" % (f["name"], to_lua_type_annot(f["type"])) for f in api["types"]["Context"]["fields"])
|
||||
|
||||
for annot in type_annotations:
|
||||
print("---@type " + type_annotations[annot])
|
||||
print(r"%s = nil" % annot)
|
||||
|
||||
procedure_annotations = {}
|
||||
for procedure, procedure_desc in api["procedures"].items():
|
||||
procedure_annotations[procedure] = {}
|
||||
procedure_annotations[procedure]["params"] = r"{ %s }" % \
|
||||
', '.join("%s: %s" % (p["name"], to_lua_type_annot(p["type"]) + '?' * ("default" in p)) for p in procedure_desc["params"])
|
||||
if "return" in procedure_desc:
|
||||
procedure_annotations[procedure]["return"] = to_lua_type_annot(procedure_desc["return"])
|
||||
|
||||
for annot in procedure_annotations:
|
||||
print("---@param args " + procedure_annotations[annot]["params"])
|
||||
if "return" in procedure_annotations[annot]:
|
||||
print("---@return " + procedure_annotations[annot]["return"])
|
||||
print("function %s(args) end" % annot)
|
@ -1,259 +1,191 @@
|
||||
#include "twn_game_api.h"
|
||||
|
||||
/* TODO: actually move it back it its own file, it doesn't give any compilation benefits */
|
||||
#include "minilua.h"
|
||||
|
||||
#include "state.h"
|
||||
#include "luabind.c"
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include <lua.h>
|
||||
#include <lualib.h>
|
||||
#include <lauxlib.h>
|
||||
#define UDATA_NESTING_LIMIT 128
|
||||
|
||||
#include <malloc.h>
|
||||
|
||||
/* generated by bindgen.py */
|
||||
void bindgen_load_twn(lua_State *L);
|
||||
void bindgen_unload_twn(lua_State *L);
|
||||
void bindgen_build_context(lua_State *L);
|
||||
void bindgen_upload_context(lua_State *L);
|
||||
|
||||
|
||||
/* require will go through physicsfs exclusively so that scripts can be in the data dir */
|
||||
/* TODO: allow for bytecode files */
|
||||
/* TODO: support .lua suffixes files? */
|
||||
static int physfs_loader(lua_State *L) {
|
||||
const char *name = luaL_checkstring(L, 1);
|
||||
char *final_path = NULL;
|
||||
SDL_asprintf(&final_path, "%s.lua", name);
|
||||
|
||||
if (!file_exists(final_path)) {
|
||||
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 */
|
||||
char *path_copy = SDL_strdup(name);
|
||||
char *ch = NULL;
|
||||
while ((ch = SDL_strchr(path_copy, '.')))
|
||||
*ch = '/';
|
||||
|
||||
char *final_path = NULL;
|
||||
SDL_asprintf(&final_path, "/scripts/%s.lua", path_copy);
|
||||
SDL_free(path_copy);
|
||||
|
||||
if (SDL_strcmp(file_read(final_path, ":exists").data, "yes") == 0) {
|
||||
char *error_message = NULL;
|
||||
SDL_asprintf(&error_message, "could not find module %s in filesystem", name);
|
||||
lua_pushstring(L, error_message);
|
||||
free(error_message);
|
||||
SDL_free(error_message);
|
||||
|
||||
free(final_path);
|
||||
SDL_free(final_path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
unsigned char *buf = NULL;
|
||||
int64_t buf_size = file_to_bytes(final_path, &buf);
|
||||
free(final_path);
|
||||
char *final_path_binary = NULL;
|
||||
SDL_asprintf(&final_path_binary, "%s%s", final_path, ":binary");
|
||||
|
||||
luaL_loadbuffer(L, (char *)buf, buf_size, name);
|
||||
free(buf);
|
||||
String file = file_read(final_path, ":binary");
|
||||
SDL_free(final_path);
|
||||
|
||||
return 1;
|
||||
/* TODO: use reader interface for streaming instead */
|
||||
int const result = luaL_loadbuffer(L, file.data, (size_t)file.length, name);
|
||||
if (result != LUA_OK)
|
||||
log_string(lua_tostring(L, -1), NULL);
|
||||
|
||||
return result == LUA_OK;
|
||||
}
|
||||
|
||||
|
||||
static Rect table_to_rect(lua_State *L, int idx, const char *name) {
|
||||
/* types are checked here to help prevent unexpected results */
|
||||
Rect rect;
|
||||
int is_num;
|
||||
/* WARN! experimental and will probably be removed */
|
||||
/* it is an attempt to ease memory usage problems posed by using lua, partially successful */
|
||||
static void *custom_alloc(void *ud, void *ptr, size_t osize, size_t nsize) {
|
||||
(void)ud;
|
||||
|
||||
lua_getfield(L, idx, "x");
|
||||
rect.x = (float)lua_tonumberx(L, -1, &is_num);
|
||||
if (!is_num)
|
||||
luaL_error(L, "bad field 'x' in '%s' (number expected, got %s)", name, luaL_typename(L, -1));
|
||||
lua_pop(L, 1);
|
||||
/* small allocations are placed in slots, as there's a big chance they will not need to be resized */
|
||||
static char slots[1024][128];
|
||||
static int16_t free_slots[1024] = { [0] = -1 };
|
||||
static size_t free_slot_count = 1024;
|
||||
|
||||
lua_getfield(L, idx, "y");
|
||||
rect.y = (float)lua_tonumberx(L, -1, &is_num);
|
||||
if (!is_num)
|
||||
luaL_error(L, "bad field 'y' in '%s' (number expected, got %s)", name, luaL_typename(L, -1));
|
||||
lua_pop(L, 1);
|
||||
if (free_slots[0] == -1)
|
||||
for (int i = 0; i < 1024; i++)
|
||||
free_slots[i] = (int16_t)i;
|
||||
|
||||
lua_getfield(L, idx, "w");
|
||||
rect.w = (float)lua_tonumberx(L, -1, &is_num);
|
||||
if (!is_num)
|
||||
luaL_error(L, "bad field 'w' in '%s' (number expected, got %s)", name, luaL_typename(L, -1));
|
||||
lua_pop(L, 1);
|
||||
if (nsize == 0) {
|
||||
if (ptr && (char *)ptr >= &slots[0][0] && (char *)ptr <= &slots[1024-1][128-1])
|
||||
free_slots[free_slot_count++] = (int16_t)(((uintptr_t)ptr - (uintptr_t)slots) / 128);
|
||||
else if (osize)
|
||||
SDL_free(ptr);
|
||||
return NULL;
|
||||
} else {
|
||||
if (!ptr && nsize <= 128 && free_slot_count > 0) {
|
||||
/* use a slot */
|
||||
return slots[free_slots[--free_slot_count]];
|
||||
}
|
||||
|
||||
lua_getfield(L, idx, "h");
|
||||
rect.h = (float)lua_tonumberx(L, -1, &is_num);
|
||||
if (!is_num)
|
||||
luaL_error(L, "bad field 'h' in '%s' (number expected, got %s)", name, luaL_typename(L, -1));
|
||||
lua_pop(L, 1);
|
||||
if ((char *)ptr >= &slots[0][0] && (char *)ptr <= &slots[1024-1][128-1]) {
|
||||
/* still fits */
|
||||
if (nsize <= 128)
|
||||
return ptr;
|
||||
|
||||
return rect;
|
||||
/* move from slot to dynamic memory */
|
||||
void *mem = SDL_malloc(nsize);
|
||||
SDL_memcpy(mem, ptr, osize);
|
||||
free_slots[free_slot_count++] = (int16_t)(((uintptr_t)ptr - (uintptr_t)slots) / 128);
|
||||
return mem;
|
||||
}
|
||||
|
||||
return SDL_realloc(ptr, nsize);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static Color table_to_color(lua_State *L, int idx) {
|
||||
Color color;
|
||||
|
||||
lua_getfield(L, idx, "r");
|
||||
color.r = (uint8_t)lua_tointeger(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, idx, "g");
|
||||
color.g = (uint8_t)lua_tointeger(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, idx, "b");
|
||||
color.b = (uint8_t)lua_tointeger(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, idx, "a");
|
||||
color.a = (uint8_t)lua_tointeger(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
|
||||
/* sprite(data [table]) */
|
||||
/* data should contain the following fields: */
|
||||
/*
|
||||
* path [string]
|
||||
* rect [table]
|
||||
* texture_region [table], optional
|
||||
* color [table], optional
|
||||
* rotation [number], optional
|
||||
* flip_x [boolean], optional
|
||||
* flip_y [boolean], optional
|
||||
* stretch [boolean], optional
|
||||
*/
|
||||
static int b_sprite(lua_State *L) {
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
|
||||
DrawSpriteArgs args = { 0 };
|
||||
|
||||
lua_getfield(L, 1, "path");
|
||||
const char *field_path = lua_tostring(L, -1);
|
||||
if (field_path == NULL)
|
||||
luaL_error(L, "bad field 'path' in 'data' (string expected, got %s)", luaL_typename(L, -1));
|
||||
args.path = field_path;
|
||||
|
||||
lua_getfield(L, 1, "rect");
|
||||
if (!lua_istable(L, -1))
|
||||
luaL_error(L, "bad field 'rect' in 'data' (table expected, got %s)", luaL_typename(L, -1));
|
||||
args.rect = table_to_rect(L, -1, "data.rect");
|
||||
|
||||
lua_getfield(L, 1, "texture_region");
|
||||
if (lua_istable(L, -1)) {
|
||||
args.texture_region_opt = table_to_rect(L, -1, "data.texture_region");
|
||||
args.texture_region_opt_set = true;
|
||||
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;
|
||||
}
|
||||
|
||||
lua_getfield(L, 1, "color");
|
||||
if (lua_istable(L, -1)) {
|
||||
args.color_opt = table_to_color(L, -1);
|
||||
args.color_opt_set = true;
|
||||
/* 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;
|
||||
}
|
||||
|
||||
lua_getfield(L, 1, "rotation");
|
||||
if (lua_isnumber(L, -1)) {
|
||||
args.rotation_opt = (float)lua_tonumber(L, -1);
|
||||
args.rotation_opt_set = true;
|
||||
}
|
||||
|
||||
lua_getfield(L, 1, "flip_x");
|
||||
if (lua_isboolean(L, -1)) {
|
||||
args.flip_x_opt = lua_toboolean(L, -1);
|
||||
args.flip_x_opt_set = true;
|
||||
}
|
||||
|
||||
lua_getfield(L, 1, "flip_y");
|
||||
if (lua_isboolean(L, -1)) {
|
||||
args.flip_y_opt = lua_toboolean(L, -1);
|
||||
args.flip_y_opt_set = true;
|
||||
}
|
||||
|
||||
lua_getfield(L, 1, "stretch");
|
||||
if (lua_isboolean(L, -1)) {
|
||||
args.stretch_opt = lua_toboolean(L, -1);
|
||||
args.stretch_opt_set = true;
|
||||
}
|
||||
|
||||
draw_sprite_args(args);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* rectangle(data [table]) */
|
||||
/* data should contain the following fields: */
|
||||
/*
|
||||
* rect [table]
|
||||
* color [table], optional, defaults to white
|
||||
*/
|
||||
static int b_rectangle(lua_State *L) {
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
|
||||
lua_getfield(L, 1, "rect");
|
||||
if (!lua_istable(L, -1))
|
||||
luaL_error(L, "bad field 'rect' in 'data' (table expected, got %s)", luaL_typename(L, -1));
|
||||
Rect rect = table_to_rect(L, -1, "data.rect");
|
||||
|
||||
Color color = { 255, 255, 255, 255 };
|
||||
lua_getfield(L, 1, "color");
|
||||
if (lua_istable(L, -1))
|
||||
color = table_to_color(L, -1);
|
||||
|
||||
draw_rectangle(rect, color);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* text(data [table]) */
|
||||
/* data should contain the following fields: */
|
||||
/*
|
||||
* string [string]
|
||||
* position [table]
|
||||
* height_px [number], optional, defaults to 22
|
||||
* color [table], optional, defaults to black
|
||||
* font [string]
|
||||
*/
|
||||
static int b_text(lua_State *L) {
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
|
||||
lua_getfield(L, 1, "string");
|
||||
const char *string = lua_tostring(L, -1);
|
||||
if (string == NULL)
|
||||
luaL_error(L, "bad field 'string' in 'data' (string expected, got %s)", luaL_typename(L, -1));
|
||||
|
||||
lua_getfield(L, 1, "position");
|
||||
if (!lua_istable(L, -1))
|
||||
luaL_error(L, "bad field 'position' in 'data' (table expected, got %s)", luaL_typename(L, -1));
|
||||
lua_getfield(L, -1, "x");
|
||||
float x = (float)lua_tonumber(L, -1);
|
||||
lua_getfield(L, -2, "y");
|
||||
float y = (float)lua_tonumber(L, -1);
|
||||
|
||||
lua_getfield(L, 1, "height_px");
|
||||
int is_num;
|
||||
int height_px = (int)lua_tointegerx(L, -1, &is_num);
|
||||
if (!is_num)
|
||||
height_px = 22;
|
||||
|
||||
lua_getfield(L, 1, "color");
|
||||
Color color = { 0, 0, 0, 255 };
|
||||
if (lua_istable(L, -1))
|
||||
color = table_to_color(L, -1);
|
||||
|
||||
lua_getfield(L, 1, "font");
|
||||
const char *font = lua_tostring(L, -1);
|
||||
if (font == NULL)
|
||||
luaL_error(L, "bad field 'font' in 'data' (string expected, got %s)", luaL_typename(L, -1));
|
||||
|
||||
draw_text(string, (Vec2) { x, y }, height_px, color, font);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void game_tick(void) {
|
||||
if (ctx.initialization_needed) {
|
||||
if (!ctx.udata)
|
||||
ctx.udata = ccalloc(1, sizeof (State));
|
||||
ctx.udata = SDL_calloc(1, sizeof (State));
|
||||
|
||||
State *state = ctx.udata;
|
||||
|
||||
/* 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) {
|
||||
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);
|
||||
}
|
||||
state->L = luaL_newstate();
|
||||
|
||||
/* fakey version of luaL_openlibs() that excludes file i/o */
|
||||
state->L = new_state;
|
||||
|
||||
/* fakey version of luaL_openlibs() that excludes file i/o and os stuff */
|
||||
{
|
||||
static const luaL_Reg loaded_libs[] = {
|
||||
{ LUA_GNAME, luaopen_base },
|
||||
{ LUA_LOADLIBNAME, luaopen_package },
|
||||
{ LUA_COLIBNAME, luaopen_coroutine },
|
||||
{ LUA_TABLIBNAME, luaopen_table },
|
||||
{ LUA_OSLIBNAME, luaopen_os },
|
||||
{ LUA_STRLIBNAME, luaopen_string },
|
||||
{ LUA_MATHLIBNAME, luaopen_math },
|
||||
{ LUA_UTF8LIBNAME, luaopen_utf8 },
|
||||
@ -269,7 +201,7 @@ void game_tick(void) {
|
||||
|
||||
/* package.searchers = { physfs_loader } */
|
||||
lua_getglobal(state->L, "package");
|
||||
lua_newtable(state->L);
|
||||
lua_createtable(state->L, 0, 1);
|
||||
lua_setfield(state->L, -2, "searchers");
|
||||
|
||||
lua_getfield(state->L, -1, "searchers");
|
||||
@ -280,35 +212,54 @@ void game_tick(void) {
|
||||
lua_pop(state->L, 2);
|
||||
|
||||
/* binding */
|
||||
lua_register(state->L, "sprite", b_sprite);
|
||||
lua_register(state->L, "rectangle", b_rectangle);
|
||||
lua_register(state->L, "text", b_text);
|
||||
bindgen_load_twn(state->L);
|
||||
|
||||
/* now finally get to running the code */
|
||||
unsigned char *game_buf = NULL;
|
||||
size_t game_buf_size = file_to_bytes("/scripts/game.lua", &game_buf);
|
||||
if (luaL_loadbuffer(state->L, (char *)game_buf, game_buf_size, "game.lua") == LUA_OK) {
|
||||
String file = file_read("/scripts/game.lua", ":binary");
|
||||
/* TODO: use reader interface for streaming instead */
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
} else
|
||||
state->loaded_successfully = true;
|
||||
} else {
|
||||
/* got some sort of error, it should be pushed on top of the stack */
|
||||
SDL_assert(lua_isstring(state->L, -1));
|
||||
log_string(luaL_tolstring(state->L, -1, NULL), "Error loading /scripts/game.lua entry");
|
||||
lua_pop(state->L, 1);
|
||||
}
|
||||
free(game_buf);
|
||||
|
||||
/* from this point we have access to everything defined in lua */
|
||||
}
|
||||
|
||||
State *state = ctx.udata;
|
||||
|
||||
lua_getglobal(state->L, "game_tick");
|
||||
if (lua_pcall(state->L, 0, 0, 0) != LUA_OK) {
|
||||
log_critical("%s", lua_tostring(state->L, -1));
|
||||
if (state->loaded_successfully) {
|
||||
bindgen_build_context(state->L);
|
||||
lua_getglobal(state->L, "ctx");
|
||||
if (!lua_isnoneornil(state->L, -1)) {
|
||||
lua_getfield(state->L, -1, "udata");
|
||||
lua_setfield(state->L, -3, "udata");
|
||||
}
|
||||
lua_pop(state->L, 1);
|
||||
lua_setglobal(state->L, "ctx");
|
||||
|
||||
lua_getglobal(state->L, "game_tick");
|
||||
if (lua_pcall(state->L, 0, 0, 0) != LUA_OK) {
|
||||
log_string(luaL_tolstring(state->L, -1, NULL), "Error executing game_tick()");
|
||||
lua_pop(state->L, 1);
|
||||
}
|
||||
|
||||
lua_getglobal(state->L, "ctx");
|
||||
bindgen_upload_context(state->L);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void game_end(void) {
|
||||
State *state = ctx.udata;
|
||||
bindgen_unload_twn(state->L);
|
||||
lua_close(state->L);
|
||||
free(state);
|
||||
SDL_free(state);
|
||||
}
|
||||
|
@ -1,106 +0,0 @@
|
||||
# Makefile for installing Lua
|
||||
# See doc/readme.html for installation and customization instructions.
|
||||
|
||||
# == CHANGE THE SETTINGS BELOW TO SUIT YOUR ENVIRONMENT =======================
|
||||
|
||||
# Your platform. See PLATS for possible values.
|
||||
PLAT= guess
|
||||
|
||||
# Where to install. The installation starts in the src and doc directories,
|
||||
# so take care if INSTALL_TOP is not an absolute path. See the local target.
|
||||
# You may want to make INSTALL_LMOD and INSTALL_CMOD consistent with
|
||||
# LUA_ROOT, LUA_LDIR, and LUA_CDIR in luaconf.h.
|
||||
INSTALL_TOP= /usr/local
|
||||
INSTALL_BIN= $(INSTALL_TOP)/bin
|
||||
INSTALL_INC= $(INSTALL_TOP)/include
|
||||
INSTALL_LIB= $(INSTALL_TOP)/lib
|
||||
INSTALL_MAN= $(INSTALL_TOP)/man/man1
|
||||
INSTALL_LMOD= $(INSTALL_TOP)/share/lua/$V
|
||||
INSTALL_CMOD= $(INSTALL_TOP)/lib/lua/$V
|
||||
|
||||
# How to install. If your install program does not support "-p", then
|
||||
# you may have to run ranlib on the installed liblua.a.
|
||||
INSTALL= install -p
|
||||
INSTALL_EXEC= $(INSTALL) -m 0755
|
||||
INSTALL_DATA= $(INSTALL) -m 0644
|
||||
#
|
||||
# If you don't have "install" you can use "cp" instead.
|
||||
# INSTALL= cp -p
|
||||
# INSTALL_EXEC= $(INSTALL)
|
||||
# INSTALL_DATA= $(INSTALL)
|
||||
|
||||
# Other utilities.
|
||||
MKDIR= mkdir -p
|
||||
RM= rm -f
|
||||
|
||||
# == END OF USER SETTINGS -- NO NEED TO CHANGE ANYTHING BELOW THIS LINE =======
|
||||
|
||||
# Convenience platforms targets.
|
||||
PLATS= guess aix bsd c89 freebsd generic ios linux linux-readline macosx mingw posix solaris
|
||||
|
||||
# What to install.
|
||||
TO_BIN= lua luac
|
||||
TO_INC= lua.h luaconf.h lualib.h lauxlib.h lua.hpp
|
||||
TO_LIB= liblua.a
|
||||
TO_MAN= lua.1 luac.1
|
||||
|
||||
# Lua version and release.
|
||||
V= 5.4
|
||||
R= $V.7
|
||||
|
||||
# Targets start here.
|
||||
all: $(PLAT)
|
||||
|
||||
$(PLATS) help test clean:
|
||||
@cd src && $(MAKE) $@
|
||||
|
||||
install: dummy
|
||||
cd src && $(MKDIR) $(INSTALL_BIN) $(INSTALL_INC) $(INSTALL_LIB) $(INSTALL_MAN) $(INSTALL_LMOD) $(INSTALL_CMOD)
|
||||
cd src && $(INSTALL_EXEC) $(TO_BIN) $(INSTALL_BIN)
|
||||
cd src && $(INSTALL_DATA) $(TO_INC) $(INSTALL_INC)
|
||||
cd src && $(INSTALL_DATA) $(TO_LIB) $(INSTALL_LIB)
|
||||
cd doc && $(INSTALL_DATA) $(TO_MAN) $(INSTALL_MAN)
|
||||
|
||||
uninstall:
|
||||
cd src && cd $(INSTALL_BIN) && $(RM) $(TO_BIN)
|
||||
cd src && cd $(INSTALL_INC) && $(RM) $(TO_INC)
|
||||
cd src && cd $(INSTALL_LIB) && $(RM) $(TO_LIB)
|
||||
cd doc && cd $(INSTALL_MAN) && $(RM) $(TO_MAN)
|
||||
|
||||
local:
|
||||
$(MAKE) install INSTALL_TOP=../install
|
||||
|
||||
# make may get confused with install/ if it does not support .PHONY.
|
||||
dummy:
|
||||
|
||||
# Echo config parameters.
|
||||
echo:
|
||||
@cd src && $(MAKE) -s echo
|
||||
@echo "PLAT= $(PLAT)"
|
||||
@echo "V= $V"
|
||||
@echo "R= $R"
|
||||
@echo "TO_BIN= $(TO_BIN)"
|
||||
@echo "TO_INC= $(TO_INC)"
|
||||
@echo "TO_LIB= $(TO_LIB)"
|
||||
@echo "TO_MAN= $(TO_MAN)"
|
||||
@echo "INSTALL_TOP= $(INSTALL_TOP)"
|
||||
@echo "INSTALL_BIN= $(INSTALL_BIN)"
|
||||
@echo "INSTALL_INC= $(INSTALL_INC)"
|
||||
@echo "INSTALL_LIB= $(INSTALL_LIB)"
|
||||
@echo "INSTALL_MAN= $(INSTALL_MAN)"
|
||||
@echo "INSTALL_LMOD= $(INSTALL_LMOD)"
|
||||
@echo "INSTALL_CMOD= $(INSTALL_CMOD)"
|
||||
@echo "INSTALL_EXEC= $(INSTALL_EXEC)"
|
||||
@echo "INSTALL_DATA= $(INSTALL_DATA)"
|
||||
|
||||
# Echo pkg-config data.
|
||||
pc:
|
||||
@echo "version=$R"
|
||||
@echo "prefix=$(INSTALL_TOP)"
|
||||
@echo "libdir=$(INSTALL_LIB)"
|
||||
@echo "includedir=$(INSTALL_INC)"
|
||||
|
||||
# Targets that do not create files (not all makes understand .PHONY).
|
||||
.PHONY: all $(PLATS) help test clean install uninstall local dummy echo pc
|
||||
|
||||
# (end of Makefile)
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user