From 9e1a4bfb78f90f0d6308a5bdfbcade7dec06276e Mon Sep 17 00:00:00 2001 From: LeandroSQ Date: Tue, 20 Feb 2024 13:04:14 -0300 Subject: [PATCH] Revert "Overhaul" This reverts commit 11814dc7b667cd82186436461df13222b29c08c8. --- .vscode/launch.json | 2 - .vscode/settings.json | 6 +- .vscode/tasks.json | 25 -- CMakeLists.txt | 57 +---- assets/audio/explosion.wav | Bin 70078 -> 0 bytes assets/audio/hit.wav | Bin 54734 -> 0 bytes assets/audio/original/explosion.rfx | Bin 104 -> 0 bytes assets/audio/original/hit.sfxr | 29 --- assets/audio/original/shoot.sfxr | 29 --- assets/audio/original/shoot2.sfxr | 29 --- assets/audio/original/spawn.rfx | Bin 104 -> 0 bytes assets/audio/shoot.wav | Bin 20390 -> 0 bytes assets/audio/spawn.wav | Bin 38780 -> 0 bytes assets/audio/thrust.wav | Bin 2022 -> 0 bytes assets/audio/thrust2.wav | Bin 2453 -> 0 bytes assets/shaders/all.fs | 114 --------- assets/shaders/background.fs | 35 --- assets/shaders/particles.fs | 22 -- assets/shaders/stars.fs | 55 ----- lib/CMakeLists.txt | 6 +- scripts/assets.sh | 23 -- src/core/data/dummy-list.cpp | 2 +- src/core/data/dummy-list.hpp | 2 +- src/core/data/icontainer.hpp | 3 +- src/core/data/spatial-hash-grid.cpp | 92 +++---- src/core/data/spatial-hash-grid.hpp | 7 +- src/core/main.cpp | 55 ++++- src/core/models/app.cpp | 295 +++------------------- src/core/models/app.hpp | 62 +---- src/core/models/asteroid.cpp | 337 +++++++++++++------------- src/core/models/asteroid.hpp | 64 +++-- src/core/models/bullet.cpp | 73 ++---- src/core/models/bullet.hpp | 22 +- src/core/models/collision-handler.hpp | 40 +-- src/core/models/collision-hanlder.cpp | 126 +++++----- src/core/models/gui.cpp | 155 ++++-------- src/core/models/hotreload-shader.cpp | 55 ----- src/core/models/hotreload-shader.hpp | 31 --- src/core/models/particle.cpp | 32 --- src/core/models/particle.hpp | 58 ----- src/core/models/polygon.cpp | 57 +++++ src/core/models/polygon.hpp | 17 ++ src/core/models/profiler.hpp | 52 ---- src/core/models/ship.cpp | 127 +++------- src/core/models/ship.hpp | 17 +- src/core/models/soundfx.hpp | 58 ----- src/core/models/star.hpp | 73 ------ src/core/models/wave.cpp | 147 ++++------- src/core/models/wave.hpp | 22 +- src/core/precomp.hpp | 5 +- src/core/settings.cpp | 5 + src/core/settings.hpp | 40 +-- src/core/utils.hpp | 129 +--------- 53 files changed, 666 insertions(+), 2026 deletions(-) delete mode 100644 .vscode/tasks.json delete mode 100644 assets/audio/explosion.wav delete mode 100644 assets/audio/hit.wav delete mode 100644 assets/audio/original/explosion.rfx delete mode 100644 assets/audio/original/hit.sfxr delete mode 100644 assets/audio/original/shoot.sfxr delete mode 100644 assets/audio/original/shoot2.sfxr delete mode 100644 assets/audio/original/spawn.rfx delete mode 100644 assets/audio/shoot.wav delete mode 100644 assets/audio/spawn.wav delete mode 100644 assets/audio/thrust.wav delete mode 100644 assets/audio/thrust2.wav delete mode 100644 assets/shaders/all.fs delete mode 100644 assets/shaders/background.fs delete mode 100644 assets/shaders/particles.fs delete mode 100644 assets/shaders/stars.fs delete mode 100644 scripts/assets.sh delete mode 100644 src/core/models/hotreload-shader.cpp delete mode 100644 src/core/models/hotreload-shader.hpp delete mode 100644 src/core/models/particle.cpp delete mode 100644 src/core/models/particle.hpp create mode 100644 src/core/models/polygon.cpp create mode 100644 src/core/models/polygon.hpp delete mode 100644 src/core/models/profiler.hpp delete mode 100644 src/core/models/soundfx.hpp delete mode 100644 src/core/models/star.hpp create mode 100644 src/core/settings.cpp diff --git a/.vscode/launch.json b/.vscode/launch.json index e44b60d..f969458 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -3,7 +3,6 @@ "configurations": [ { "name": "Debug (lldb)", - "preLaunchTask": "CMake: build", "type": "cppdbg", "request": "launch", "program": "${command:cmake.launchTargetPath}", @@ -19,6 +18,5 @@ "console": "externalTerminal", "MIMode": "lldb" }, - ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 1418bba..cf84160 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -82,8 +82,6 @@ "__availability": "cpp" }, "cSpell.words": [ - "Cooldown", - "cpptrace", "emscripten", "Emscripten", "EMSDK", @@ -95,9 +93,7 @@ "SMPTE", "substeps" ], - "cmake.configureOnOpen": true, - "cmake.buildBeforeRun": true, - "cmake.parseBuildDiagnostics": true, + "cmake.configureOnOpen": false, "C_Cpp.intelliSenseEngine": "disabled", "clangd.path": "/usr/bin/clangd", "clangd.arguments": [ diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 3266555..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "CMake: launch current target", - "type": "shell", - "command": "${command:cmake.launchTargetPath}", - "args": [], - "group": "build", - "dependsOn": "CMake: build", - }, - { - "type": "cmake", - "label": "CMake: build", - "command": "build", - "targets": [ - "asteroids" - ], - "preset": "${command:cmake.activeBuildPresetName}", - "group": "build", - "problemMatcher": [], - "detail": "CMake template build task" - } - ] -} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 9de58f9..e052cc5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,34 +59,14 @@ function(define_project_variables) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) endif() - if("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") - add_compile_options(-fsanitize=address) - add_link_options(-fsanitize=address) - endif() - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra") set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O3") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") set(CMAKE_CXX_STANDARD 23) - # Set colors for ninja - if(CMAKE_GENERATOR STREQUAL "Ninja") - if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always") - elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcolor-diagnostics") - endif() - - if("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fdiagnostics-color=always") - elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fcolor-diagnostics") - endif() - endif() - # For clang use -std=c++23 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++23") @@ -158,35 +138,6 @@ add_subdirectory(lib) add_executable(${PROJECT_NAME} ${SOURCES}) define_target_properties(${PROJECT_NAME}) -# region dSYM -if(APPLE) - add_custom_command( - TARGET ${PROJECT_NAME} - POST_BUILD - COMMAND dsymutil $ - ) -endif() -# endregion - -# # region Run assets.sh after build -# add_custom_command( -# TARGET ${PROJECT_NAME} -# PRE_BUILD -# COMMAND sh ${CMAKE_SOURCE_DIR}/scripts/assets.sh -# WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} -# ) -# # endregion - -# region Format -file(GLOB_RECURSE FORMAT_SOURCES "src/core/*.cpp" "src/core/*.hpp") -add_custom_target(${PROJECT_NAME}_format - COMMAND clang-format -i ${FORMAT_SOURCES} - COMMENT "Running clang-format" - DEPENDS ${FORMAT_SOURCES} - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} -) -# endregion - # region Linter file(GLOB_RECURSE LINT_SOURCES "src/core/*.cpp" "src/core/*.hpp") add_custom_target(${PROJECT_NAME}_lint @@ -194,10 +145,8 @@ add_custom_target(${PROJECT_NAME}_lint COMMENT "Running clang-tidy" DEPENDS ${LINT_SOURCES} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} -) -# endregion - -# endregion# region Clean +)# endregion +# region Clean add_custom_target(${PROJECT_NAME}_clean COMMAND sh ${CMAKE_SOURCE_DIR}/scripts/clean.sh COMMENT "Running clean.sh" diff --git a/assets/audio/explosion.wav b/assets/audio/explosion.wav deleted file mode 100644 index ffe6a67d2710a1d05e01d2d506993a6c7bea65b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 70078 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 diff --git a/assets/audio/hit.wav b/assets/audio/hit.wav deleted file mode 100644 index 40506afc1d66c3d6c5376544672d623be73e519e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54734 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 BD#8E& diff --git a/assets/audio/original/hit.sfxr b/assets/audio/original/hit.sfxr deleted file mode 100644 index 8009c5a..0000000 --- a/assets/audio/original/hit.sfxr +++ /dev/null @@ -1,29 +0,0 @@ -{ - "oldParams": true, - "wave_type": 3, - "p_env_attack": 0, - "p_env_sustain": 0.3400512095246295, - "p_env_punch": 0.48519889379528036, - "p_env_decay": 0.3972516406875707, - "p_base_freq": 0.05054721600947243, - "p_freq_limit": 0, - "p_freq_ramp": -0.008087208760721859, - "p_freq_dramp": 0, - "p_vib_strength": 0, - "p_vib_speed": 0, - "p_arp_mod": 0, - "p_arp_speed": 0, - "p_duty": 0, - "p_duty_ramp": 0, - "p_repeat_speed": 0.5865777415692228, - "p_pha_offset": 0, - "p_pha_ramp": 0, - "p_lpf_freq": 1, - "p_lpf_ramp": 0, - "p_lpf_resonance": 0, - "p_hpf_freq": 0, - "p_hpf_ramp": 0, - "sound_vol": 0.25, - "sample_rate": 44100, - "sample_size": 16 -} diff --git a/assets/audio/original/shoot.sfxr b/assets/audio/original/shoot.sfxr deleted file mode 100644 index 8153717..0000000 --- a/assets/audio/original/shoot.sfxr +++ /dev/null @@ -1,29 +0,0 @@ -{ - "oldParams": true, - "wave_type": 0, - "p_env_attack": 0, - "p_env_sustain": 0.186, - "p_env_punch": 0.14416162357904438, - "p_env_decay": 0.359, - "p_base_freq": 0.515, - "p_freq_limit": 0.134, - "p_freq_ramp": -0.2619783169500224, - "p_freq_dramp": 0, - "p_vib_strength": 0.283, - "p_vib_speed": 1, - "p_arp_mod": -0.053, - "p_arp_speed": 0, - "p_duty": 0.718, - "p_duty_ramp": -0.158, - "p_repeat_speed": 0, - "p_pha_offset": 0.189, - "p_pha_ramp": -0.234, - "p_lpf_freq": 1, - "p_lpf_ramp": 0, - "p_lpf_resonance": 0, - "p_hpf_freq": 0.126, - "p_hpf_ramp": 0, - "sound_vol": 0.25, - "sample_rate": 44100, - "sample_size": 16 -} \ No newline at end of file diff --git a/assets/audio/original/shoot2.sfxr b/assets/audio/original/shoot2.sfxr deleted file mode 100644 index 45a1b40..0000000 --- a/assets/audio/original/shoot2.sfxr +++ /dev/null @@ -1,29 +0,0 @@ -{ - "oldParams": true, - "wave_type": 1, - "p_env_attack": 0, - "p_env_sustain": 0.103, - "p_env_punch": 0.254, - "p_env_decay": 0.76, - "p_base_freq": 0.605, - "p_freq_limit": 0.185, - "p_freq_ramp": -0.285, - "p_freq_dramp": 0.06, - "p_vib_strength": 0.361, - "p_vib_speed": 0, - "p_arp_mod": -0.712, - "p_arp_speed": 0, - "p_duty": 0.26, - "p_duty_ramp": 0.647, - "p_repeat_speed": 0, - "p_pha_offset": -0.007, - "p_pha_ramp": -0.024, - "p_lpf_freq": 1, - "p_lpf_ramp": 0, - "p_lpf_resonance": 0, - "p_hpf_freq": 0.021623842742273, - "p_hpf_ramp": 0, - "sound_vol": 0.25, - "sample_rate": 44100, - "sample_size": 16 -} \ No newline at end of file diff --git a/assets/audio/original/spawn.rfx b/assets/audio/original/spawn.rfx deleted file mode 100644 index 702a07fd8216c12c2533d9dd266eea4addcc269a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 104 zcmXSNi%>Yhkic+~k%57Ufq_BwM7?c6gr6Od$#8AoQM)Ia+;%5|H`#3X7HX&9uWOf< wcEk4fF=KlMhKBthHOSz5ujl@xx6S*~(#q{#Eb`vBxVyv7R&|+ekKV?80FKTh5C8xG diff --git a/assets/audio/shoot.wav b/assets/audio/shoot.wav deleted file mode 100644 index dc1096613de834ec4f05dff7c607e7208610c72d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20390 zcmWJrWmsZc7Y0NS0~JM)P%IQg6ch^)b7mZ;JMZK--QC@Hy6$v$ci4(zfCz{J0tP4` zVxpi3`0<>-=f~M=y|vcavvMa)cr+aVn4d8(XU*nacrO3|0CYUr9{_-J3xEI+zzeW? z#jX_x9PbP~z4gJn_*}iUZPTF4DFYcs*qoR@$!QU7J$|)z;g{)$a`G5B>L8&O`x0@& zWtBZ>7}xFD?kutKM#@cpOrI<-fcL-L@p(HX{}}h1qP6F}y}*-?S5kGEeY5LVlh%YT z37Qo+37-}oPbMYet$x8COsB=8hx?v&*2s-QeD#dtlRy5v8+Zb`v-jziZ^>+b@lZ$I z@H5CR|IA4DWKr(zMVHrpS>7@aJ~boDBV|GCLqZ3Z;&Twj9S=0^G;mb}*{S-@%1MQv zzWKks|HOLd?2D+MyvhwtPTIR>vP%JK0+F9QYqDt3xiy=YDd*-+`IAXZei-9RxQuo6 zIS+FKTs5J3o~dJ+O9WZf1I5>V#(!vjHuK(r=gRNQio4>0j-27!&K*b!!9Yh%m^AOs z>VW0n=gpYfpLIPYk=hz@AutK4bGr}nwY2xY&~9jnYqWFg${+sS{$=88{G)e|?tS`O z+{o`~`P2K`{tNy!@Ga$G+U02n|52?hUij~f#S|n-?iRYS?oc(ZZNR18 zYw^`F(X7kU#7loH)6T<9o0oGfwLZ=>YF5adfHtp27dqggS<{=Pd9HMBT+b7*Qwm;v zL%uunH1Ju**Bzyw>-8-mz4L5m+`nU#B&QTYZq9~2Y1ca z+x?~!dhFR?v_8LmvP>b^UforC_fP5<_3H^w3g49eF5{Fm{_Xf;d=9$deGET9DQDi9 zvS9J?rRcdor_9bCV*D4!jOq-zf@UHNun8b{3wPjR*YtLzOeuI%y}LB$59&+L>lIHw zz2BLSs416h)1ZgToc@P^1V4*OO%qL_FMhXp$81L4-mEzcavU_OBcuZT3h}S&4$xxD z#K8o8gbLH_Et1yi$_@F0-^Sj>KS#g4_1m3uRNSvxI=~qx!=teKNL5L`6PM2Yw1hS{ zc4}-6jhRSiMdyak#jQrV!DAqK_Kl{Cy50|c zZW(NvS^vGtuk_HL&M(v74!m6PZB{9ZPgF>{-kXm)&+x$oYbl>ncTCpIZ<(Jm+f%@3ajL@9H&c4!1(2*92`fC(AJfKfllVu<@1U^Yfxf+%D-k zO~_C_aJuJlteto`aWs3!tl9;?W=@ zJ)RanXn-#|*pK4HfKIwFM>{)wDsBRT<(lL~1(56^b_@^+fy1HxfY4Fupt2j#iEc@h z%oXgZxmR9Np#9HK6f zErdKh{Al18{|+y->nx{B*4klM-(($GbwXY*4&-mGVwVQwZ~xByQ2BXDVSn}UMs@p& z-WJOODBmX&|BW1!d_L#(%-1sqC#Pj!Vloo%#tso@hJL|%`|a{lPoPFHA-O3tu~RB?wy@C2!Es9RO~N!h3|-}O)*~0{0c^0` zA6nB()7@0WfqtDDe($*N7TGChF3 zW4H&AKjLcBw&n&+nKj{bdVb14JSpaX#Al)HxDeF;5bxZVLB9h**4d`l{Z{?#&JV57 z=G9_0KddIK;=f|hU*q@e{G>{v03~lxpTNkdWZX_5Q%j@f5_<4wFu{JsUQ6BV&QAdo z#tg%517+RYHKFZN`Q@e@;W)Q|v#%_vFyQxvg1*W*g5S;loxA$#M+x8qa4rfR`oHK; ziPtjfCcVgB#PUwz#-EMJCV|5%gU+F+A(38bZaxq-;OE$R)3Sj?gSWP)y;ku~@~>#5 zPG2o%mlc2hyEFfA#WQ}Tv{MD_!I)`4H#cu2DhNx&#bKELOz6wzvp%I)v6oiw~G*en~K@h-$gGJ zH#H~wcUYE#r@?2@{Q z$@SA}w^!aQom4peZ(bRW+aoS+xujb<7-b!GuJXJYfC`n8A0{kMH%=JL^h%>7PmSLk zbD8ux91}bNyUG6sqQFD#;tr;dFSA@2{@LH&eN_8frBkA1Q^ZH>zt`$2ab>d#7yX@9 z2IW?WkGG`h#s+?k?QkZ0YEe%@7LgGN+gPFrJF}jpiIPF|xY++i{YR(`wqt3izdmMo ztm_8nmw>i0ta-uUt)2#*cgKuYsC-t_ZK0fpuU^CcSUmK1dto?7Qhz}fq^>e-H(>x3 zuuVR`xB|k;SeKMfS(7JR%BV~AOqvmQozh4Q2w#eShSB;(BdR@6uvOsC<71XdrrQG@ z2Ab}KTG$fOY-r3CKH?2m<*}a^kNw?O7{JM|Uo0C_?K8kluf~}$F=7K2K)4k{N-oaa zJ^_-si1{JO9yc@QC22H#YVdRHi2rn-SMb@cKb*aQo2|9xNaNXFO;=9mt2T&Yog}N_ zE8oBNNM&c~w8DdbCzeTS*NRQb|7p7&_!x0I;sN$QkM|&5i$gNoavo+!r(b6bCCsPg zM+Xv5gz18&Vt)8x5RD!=u&-bo;KJC5Y0Y3`Pj=Ujj_|hI@D0or zeC>`r(#SPNVKy{M147iM2Fl>g=5OoHywKssAS0Y1=8C#6{ut_-t%} z|4|=1e820k^LAjbb>m2vab2HQzeb~NZ&I$5X~mlbM(%+sAp3l=cmCx9l9-uiQ)RqL9}?RhYf*` z0X(et9JTq?`%a&wX=-1uv`MdvBLqd<*;V@T&f|3wz6pd-@f9M>Qh2~vwz3(p5vynaS$vEaT3E015q*( zkETDL;FKjw+n0h#D50*3b|HQWn}Z+2JoBII!}9EReE`V?nQSjcXAgr0z8Y5Q5FOEN zI{8D%>;_nUY3)8vY`Lze@NZoapo%WoD0`w3ce@Oy+P6ZVdNl+%hxSDw>8v!;1XOl4 z>i~nC*h0HT$sr{W27}+>Rt8{^jb2yWC%ZtvW#dQ2ZkiVvF?|aCBh3m`Sc_KnzHyt7 zUbn3#z0$QzTzKU#rZl@|ljw##zoWfprfJjozb@~*tI?WJa5O%F$(ldman^a(Oonfw zl=gtKf)qh81b@dJ2snwH?;YaN>yi&X3z%aK85uB^_Fe5R(B4;XY0Z+OBt0TF|6c8O z&ZKgD@#cJHVN&H({z}Q7w)0&N20xFLfg3!&_0 z@|(ybVRP|hEEuKq`Q>@V?JM-I)1L9UV{v95qp`O^|3h<2wYMd|`BT$P(ED=~t`uWEB*7?r!;B)u%1$j3rEFGM)~ic9QLp zhOh>F0rolSqVI06W$w8y@!$=BRn}P}nM1Vx5QC4-xx?J5mn$W4;v_)~FQPiA0#)i= zNX{3QRo8xQc&ND4d8l`zc_CmTEFKYz@dz6wKcOE^8_%(2^|D$S!bEo5ub6jHq6k4~ zRZt1$kN;Pncb+fY9yw0aI<6bjnl(mEpSD}4?NWEQ_R0q&^TZnj+j;w{k5!y6ykw)e+E6`wJ3ldA|gGG+m))$?#k{;*Qa(T^~CqZ4w8w)gm7kX zHf~D59He7j@z@SK=*$Op+J;7*Oo+jdUYdg^b5v_u8k@~c?hT>!$=sP$>)6Lj?iN(% zcbEU(SO`1i`~|46xmf6?m4mIlnC>asBkC`$YPp{@SA0b9jh9<fvR%l+;kgm9Q!7WA-_bv(;@Y@~R|)}PvQu&bbRto?e6s+l0!(D1o_n9Ha> zUQu1@Sp>_!UQDUt)~}U1wLj`g8f>s^c9Oeo@U>yDhKEvqCQM{mavoyQ#y;yE1t>%*=~v z@)T0y^SH5?>EvgTrm(!=C%EB&Jin)iIL}*dz0h3nbHKQD(dh4CpTTX8TAtQw)Dzp@ zC|qTm8wJ8RzOi;~6`xHneNf2CFD+%({1wuh@2fn!Pa7>`Tfu7g`A9AjyoAJg4~Qy!*9AFT`q%NfG2F$ z(Y+?);MP96p;*W4ENoA0DQZrWlr&@ruyr46Xq-RgX(bf}kp(T~N!;rVa(Q&eaYN%! zq|L!054_)wz{`XNN>JiPmLTVR79{;-Dlqv4ok)8}iHiCd5fkeCS0#wWp+Osz$Eb&^jtVC*9QODtf?=<+7{hR~Sl<7cI^IRcz&C z)}NK|TD^30`tOV=0D-VX@2vqJg1d;}v=u22GB4#&GG%ES8J5Iz@&44;(TSug!h%o~ z{u_2eKr?bH0_=IuE!L$9d>@!$yS2SjOqS^z{}qwz`P^;Q zh)S>W^5V4x{RJv^0B^eZv?9MlVgL`v+g3Z@@F++2;{pkJl(d9@(pKjTWnD>+Vpb)u zP5{PL#%v(F65oeU3F!&Ef}x_szVE#k!0oOtpn2dyz+GF0MQ>^}p6`$EY3sVENmgrG zZz?in=}lJA$NGPGUNz;FyUQa>%kgJHK@(iia=rFL#Icr zk2}RUo|Tx>nQ<*Gm7z<#6`x7%i+)Jj9T63#z+b|#&;x$Yedc+&dJMt7LDo410h+9r zM<ju)Ek0ckRVZWEa`PMB$?vMqcP}+&k5QaZZa^PBx*?>1 z^qe*`g`Bx6XHh1N<(jHaDy09W{TH*7Je^1;z(O@a71(D1pZ#w8?D1OQk>ZMl+MU_~ zHk;leF#j5QIB=?WWA`i_qjQ~VN$W%fO@?cNHJIvEyr9~EDwm3pQe9DB{yw;B=ev3L2w}^qsg68yR45ld=oTN|m|7gc!Hj?KO*Emp~ zgQwx}Xt=-Cr`t>JVRapbYQZuf&t5Y2edN`!WU#4^XJB^~Xuhf6wLMp=nza&@xJB5^ zZ{jvo3o7f%aizqf()@vv(yGh#OC+RLwdP&#P7}lK1mSvIM=r$S!+(&s#nVztvOZ=% z%lI$t660yoL;5Y+<(RYNW5ff5U7@$}cW}4R*Zr^hUiQ8S|JSY1rNX%qR5e~><&E-9 zB4cB}UvGGKl#bS!tV(a4q?jRl-1MWNSWr{fSld?BQ!!dLzhrx1MuD#ERn1CakWAio zLYLUzYkoPt1d8yi_B$LHNB9-JobJV(mOU+dR>r)vMT|p9XX%$|w_+ZWUlKnNeurxD zW}Fko6NN!WAmTkU-H*X;LtZ)k0#wq&__*5J!dr@3ou+f2p(_CtUvU#rxt7i|aAN2(ObXn{LKphA|M*O18OE58u zvwO0})1%U|7z>g%(@)Uu#C#%G5XTAFFnaLxz;&1tsE5cu2!L0pN4o1j&_m#Rz`u6s z7-e*xX`k_4e_>Cn9;6HJJg55Is#G{N6D2dm`-G4AUcBs@Eu1@Sc4==>OhI^YQRUpa zUhx@4V8_qyDMqz}i+b20Z+O5HJTv|R_9v!=3?`8x zW`ykxeiYb)!3N~`ZS}e5RpHU+igDTN{2nA5huY|t)#e*R>;ZD$8pEBgT8&K|+rFwr zBKMO`X*$vHN6=SC;QmwHS{ce-TKZp+S3y|GCr%RoS7VB@v?Iq*Yn(pT0GjXm%bObT z6(1YEPqHex)C9|v1M!NdU^1A1tawWMubshvU0q1NDmY|W1 z!##tU{nvWh^f9_4oqSbb+Xp2?zEJwU(Jq=Lcv&}Ed!yP|nah4sYA#w?FunMDC93X_ zxJ{m|exr9E+&cOb;O(-_lkZ0kyb!J= zNYRygK;4d7b=ADe>hj~I<3(o+HWz=bH1U$fhvi>YeO;7+%_ASiyCFn)Hu3=WM`$l8 zlC~oGUOFI0l9`eIp6SOpo@AiUk86pYPAQ6tBHksqIIs*2I*jcNSmrPE%|iV2oZ(U9 zn&$G&Il}27fMNeSHhZ+uG1fR7Sx>(f0ehXy!5PrEh7)dzd#y207w#cPUsQR8|urXGFC-4 zB^k+4&MKcsHbS?w$!6pF4>ned|CHX zLsH%*Qt1NPKe4*#9Z}ZEGX(Fjzrj<2WY{%mqyKTFr%#F3e7M$aFANI3115pg<40|X zv6mysraI$_fzUp-VUfOHd$u!B{j2R*OSqy~wm>o{zUUaqEOpPhtQrMpe}#YfkCLlJ zp9=;{Kvh2c&_Pxp^LFVPQzw?;0GnVyize3@06wK456Y$v{qm=bObxsP8K2*>^jSmE!6{DoNV6%B85y97&yYQQ%^YXO0F z{@CHsXtQ$Y(%`!Ou->Nbb6pH=Z^u*BskZEvQTZp?Y6-egASw_XsGQA4 zmo*k&Erb-EW$&q7Dx4^#wc<1`Jw~I_@&$O^WuNCVzZ_gtn1NJ9eVgehdi)8yVVaZ|1MzG%51oZrp;TJy2$ zxI@pp%Vfn*3vopk*=uXFgn`o07IEii!(QWLOE7Q*S`UATT!JNrT8aGFr-?h$P}x(m z;2FHM3#qv&up~MCN!$u*NK6O$C24JBCc!m~AACJ%9u9-CqN@F__|8ROz0@A>+-|^D zLCMZ0Pzm6?eV=uT#eYOS{MmSDAh(az`=L46F-tYQjnpzG=gA&Qwl-!otMLO5N#Zw-fqO)FS9nV*BkG&c*vHg1NQA z9a3~_dFLX7b@1tE4xkHi$%E?q2R$p;7;%FVM{i(6W*V{-8En=&=5@w}A7)n zR9p-u+AYcysUq;h@{U5!}el?P|IlU;Ex6i5UZ4e|g0>_cO& z7U+m|c)+L`Q1p%TjCA*PX|*k#P3n>MvDP7FuUscnOI(}0#O@*&0hkZqQEC&b>72L< zYI$tws^VjXb;WZld%0&sL9!36EKQ?fgK>QH2EZHo$RpOb3_UxzBVsm17QdFE$k1l# zGW0ApQ_A2cS0v`s%j1fuKVv>dzl?fB{1EXj{6*-a;JZOLaj!9N0$%t(MLzPm@BQ8L zkH;^!pRga$@6IA88L)X=Zc~gYM}Z^mCeIQ>v!mJf;_ z&4tp6rV+7Q15y}NPppgMZmv05b-D5$`&HTZlEI?wg}X~_oYQq4;)~6m?Kiany$^?i z$F2e4E?3}Q$ZHrx$j(STWgWefLC^e^Rh7YG)iXtm#$;)tfj$&BO0~y0QDEc{QgmcI zff1G!l80X(xEFg2eF1gD@1buQLgb}{cexL{;$T#0nsc7hLg3o*TekOO1*5g*=3%`N zH<;KzsdtfKlm3YAmqyf~QCZsHts%%z4 zW0uS>N+=pG-&munU(m!?B&*+b;rg$doNRlXL@=h;M?Wm?V(1t#H}+Ekn(34^F>7AN z4%TVrEykei1+ zseDX^lV&un7Jn1937j2OpU!<+Bdi*!46Mj0-&9&rY%W|^{HVg2yHQvxA-7!Z7}C!l zC@{O**MVzXiQeb^`8Yz@Nm5%ZGw~iXDhr*Jm@$X7oq2_kpR7o<&;#O=Y3pJyQa+OT zq`pY6h@9|^p_hZd28nTI3>Ccyb=2>LZw&(KP4isnan$V<>Dw_Ay0 zi$+hGKMyw>34;s!PxpQ^wCLTsmT0ecma2Q(acy}mPZiD0uCf%#hQ^A9aUoT(ihqk& zRZFN@UG-ljmpxv#vh-Q8wvbY?tCGvbh_*<}TacaWy9)=#%~R|T!9A`_@7?}IIJd9` zq?fUS30S5)6Q4CXV;Aea1MlFJn54xCXXDFh##nsJ)aZjz`NWf}~ zF=qmP`vd$EeK#N;dI{i89y8o7z)GM-XCn9?&}V?b9%@@Sc6C%^4mT|wx;4n}hxV=L zdETw;^4HDMeCZfc#k6m1eXZ<}Pi(#<mEz~$hawc_`I=qfYE=vx7y&@y|;^_g=^+@ zJXX22FKK$T!{RlJ|gT7W|rf zt1AU|3#xZc2A>DX09oVrY~5o_%jJb*Hs|S(h61iq_P(!fa2XnC(2({*Yf+s zKJtY2>DsM5SB+msMdR(xRyQ;v88ts}ao7&h<=A%#m8q*T$1`VSJY^Y~(^Frhn3EA(5qi@;FWvWt!3LwJBpVUJuG=%`IlQG zR7m=ipbn%S(@!#`So1)OUFO4A_-;iX#{Ww=LOw~ml5{UEF6)1pob)i(31&MZFXdyB zN8)CBT^xhN+W7IvlUK51~B zAJn0$8*NUlJC)9gBh7u%6_SR=bz+rhuCThE!EfNr;8xTmSAXQ>R#vf@<=;vpOR9>_ z6vtL1)G`DqO{of&DpQx;n=+JSNdhE6lHJ1)fhcSsIt)d!#Prd-8DBH3nOidytVwC# zQ-c_{lbw^cC$z-p#Z^)hVqZ{vqK`+}iQ6OF2{Xb=Lo-5t;t4@_aA@o`v{S%te;sm* zuLiNmyUsHM&UMdr`wNS4c>$q1e{c!~-354!Ke6Ggw=5o`r_B`8=OLo;*#Nfxb1$vu zV|S$fxh`2-+?mqxOBL77Yn|O9R7{iWWjmzj$mVJ5cSYTQ?s_MSyN{y9eg!%!+r|9%XPL_s$Uc~AOsqzj4qG+ zlzflnmNk^IHC>aoj45F(OleG-=5U>icn&R_S`w2&DI;e@6%msoe-c=Z6Q+d};FE(m zxLj;CdSU>_f3Bb0cd?J$d$Ct9e7A?mZJ(^2kyxepF>`UOk}RM;okcw=iVLnHeg z$UHR^Z2U5i&|lQMw5O+gm)@#7rH#=1>BvxX+vl`dS}rO570;T^i3Ki4p;d7MR+t`#rK6m9DXJtlI%=VCzdkLXU@-5q%UW6GIyj}QjR+Mcq<{0{xXhC`yQJUBaB{1 zZYQlL+9UoYAj2PpCWKVrrw7S!tFd
    jvAl^+dR@3Ruo=XJ;v1OM#KbmPJnxmY3B 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 diff --git a/assets/audio/thrust.wav b/assets/audio/thrust.wav deleted file mode 100644 index 779371b7764b095d8bcd5951243aae6e9d77f548..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2022 zcmXYy3v`Z08pofxyx*5DvI(uaRg#tv*T|7*NJx;x7m*7=lSm{hs5%@fs!B^~H(@R5 zNvpKhy{^GJj&55KMA`&7ti{r5)3V6f)@8LW`QDj%o*mKmcV^zpnKSRq^Ze)kjP)fa zH+lgkCQeA2Hmf8w2mqL+>^Xp--T)keAk8-}yOaIyOHa=5jZ4lBw9jcpZ%m7h?H4tu z-+;(SC@v}~T4!fT6&rXY&*ev)PR?N&&JV*0*aR84Lx-99X1IwkJIz{CZdRKP_(u%F z&G--cJJrwydI0wU;a#|Frkm^Lkh$nybraR+x;M7PgLsLSvDaae3CH$0$Lw%ZSQ&rB zVtA2vpHt%Wa~fo=@#{&38SO9hSNlJ5L(F3HzS&|5sgT~J3|`HCX1TP>uI6Kyfn#)| z-lNaZ9r3dGg%>+n&$RYjk`;2B{GNRW|AakI26JEvOoS{L4Wl3#M*L1P_#hS1V2mZ* z-cun3MnVF_!OJiZBB3|*gqI)`y1?`BECfRvXaxb_0byhM^g=SEXbDs>gmx^Ho#iq7 zIID*9c8(Y@rbyq=@n(Ywv;zSJ9)s;L&#c#5bOhF_5o(-1Z@hN(159_bSaH=>drZDQ z?%q}v_!=(4ZsfD8-pET?ZfdBqHMs~?_uwD|FW<|WPdnrHhT_?NlK zraN!Xudo`vI3UNn!86;l%Bgg^I~!z>`~XLghmK*myVd`mo1`4|k*=e$ES@gtw^$U! z(sDHqqj8P-!rxjmY{j;keAAgO@;&@Kuatx2x1wD3cMiw|nJx3>MG?w2&{}$px?z^O zsv>o%o~o;0rOo$uFi>R6YhtzNFOKmV3 zupaAdpG7`Oqf8oW$)XHOr&LOzk(5aBlm^2f2{PfPR(XX_bC5Rwa13$twI8A-<1a84FcbMBt%~ngA zXFdMu@zLf$|6+f$e~~Lyxe8=+*cO)03Sfb^A`%&~`|KL)O4Ha_9>)XS&N`f`F~T3Dmg=+m4z_0J z#XNb|c~u%#Aim)>yb#;bE&Loqh|{ll3un?~%4;E)axA&OzvZ>OO|%Nmq%l^@PxCXB z;%G1pqG*bue0G-?a$*sDh0`RL$#D6VXb{6i72n0WvbM~?WXQE=emD$)43@}7vGMGp zjq44FzzDO)x=n+2{TpqM8tMz*N1s+I?D7l)#j-A z+T>u4Ro+aTYfmmGZBMTtuf;=-g%c1Vp+Uqi*ljsp{z2A=rhxc>d)_ebInO1hz_~1a zGD%j7Nif+)v_0kF1GiNDprWi^v)ub`vAU>sxnb^ZcRJ?5D}ZzY8k{@)5siRY=m_C@ zoH>E-;0&CKommboq7}4?5PvqmnYoycH?R?3r}?y$R@(ior;W6gw$m<(f&nlXj!-?F zp;NZ6w|zW~usqG@Ae+;t#M=BmVLu+SJ{Cipa64|pP53FU!?hSn9jGIPP#C>r>DJPo z^e<9q3nA3i@`9xc?WS$CgZ9%9`{x8(6UM>>$b$k~Bc{TuaMS9p*lH}3s&KNc2w&4> z`jMu?Rk}t0mc(Pkx4a&*oeH@|l(Bd1ek7aAYgq~#X}!9Ljb=yTQ(SAS;ysg(7p$kG zSvSeC4*jHl6<8JL&;;wg8CJbM>n+2m68Gb0xZbMCH9cVn-^}XRWOhpSkn^F|R^1So Og|WIu?=Z^N)Bgjzx*2}} diff --git a/assets/audio/thrust2.wav b/assets/audio/thrust2.wav deleted file mode 100644 index e71dd34f6bfa887c9e568a9b5b61c6155f55bc9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2453 zcmb7GO^@3)5Or?NEtlT=S9(Z*UUO*A0fGQMEQ0M?7R68TLzHBDKl+dR-cVj_Z1fN= zY|$b)^XAQ);eP(()2GMxyWL;E|M~IT-Tt=^yWQ>`{+3^N@_D!WW%u>V{>$IkQ>Rjn z>ypQDw1YOrSPbW!$BQwg6fNHLy)xd1SQ3_OpX)R|op8zwT6g=d=K*YosSsmycsnk0 zJ^p)I<~h;OPmY-3c`7-^hKlxGN>*z(DD5;L;c1Q1Aq1eTCG!M))-$k`F?bn_Mr&+m zKpuNBxIlAlN0@Qw{Pf6Zyc%?O_x=81-yM48YMtuK)A_Wn*g^~4{-BhJWi&Yk1tPk^ zWoJXJ6}D!*QXs>MZQH`u82`Q^E> z0Ym{ISTdmb)gO8fTkwhHnMdkxjEM^u$;G47p^b?#>&Yz6j# zJfNtw$U6v({DDhTspgkwX&E;l#}VyNsk`rc`n;e*8Y=t4d6k4frjjzZATB;-mGf`So=ob9d;|)KTspVFj3P8nFg7f$#C|X!JhvMQv?Lszj#G&NG)*`qsg>TKD1?i9qD|D_w6Q6T zntp`n=~jY7;0T_g*@7@i4eZWn!?*-fLX9oGca1TSYUUl6;OMH21SwoG=Mz(k*)qk%b0PC~V4X7pq{x(y*9C7$$_ o{|by2{ek^xDZIvcb&72eFPViRAV%GIZL${F>~HA9k`}=H4|-a-1poj5 diff --git a/assets/shaders/all.fs b/assets/shaders/all.fs deleted file mode 100644 index 2f9c850..0000000 --- a/assets/shaders/all.fs +++ /dev/null @@ -1,114 +0,0 @@ -#version 330 - -precision mediump float; - -in vec2 fragTexCoord; -in vec4 fragColor; -out vec4 finalColor; -uniform sampler2D texture0; -uniform vec4 colDiffuse; - -uniform float aberration; - -#define USE_CHROMATIC_ABERRATION -#define USE_BLOOM -#define USE_GAMMA_CORRECTION -#define USE_SATURATION -#define USE_CONTRAST -#define USE_BRIGHTNESS -#define USE_PIXELATION - -void applyPixelation(inout vec2 normalizedFragTexCoord) { -#ifdef USE_PIXELATION - float pixelation = 0.005; - float pixelationIntensity = pow(aberration, 1.0 / 2.0) * 0.5; - - vec2 pixelatedFragTexCoord = floor(normalizedFragTexCoord / pixelation) * pixelation; - normalizedFragTexCoord = mix(normalizedFragTexCoord, pixelatedFragTexCoord, pixelationIntensity); -#endif -} - -void applyChromaticAberration(inout vec4 texelColor, in vec2 normalizedFragTexCoord, in vec2 texelSize) { -#ifdef USE_CHROMATIC_ABERRATION - float edgeFactor = max(length(normalizedFragTexCoord - 0.5) * 2.0, 0.35); - float aberrationIntensity = edgeFactor * aberration * 1.5; - vec2 aberrationOffset = vec2(aberrationIntensity, -aberrationIntensity); - float red = texture(texture0, normalizedFragTexCoord + aberrationOffset * texelSize).r; - float green = texture(texture0, normalizedFragTexCoord).g; - float blue = texture(texture0, normalizedFragTexCoord - aberrationOffset * texelSize).b; - texelColor.rgb = vec3(red, green, blue); -#endif -} - -void applyGammaCorrection(inout vec4 texelColor) { -#ifdef USE_GAMMA_CORRECTION - float gammmaCorrectionIntensity = 0.25; - texelColor.rgb = mix(texelColor.rgb, pow(texelColor.rgb, vec3(1.0 / 2.2)), gammmaCorrectionIntensity); -#endif -} - -void applyBloom(inout vec4 texelColor, in vec2 normalizedFragTexCoord, in vec2 texelSize) { -#ifdef USE_BLOOM - float bloomIntensity = 0.95; - float bloomThreshold = 0.60; - int bloomRadius = 4; - vec4 bloomColor = vec4(0.0); - for (int i = -bloomRadius; i <= bloomRadius; i++) { - for (int j = -bloomRadius; j <= bloomRadius; j++) { - vec2 offset = vec2(i, j) * texelSize; - vec4 bloomTexelColor = texture(texture0, normalizedFragTexCoord + offset); - if (length(bloomTexelColor.rgb) > bloomThreshold) { - bloomColor += bloomTexelColor; - } - } - } - bloomColor /= float((bloomRadius * 2 + 1) * (bloomRadius * 2 + 1)); - // Tint the bloom color - vec4 tint = vec4(1.0, 0.9, 0.7, 1.0); - bloomColor *= tint; - - texelColor.rgb += bloomColor.rgb * bloomIntensity; -#endif -} - -void applySaturation(inout vec4 texelColor) { -#ifdef USE_SATURATION - float saturation = 1.25; - float luminance = dot(texelColor.rgb, vec3(0.2126, 0.7152, 0.0722)); - texelColor.rgb = mix(vec3(luminance), texelColor.rgb, saturation); -#endif -} - -void applyContrast(inout vec4 texelColor) { -#ifdef USE_CONTRAST - float contrast = 0.98; - texelColor.rgb = (texelColor.rgb - 0.5) * pow(contrast, 2.0) + 0.5; -#endif -} - -void applyBrightness(inout vec4 texelColor) { -#ifdef USE_BRIGHTNESS - float brightness = 1.05; - texelColor.rgb *= pow(brightness, 2.0); -#endif -} - -void main() { - vec2 texelSize = 1.0 / textureSize(texture0, 0); - // Flip vertically to match OpenGL's texture coordinate system - vec2 normalizedFragTexCoord = vec2(fragTexCoord.x, 1.0 - fragTexCoord.y); - - applyPixelation(normalizedFragTexCoord); - - vec4 texelColor = texture(texture0, normalizedFragTexCoord); - - applyChromaticAberration(texelColor, normalizedFragTexCoord, texelSize); - applyGammaCorrection(texelColor); - applyBloom(texelColor, normalizedFragTexCoord, texelSize); - - applySaturation(texelColor); - applyContrast(texelColor); - applyBrightness(texelColor); - - finalColor = texelColor * colDiffuse * fragColor; -} \ No newline at end of file diff --git a/assets/shaders/background.fs b/assets/shaders/background.fs deleted file mode 100644 index c632515..0000000 --- a/assets/shaders/background.fs +++ /dev/null @@ -1,35 +0,0 @@ -#version 330 - -precision mediump float; - -in vec2 fragTexCoord; -in vec4 fragColor; -out vec4 finalColor; -uniform sampler2D texture0; -uniform vec4 colDiffuse; - -uniform vec2 resolution; - -vec3 applyVignetteLinear(vec3 color) { - // Apply vignette - float radius = 0.95; - float softness = 0.9999; - float intensity = 0.6; - vec2 position = gl_FragCoord.xy / resolution.xy; - - float vignette = smoothstep(radius, radius - softness, length(position - 0.5)); - return mix(color, color * vignette, intensity); -} - -void main() { - // DEBUG: Render gl_FragCoord gradient - // finalColor = vec4(gl_FragCoord.x / resolution.x, gl_FragCoord.y / resolution.y, 0.0, 1.0); return; - - finalColor = vec4(0.0, 0.06, 0.1, 1.0); - - // DEBUG: Apply gamma correction - // finalColor.rgb = pow(finalColor.rgb, vec3(1.0 / 2.2)); - - // Apply vignette - finalColor.rgb = applyVignetteLinear(finalColor.rgb); -} \ No newline at end of file diff --git a/assets/shaders/particles.fs b/assets/shaders/particles.fs deleted file mode 100644 index 2fc53cb..0000000 --- a/assets/shaders/particles.fs +++ /dev/null @@ -1,22 +0,0 @@ -#version 330 - -precision mediump float; - -in vec2 fragTexCoord; -in vec4 fragColor; -out vec4 finalColor; -uniform sampler2D texture0; -uniform vec4 colDiffuse; - -void main() { - // Flip vertically to match OpenGL's texture coordinate system - vec4 texelColor = fragColor; - - // Apply color correction - texelColor = pow(texelColor, vec4(2.45)); - texelColor.r *= 0.95; - texelColor.g *= 0.95; - texelColor.b *= 1.05; - - finalColor = texelColor * colDiffuse; -} \ No newline at end of file diff --git a/assets/shaders/stars.fs b/assets/shaders/stars.fs deleted file mode 100644 index 3683916..0000000 --- a/assets/shaders/stars.fs +++ /dev/null @@ -1,55 +0,0 @@ -#version 330 - -precision mediump float; - -in vec2 fragTexCoord; -in vec4 fragColor; -out vec4 finalColor; -uniform sampler2D texture0; -uniform vec4 colDiffuse; - -// Bloom configuration constants -const int radius = 2; -const float threshold = 0.35; -const float intensity = 0.95; -const float size = 1.25; -const float PI = 3.14159; - -float calculateLuminance(vec4 color) { - return dot(color.rgb, vec3(0.2126, 0.7152, 0.0722)); -} - -void main() { - // Texel size - vec2 texelSize = 1.0 / textureSize(texture0, 0); - vec2 normalizedFragTexCoord = vec2(fragTexCoord.x, 1.0 - fragTexCoord.y); - - // Flip vertically to match OpenGL's texture coordinate system - vec4 texelColor = texture(texture0, normalizedFragTexCoord); - - // Apply gamma correction - texelColor = pow(texelColor, vec4(2.2)); - - // Calculate luminance - float luminance = calculateLuminance(texelColor); - - // Apply bloom - vec4 bloom = vec4(0.0); - for (int i = -radius; i <= radius; i++) { - for (int j = -radius; j <= radius; j++) { - vec2 offset = vec2(i, j) * texelSize * size; - vec4 sample = texture(texture0, normalizedFragTexCoord + offset); - float sampleLuminance = calculateLuminance(sample); - if (sampleLuminance > threshold) { - bloom += sample * intensity; - } - } - } - bloom /= (radius * radius * 4.0 + 2.0); - texelColor += bloom * intensity; - texelColor.a *= 0.75; - - // texelColor = vec4(1.0, 1.0, 0.0, 1.0); - - finalColor = texelColor * colDiffuse * fragColor; -} \ No newline at end of file diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 3ca1852..256bad4 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -76,13 +76,13 @@ function(include_library name version source) # Include if(EXISTS ${INSTALL_PATH}/${name}-${version}/CMakeLists.txt) - message(STATUS "Including library's CMakeLists.txt ${name} ${version}") + message(STATUS "Including library's directory ${name} ${version}") add_subdirectory(${INSTALL_PATH}/${name}-${version} ${CMAKE_CURRENT_BINARY_DIR}/${name}-${version}) elseif(EXISTS ${INSTALL_PATH}/${name}-${version}/projects/CMake/CMakeLists.txt) - message(STATUS "Including library's projects/CMake/CMakeLists.txt ${name} ${version}") + message(STATUS "Including library's directory ${name} ${version}") add_subdirectory(${INSTALL_PATH}/${name}-${version}/projects/CMake ${CMAKE_CURRENT_BINARY_DIR}/${name}-${version}) elseif(EXISTS ${INSTALL_PATH}/${name}-${version}/include) - message(STATUS "Including library 'include' folder ${name} ${version}") + message(STATUS "Including library ${name} ${version}") add_library(${name} INTERFACE) else() # Check if on the root there is .h, .hpp, .c or .cpp files diff --git a/scripts/assets.sh b/scripts/assets.sh deleted file mode 100644 index 269ad3b..0000000 --- a/scripts/assets.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -set -e - -# Get current directory -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -SOURCE_DIR=$DIR/assets -TARGET_DIR=$DIR/build/assets - -# Check if there is already a symlink to the assets directory -if [ -L $TARGET_DIR ]; then - echo "Symlink already exists" -else - echo "Creating symlink" - ln -s $SOURCE_DIR $TARGET_DIR - - if [ $? -eq 0 ]; then - echo "Symlink created" - else - echo "Symlink creation failed" - fi -fi \ No newline at end of file diff --git a/src/core/data/dummy-list.cpp b/src/core/data/dummy-list.cpp index f7afc2a..284ceae 100644 --- a/src/core/data/dummy-list.cpp +++ b/src/core/data/dummy-list.cpp @@ -37,6 +37,6 @@ std::vector> DummyList::retrieve(raylib::Vector2 posit return asteroids; } -std::vector> DummyList::getAll() { +std::vector> DummyList::all() { return asteroids; } \ No newline at end of file diff --git a/src/core/data/dummy-list.hpp b/src/core/data/dummy-list.hpp index 8ead2ec..b98a307 100644 --- a/src/core/data/dummy-list.hpp +++ b/src/core/data/dummy-list.hpp @@ -22,5 +22,5 @@ class DummyList : public IContainer { void insert(std::shared_ptr asteroid) override; void remove(std::shared_ptr asteroid) override; std::vector> retrieve(raylib::Vector2 position, uint16_t radius) override; - std::vector> getAll() override; + std::vector> all() override; }; \ No newline at end of file diff --git a/src/core/data/icontainer.hpp b/src/core/data/icontainer.hpp index 516753d..b5fbd0d 100644 --- a/src/core/data/icontainer.hpp +++ b/src/core/data/icontainer.hpp @@ -9,12 +9,11 @@ class IContainer { virtual void clear() = 0; virtual bool isEmpty() = 0; virtual uint32_t size() = 0; - virtual bool isInitialized() = 0; virtual uint16_t getCellIndex(raylib::Vector2 position) = 0; virtual raylib::Vector2 getCellPosition(uint16_t index) = 0; virtual void resize(uint16_t rows, uint16_t cols) = 0; virtual void insert(std::shared_ptr asteroid) = 0; virtual void remove(std::shared_ptr asteroid) = 0; virtual std::vector> retrieve(raylib::Vector2 position, uint16_t radius) = 0; - virtual std::vector> getAll() = 0; + virtual std::vector> all() = 0; }; \ No newline at end of file diff --git a/src/core/data/spatial-hash-grid.cpp b/src/core/data/spatial-hash-grid.cpp index 78b972f..27f7c8a 100644 --- a/src/core/data/spatial-hash-grid.cpp +++ b/src/core/data/spatial-hash-grid.cpp @@ -1,39 +1,27 @@ #include "spatial-hash-grid.hpp" #include "../settings.hpp" #include "../models/asteroid.hpp" -#include "Color.hpp" -#include "Rectangle.hpp" -#include "icontainer.hpp" -#include "raylib.h" -#include void SpatialHashGrid::clear() { for (auto &cell : cells) { cell.asteroids.resize(0); } - cache.clear(); + count = 0; } bool SpatialHashGrid::isEmpty() { - return cells.empty() || cache.empty(); -} - -bool SpatialHashGrid::isInitialized() { - return !cells.empty(); + return cells.empty() || count <= 0; } uint32_t SpatialHashGrid::size() { - return cache.size(); + return count; } uint16_t SpatialHashGrid::getCellIndex(raylib::Vector2 position) { - uint16_t row = position.y / cellSize; - uint16_t col = position.x / cellSize; - if (row < 0) row = 0; - else if (row >= rows) row = rows - 1; - if (col < 0) col = 0; - else if (col >= cols) col = cols - 1; - return row * cols + col; + uint16_t index = (uint16_t)(position.y / cellSize) * cols + (uint16_t)(position.x / cellSize); + if (index < 0) index = 0; + if (index >= cells.size()) index = cells.size() - 1; + return index; } raylib::Vector2 SpatialHashGrid::getCellPosition(uint16_t index) { @@ -41,11 +29,9 @@ raylib::Vector2 SpatialHashGrid::getCellPosition(uint16_t index) { } void SpatialHashGrid::resize(uint16_t rows, uint16_t cols) { - std::lock_guard lock(mutex); - - const auto backup = cache; - cache.clear(); + const auto backup = all(); cells.clear(); + count = 0; cells.resize(rows * cols); this->rows = rows; @@ -53,50 +39,36 @@ void SpatialHashGrid::resize(uint16_t rows, uint16_t cols) { cellSize = std::min(ceilf(HEIGHT / (float)rows), ceilf(WIDTH / (float)cols)); - for (auto &asteroid : backup) insert(asteroid); - - // Assign color palette - for (uint16_t row = 0; row < rows; row++) { - for (uint16_t col = 0; col < cols; col++) { - const float index = (row * cols + col) / (float)(rows * cols) * 240.0f; - cells[index].color = raylib::Color::FromHSV(index, 0.5f, 0.5f); - } + for (auto &asteroid : backup) { + insert(asteroid); } } void SpatialHashGrid::insert(std::shared_ptr asteroid) { - std::lock_guard lock(mutex); - auto index = getCellIndex(asteroid->position); - asteroid->gridCellIndex = index; + asteroid->index = index; asteroid->color = cells[index].color; cells[index].asteroids.push_back(asteroid); - cache.push_back(asteroid); + count++; } void SpatialHashGrid::remove(std::shared_ptr asteroid) { - std::lock_guard lock(mutex); - auto index = getCellIndex(asteroid->position); - asteroid->gridCellIndex = -1; + asteroid->index = -1; cells[index].asteroids.remove(asteroid); - cache.erase(std::remove(cache.begin(), cache.end(), asteroid), cache.end()); + count--; } void SpatialHashGrid::update() { - std::lock_guard lock(mutex); - for (auto &cell : cells) { auto it = cell.asteroids.begin(); while (it != cell.asteroids.end()) { - const auto &asteroid = *it; - const uint16_t currentIndex = asteroid->gridCellIndex; - const uint16_t newIndex = getCellIndex(asteroid->position); - if (currentIndex != newIndex) { - asteroid->gridCellIndex = newIndex; - asteroid->color = cells[newIndex].color; - cells[newIndex].asteroids.push_back(asteroid); - it = cell.asteroids.erase(it); + uint16_t index = getCellIndex(it->get()->position); + if (index != it->get()->index) { + it->get()->index = index; + it->get()->color = cell.color; + cells[index].asteroids.push_back(*it); + it = cells[it->get()->index].asteroids.erase(it); } else { it++; } @@ -105,8 +77,6 @@ void SpatialHashGrid::update() { } std::vector> SpatialHashGrid::retrieve(raylib::Vector2 position, uint16_t radius) { - std::lock_guard lock(mutex); - // Get the adjacent cells based on the radius uint16_t startRow = std::max(0, (int)(position.y - radius) / cellSize); uint16_t endRow = std::min(rows - 1, (int)(position.y + radius) / cellSize); @@ -117,10 +87,8 @@ std::vector> SpatialHashGrid::retrieve(raylib::Vector2 for (uint16_t row = startRow; row <= endRow; row++) { for (uint16_t col = startCol; col <= endCol; col++) { uint16_t index = row * cols + col; - if (index >= cells.size() || index < 0) continue; - for (auto &asteroid : cells[index].asteroids) { - if (asteroid && position.Distance(asteroid->position) <= radius) { + if (position.Distance(asteroid->position) <= radius) { result.push_back(asteroid); } } @@ -130,19 +98,19 @@ std::vector> SpatialHashGrid::retrieve(raylib::Vector2 return result; } -std::vector> SpatialHashGrid::getAll() { - return cache; +std::vector> SpatialHashGrid::all() { + std::vector> result; + for (auto &cell : cells) { + result.insert(result.end(), cell.asteroids.begin(), cell.asteroids.end()); + } + return result; } void SpatialHashGrid::render() { for (uint16_t row = 0; row < rows; row++) { for (uint16_t col = 0; col < cols; col++) { - const uint16_t index = row * cols + col; - const raylib::Rectangle rect(col * cellSize, row * cellSize, cellSize, cellSize); - DrawRectangleLinesEx(rect, 1.0f, cells[index].color); - cells[index].color.SetA(50); - DrawRectangleRec(rect, cells[index].color); - cells[index].color.SetA(255); + uint16_t index = row * cols + col; + DrawRectangleLines(col * cellSize, row * cellSize, cellSize, cellSize, cells[index].color); DrawText(TextFormat("%d", index), col * cellSize + 5, row * cellSize + 5, 10, WHITE); } } diff --git a/src/core/data/spatial-hash-grid.hpp b/src/core/data/spatial-hash-grid.hpp index 6afdc37..ef5e69d 100644 --- a/src/core/data/spatial-hash-grid.hpp +++ b/src/core/data/spatial-hash-grid.hpp @@ -3,7 +3,6 @@ #include "../precomp.hpp" #include "icontainer.hpp" #include "../utils.hpp" -#include class SpatialHashGrid : public IContainer { private: @@ -11,9 +10,8 @@ class SpatialHashGrid : public IContainer { std::list> asteroids; raylib::Color color = randomColor(); }; - std::vector> cache; std::vector cells; - std::mutex mutex; + uint16_t count; public: uint16_t rows; @@ -25,7 +23,6 @@ class SpatialHashGrid : public IContainer { void clear() override; bool isEmpty() override; - bool isInitialized() override; uint32_t size() override; uint16_t getCellIndex(raylib::Vector2 position) override; raylib::Vector2 getCellPosition(uint16_t index) override; @@ -34,6 +31,6 @@ class SpatialHashGrid : public IContainer { void remove(std::shared_ptr asteroid) override; void update(); std::vector> retrieve(raylib::Vector2 position, uint16_t radius) override; - std::vector> getAll() override; + std::vector> all() override; void render(); }; \ No newline at end of file diff --git a/src/core/main.cpp b/src/core/main.cpp index 2ce78c1..566d66d 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -1,21 +1,54 @@ #include "precomp.hpp" +#include "raylib.h" +#include "settings.hpp" #include "models/app.hpp" -#if defined(PLATFORM_DESKTOP) - #define GLSL_VERSION 330 -#else // PLATFORM_ANDROID, PLATFORM_WEB - #define GLSL_VERSION 100 -#endif +App app; + +void render() { + BeginDrawing(); + ClearBackground(BLACK); + app.renderGUI(); + EndDrawing(); + + app.update(); + app.render(); +} int main() { - /* int* p = NULL; - *p = 0; */ + SetConfigFlags(FLAG_MSAA_4X_HINT | FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_HIGHDPI); + +#ifdef PLATFORM_WEB + InitWindow(WIDTH, HEIGHT, "Asteroids"); +#else + const int monitor = GetCurrentMonitor(); + if (monitor > 0) { + WIDTH = GetMonitorWidth(monitor) / 2.0f; + HEIGHT = GetMonitorHeight(monitor) / 2.0f; + TARGET_FPS = GetMonitorRefreshRate(monitor); + + TraceLog(LOG_INFO, "Monitor: %i", monitor); + TraceLog(LOG_INFO, "Viewport: %i, %i", WIDTH, HEIGHT); + TraceLog(LOG_INFO, "Refresh Rate: %i", TARGET_FPS); + } - char* p = new char[10]; + InitWindow(WIDTH, HEIGHT, "Asteroids"); + SetWindowMinSize(WIDTH, HEIGHT); +#endif + + app.setup(); + +#ifdef PLATFORM_WEB + emscripten_set_main_loop(render, 0, 1); +#else + SetTargetFPS(TARGET_FPS); + + while (!WindowShouldClose()) { + render(); + } +#endif - auto app = std::make_shared(); - app->setup(); - app->run(); + CloseWindow(); return 0; } \ No newline at end of file diff --git a/src/core/models/app.cpp b/src/core/models/app.cpp index bad9ac8..f759728 100644 --- a/src/core/models/app.cpp +++ b/src/core/models/app.cpp @@ -1,131 +1,57 @@ #include "app.hpp" #include "../settings.hpp" -#include "Shader.hpp" -#include "Vector2.hpp" +#include "RenderTexture.hpp" #include "asteroid.hpp" -#include "particle.hpp" +#include "imgui.h" +#include "polygon.hpp" #include "raylib.h" -#include "raymath.h" -#include "soundfx.hpp" -#include "wave.hpp" #include -#include - -App::App(): starsShader("assets/shaders/stars.fs"), particlesShader("assets/shaders/particles.fs"), allShader("assets/shaders/all.fs"), backgroundShader("assets/shaders/background.fs") { - TraceLog(LOG_INFO, "App::App()"); - - // Setup camera - camera.offset = raylib::Vector2::Zero(); - camera.target = raylib::Vector2::Zero(); - camera.zoom = 1.0f; - camera.rotation = 0.0f; -} +#include App::~App() { TraceLog(LOG_INFO, "App::~App()"); rlImGuiShutdown(); - SoundFX::release(); - CloseAudioDevice(); - CloseWindow(); } -void App::setup() { -#ifdef DEBUG - SetTraceLogLevel(LOG_DEBUG); -#endif - TraceLog(LOG_INFO, "App::setup()"); - - setupWindow(); - - // Shaders - backgroundShader.load(); - particlesShader.load(); - starsShader.load(); - allShader.load(); - - // Setup uniforms - uniformAberrationLocation = allShader.getUniformLocation("aberration"); - uniformAberrationValue = 0.0f; - uniformResolutionLocation = backgroundShader.getUniformLocation("resolution"); - uniformResolutionValue = raylib::Vector2((float)WIDTH, (float)HEIGHT); - - // Misc - setupGUI(); - wave.start(shared_from_this()); - SoundFX::setup(); +void App::shoot() { + Bullet bullet { + ship.position, + raylib::Vector2(cosf(ship.angle), sinf(ship.angle)) * BULLET_VELOCITY + }; + bullets.push_back(bullet); } -void App::setupWindow() { - SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_HIGHDPI); +void App::setup() { #ifdef DEBUG - SetConfigFlags(FLAG_WINDOW_TOPMOST); -#endif - -#ifdef PLATFORM_WEB - InitWindow(WIDTH, HEIGHT, "Asteroids"); -#else - const int monitor = GetCurrentMonitor(); - const int monitorCount = GetMonitorCount(); - TraceLog(LOG_INFO, "Main::main() - Monitor: %i of %i", monitor, monitorCount); - if (monitor >= 0 && monitorCount > 0) { - WIDTH = GetMonitorWidth(monitor) / 2.0f; - HEIGHT = GetMonitorHeight(monitor) / 2.0f; - TARGET_FPS = GetMonitorRefreshRate(monitor); - - TraceLog(LOG_INFO, "Main::main() - Monitor id: %i", monitor); - TraceLog(LOG_INFO, "Main::main() - Monitor viewport: %ix%i", WIDTH, HEIGHT); - TraceLog(LOG_INFO, "Main::main() - Monitor refresh Rate: %i", TARGET_FPS); - } - - InitWindow(WIDTH, HEIGHT, "Asteroids"); - SetWindowMinSize(WIDTH, HEIGHT); -#endif - - InitAudioDevice(); -} - -void App::run() { -#ifdef PLATFORM_WEB - emscripten_set_main_loop(loop, 0, 1); -#else - SetTargetFPS(TARGET_FPS); - while (!WindowShouldClose()) { - loop(); - } + SetTraceLogLevel(LOG_DEBUG); #endif -} -void App::setupStars() { - const size_t count = 150; + TraceLog(LOG_INFO, "App::setup()"); - stars.clear(); - stars.reserve(count); + rlImGuiSetup(true); + ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_DockingEnable; + ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - for (size_t i = 0; i < count; i++) { - stars.emplace_back( - raylib::Vector2( - getRandomValue(0, WIDTH), - getRandomValue(0, HEIGHT) - ), - getRandomValue(0.75f, 4.5f) * 1.25f - ); - } + ship.setup(); } void App::updateAsteroids() { Asteroid::collisionCount = 0; - const int substeps = 1; - const float dt = GetFrameTime() / substeps; + for (auto &asteroid : asteroids.all()) asteroid->update(asteroids); - for (auto &asteroid : grid.getAll()) asteroid->update(grid, dt); - - grid.update(); + asteroids.update(); } void App::updateBullets() { + if (shootTimer > BULLET_SHOOT_INTERVAL) shootTimer = BULLET_SHOOT_INTERVAL; + else shootTimer += GetFrameTime(); + if (shootTimer >= BULLET_SHOOT_INTERVAL && IsKeyDown(KEY_SPACE)) { + shoot(); + shootTimer -= BULLET_SHOOT_INTERVAL; + } + for (auto it = bullets.begin(); it != bullets.end();) { - if (it->update(grid)) { - shake(0.25f); + if (it->update(asteroids)) { it = bullets.erase(it); } else { it++; @@ -133,165 +59,32 @@ void App::updateBullets() { } } -void App::updateStars(raylib::Vector2 &offset) { - if (fabsf(offset.x) < WIDTH / 2.0f && fabsf(offset.y) < HEIGHT / 2.0f) { - for (auto &star : stars) { - star.update(offset); - } - } -} - -void App::updateScreenShake() { - - if (IsKeyDown(KEY_ENTER)) trauma += 0.15f; - if (trauma >= 1.0f) trauma = 1.0f; - - if (trauma <= EPSILON) return; - - const float traumaPower = powf(trauma, 2.0f); - const auto center = raylib::Vector2(WIDTH / 2.0f, HEIGHT / 2.0f); - cameraShakeOffset = raylib::Vector2(GetRandomValue(-1, 1), GetRandomValue(-1, 1)) * traumaPower * 25.5f; - cameraShakeRotation = GetRandomValue(-1, 1) * traumaPower * 15.5f; - - camera.offset = Vector2Lerp(camera.offset, cameraShakeOffset + center, GetFrameTime() * 25.0f); - camera.rotation = Lerp(camera.rotation, cameraShakeRotation, GetFrameTime() * 10.0f); - camera.zoom = Lerp(camera.zoom, /* 1.0f + */ 1.0f - traumaPower * 0.1f, GetFrameTime() * 10.0f); - - uniformAberrationValue = traumaPower * 5.0f; - allShader.setUniform(uniformAberrationLocation, &uniformAberrationValue, ShaderUniformDataType::SHADER_UNIFORM_FLOAT); - - trauma *= 0.92f; - if (trauma <= EPSILON) { - trauma = 0.0f; - camera.offset = center; - camera.rotation = 0.0f; - camera.zoom = 1.0f; - } -} - -void App::shake(float amount) { - trauma += amount; - if (trauma >= 1.0f) trauma = 1.0f; -} - -void App::loop() { - BeginDrawing(); - ClearBackground(BLACK); - renderGUI(); - EndDrawing(); - - update(); - render(); -} - -static long lastShaderUpdateTime = 0; - void App::update() { - profiler.onFrameStart(); - updateScreenShake(); updateAsteroids(); updateBullets(); - ParticleSystem::update(); - - auto offset = ship.position; - ship.update(bullets); - offset = ship.position - offset; - updateStars(offset); - -#ifdef DEBUG - backgroundShader.autoReloadIfNeeded(); - particlesShader.autoReloadIfNeeded(); - starsShader.autoReloadIfNeeded(); - allShader.autoReloadIfNeeded(); - - backgroundShader.setUniform(uniformResolutionLocation, &uniformResolutionValue, ShaderUniformDataType::SHADER_UNIFORM_VEC2); -#endif - - // TODO: Research bout the spaceship that appears sometimes, is it an enemy? - - // TODO: Move to function, and maybe ship - // TODO: Improve particles - // const auto reflectedAngle = ship.angle + PI; - // const auto shipDirection = raylib::Vector2(cosf(reflectedAngle), sinf(reflectedAngle)); - // const auto shipBottom = ship.position + shipDirection * 10.0f; - // const int count = 5; - // for (int i = 0; i < count; i++) { - // const float angle = (180.0f / count * i) * DEG2RAD + ship.angle + PI / 2.0f + getRandomValue(-0.1f, 0.1f); - // const auto direction = raylib::Vector2(cosf(angle), sinf(angle)); - // const auto position = shipBottom; - // const auto velocity = direction * ship.velocity; - // particles.emplace_back( - // position, - // velocity, - // LIGHTGRAY, - // 2.0f, - // 0.25f - // ); - // } + ship.update(); + wave.update(asteroids, bullets, ship); } void App::render() { - if (!frameBuffer.IsReady() || !tempFrameBuffer.IsReady()) { - TraceLog(LOG_WARNING, "App::render() - Frame buffer not ready"); - return; - } - - // First pass is drawing the stars to a temporary frame buffer - tempFrameBuffer.BeginMode(); - ClearBackground(raylib::Color(0, 0, 0, 0)); - - tempFrameBuffer.EndMode(); - - // Second pass is drawing the game to the main frame buffer frameBuffer.BeginMode(); + ClearBackground(BLACK); - // Render background - backgroundShader.beginMode(); - DrawRectangle(0, 0, WIDTH, HEIGHT, WHITE); - backgroundShader.endMode(); - - camera.BeginMode(); - - // Render stars - starsShader.beginMode(); - tempFrameBuffer.GetTexture().Draw(0, 0, WHITE); - starsShader.endMode(); - - for (auto &star : stars) star.render(); - - // DEBUG: Render debug grid overlay - if (RENDER_GRID) grid.render(); - - // Render asteroids - for (auto &asteroid : grid.getAll()) asteroid->render(); + for (auto &asteroid : asteroids.all()) asteroid->render(); - // Render bullets for (auto &bullet : bullets) bullet.render(); - // Render particles - particlesShader.beginMode(); - ParticleSystem::render(); - particlesShader.endMode(); - - camera.EndMode(); - frameBuffer.EndMode(); - - // Third pass is drawing the frame buffer to the screen - tempFrameBuffer.BeginMode(); - allShader.beginMode(); - frameBuffer.GetTexture().Draw(0, 0, WHITE); - allShader.endMode(); - // Render space ship ship.render(); - tempFrameBuffer.EndMode(); - profiler.onFrameEnd(); + // Render grid + asteroids.render(); + + frameBuffer.EndMode(); } void App::onResize(uint32_t width, uint32_t height) { TraceLog(LOG_INFO, "App::onResize(%i, %i)", width, height); frameBuffer = raylib::RenderTexture2D(width, height); - tempFrameBuffer = raylib::RenderTexture2D(width, height); // Update global settings WIDTH = width; @@ -299,21 +92,5 @@ void App::onResize(uint32_t width, uint32_t height) { // Update the grid const auto size = (float)ASTEROID_RADIUS * 4; - grid.resize(ceilf(height / size), ceilf(width / size)); - - // Update the camera - camera.offset = raylib::Vector2(width / 2.0f, height / 2.0f); - camera.target = raylib::Vector2(width / 2.0f, height / 2.0f); - - // Update the uniforms - uniformResolutionValue = raylib::Vector2((float)width, (float)height); - backgroundShader.setUniform(uniformResolutionLocation, &uniformResolutionValue, ShaderUniformDataType::SHADER_UNIFORM_VEC2); - - // Update the ship - if (!isViewportDefined) { - isViewportDefined = true; - ship.setup(); - } - - setupStars(); + asteroids.resize(ceilf(HEIGHT / size), ceilf(WIDTH / size)); } diff --git a/src/core/models/app.hpp b/src/core/models/app.hpp index 8632f17..110459c 100644 --- a/src/core/models/app.hpp +++ b/src/core/models/app.hpp @@ -1,74 +1,34 @@ #pragma once #include "../precomp.hpp" -#include "Vector2.hpp" #include "bullet.hpp" -#include "hotreload-shader.hpp" -#include "particle.hpp" -#include "profiler.hpp" #include "ship.hpp" #include "asteroid.hpp" -#include "star.hpp" #include "wave.hpp" -#include "particle.hpp" #include "../data/spatial-hash-grid.hpp" -class App : public std::enable_shared_from_this { +class App { private: - raylib::Camera2D camera; raylib::RenderTexture2D frameBuffer; - raylib::RenderTexture2D tempFrameBuffer; - // Shaders - HotReloadShader starsShader; - HotReloadShader particlesShader; - HotReloadShader backgroundShader; - HotReloadShader allShader; + SpatialHashGrid asteroids; + std::list bullets; + float shootTimer; + Ship ship; - // Uniforms - int uniformResolutionLocation = -1; - raylib::Vector2 uniformResolutionValue; - int uniformAberrationLocation = -1; - float uniformAberrationValue = 0.0f; - - Profiler profiler; WaveController wave; - float trauma; - raylib::Vector2 cameraShakeOffset; - float cameraShakeRotation; - - bool isGUIBuilt; - bool isViewportDefined; - - void setupWindow(); - void loop(); - + void shoot(); void updateAsteroids(); void updateBullets(); - void setupStars(); - - void setupGUI(); - void buildGUIDockSpace(); - - void updateStars(raylib::Vector2 &offset); - void update(); - void render(); - void renderGUI(); - void onResize(uint32_t width, uint32_t height); - void updateScreenShake(); - public: - SpatialHashGrid grid; - std::list bullets; - std::vector stars; - Ship ship; - - App(); + App() = default; ~App(); void setup(); - void run(); - void shake(float amount); + void update(); + void render(); + void onResize(uint32_t width, uint32_t height); + void renderGUI(); }; \ No newline at end of file diff --git a/src/core/models/asteroid.cpp b/src/core/models/asteroid.cpp index bc0f6b0..cdc535e 100644 --- a/src/core/models/asteroid.cpp +++ b/src/core/models/asteroid.cpp @@ -1,208 +1,211 @@ #include "asteroid.hpp" #include "../utils.hpp" -#include "Vector2.hpp" -#include "collision-handler.hpp" -#include "raylib.h" -#include "raymath.h" -#include -#include - -// Static +#include "polygon.hpp" + size_t Asteroid::idCounter = 0; uint32_t Asteroid::collisionCount = 0; -// Utility -void Asteroid::updatePhysics(float deltaTime) { - position += velocity * deltaTime; - angle += angularVelocity * deltaTime; +#pragma region SAT +struct CollisionInfo { + raylib::Vector2 normal; + float penetration; +}; + +struct Projection { + float min; + float max; +}; + +inline std::vector getGlobalVertices(const Asteroid &a) { + std::vector vertices(a.polygon.vertices.size()); + for (size_t i = 0; i < a.polygon.vertices.size(); i++) { + vertices[i] = rotateAround(a.position + a.polygon.vertices[i], a.position, a.angle); + } + return vertices; } -void Asteroid::wrapAroundScreen() { - const float padding = radius * 3; - - if (position.x - padding > WIDTH) { - position.x = -padding; - } else if (position.x < -padding) { - position.x = WIDTH + padding; - } - - if (position.y - padding > HEIGHT) { - position.y = -padding; - } else if (position.y < -padding) { - position.y = HEIGHT + padding; - } +void getAxes(const std::vector &vertices, std::vector &axes) { + for (size_t i = 0; i < vertices.size(); i++) { + const auto p1 = vertices[i]; + const auto p2 = vertices[(i + 1) % vertices.size()]; + const auto edge = p1 - p2; + axes.emplace_back(-edge.y, edge.x); + } } -void Asteroid::solveCollisions(IContainer &grid) { - if (!ASTEROIDS_SELF_COLLISION) return; - - const auto others = grid.retrieve(position, radius * 2); - for (auto &other : others) { - if (id == other->id) continue; - if (fabsf(radius - other->radius) > 7.5f) continue; - - Asteroid::collisionCount++; - - // Time to bring out the big guns - const auto contact = CollisionHandler::getCollisionInfo(*this, *other.get()); - if (contact.has_value()) { - // Move the asteroids apart - const auto correction = contact->normal * contact->penetration; - position -= correction / 2.0f; - other->position += correction / 2.0f; - - // Calculate the new velocities - const auto relativeVelocity = velocity - other->velocity; - const auto velocityAlongNormal = relativeVelocity.DotProduct(contact->normal); - - // Use asteroid.radius / ASTEROID_RADIUS for mass - const float j = -(1 + ASTEROID_RESTITUTION) * velocityAlongNormal; - const float mass1 = 1.0f + radius / ASTEROID_RADIUS; - const float mass2 = 1.0f + other->radius / ASTEROID_RADIUS; - const float impulse = j / (mass1 + mass2); - velocity += contact->normal * impulse * mass1; - other->velocity -= contact->normal * impulse * mass2; - - // Ensure the asteroids are not stationary - if (velocity.Length() < EPSILON) velocity = raylib::Vector2::One() * getRandomValue(-1.0f, 1.0f) * ASTEROID_VELOCITY; - if (other->velocity.Length() < EPSILON) other->velocity = raylib::Vector2::One() * getRandomValue(-1.0f, 1.0f) * ASTEROID_VELOCITY; - - // Ensure the asteroids are not too fast - if (velocity.Length() > ASTEROID_VELOCITY) velocity = velocity.Normalize() * ASTEROID_VELOCITY; - if (other->velocity.Length() > ASTEROID_VELOCITY) other->velocity = other->velocity.Normalize() * ASTEROID_VELOCITY; - - // Calculate the new angular velocities - const float angularVelocityAlongNormal = angularVelocity - other->angularVelocity; - const float torque = angularVelocityAlongNormal * mass1; - const float angularImpulse = torque / (mass1 + mass2); - angularVelocity -= angularImpulse * mass1; - other->angularVelocity += angularImpulse * mass2; - - // Ensure the asteroids are not stationary - if (fabsf(angularVelocity) < 0.1f) angularVelocity = getRandomValue(-1.0f, 1.0f) * ASTEROID_ANGULAR_VELOCITY; - if (fabsf(other->angularVelocity) < 0.1f) other->angularVelocity = getRandomValue(-1.0f, 1.0f) * ASTEROID_ANGULAR_VELOCITY; - - // Ensure the asteroids are not too fast - if (fabsf(angularVelocity) > ASTEROID_ANGULAR_VELOCITY) angularVelocity = ASTEROID_ANGULAR_VELOCITY * (angularVelocity < 0 ? -1 : 1); - if (fabsf(other->angularVelocity) > ASTEROID_ANGULAR_VELOCITY) other->angularVelocity = ASTEROID_ANGULAR_VELOCITY * (other->angularVelocity < 0 ? -1 : 1); - } - } +Projection project(const std::vector &vertices, const raylib::Vector2 &axis) { + float min = axis.DotProduct(vertices[0]); + float max = min; + for (size_t i = 1; i < vertices.size(); i++) { + const float p = axis.DotProduct(vertices[i]); + if (p < min) min = p; + else if (p > max) max = p; + } + return { min, max }; } -void Asteroid::updateAnimations() { - float animationSpeed = 2.0f; // 500ms +std::optional getCollisionInfo(const Asteroid &a, const Asteroid &b) { + auto normal = raylib::Vector2::Zero(); + float overlap = std::numeric_limits::max(); - if (radius < ASTEROID_RADIUS) { - animationSpeed = 4.0f; // 250ms - } + // Get the global world position of the vertices + const auto verticesA = getGlobalVertices(a); + const auto verticesB = getGlobalVertices(b); - // Scale up the asteroid - scale += GetFrameTime() * radius; // 500ms - if (scale > 1.0f) scale = 1.0f; -} + // Get the axes of the polygons + std::vector axes; + getAxes(verticesA, axes); + getAxes(verticesB, axes); -void Asteroid::updateVertices() { - // After updating the position and angle, update the vertices of the asteroid - // Translating and rotating the vertices but keeping the same shape - const float diffAngle = angle - lastAngle; - for (size_t i = 0; i < vertices.size(); i++) { - // Translate the vertex to the new position - vertices[i] -= lastPosition; + // Iterate through the axes + for (auto it = axes.begin(); it != axes.end(); it++) { + const auto axis = *it; + const auto projectionA = project(verticesA, axis); + const auto projectionB = project(verticesB, axis); - // Rotate the vertex around the center of the asteroid - vertices[i] = vertices[i].Rotate(diffAngle); - vertices[i] += position; - } + // Detected a gap, no collision + if (projectionA.max < projectionB.min || projectionB.max < projectionA.min) { + return std::nullopt; + } + // Calculate the overlap + const float depth = std::min(projectionA.max, projectionB.max) - std::max(projectionA.min, projectionB.min); + if (depth < overlap) { + overlap = depth; + normal = axis; + } + } - lastPosition = position; - lastAngle = angle; -} + // Normalize the depth and normal + const auto length = normal.Length(); + normal /= length; + overlap /= length; -void Asteroid::generateVertices(float radius, uint8_t vertexCount) { - this->radius = 0.0f; - innerRadius = 0.0f; + // If the normal is pointing from A to B, invert it + const auto center = b.position - a.position; + if (center.DotProduct(normal) < 0) { + normal = -normal; + } - float scale = radius / ASTEROID_RADIUS; - for (uint8_t i = 0; i < vertexCount; i++) { - float angle = (i / (float)vertexCount) * 2.0f * PI; + return CollisionInfo{ normal, overlap }; +} +#pragma endregion - float r = radius + GetRandomValue(-ASTEROID_JAGGEDNESS * scale, ASTEROID_JAGGEDNESS * scale); +void Asteroid::updatePhysics() { + const auto deltaTime = GetFrameTime(); - // Keep track of the largest radius - if (this->radius == 0.0f || r > this->radius) this->radius = r; + position += velocity * deltaTime; + angle += angularVelocity * deltaTime; +} - // Keep track of the smallest radius - if (innerRadius == 0.0f || r < innerRadius) innerRadius = r; +void Asteroid::wrapAroundScreen() { + if (position.x - polygon.outerRadius > WIDTH) { + position.x = -polygon.outerRadius; + } else if (position.x < -polygon.outerRadius) { + position.x = WIDTH + polygon.outerRadius; + } - vertices.emplace_back(position.x + cosf(angle) * r, position.y + sinf(angle) * r); + if (position.y - polygon.outerRadius > HEIGHT) { + position.y = -polygon.outerRadius; + } else if (position.y < -polygon.outerRadius) { + position.y = HEIGHT + polygon.outerRadius; } } -// Public API -Asteroid::Asteroid(raylib::Vector2 position, float radius) { - id = Asteroid::idCounter++; - this->position = position; - generateVertices(radius, GetRandomValue(ASTEROID_MIN_VERTEX_COUNT, ASTEROID_MAX_VERTEX_COUNT)); - - // Generate random values - velocity = raylib::Vector2::One() * getRandomValue(-1.0f, 1.0f) * ASTEROID_VELOCITY; - angularVelocity = getRandomValue(-1.0f, 1.0f) * ASTEROID_ANGULAR_VELOCITY; - - // Store the initial position and angle - lastPosition = position; - lastAngle = angle; -} +void Asteroid::checkForCollisions(IContainer &asteroids) { + const auto others = asteroids.retrieve(position, polygon.outerRadius * 2); + for (auto &other : others) { + if (id == other->id) continue; + + // Check if asteroids are close enough to collide + // const float distanceLength = (position - other->position).Length(); + // const float minimumDistance = powf(polygon.outerRadius + other->polygon.outerRadius, 2.0f); + // if (distanceLength > minimumDistance) continue; + + // Time to bring out the big guns + const auto contact = getCollisionInfo(*this, *other.get()); + if (contact.has_value()) { + Asteroid::collisionCount++; + // Move the asteroids apart + const auto correction = contact->normal * contact->penetration; + position -= correction / 2.0f; + other->position += correction / 2.0f; + + // Calculate the new velocities + const auto relativeVelocity = velocity - other->velocity; + const auto velocityAlongNormal = relativeVelocity.DotProduct(contact->normal); + + // Use asteroid.radius / ASTEROID_RADIUS for mass + const float e = 1.0f; // Coefficient of restitution + const float j = -(1 + e) * velocityAlongNormal; + const float mass1 = polygon.outerRadius / ASTEROID_RADIUS; + const float mass2 = other->polygon.outerRadius / ASTEROID_RADIUS; + const float impulse = j / (mass1 + mass2); + velocity += contact->normal * impulse * mass1; + other->velocity -= contact->normal * impulse * mass2; + + // Ensure the asteroids are not stationary + if (velocity.Length() < 0.1f) velocity = raylib::Vector2::One() * getRandomValue(-1.0f, 1.0f) * ASTEROID_VELOCITY; + if (other->velocity.Length() < 0.1f) other->velocity = raylib::Vector2::One() * getRandomValue(-1.0f, 1.0f) * ASTEROID_VELOCITY; + + // Ensure the asteroids are not too fast + if (velocity.Length() > ASTEROID_VELOCITY) velocity = velocity.Normalize() * ASTEROID_VELOCITY; + if (other->velocity.Length() > ASTEROID_VELOCITY) other->velocity = other->velocity.Normalize() * ASTEROID_VELOCITY; + + // Calculate the new angular velocities + const float angularVelocityAlongNormal = angularVelocity - other->angularVelocity; + const float torque = angularVelocityAlongNormal * mass1; + const float angularImpulse = torque / (mass1 + mass2); + angularVelocity -= angularImpulse * mass1; + other->angularVelocity += angularImpulse * mass2; + + // Ensure the asteroids are not stationary + if (fabsf(angularVelocity) < 0.1f) angularVelocity = getRandomValue(-1.0f, 1.0f) * ASTEROID_ANGULAR_VELOCITY; + if (fabsf(other->angularVelocity) < 0.1f) other->angularVelocity = getRandomValue(-1.0f, 1.0f) * ASTEROID_ANGULAR_VELOCITY; + + // Ensure the asteroids are not too fast + if (fabsf(angularVelocity) > ASTEROID_ANGULAR_VELOCITY) angularVelocity = ASTEROID_ANGULAR_VELOCITY * (angularVelocity < 0 ? -1 : 1); + if (fabsf(other->angularVelocity) > ASTEROID_ANGULAR_VELOCITY) other->angularVelocity = ASTEROID_ANGULAR_VELOCITY * (other->angularVelocity < 0 ? -1 : 1); -std::vector> Asteroid::split() { - std::vector> newAsteroids; - - // Split the asteroid into two - if (radius >= ASTEROID_MIN_RADIUS_TO_SPLIT) { - const uint8_t fragmentCount = GetRandomValue(2, 4); - float newRadius = radius / fragmentCount; - if (newRadius < ASTEROID_MIN_FRAGMENT_RADIUS) newRadius = ASTEROID_MIN_FRAGMENT_RADIUS; - - for (int i = 0; i < fragmentCount; i++) { - const float angle = 360.0f * (i / (float)fragmentCount) * DEG2RAD; - const auto newPosition = position + raylib::Vector2(cosf(angle), sinf(angle)) * newRadius; - auto newAsteroid = std::make_shared(newPosition, newRadius); - newAsteroid->velocity = raylib::Vector2(cosf(angle), sinf(angle)) * getRandomValue(-1.0f, 1.0f) * ASTEROID_VELOCITY * 2.0f; - // newAsteroid->scale = 1.0f; - newAsteroids.push_back(newAsteroid); } } +} - return newAsteroids; +void Asteroid::updateAnimations() { + // Scale up the asteroid + scale += GetFrameTime() * 2.0f; // 500ms + if (scale > 1.0f) scale = 1.0f; } -void Asteroid::update(IContainer &others, float deltaTime) { - updatePhysics(deltaTime); - updateVertices(); +Asteroid::Asteroid() { + id = Asteroid::idCounter++; + polygon.generateVertices(ASTEROID_RADIUS, GetRandomValue(ASTEROID_MIN_VERTEX_COUNT, ASTEROID_MAX_VERTEX_COUNT)); + position = raylib::Vector2(GetRandomValue(0, WIDTH), GetRandomValue(0, HEIGHT)); + velocity = raylib::Vector2::One() * getRandomValue(-1.0f, 1.0f) * ASTEROID_VELOCITY; + angle = getRandomValue(0.0f, 360.0f) * DEG2RAD; + angularVelocity = getRandomValue(-1.0f, 1.0f) * ASTEROID_ANGULAR_VELOCITY; +} - if (scale >= 1.0f) - solveCollisions(others); - else - updateAnimations(); +Asteroid::Asteroid(Polygon &&polygon, raylib::Vector2 position) : polygon(polygon), position(position) { + id = Asteroid::idCounter++; + angle = getRandomValue(0.0f, 360.0f) * DEG2RAD; + angularVelocity = getRandomValue(-1.0f, 1.0f) * ASTEROID_ANGULAR_VELOCITY; + scale = 1.0f; +} - wrapAroundScreen(); +void Asteroid::update(IContainer &others) { + updatePhysics(); + if (scale >= 1.0f) checkForCollisions(others); + else updateAnimations(); + wrapAroundScreen(); } void Asteroid::render() { - // Check if the asteroid is inside the screen - if (position.x + radius < 0 || position.x - radius > WIDTH || position.y + radius < 0 || position.y - radius > HEIGHT) return; - - drawFilledPolygon(position, vertices.data(), vertices.size(), BLACK);// Only to occlude other asteroids - drawPolygonOutline(vertices.data(), vertices.size(), WHITE); - - if (RENDER_GRID) { - auto text = TextFormat("%i", gridCellIndex); - auto size = MeasureTextEx(GetFontDefault(), text, 12, 1); - DrawText(text, position.x - size.x / 2.0f, position.y - size.y / 2.0f, 12, LIGHTGRAY); - } + polygon.render(position, easeInOutBack(scale), angle, color); #ifdef DEBUG - /* DrawCircleLinesV(position, radius, LIGHTGRAY); - DrawCircleLinesV(position, innerRadius, GRAY); */ + auto text = TextFormat("%i", index); + auto size = MeasureTextEx(GetFontDefault(), text, 12, 1); + DrawText(text, position.x - size.x / 2.0f, position.y - size.y / 2.0f, 12, LIGHTGRAY); #endif } diff --git a/src/core/models/asteroid.hpp b/src/core/models/asteroid.hpp index 1164d98..79b4dbd 100644 --- a/src/core/models/asteroid.hpp +++ b/src/core/models/asteroid.hpp @@ -1,46 +1,40 @@ #pragma once -#include "../data/icontainer.hpp" #include "../precomp.hpp" -#include "Vector2.hpp" -#include "collision-handler.hpp" +#include "polygon.hpp" +#include "../data/icontainer.hpp" + +class Asteroid { -class Asteroid : public CollisionHandler::Shape { public: - static size_t idCounter; - static uint32_t collisionCount; + static size_t idCounter; + static uint32_t collisionCount; - // Identifiers - size_t id; - uint16_t gridCellIndex = 0; - raylib::Color color = raylib::Color::White(); + uint32_t index; + size_t id; - private: - float innerRadius; - - // Animation - float scale; - - // Physics - raylib::Vector2 velocity; - raylib::Vector2 lastPosition; - float lastAngle; - float angle; - float angularVelocity; - - // Utility - void updatePhysics(float deltaTime); - void solveCollisions(IContainer &grid); - void wrapAroundScreen(); - void updateAnimations(); - void updateVertices(); - void generateVertices(float radius, uint8_t vertexCount); + Polygon polygon; + raylib::Vector2 position; + raylib::Vector2 velocity; + float angle; + float angularVelocity; + raylib::Color color = raylib::Color::White(); + + // Animation + float scale; + + void updatePhysics(); + void wrapAroundScreen(); public: - Asteroid(raylib::Vector2 position, float radius); - ~Asteroid() = default; + Asteroid(); + Asteroid(Polygon &&polygon, raylib::Vector2 position); + ~Asteroid() = default; - std::vector> split(); - void update(IContainer &others, float deltaTime); - void render(); + void update(IContainer &others); + void render(); + + private: + void checkForCollisions(IContainer &others); + void updateAnimations(); }; \ No newline at end of file diff --git a/src/core/models/bullet.cpp b/src/core/models/bullet.cpp index d374a11..b4eccf0 100644 --- a/src/core/models/bullet.cpp +++ b/src/core/models/bullet.cpp @@ -1,31 +1,39 @@ #include "bullet.hpp" #include "../settings.hpp" #include "asteroid.hpp" -#include "particle.hpp" -#include "soundfx.hpp" -void Bullet::updatePhysics() { +bool Bullet::update(IContainer &asteroids) { + // Update physics position = position + velocity * GetFrameTime(); -} -bool Bullet::solveCollisions(IContainer &grid) { + // Check if outside screen bounds + if (position.x < 0 || position.x > WIDTH || position.y < 0 || position.y > HEIGHT) { + return true; + } + // Check for collisions - const auto list = grid.retrieve(position, ASTEROID_RADIUS * 2); + const auto list = asteroids.retrieve(position, ASTEROID_RADIUS * 2); for (auto &asteroid : list) { - if (position.Distance(asteroid->position) < asteroid->radius) { - if (asteroid->radius >= ASTEROID_MIN_RADIUS_TO_SPLIT) { - const auto newAsteroids = asteroid->split(); - for (auto &newAsteroid : newAsteroids) grid.insert(newAsteroid); + if (position.Distance(asteroid->position) < asteroid->polygon.outerRadius) { + if (asteroid->polygon.outerRadius >= ASTEROID_MIN_RADIUS_TO_SPLIT) { + // Split the asteroid + auto offset = raylib::Vector2::Zero(); + for (auto &poly : asteroid->polygon.split()) { + auto r = raylib::Vector2::One() * poly.outerRadius; + + const auto newAsteroid = std::make_shared(std::move(poly), asteroid->position + offset - r); + asteroids.insert(newAsteroid); + + // Calculate the velocity of the new asteroid + float angle = atan2f(newAsteroid->position.y - position.y, newAsteroid->position.x - position.x); + newAsteroid->velocity = asteroid->velocity + raylib::Vector2(cosf(angle), sinf(angle)) * ASTEROID_VELOCITY; + + offset += r; + } } - // Particles - ParticleSystem::addExplosion(position, 30, 0, 360); - - // Play sound - SoundFX::play(SoundFX::explosion, 0.25f, -1.0f, 1.0f - asteroid->position.x / (float)WIDTH); - // Kill the asteroid - grid.remove(asteroid); + asteroids.remove(asteroid); return true; } @@ -34,37 +42,6 @@ bool Bullet::solveCollisions(IContainer &grid) { return false; } -Bullet::Bullet(raylib::Vector2 position, raylib::Vector2 velocity) : position(position), velocity(velocity) { } - - -bool Bullet::update(IContainer &grid) { - timeAlive += GetFrameTime(); - if (timeAlive > BULLET_TIME_TO_LIVE) return true; - - updatePhysics(); - - // Check if outside screen bounds - if (BULLETS_WRAP_AROUND_SCREEN) { - if (position.x < 0) position.x = WIDTH; - if (position.x > WIDTH) position.x = 0; - if (position.y < 0) position.y = HEIGHT; - if (position.y > HEIGHT) position.y = 0; - } else if (position.x < 0 || position.x > WIDTH || position.y < 0 || position.y > HEIGHT) { - return true; - } - - if (solveCollisions(grid)) { - // TODO: Spawn particles - // TODO: Play sound - return true; - } - - // TODO: Emit particle tail - // TODO: Emit light - - return false; -} - void Bullet::render() { DrawCircleV(position, BULLET_RADIUS, WHITE); } diff --git a/src/core/models/bullet.hpp b/src/core/models/bullet.hpp index 753fcb7..82055d3 100644 --- a/src/core/models/bullet.hpp +++ b/src/core/models/bullet.hpp @@ -1,22 +1,12 @@ #pragma once -#include "../data/spatial-hash-grid.hpp" #include "../precomp.hpp" +#include "../data/spatial-hash-grid.hpp" -class Bullet { - private: - raylib::Vector2 velocity; - float timeAlive; - - void updatePhysics(); - bool solveCollisions(IContainer &grid); - - public: - raylib::Vector2 position; - - Bullet(raylib::Vector2 position, raylib::Vector2 velocity); - ~Bullet() = default; +struct Bullet { + raylib::Vector2 position; + raylib::Vector2 velocity; - bool update(IContainer &asteroids); - void render(); + bool update(IContainer &asteroids); + void render(); }; \ No newline at end of file diff --git a/src/core/models/collision-handler.hpp b/src/core/models/collision-handler.hpp index 6b771c0..d0c855d 100644 --- a/src/core/models/collision-handler.hpp +++ b/src/core/models/collision-handler.hpp @@ -1,25 +1,25 @@ #pragma once -#include "../precomp.hpp" +// #include "../precomp.hpp" -namespace CollisionHandler { - struct CollisionInfo { - raylib::Vector2 normal; - float penetration; - }; +// namespace CollisionHandler { +// struct CollisionInfo { +// raylib::Vector2 normal; +// float penetration; +// }; - struct Shape { - std::vector vertices; - raylib::Vector2 position; - float radius; - }; +// struct Shape { +// std::vector vertices; +// raylib::Vector2 position; +// float radius; +// }; - /** - * @brief Implements the Separating Axis Theorem (SAT) to detect collisions between two polygons - * - * @param a The first polygon - * @param b The second polygon - * @return std::optional The collision info if a collision is detected, otherwise std::nullopt - */ - std::optional getCollisionInfo(const Shape &a, const Shape &b); -} \ No newline at end of file +// /** +// * @brief Implements the Separating Axis Theorem (SAT) to detect collisions between two polygons +// * +// * @param a The first polygon +// * @param b The second polygon +// * @return std::optional The collision info if a collision is detected, otherwise std::nullopt +// */ +// std::optional getCollisionInfo(const Shape &a, const Shape &b); +// } \ No newline at end of file diff --git a/src/core/models/collision-hanlder.cpp b/src/core/models/collision-hanlder.cpp index 5e85ac4..f0c48c1 100644 --- a/src/core/models/collision-hanlder.cpp +++ b/src/core/models/collision-hanlder.cpp @@ -1,79 +1,73 @@ -#include "collision-handler.hpp" +// #include "collision-handler.hpp" -using namespace CollisionHandler; +// using namespace CollisionHandler; -struct Projection { - float min; - float max; -}; +// struct Projection { +// float min; +// float max; +// }; -void getAxes(const std::vector &vertices, std::vector &axes) { - for (size_t i = 0; i < vertices.size(); i++) { - const auto p1 = vertices[i]; - const auto p2 = vertices[(i + 1) % vertices.size()]; - const auto edge = p1 - p2; - axes.emplace_back(-edge.y, edge.x); // Perpendicular vector - } -} +// void getAxes(const std::vector &vertices, std::vector &axes) { +// for (size_t i = 0; i < vertices.size(); i++) { +// const auto p1 = vertices[i]; +// const auto p2 = vertices[(i + 1) % vertices.size()]; +// const auto edge = p1 - p2; +// axes.emplace_back(-edge.y, edge.x); // Perpendicular vector +// } +// } -Projection project(const std::vector &vertices, const raylib::Vector2 &axis) { - float min = axis.DotProduct(vertices[0]); - float max = min; - for (size_t i = 1; i < vertices.size(); i++) { - const float p = axis.DotProduct(vertices[i]); - if (p < min) - min = p; - else if (p > max) - max = p; - } +// Projection project(const std::vector &vertices, const raylib::Vector2 &axis) { +// float min = axis.DotProduct(vertices[0]); +// float max = min; +// for (size_t i = 1; i < vertices.size(); i++) { +// const float p = axis.DotProduct(vertices[i]); +// if (p < min) +// min = p; +// else if (p > max) +// max = p; +// } - return { min, max }; -} +// return { min, max }; +// } -std::optional CollisionHandler::getCollisionInfo(const Shape &a, const Shape &b) { - // Ignore if AABBs are not colliding - if (a.position.x + a.radius < b.position.x - b.radius || a.position.x - a.radius > b.position.x + b.radius || - a.position.y + a.radius < b.position.y - b.radius || a.position.y - a.radius > b.position.y + b.radius) { - return std::nullopt; - } +// std::optional CollisionHandler::getCollisionInfo(const Shape &a, const Shape &b) { +// auto normal = raylib::Vector2::Zero(); +// float overlap = std::numeric_limits::max(); - auto normal = raylib::Vector2::Zero(); - float overlap = std::numeric_limits::max(); +// // Get the axes of the polygons +// std::vector axes; +// axes.reserve(a.vertices.size() + b.vertices.size()); +// getAxes(a.vertices, axes); +// getAxes(b.vertices, axes); - // Get the axes of the polygons - std::vector axes; - axes.reserve(a.vertices.size() + b.vertices.size()); - getAxes(a.vertices, axes); - getAxes(b.vertices, axes); +// // Iterate through the axes +// for (auto &axis : axes) { +// const auto projectionA = project(a.vertices, axis); +// const auto projectionB = project(b.vertices, axis); - // Iterate through the axes - for (auto &axis : axes) { - const auto projectionA = project(a.vertices, axis); - const auto projectionB = project(b.vertices, axis); +// // Detected a gap, no collision +// if (projectionA.max < projectionB.min || projectionB.max < projectionA.min) { +// return std::nullopt; +// } - // Detected a gap, no collision - if (projectionA.max < projectionB.min || projectionB.max < projectionA.min) { - return std::nullopt; - } +// // Calculate the overlap +// const float depth = std::min(projectionA.max, projectionB.max) - std::max(projectionA.min, projectionB.min); +// if (depth < overlap) { +// overlap = depth; +// normal = axis; +// } +// } - // Calculate the overlap - const float depth = std::min(projectionA.max, projectionB.max) - std::max(projectionA.min, projectionB.min); - if (depth < overlap) { - overlap = depth; - normal = axis; - } - } +// // Normalize the depth and normal +// const auto length = normal.Length(); +// normal /= length; +// overlap /= length; - // Normalize the depth and normal - const auto length = normal.Length(); - normal /= length; - overlap /= length; +// // If the normal is pointing from A to B, invert it +// const auto center = b.position - a.position; +// if (center.DotProduct(normal) < 0) { +// normal = -normal; +// } - // If the normal is pointing from A to B, invert it - const auto center = b.position - a.position; - if (center.DotProduct(normal) < 0) { - normal = -normal; - } - - return CollisionInfo{ normal, overlap }; -} \ No newline at end of file +// return CollisionInfo{ normal, overlap }; +// } \ No newline at end of file diff --git a/src/core/models/gui.cpp b/src/core/models/gui.cpp index cdf4e3b..adf6633 100644 --- a/src/core/models/gui.cpp +++ b/src/core/models/gui.cpp @@ -1,104 +1,29 @@ #include "app.hpp" +#include "asteroid.hpp" #include "imgui.h" -#include "imgui_internal.h" -#include "../utils.hpp" #include "raylib.h" -#include "rlImGui.h" - -void App::setupGUI() { - TraceLog(LOG_INFO, "App::setupGUI()"); - - rlImGuiSetup(true); - auto &io = ImGui::GetIO(); - io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; - - // Ensure the ini file is in the same directory as the executable - std::string cwd = GetApplicationDirectory(); - cwd += "imgui.ini"; - io.IniFilename = cwd.c_str(); -#ifdef DEBUG - if (raylib::FileExists(cwd)) { - raylib::SaveFileText(cwd, ""); - } -#endif -} - -void App::buildGUIDockSpace() { - const auto workPosition = ImGui::GetMainViewport()->WorkPos; - const auto workSize = ImGui::GetMainViewport()->WorkSize; - const auto nodeSize = ImVec2 { (float)WIDTH, (float)HEIGHT }; - const auto center = ImVec2 { workPosition.x + workSize.x / 2.0f, workPosition.y + workSize.y / 2.0f }; - const auto nodePos = ImVec2 { center.x - nodeSize.x / 2.0f, center.y - nodeSize.y / 2.0f }; - - auto id = ImGui::GetID("MainDockSpace"); - ImGui::DockBuilderRemoveNode(id); - ImGui::DockBuilderAddNode(id); - - ImGui::DockBuilderSetNodeSize(id, nodeSize); - ImGui::DockBuilderSetNodePos(id, nodePos); - - auto dockTop = ImGui::DockBuilderSplitNode(id, ImGuiDir_Up, 0.20f, nullptr, &id); - const auto dockBottom = ImGui::DockBuilderSplitNode(id, ImGuiDir_Down, 0.80f, nullptr, &id); - - ImGui::DockBuilderDockWindow("Viewport", dockBottom); - - auto dockLeft = ImGui::DockBuilderSplitNode(dockTop, ImGuiDir_Left, 0.5f, nullptr, &dockTop); - ImGui::DockBuilderDockWindow("Stats", dockLeft); - - dockLeft = ImGui::DockBuilderSplitNode(dockLeft, ImGuiDir_Right, 0.5f, nullptr, &dockLeft); - ImGui::DockBuilderDockWindow("Ship", dockLeft); - - ImGui::DockBuilderDockWindow("Wave", dockTop); - - auto dockRight = ImGui::DockBuilderSplitNode(dockTop, ImGuiDir_Right, 0.5f, nullptr, &dockTop); - ImGui::DockBuilderDockWindow("Bullets", dockRight); - ImGui::DockBuilderDockWindow("Asteroids", dockRight); - - ImGui::DockBuilderFinish(id); - - isGUIBuilt = true; -} void App::renderGUI() { rlImGuiBegin(); - if (!isGUIBuilt) buildGUIDockSpace(); + ImGui::DockSpaceOverViewport(NULL, ImGuiDockNodeFlags_PassthruCentralNode); - ImGui::Begin("Viewport", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize); + ImGui::Begin("Viewport"); + if (frameBuffer.IsReady()) rlImGuiImageRenderTexture(&frameBuffer); auto min = ImGui::GetWindowContentRegionMin(); auto max = ImGui::GetWindowContentRegionMax(); auto size = ImVec2(max.x - min.x, max.y - min.y); - if ((frameBuffer.texture.id <= 0 || size.x != frameBuffer.texture.width || size.y != frameBuffer.texture.height) && size.x > 0 && size.y > 0) { + if (frameBuffer.texture.id <= 0 || size.x != frameBuffer.texture.width || size.y != frameBuffer.texture.height) { onResize(size.x, size.y); - } else if (tempFrameBuffer.IsReady()) rlImGuiImageRenderTexture(&tempFrameBuffer); + } ImGui::End(); ImGui::Begin("Stats"); ImGui::Text("FPS: %i", GetFPS()); ImGui::Text("Frame Time: %.2f ms", GetFrameTime() * 1000); - - ImGui::SliderFloat("Tension:", &trauma, 0.0f, 1.0f); - - /* int targetFps = TARGET_FPS; - if (ImGui::SliderInt("Target FPS", &targetFps, 0, 100)) { - TARGET_FPS = targetFps; - SetTargetFPS(targetFps); - } */ - ImGui::Checkbox("Render grid overlay", &RENDER_GRID); - ImGui::Checkbox("Render star halo", &RENDER_STAR_HALO); - - profiler.renderGUI(); - ImGui::End(); - // TODO: Move this to Ship::renderGUI() ImGui::Begin("Ship"); - /* ImGui::SliderFloat("SHIP_ANGULAR_ACCELERATION", &SHIP_ANGULAR_ACCELERATION, 0.0f, PI * 2.0f); - ImGui::SliderFloat("SHIP_MAX_ANGULAR_VELOCITY", &SHIP_MAX_ANGULAR_VELOCITY, 0.0f, PI * 2.0f); - ImGui::SliderFloat("SHIP_ACCELERATION", &SHIP_ACCELERATION, 10.0f, 300.0f); - ImGui::SliderFloat("SHIP_DAMPING", &SHIP_DAMPING, 0.0f, 1.0f); - ImGui::SliderFloat("SHIP_ANGULAR_DAMPING", &SHIP_ANGULAR_DAMPING, 0.0f, 1.0f); */ - ImGui::Text("Position: { %.2f, %.2f }", ship.position.x, ship.position.y); ImGui::Text("Velocity: { %.2f, %.2f }", ship.velocity.x, ship.velocity.y); ImGui::Text("Acceleration: { %.2f, %.2f }", ship.acceleration.x, ship.acceleration.y); @@ -107,47 +32,49 @@ void App::renderGUI() { ImGui::End(); ImGui::Begin("Asteroids"); - ImGui::Text("Alive: %i", grid.size()); + ImGui::Text("Alive: %i", asteroids.size()); ImGui::Text("Collisions: %i", Asteroid::collisionCount); - if (ImGui::Button("Kill all")) grid.clear(); - - // int i = 0; - // for (auto &asteroid : asteroids.all()) { - // TODO: Move this to Asteroid::renderGUI() - // if (ImGui::TreeNode((void *)(intptr_t)i, "Asteroid %i", i)) { - // ImGui::Text("Position: { %.2f, %.2f }", asteroid->position.x, asteroid->position.y); - // ImGui::Text("Velocity: { %.2f, %.2f }", asteroid->velocity.x, asteroid->velocity.y); - // ImGui::Text("Angle: %.2f", asteroid->angle * RAD2DEG); - // ImGui::Text("Angular Velocity: %.2f", asteroid->angularVelocity * RAD2DEG); - // ImGui::Text("Radius: %.2f", asteroid->polygon.outerRadius); - // ImGui::Text("Vertices: %zu", asteroid->polygon.vertices.size()); - // for (size_t j = 0; j < asteroid->polygon.vertices.size(); j++) { - // ImGui::Text("Vertex %zu: { %.2f, %.2f }", j, asteroid->polygon.vertices[j].x, asteroid->polygon.vertices[j].y); - // } - // ImGui::TreePop(); - // } - - // i++; - // } + if (ImGui::Button("Kill all")) { + asteroids.clear(); + } + int i = 0; + for (auto &asteroid : asteroids.all()) { + if (ImGui::TreeNode((void *)(intptr_t)i, "Asteroid %i", i)) { + ImGui::Text("Position: { %.2f, %.2f }", asteroid->position.x, asteroid->position.y); + ImGui::Text("Velocity: { %.2f, %.2f }", asteroid->velocity.x, asteroid->velocity.y); + ImGui::Text("Angle: %.2f", asteroid->angle * RAD2DEG); + ImGui::Text("Angular Velocity: %.2f", asteroid->angularVelocity * RAD2DEG); + ImGui::Text("Radius: %.2f", asteroid->polygon.outerRadius); + ImGui::Text("Vertices: %zu", asteroid->polygon.vertices.size()); + for (size_t j = 0; j < asteroid->polygon.vertices.size(); j++) { + ImGui::Text("Vertex %zu: { %.2f, %.2f }", j, asteroid->polygon.vertices[j].x, asteroid->polygon.vertices[j].y); + } + ImGui::TreePop(); + } + + i++; + } ImGui::End(); ImGui::Begin("Bullets"); ImGui::Text("Alive: %zu", bullets.size()); - // i = 0; - // for (auto &bullet : bullets) { - // TODO: Move this to Bullet::renderGUI() - // if (ImGui::TreeNode((void *)(intptr_t)i, "Bullet %i", i)) { - // ImGui::Text("Position: { %.2f, %.2f }", bullet.position.x, bullet.position.y); - // ImGui::Text("Velocity: { %.2f, %.2f }", bullet.velocity.x, bullet.velocity.y); - // ImGui::TreePop(); - // } - - // i++; - // } + i = 0; + for (auto &bullet : bullets) { + if (ImGui::TreeNode((void *)(intptr_t)i, "Bullet %i", i)) { + ImGui::Text("Position: { %.2f, %.2f }", bullet.position.x, bullet.position.y); + ImGui::Text("Velocity: { %.2f, %.2f }", bullet.velocity.x, bullet.velocity.y); + ImGui::TreePop(); + } + + i++; + } ImGui::End(); ImGui::Begin("Wave"); - wave.renderGUI(); + ImGui::Text("Current: %i", wave.currentWave); + ImGui::Text("Time Since Last Wave: %.2f", wave.timeSinceLastWave); + ImGui::Text("Time Since Last Spawn: %.2f", wave.timeSinceLastSpawn); + ImGui::Text("Asteroids Spawned: %i", wave.asteroidsSpawned); ImGui::End(); rlImGuiEnd(); diff --git a/src/core/models/hotreload-shader.cpp b/src/core/models/hotreload-shader.cpp deleted file mode 100644 index d0160bc..0000000 --- a/src/core/models/hotreload-shader.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include "hotreload-shader.hpp" -#include "raylib.h" - -bool HotReloadShader::load() { - auto shader = std::make_unique(nullptr, path.c_str()); - if (shader->IsReady()) { - this->shader = std::move(shader); -#ifdef DEBUG - lastModified = raylib::GetFileModTime(path); -#endif - return true; - } - - return false; -} - -#ifdef DEBUG -void HotReloadShader::autoReloadIfNeeded() { - timer += GetFrameTime(); - if (timer >= 1.0) timer -= 1.0; - else return; - - const unsigned long modifiedTime = raylib::GetFileModTime(path); - if (modifiedTime > lastModified) { - TraceLog(LOG_INFO, "HotReloadShader::autoReloadIfNeeded() - Reloading shader: %s", path.c_str()); - load(); - } -} -#endif - -void HotReloadShader::beginMode() { - if (shader != nullptr && shader->IsReady()) { - shader->BeginMode(); - } -} - -void HotReloadShader::endMode() { - if (shader != nullptr && shader->IsReady()) { - shader->EndMode(); - } -} - -const int HotReloadShader::getUniformLocation(std::string name) { - if (shader != nullptr && shader->IsReady()) { - return shader->GetLocation(name); - } - - return -1; -} - -void HotReloadShader::setUniform(const int location, const void *value, ShaderUniformDataType uniformType) { - if (shader != nullptr && shader->IsReady()) { - shader->SetValue(location, value, uniformType); - } -} diff --git a/src/core/models/hotreload-shader.hpp b/src/core/models/hotreload-shader.hpp deleted file mode 100644 index e7a62d0..0000000 --- a/src/core/models/hotreload-shader.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include "../precomp.hpp" - -class HotReloadShader { - private: - std::unique_ptr shader = nullptr; - std::string path; - -#ifdef DEBUG - unsigned long lastModified = 0L; - float timer = 0; -#endif - - public: - HotReloadShader(std::string_view path) : path(path) { } - - bool load(); - -#ifdef DEBUG - void autoReloadIfNeeded(); -#endif - - void beginMode(); - - void endMode(); - - const int getUniformLocation(std::string name); - - void setUniform(const int location, const void *value, ShaderUniformDataType uniformType); -}; diff --git a/src/core/models/particle.cpp b/src/core/models/particle.cpp deleted file mode 100644 index dc786ac..0000000 --- a/src/core/models/particle.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "particle.hpp" -#include "../utils.hpp" -#include "Color.hpp" - -bool Particle::update() { - position = position + velocity * GetFrameTime(); - - // Deviation - if (GetRandomValue(0, 100) < 2) { - velocity = raylib::Vector2(GetRandomValue(-1.0f, 1.0f), GetRandomValue(-1.0f, 1.0f)).Normalize() * velocity.Length(); - } - - timeAlive += GetFrameTime(); - - if (timeAlive >= timeToLive) { - timeAlive = timeToLive; - return true; - } - - return false; -} - -void Particle::render() { - const float progress = powf(1.0f - timeAlive / timeToLive, 2.0f); - const float alpha = this->color.a * progress; - const float radius = this->radius * progress; - raylib::Color currentColor; - if (changeColorBasedOnTime) currentColor = convertKelvinToColor(1000.0f + progress * 9000.0f); - else raylib::Color(color.r, color.g, color.b, alpha); - - DrawCircleV(position, radius, currentColor); -} diff --git a/src/core/models/particle.hpp b/src/core/models/particle.hpp deleted file mode 100644 index d6ded4a..0000000 --- a/src/core/models/particle.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -#include "../precomp.hpp" -#include "../utils.hpp" -#include "Color.hpp" -#include "Vector2.hpp" - -struct Particle { - raylib::Vector2 position; - raylib::Vector2 velocity; - raylib::Color color; - float radius; - float timeToLive = 1.0f; - float timeAlive = 0.0f; - bool changeColorBasedOnTime = false; - - Particle(raylib::Vector2 position, raylib::Vector2 velocity, raylib::Color color, float radius, float timeToLive, bool changeColorBasedOnTime = false) : position(position), velocity(velocity), color(color), radius(radius), timeToLive(timeToLive), changeColorBasedOnTime(changeColorBasedOnTime) { } - - bool update(); - void render(); - -}; - -namespace ParticleSystem { - inline std::list particles; - - static void update() { - for (auto it = particles.begin(); it != particles.end();) { - if (it->update()) { - it = particles.erase(it); - } else { - it++; - } - } - } - - static void render() { - for (auto &particle : particles) { - particle.render(); - } - } - - static void addExplosion(raylib::Vector2 position, unsigned int count, float startAngle = -PI, float endAngle = PI, raylib::Vector2 baseVelocity = raylib::Vector2::Zero(), float minVelocity = 50.0f, float maxVelocity = 100.0f, float radius = 2.0f, float timeToLive = 1.0f, raylib::Color color = LIGHTGRAY) { - for (int i = 0; i < count; i++) { - const float angle = i / (float)count * (endAngle - startAngle) + startAngle; - const auto velocity = raylib::Vector2(cosf(angle), sinf(angle)) * getRandomValue(minVelocity, maxVelocity) + baseVelocity; - particles.emplace_back(position, velocity, color, radius, timeToLive, true); - } - } - - static void addTrail(raylib::Vector2 position, unsigned int count, float baseAngle, raylib::Vector2 velocity, raylib::Color color = RED, float radius = 2.0f, float timeToLive = 0.250f) { - for (int i = 0; i < count; i++) { - const float angle = getRandomValue(-PI, PI) + baseAngle; - const auto vel = raylib::Vector2(cosf(angle), sinf(angle)) * getRandomValue(-8.5f, 40.0f) + velocity; - particles.emplace_back(position + vel / SHIP_SIZE, vel, color, radius, timeToLive, true); - } - } -} \ No newline at end of file diff --git a/src/core/models/polygon.cpp b/src/core/models/polygon.cpp new file mode 100644 index 0000000..08c081a --- /dev/null +++ b/src/core/models/polygon.cpp @@ -0,0 +1,57 @@ +#include "polygon.hpp" +#include "../settings.hpp" +#include "../utils.hpp" +#include "Vector2.hpp" +#include "raylib.h" +#include + +void Polygon::generateVertices(float radius, uint8_t vertexCount) { + outerRadius = 0.0f; + innerRadius = 0.0f; + + float scale = radius / ASTEROID_RADIUS; + + for (int i = 0; i < vertexCount; i++) { + float angle = (i / (float)vertexCount) * 2.0f * PI; + + float r = radius + GetRandomValue(-ASTEROID_JAGGEDNESS * scale, ASTEROID_JAGGEDNESS * scale); + + // Keep track of the largest radius + if (outerRadius == 0.0f || r > outerRadius) outerRadius = r; + + // Keep track of the smallest radius + if (innerRadius == 0.0f || r < innerRadius) innerRadius = r; + + vertices.emplace_back(cosf(angle) * r, sinf(angle) * r); + } +} + +void Polygon::render(raylib::Vector2 center, float scale, float angle, raylib::Color color) { + for (size_t i = 0; i < vertices.size(); i++) { + size_t j = (i + 1) % vertices.size(); + DrawLineEx( + rotateAround(center + vertices[i] * scale, center, angle), + rotateAround(center + vertices[j] * scale, center, angle), + 1.5f, + color + ); + } + +// #ifdef DEBUG +// DrawCircleLinesV(center, outerRadius, LIGHTGRAY); +// DrawCircleLinesV(center, innerRadius, GRAY); +// #endif +} + +std::vector Polygon::split() { + std::vector buffer; + + float radius = ceilf(outerRadius / (float)(ASTEROID_FRAGMENTS_COUNT - 1)); + uint8_t vertexCount = GetRandomValue(ASTEROID_MIN_VERTEX_COUNT, ASTEROID_MAX_VERTEX_COUNT); + for (size_t i = 0; i < ASTEROID_FRAGMENTS_COUNT; i++) { + buffer.emplace_back(); + buffer.back().generateVertices(radius, vertexCount); + } + + return buffer; +} \ No newline at end of file diff --git a/src/core/models/polygon.hpp b/src/core/models/polygon.hpp new file mode 100644 index 0000000..5177c1c --- /dev/null +++ b/src/core/models/polygon.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "../precomp.hpp" +#include "Vector2.hpp" +#include +#include + +class Polygon { + public: + std::vector vertices; + float innerRadius; + float outerRadius; + + void generateVertices(float radius, uint8_t vertexCount); + void render(raylib::Vector2 center, float scale, float angle, raylib::Color color); + std::vector split(); +}; \ No newline at end of file diff --git a/src/core/models/profiler.hpp b/src/core/models/profiler.hpp deleted file mode 100644 index 49951d2..0000000 --- a/src/core/models/profiler.hpp +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include "../precomp.hpp" - -class Profiler { - private: - std::array frameTimeBuffer; - size_t frameTimeIndex = 0; - - uint32_t frameCount = 0; - double frameTimeSum = 0.0; - - std::chrono::time_point frameStart; - - public: - Profiler() = default; - ~Profiler() = default; - - void onFrameStart() { - frameStart = std::chrono::high_resolution_clock::now(); - } - - void onFrameEnd() { - const auto frameEnd = std::chrono::high_resolution_clock::now(); - const double frameTime = std::chrono::duration(frameEnd - frameStart).count(); - - if (frameTimeIndex >= frameTimeBuffer.size()) frameTimeIndex = 0; - frameTimeBuffer[frameTimeIndex++] = frameTime; - frameTimeSum += frameTime; - frameCount++; - } - - void renderGUI() { - const float averageFrameTime = frameTimeSum / frameCount; - - // ImGui::Begin("Profiler"); - ImGui::Text("Average frame time: %.3f ms", averageFrameTime * 1000.0f); - ImGui::PushStyleColor(ImGuiCol_PlotLines, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); - ImGui::PlotLines( - "Frame Time", - frameTimeBuffer.data(), - frameTimeBuffer.size(), - 0, - NULL, - 0.0f, - 0.033f, - ImVec2(0, 20) - ); - ImGui::PopStyleColor(); - // ImGui::End(); - } -}; \ No newline at end of file diff --git a/src/core/models/ship.cpp b/src/core/models/ship.cpp index ce1c006..7024fc7 100644 --- a/src/core/models/ship.cpp +++ b/src/core/models/ship.cpp @@ -1,55 +1,27 @@ #include "ship.hpp" #include "../utils.hpp" -#include "Vector2.hpp" -#include "particle.hpp" -#include "raylib.h" -#include "soundfx.hpp" - -void Ship::shoot(std::list &bullets) { - const auto shipDirection = raylib::Vector2(cosf(angle), sinf(angle)); - const auto spawnPosition = position + shipDirection * SHIP_SIZE * 1.5f; - - // Spawn particles - ParticleSystem::addExplosion(spawnPosition, 5, angle - PI / 4.0f, angle + PI / 4.0f); - - // Spawn bullet - bullets.emplace_back( - spawnPosition, - shipDirection * BULLET_VELOCITY + velocity - ); - - // Play sound - SoundFX::play( - SoundFX::shoot, - 0.05f, - -1.0f, - 1.0f - position.x / (float)WIDTH - ); -} void Ship::updatePhysics() { const auto deltaTime = GetFrameTime(); - const float maxSpeed = BULLET_VELOCITY * 0.75f; - // Movement - velocity += acceleration * deltaTime * 0.5f; - if (velocity.Length() >= maxSpeed) velocity = velocity.Normalize() * maxSpeed; + velocity += acceleration * deltaTime; position += velocity * deltaTime; - velocity += acceleration * deltaTime * 0.5f; - velocity *= powf(SHIP_DAMPING, deltaTime * TARGET_FPS); + acceleration = raylib::Vector2::Zero(); // Reset acceleration + velocity *= SHIP_DAMPING; // Angle angle += angularVelocity * deltaTime; - if (fabsf(angularVelocity) > SHIP_MAX_ANGULAR_VELOCITY) angularVelocity = SHIP_MAX_ANGULAR_VELOCITY * (angularVelocity > 0 ? 1 : -1); - angularVelocity *= powf(SHIP_ANGULAR_DAMPING, deltaTime * TARGET_FPS); + angularVelocity *= SHIP_ANGULAR_DAMPING; if (angle > 2 * PI) angle -= 2 * PI; if (angle < 0) angle += 2 * PI; - if (fabs(angularVelocity) < 0.1f) angularVelocity = 0;// Prevents the ship from rotating forever + + // Reset angular velocity if it's too small + if (fabs(angularVelocity) < 0.1f) angularVelocity = 0; } -void Ship::updateInput(std::list &bullets) { +void Ship::updateInput() { // Rotation if (IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_D)) angularVelocity += SHIP_ANGULAR_ACCELERATION; if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_A)) angularVelocity -= SHIP_ANGULAR_ACCELERATION; @@ -58,18 +30,11 @@ void Ship::updateInput(std::list &bullets) { if (IsKeyDown(KEY_UP) || IsKeyDown(KEY_W)) { acceleration.x += cosf(angle) * SHIP_ACCELERATION; acceleration.y += sinf(angle) * SHIP_ACCELERATION; - thrust += 0.1f; } - /* if (IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_S)) { + if (IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_S)) { acceleration.x -= cosf(angle) * SHIP_ACCELERATION; acceleration.y -= sinf(angle) * SHIP_ACCELERATION; - } */ - - // Shooting - if (shotCooldown >= BULLET_SHOOT_INTERVAL && IsKeyDown(KEY_SPACE)) { - shoot(bullets); - shotCooldown = 0; - } else shotCooldown += GetFrameTime(); + } } void Ship::wrapAroundScreen() { @@ -93,63 +58,27 @@ void Ship::setup() { angularVelocity = 0.0f; } -void Ship::updateVertices() { - vertices = { - rotateAround(position + raylib::Vector2(SHIP_SIZE, 0), position, angle), - rotateAround(position + raylib::Vector2(-SHIP_SIZE, -SHIP_SIZE), position, angle), - rotateAround(position + raylib::Vector2(-SHIP_SIZE, SHIP_SIZE), position, angle) - }; -} - -void Ship::updateTrail() { - const auto reflectedAngle = angle + PI; - const auto shipDirection = raylib::Vector2(cosf(reflectedAngle), sinf(reflectedAngle)); - const auto shipBottom = position + shipDirection * SHIP_SIZE; - - auto baseParticleVelocity = raylib::Vector2::Zero(); - - // Small bursts of particles, when the ship is not thrusting - if (thrust <= 0.1f && getRandomValue(0, 100) < 10) baseParticleVelocity += shipDirection * 50.0f; - - ParticleSystem::addTrail(shipBottom, 10, angle, baseParticleVelocity); -} - -void Ship::updateThrustSound() { - if (!SoundFX::thrust.IsReady()) return; - - // Postion sound - SoundFX::thrust.SetPan(1.0f - position.x / (float)WIDTH); - - // Decay thrust force - thrust *= 0.95f; - - // Set volume based on thrust force - float volume = std::max(thrust, 1.0f) * 0.075f; - if (thrust <= 0.1) volume = 0.0f; - SoundFX::thrust.SetVolume(volume); - - // Play sound - if (SoundFX::thrust.IsPlaying()) { - if (volume <= 0.1f) SoundFX::thrust.Pause(); - } else { - if (volume >= 0.1f) SoundFX::thrust.Play(); - else SoundFX::thrust.Pause(); - } - -} - -void Ship::update(std::list &bullets) { - updateInput(bullets); +void Ship::update() { updatePhysics(); + updateInput(); wrapAroundScreen(); - updateVertices(); - updateTrail(); - updateThrustSound(); - - // TODO: Check for collisions with asteroids } void Ship::render() { - drawFilledPolygon(position, vertices.data(), vertices.size(), WHITE); - // drawPolygonOutline(vertices.data(), vertices.size(), WHITE); + // Render triangle with the pointy end facing the direction of the ship + const float size = 10.0f; + raylib::Vector2 p1 = position + raylib::Vector2(size, 0); + raylib::Vector2 p2 = position + raylib::Vector2(-size, -size); + raylib::Vector2 p3 = position + raylib::Vector2(-size, size); + + /* DrawCircleV(position, size, WHITE); + DrawLineV(position, position + raylib::Vector2(cosf(angle) * size, sinf(angle) * size), RED); */ + + DrawTriangleLines( + rotateAround(p1, position, angle), + rotateAround(p2, position, angle), + rotateAround(p3, position, angle), + WHITE + ); + } diff --git a/src/core/models/ship.hpp b/src/core/models/ship.hpp index bde51cc..14b6d6e 100644 --- a/src/core/models/ship.hpp +++ b/src/core/models/ship.hpp @@ -1,11 +1,10 @@ #pragma once #include "../precomp.hpp" -#include "bullet.hpp" +#include "Vector2.hpp" class Ship { public: - std::array vertices; raylib::Vector2 position; raylib::Vector2 velocity; raylib::Vector2 acceleration; @@ -13,25 +12,15 @@ class Ship { float angularVelocity; private: - float thrust; - float shotCooldown; - - void updateVertices(); void updatePhysics(); - void updateInput(std::list &bullets); + void updateInput(); void wrapAroundScreen(); - void updateShootingLogic(std::list &bullets); - void shoot(std::list &bullets); - - void updateTrail(); - - void updateThrustSound(); public: Ship() = default; ~Ship() = default; void setup(); - void update(std::list &bullets); + void update(); void render(); }; \ No newline at end of file diff --git a/src/core/models/soundfx.hpp b/src/core/models/soundfx.hpp deleted file mode 100644 index 12d43d1..0000000 --- a/src/core/models/soundfx.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -#include "../precomp.hpp" -#include "../utils.hpp" -#include "raylib.h" -#include "utils.h" - -namespace SoundFX { - inline raylib::Sound shoot; - inline raylib::Sound thrust; - inline raylib::Sound explosion; - inline raylib::Sound hit; - inline raylib::Sound spawn; - - static void setup() { - TraceLog(LOG_INFO, "SoundFX::setup()"); - - shoot.Load("assets/audio/shoot.wav"); - if (!shoot.IsReady()) TraceLog(LOG_ERROR, "Failed to load shoot sound"); - - thrust.Load("assets/audio/thrust.wav"); - if (!thrust.IsReady()) TraceLog(LOG_ERROR, "Failed to load thrust sound"); - - explosion.Load("assets/audio/explosion.wav"); - if (!explosion.IsReady()) TraceLog(LOG_ERROR, "Failed to load explosion sound"); - - hit.Load("assets/audio/hit.wav"); - if (!hit.IsReady()) TraceLog(LOG_ERROR, "Failed to load hit sound"); - - spawn.Load("assets/audio/spawn.wav"); - if (!spawn.IsReady()) TraceLog(LOG_ERROR, "Failed to load spawn sound"); - } - - static void release() { - TraceLog(LOG_INFO, "SoundFX::release()"); - - shoot.Unload(); - explosion.Unload(); - hit.Unload(); - spawn.Unload(); - } - - static void play(raylib::Sound &sound, float volume = -1.0f, float pitch = -1.0f, float pan = -1.0f) { - if (!sound.IsReady()) { - TraceLog(LOG_ERROR, "SoundFX::play() - Sound is not ready"); - return; - } - - if (volume <= 0.0f) volume = 0.5f; - if (pitch <= 0.0f) pitch = getRandomValue(0.65f, 1.35f); - if (pan <= 0.0f) pan = getRandomValue(0.25f, 0.75f); - - sound.SetVolume(volume); - sound.SetPitch(pitch); - sound.SetPan(pan); - sound.Play(); - } -}; \ No newline at end of file diff --git a/src/core/models/star.hpp b/src/core/models/star.hpp deleted file mode 100644 index 3731d73..0000000 --- a/src/core/models/star.hpp +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once - -#include "../precomp.hpp" -#include "../settings.hpp" -#include "Color.hpp" -#include "Vector2.hpp" -#include "raylib.h" -#include "ship.hpp" -#include "../utils.hpp" -#include - -struct Star { - raylib::Vector2 position; - raylib::Color color; - float angle; - float radius; - - Star(raylib::Vector2 position, float radius) : position(position), radius(radius) { - angle = getRandomValue(0, PI * 2.0f); - - color = convertKelvinToColor(randomKelvin()); - } - - void update(raylib::Vector2 offset) { - const float parallax = powf(radius / 5.0f, 2.0f) / 25.0f; - position -= offset * parallax; - - if (position.x + radius > WIDTH) position.x -= WIDTH + radius; - if (position.x - radius < 0) position.x += WIDTH + radius; - if (position.y + radius > HEIGHT) position.y -= HEIGHT + radius; - if (position.y - radius < 0) position.y += HEIGHT + radius; - } - - void render() { - angle += 0.1f * GetFrameTime(); - - float alpha = (radius / 7.0f) + 0.25f; - alpha *= sinf((PI * 2.0f * GetTime() + radius * 10.0f) / 10.0f) * 0.15f + 0.85f; - - const float angleIncrement = 360.0f / 5.0f * DEG2RAD; - float currentAngle = -90 * DEG2RAD + angle; - float innerRadius = radius * 0.5f; - std::array vertices; - for (int i = 0; i < 5; i++) { - // Calculate the position of the outer vertex - float x1 = position.x + cosf(currentAngle) * radius; - float y1 = position.y + sinf(currentAngle) * radius; - vertices[i * 2] = raylib::Vector2(x1, y1); - currentAngle += angleIncrement; - - // Calculate the position of the inner vertex - float x2 = position.x + cosf(currentAngle + angleIncrement / 2.0f) * innerRadius; - float y2 = position.y + sinf(currentAngle + angleIncrement / 2.0f) * innerRadius; - vertices[i * 2 + 1] = raylib::Vector2(x2, y2); - } - - drawFilledPolygon(position, vertices.data(), vertices.size(), Fade(color, alpha)); - - // Draw halo around it - if (RENDER_STAR_HALO) { - DrawCircleV(position, radius * 1.5f, Fade(color, alpha * 0.15f)); - } - - /* // Extra effect, draw lines from the center to radius * 2 - // Draw angled line 1 - auto offset = raylib::Vector2(cosf(angle), sinf(angle)) * radius * 1.5f; - DrawLineV(position - offset, position + offset, Fade(color, alpha * 0.25f)); - - // Draw angled line 2 - offset = raylib::Vector2(cosf(angle + PI / 4.0f), sinf(angle + PI / 4.0f)) * radius * 1.5f; - DrawLineV(position - offset, position + offset, Fade(color, alpha * 0.25f)); */ - } -}; \ No newline at end of file diff --git a/src/core/models/wave.cpp b/src/core/models/wave.cpp index df8e8a1..a45557c 100644 --- a/src/core/models/wave.cpp +++ b/src/core/models/wave.cpp @@ -1,46 +1,62 @@ #include "wave.hpp" #include "asteroid.hpp" -#include "app.hpp" -#include "raylib.h" -#include "soundfx.hpp" -WaveController::WaveController() { -} +void WaveController::update(IContainer &asteroids, std::list &bullets, Ship &ship) { + timeSinceLastWave += GetFrameTime(); + timeSinceLastSpawn += GetFrameTime(); -WaveController::~WaveController() { - TraceLog(LOG_INFO, "WaveController::~WaveController()"); - if (thread.joinable()) thread.join(); -} + // if (asteroids.isEmpty()) { + // for (int i = 0; i < 50; i++) { + // spawn(asteroids, bullets, ship); + // } + // } + + if (timeSinceLastWave >= waveInterval || (asteroids.isEmpty() && currentWave > 0)) { + TraceLog(LOG_DEBUG, "Entering wave %i", currentWave); + + // Start new wave + timeSinceLastWave -= waveInterval; + currentWave++; + + asteroidsPerWave += LEVEL_ASTEROIDS_PER_WAVE; -void WaveController::renderGUI() { - ImGui::Text("Current: %i", currentWave); - ImGui::Text("Time Since Last Wave: %.2f", timeSinceLastWave); - ImGui::Text("Time Since Last Spawn: %.2f", timeSinceLastSpawn); - ImGui::Text("Asteroids Spawned: %i", asteroidsSpawned); + waveInterval *= LEVEL_WAVE_INTERVAL_DECREMENT; + if (waveInterval < LEVEL_WAVE_INTERVAL_MIN) waveInterval = LEVEL_WAVE_INTERVAL_MIN; + + spawnInterval *= LEVEL_ASTEROID_SPAWN_INTERVAL_DECREMENT_PER_WAVE; + if (spawnInterval < LEVEL_ASTEROID_SPAWN_INTERVAL_MIN) spawnInterval = LEVEL_ASTEROID_SPAWN_INTERVAL_MIN; + } + + if (timeSinceLastSpawn >= spawnInterval && asteroidsSpawned < asteroidsPerWave) { + // Spawn new asteroid + timeSinceLastSpawn -= spawnInterval; + asteroidsSpawned++; + spawn(asteroids, bullets, ship); + } + + if (IsKeyReleased(KEY_ENTER)) { + spawn(asteroids, bullets, ship); + } } -void WaveController::spawn(std::shared_ptr app) { - const int maxTries = 4; - const float radius = getRandomValue(0.7f, 1.0f) * ASTEROID_RADIUS; - auto position = raylib::Vector2( - GetRandomValue(0, WIDTH), - GetRandomValue(0, HEIGHT) - ); +void WaveController::spawn(IContainer &asteroids, std::list &bullets, Ship &ship) { + const auto asteroid = std::make_shared(); bool hit = false; uint8_t tries = 0; do { // Ensures it did not spawn on top of the ship - if (position.Distance(app->ship.position) < radius) { - TraceLog(LOG_DEBUG, "Ship hit on %.2f, %.2f with distance %.3f - %.2f", position.x, position.y, app->ship.position.Distance(position), radius); + if (asteroid->position.Distance(ship.position) < asteroid->polygon.outerRadius) { + TraceLog(LOG_DEBUG, "Ship hit on %.2f, %.2f with distance %.3f - %.2f", asteroid->position.x, asteroid->position.y, ship.position.Distance(asteroid->position), asteroid->polygon.outerRadius); hit = true; } // Ensures it did not spawn on top of another asteroid - if (!hit && ASTEROIDS_SELF_COLLISION) { - for (auto &other : app->grid.getAll()) { - if (other->position.Distance(position) < other->radius + radius) { - TraceLog(LOG_DEBUG, "Asteroid hit on %.2f, %.2f", position.x, position.y); + if (!hit) { + for (auto &other : asteroids.all()) { + if (&other == &asteroid) continue; + if (other->position.Distance(asteroid->position) < other->polygon.outerRadius + asteroid->polygon.outerRadius) { + TraceLog(LOG_DEBUG, "Asteroid hit on %.2f, %.2f", asteroid->position.x, asteroid->position.y); hit = true; break; } @@ -49,9 +65,9 @@ void WaveController::spawn(std::shared_ptr app) { // Ensures it did not spawn on top of bullets if (!hit) { - for (auto &bullet : app->bullets) { - if (bullet.position.Distance(position) < radius + BULLET_RADIUS) { - TraceLog(LOG_DEBUG, "Bullet hit on %.2f, %.2f", position.x, position.y); + for (auto &bullet : bullets) { + if (bullet.position.Distance(asteroid->position) < asteroid->polygon.outerRadius + BULLET_RADIUS) { + TraceLog(LOG_DEBUG, "Bullet hit on %.2f, %.2f", asteroid->position.x, asteroid->position.y); hit = true; break; } @@ -60,78 +76,17 @@ void WaveController::spawn(std::shared_ptr app) { // Assign new position if it hit something if (hit) { - position = raylib::Vector2( + asteroid->position = raylib::Vector2( GetRandomValue(0, WIDTH), GetRandomValue(0, HEIGHT) ); tries++; } - } while(hit && tries < maxTries); + } while(hit && tries < 10); - if (tries >= maxTries) { - TraceLog(LOG_INFO, "Failed to spawn asteroid after %i tries", maxTries); + if (tries >= 10) { + TraceLog(LOG_INFO, "Failed to spawn asteroid after 10 tries"); } else { - app->grid.insert(std::make_shared(position, radius)); - SoundFX::play(SoundFX::spawn, 0.15f, -1.0f, 1.0f - position.x / (float)WIDTH); + asteroids.insert(asteroid); } -} - -void WaveController::threadCallback() { - bool once = true; - auto lastTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - - while (!app.expired()) { - if (auto app = this->app.lock()) { - // Ignore if grid is not initialized - if (app->grid.rows <= 0 || app->grid.cols <= 0 || !app->grid.isInitialized()) continue; - - auto currentTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - auto deltaTime = (currentTime - lastTime) / 1000.0f; - lastTime = currentTime; - - // Update timers - timeSinceLastWave += deltaTime; - timeSinceLastSpawn += deltaTime; - - // Update wave - if (timeSinceLastWave >= waveInterval || (app->grid.isEmpty() && currentWave > 0)) { - TraceLog(LOG_DEBUG, "Entering wave %i", currentWave); - - // Start new wave - timeSinceLastWave -= waveInterval; - currentWave++; - - asteroidsPerWave += LEVEL_ASTEROIDS_PER_WAVE; - - waveInterval -= waveInterval * LEVEL_WAVE_INTERVAL_DECREMENT; - if (waveInterval < LEVEL_WAVE_INTERVAL_MIN) waveInterval = LEVEL_WAVE_INTERVAL_MIN; - - spawnInterval -= spawnInterval * LEVEL_ASTEROID_SPAWN_INTERVAL_DECREMENT_PER_WAVE; - if (spawnInterval < LEVEL_ASTEROID_SPAWN_INTERVAL_MIN) spawnInterval = LEVEL_ASTEROID_SPAWN_INTERVAL_MIN; - } - - // Spawn new asteroid - if (timeSinceLastSpawn >= spawnInterval && asteroidsSpawned < asteroidsPerWave) { - timeSinceLastSpawn -= spawnInterval; - asteroidsSpawned++; - spawn(app); - } - - if (SPAWN_AT_START && once && app->grid.isEmpty()) { - once = false; - TraceLog(LOG_DEBUG, "Spawning initial asteroids"); - for (int i = 0; i < 50; i++) spawn(app); - } - } - - std::this_thread::sleep_for(std::chrono::milliseconds((long)(spawnInterval * 1000L))); - } - - TraceLog(LOG_INFO, "WaveController::threadCallback() exited"); -} - -void WaveController::start(std::shared_ptr app) { - this->app = app; - thread = std::thread(&WaveController::threadCallback, this); - thread.detach(); } \ No newline at end of file diff --git a/src/core/models/wave.hpp b/src/core/models/wave.hpp index 7beac64..52544b4 100644 --- a/src/core/models/wave.hpp +++ b/src/core/models/wave.hpp @@ -6,12 +6,8 @@ #include "ship.hpp" #include "../data/spatial-hash-grid.hpp" -class App; - class WaveController { - private: - std::weak_ptr app; - + public: float waveInterval = LEVEL_WAVE_INTERVAL; float spawnInterval = LEVEL_ASTEROID_SPAWN_INTERVAL; uint8_t asteroidsPerWave = LEVEL_ASTEROIDS_PER_WAVE; @@ -22,16 +18,12 @@ class WaveController { float timeSinceLastSpawn = spawnInterval / 2.0f; uint16_t asteroidsSpawned = 0; - // Threading - std::thread thread; - - void spawn(std::shared_ptr app); - void threadCallback(); - public: - WaveController(); - ~WaveController(); + WaveController() = default; + ~WaveController() = default; + + void update(IContainer &asteroids, std::list &bullets, Ship &ship); - void renderGUI(); - void start(std::shared_ptr app); +private: + void spawn(IContainer &asteroids, std::list &bullets, Ship &ship); }; \ No newline at end of file diff --git a/src/core/precomp.hpp b/src/core/precomp.hpp index 1f29d1a..ae4f7d5 100644 --- a/src/core/precomp.hpp +++ b/src/core/precomp.hpp @@ -23,6 +23,5 @@ #include #include #include -#include -#include -#include +#include +#include \ No newline at end of file diff --git a/src/core/settings.cpp b/src/core/settings.cpp new file mode 100644 index 0000000..0408d49 --- /dev/null +++ b/src/core/settings.cpp @@ -0,0 +1,5 @@ +#include "settings.hpp" + +unsigned int WIDTH = 850; +unsigned int HEIGHT = 750; +unsigned int TARGET_FPS = 60; \ No newline at end of file diff --git a/src/core/settings.hpp b/src/core/settings.hpp index 9aa4822..7c75af1 100644 --- a/src/core/settings.hpp +++ b/src/core/settings.hpp @@ -1,55 +1,37 @@ #pragma once // Constants - General -inline unsigned int WIDTH = 850; -inline unsigned int HEIGHT = 750; -inline unsigned int TARGET_FPS = 60; +extern unsigned int WIDTH; +extern unsigned int HEIGHT; +extern unsigned int TARGET_FPS; // Constants - Ship -const float SHIP_SIZE = 10.0f; -const float SHIP_ANGULAR_ACCELERATION = 0.265f; -const float SHIP_MAX_ANGULAR_VELOCITY = 3.5f; -const float SHIP_ACCELERATION = 500.0f; -const float SHIP_DAMPING = 0.988f; -const float SHIP_ANGULAR_DAMPING = 0.963f; +const float SHIP_ANGULAR_ACCELERATION = 30.0f * DEG2RAD; +const float SHIP_ACCELERATION = 150.0f; +const float SHIP_DAMPING = 0.98f; +const float SHIP_ANGULAR_DAMPING = 0.90f; // Constants - Asteroid const float ASTEROID_RADIUS = 25.0f; const float ASTEROID_JAGGEDNESS = 5.0f; const float ASTEROID_ANGULAR_VELOCITY = 100.0f * DEG2RAD; const float ASTEROID_VELOCITY = 100.0f; -const float ASTEROID_RESTITUTION = 0.98f; const int ASTEROID_MIN_VERTEX_COUNT = 8; const int ASTEROID_MAX_VERTEX_COUNT = 32; const int ASTEROID_FRAGMENTS_COUNT = 3; const float ASTEROID_RADIUS_DIFFERENCE = 10.0f; const float ASTEROID_MIN_RADIUS_TO_SPLIT = 20.0f; -const float ASTEROID_MIN_FRAGMENT_RADIUS = 7.5f; // Constants - Bullet const float BULLET_VELOCITY = 500.0f; const float BULLET_RADIUS = 2.0f; const float BULLET_SHOOT_INTERVAL = 0.250f; -const float BULLET_TIME_TO_LIVE = 1.0f; // Constants - Level const uint8_t LEVEL_ASTEROIDS_PER_WAVE = 10; const float LEVEL_ASTEROID_SPAWN_INTERVAL = 5.0f; -const float LEVEL_ASTEROID_SPAWN_INTERVAL_DECREMENT_PER_WAVE = 0.15f; -const float LEVEL_ASTEROID_SPAWN_INTERVAL_MIN = 0.5f; +const float LEVEL_ASTEROID_SPAWN_INTERVAL_DECREMENT_PER_WAVE = 0.5f; +const float LEVEL_ASTEROID_SPAWN_INTERVAL_MIN = 0.05f; const float LEVEL_WAVE_INTERVAL = (LEVEL_ASTEROIDS_PER_WAVE + 1) * LEVEL_ASTEROID_SPAWN_INTERVAL; -const float LEVEL_WAVE_INTERVAL_DECREMENT = 0.25f; -const float LEVEL_WAVE_INTERVAL_MIN = 2.0f; - -// Flags - Grid -inline bool RENDER_GRID = false; - -// Flags - Bullets -const bool BULLETS_WRAP_AROUND_SCREEN = true; - -// Flags - Stars -inline bool RENDER_STAR_HALO = true; - -// Flags - Asteroids -const bool SPAWN_AT_START = true; -const bool ASTEROIDS_SELF_COLLISION = true; \ No newline at end of file +const float LEVEL_WAVE_INTERVAL_DECREMENT = 0.5f; +const float LEVEL_WAVE_INTERVAL_MIN = 2.0f; \ No newline at end of file diff --git a/src/core/utils.hpp b/src/core/utils.hpp index 12b60b8..316378c 100644 --- a/src/core/utils.hpp +++ b/src/core/utils.hpp @@ -1,14 +1,10 @@ #pragma once #include "Color.hpp" -#include "Image.hpp" -#include "Vector2.hpp" #include "precomp.hpp" #include "raylib.h" -#include "rlgl.h" #include "settings.hpp" #include -#include inline raylib::Vector2 rotateAround(raylib::Vector2 point, raylib::Vector2 center, float angle) { return { @@ -22,7 +18,7 @@ inline float getRandomValue(float min, float max) { } inline float smoothstep(float x) { - return x < 0.5f ? 2 * x * x : 1 - powf(-2 * x + 2, 2) / 2; + return x * x * (3 - 2 * x); } inline float easeInOutBack(float x) { @@ -41,127 +37,4 @@ inline raylib::Color randomColor() { GetRandomValue(128, 255), 255 ); -} - -/** - * @brief Given a center and a list of vertices, draw a filled polygon - * Raylib does provide a few functions for drawing polygons, but I don't really like the way they work and in some scenarios the results are incomplete polygons - * - * @param center - * @param vertices - * @param color - */ -static void drawFilledPolygon(raylib::Vector2 center, raylib::Vector2 *vertices, size_t size, raylib::Color color) { - if (size < 3) return; - if (size == 3) { - DrawTriangle(vertices[0], vertices[1], vertices[2], color); - } - -#if defined(SUPPORT_QUADS_DRAW_MODE) - constexpr Texture2D texShapes = { 1, 1, 1, 1, 7 }; - constexpr Rectangle texShapesRec = { 0.0f, 0.0f, 1.0f, 1.0f }; - rlSetTexture(texShapes.id); - - rlBegin(RL_QUADS); - rlColor4ub(color.r, color.g, color.b, color.a); - for (int i = 0; i < size; i++) - { - int j = (i + 1) % size; - - rlTexCoord2f(texShapesRec.x / texShapes.width, texShapesRec.y / texShapes.height); - rlVertex2f(vertices[i].x, vertices[i].y); - - rlTexCoord2f(texShapesRec.x / texShapes.width, (texShapesRec.y + texShapesRec.height) / texShapes.height); - rlVertex2f(center.x, center.y); - - rlTexCoord2f((texShapesRec.x + texShapesRec.width) / texShapes.width, (texShapesRec.y + texShapesRec.height) / texShapes.height); - rlVertex2f(center.x, center.y); - - rlTexCoord2f((texShapesRec.x + texShapesRec.width) / texShapes.width, texShapesRec.y / texShapes.height); - rlVertex2f(vertices[j].x, vertices[j].y); - } - - rlEnd(); - - rlSetTexture(0); - #else - rlBegin(RL_TRIANGLES); - rlColor4ub(color.r, color.g, color.b, color.a); - for (int i = 0; i < size; i++) - { - int j = i + 1; - if (j == size) j = 0; - - rlVertex2f(vertices[i].x, vertices[i].y); - rlVertex2f(center.x, center.y); - rlVertex2f(vertices[j].x, vertices[j].y); - } - - rlEnd(); -#endif -} - -static void drawPolygonOutline(raylib::Vector2 *vertices, size_t size, raylib::Color color) { - if (size < 3) return; - - rlBegin(RL_LINES); - rlColor4ub(color.r, color.g, color.b, color.a); - - for (int i = 0; i < size - 1; i++) { - rlVertex2f(vertices[i].x, vertices[i].y); - rlVertex2f(vertices[i + 1].x, vertices[i + 1].y); - } - - rlVertex2f(vertices[size - 1].x, vertices[size - 1].y); - rlVertex2f(vertices[0].x, vertices[0].y); - - rlEnd(); -} - -static float noise(raylib::Vector2 position) { - static std::optional image; - - if (!image.has_value()) { - image = GenImagePerlinNoise(200, 200, 0, 0, 0.2f); - } - - const int x = (int)position.x % 200; - const int y = (int)position.y % 200; - - const auto color = image->GetColor(x, y); - return color.r / 255.0f; -} - -static float randomKelvin() { - return powf(getRandomValue(0.0f, 1.0f), 1.0f / 2.0f) * 9000.0f + 1000.0f; -} - -static raylib::Color convertKelvinToColor(float temperature) { - // Using Tanner Helland's formula and Neil Bartlett's implementation - - float red, green, blue; - - if (temperature <= 6600.0f) { - red = 255.0f; - green = temperature / 100.0f - 2.0f; - green = 99.4708025861f * logf(green) - 161.1195681661f; - if (temperature <= 1900.0f) { - blue = 0.0f; - } else { - blue = temperature / 100.0f - 10.0f; - blue = 138.5177312231f * logf(blue) - 305.0447927307f; - } - } else { - red = temperature / 100.0f - 55.0f; - red = 329.698727446f * powf(red, -0.1332047592f); - green = temperature / 100.0f - 50.0f; - green = 288.1221695283f * powf(green, -0.0755148492f); - blue = 255.0f; - } - - return raylib::Color((unsigned char)red, (unsigned char)green, (unsigned char)blue, 255); -} - -inline float oscillate(float perSecond, float amplitude, float offset = 0.0f) { - return sinf(PI * 2.0f * GetTime() * perSecond + offset) * amplitude; } \ No newline at end of file