From f7a027d6bf42416292e2e5cc526750b420558ee1 Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Sat, 19 Nov 2022 17:38:40 +0100 Subject: [PATCH] initial CPC version (with some Azur refactoring) --- .gitignore | 13 + CMakeLists.txt | 49 +++ TODO | 7 + assets-cg/base_1.png | Bin 0 -> 949 bytes assets-cg/base_2.png | Bin 0 -> 1166 bytes assets-cg/base_3.png | Bin 0 -> 1137 bytes assets-cg/dot.png | Bin 0 -> 589 bytes assets-cg/expl_1.png | Bin 0 -> 1157 bytes assets-cg/expl_2.png | Bin 0 -> 1395 bytes assets-cg/expl_3.png | Bin 0 -> 1621 bytes assets-cg/expl_4.png | Bin 0 -> 1377 bytes assets-cg/font.png | Bin 0 -> 19394 bytes assets-cg/fxconv-metadata.txt | 7 + assets-cg/icon-sel.png | Bin 0 -> 19293 bytes assets-cg/icon-uns.png | Bin 0 -> 15502 bytes assets-cg/icon.xcf | Bin 0 -> 34075 bytes assets-cg/minimap.png | Bin 0 -> 827 bytes assets-cg/mountain_1.png | Bin 0 -> 1594 bytes assets-cg/plane.png | Bin 0 -> 3012 bytes assets-cg/rock_1.png | Bin 0 -> 1078 bytes assets-cg/rock_2.png | Bin 0 -> 917 bytes assets-cg/square.png | Bin 0 -> 952 bytes assets-cg/tree_1.png | Bin 0 -> 1505 bytes assets-cg/tree_2.png | Bin 0 -> 17233 bytes assets-cg/tree_3.png | Bin 0 -> 8555 bytes src/afterburner.h | 188 +++++++++ src/camera.cpp | 112 ++++++ src/horizon.cpp | 60 +++ src/horizon.s | 94 +++++ src/image.cpp | 56 +++ src/main.cpp | 722 ++++++++++++++++++++++++++++++++++ src/world.cpp | 163 ++++++++ 32 files changed, 1471 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 TODO create mode 100644 assets-cg/base_1.png create mode 100644 assets-cg/base_2.png create mode 100644 assets-cg/base_3.png create mode 100644 assets-cg/dot.png create mode 100644 assets-cg/expl_1.png create mode 100644 assets-cg/expl_2.png create mode 100644 assets-cg/expl_3.png create mode 100644 assets-cg/expl_4.png create mode 100644 assets-cg/font.png create mode 100644 assets-cg/fxconv-metadata.txt create mode 100644 assets-cg/icon-sel.png create mode 100644 assets-cg/icon-uns.png create mode 100644 assets-cg/icon.xcf create mode 100644 assets-cg/minimap.png create mode 100644 assets-cg/mountain_1.png create mode 100644 assets-cg/plane.png create mode 100644 assets-cg/rock_1.png create mode 100644 assets-cg/rock_2.png create mode 100644 assets-cg/square.png create mode 100644 assets-cg/tree_1.png create mode 100644 assets-cg/tree_2.png create mode 100644 assets-cg/tree_3.png create mode 100644 src/afterburner.h create mode 100644 src/camera.cpp create mode 100644 src/horizon.cpp create mode 100644 src/horizon.s create mode 100644 src/image.cpp create mode 100644 src/main.cpp create mode 100644 src/world.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c4f84b --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# Build files +/build-fx +/build-cg +/*.g1a +/*.g3a + +# Python bytecode + __pycache__/ + +# Common IDE files +*.sublime-project +*.sublime-workspace +.vscode diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d36ba48 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,49 @@ +cmake_minimum_required(VERSION 3.15) +project(Afterburner VERSION 1.0 LANGUAGES CXX C ASM) + +include(GenerateG3A) +include(Fxconv) + +find_package(Azur 0.1 REQUIRED) +find_package(Gint 2.8 REQUIRED) +find_package(LibProf 2.1 REQUIRED) + +set(SOURCES + src/camera.cpp + src/image.cpp + src/main.cpp + src/horizon.cpp + src/horizon.s + src/world.cpp +) +set(ASSETS + assets-cg/plane.png + assets-cg/square.png + assets-cg/dot.png + + assets-cg/tree_1.png + assets-cg/tree_2.png + assets-cg/tree_3.png + assets-cg/mountain_1.png + assets-cg/rock_1.png + assets-cg/rock_2.png + assets-cg/base_1.png + assets-cg/base_2.png + assets-cg/base_3.png + assets-cg/minimap.png + assets-cg/expl_1.png + assets-cg/expl_2.png + assets-cg/expl_3.png + assets-cg/expl_4.png + assets-cg/font.png +) + +fxconv_declare_assets(${ASSETS} WITH_METADATA) + +add_executable(afterburner ${SOURCES} ${ASSETS}) +target_compile_options(afterburner PRIVATE -Wall -Wextra -Os -std=c++20) +target_link_options(afterburner PRIVATE -Wl,-Map=map -Wl,--print-memory-usage) +target_link_libraries(afterburner Azur::Azur -lnum LibProf::LibProf Gint::Gint) + +generate_g3a(TARGET afterburner OUTPUT "AfterBur.g3a" + NAME "Afterburner" ICONS assets-cg/icon-uns.png assets-cg/icon-sel.png) diff --git a/TODO b/TODO new file mode 100644 index 0000000..0cf15c2 --- /dev/null +++ b/TODO @@ -0,0 +1,7 @@ +TODO: +* Title screen +* Multiple games +* Limit number of bombs +* Fix map boundary tests +* More interesting map generation +* Expand gameplay: missions, enemies, etc diff --git a/assets-cg/base_1.png b/assets-cg/base_1.png new file mode 100644 index 0000000000000000000000000000000000000000..836ad9eb2dd9965c170226dd22cf1b7dcdfd4f77 GIT binary patch literal 949 zcmV;m14{gfP)EX>4Tx04R}tkv&MmKpe$iKg22(DRvNX$WWauh>AFB6^c+H)C#RSm|Xe=O&XFE z7e~Rh;NZt%)xpJCR|i)?5c~jfbaGO3krMxx6k5c1aNLh~_a1le0Dq&xR5LgZsG4P@ zlL;Z4TNS!r5kv?P^dKTJQ=b#X6ge3JS*_Gq>z@3D;k>r8%ypW>NMaF75FtQD4P{hdAx^7CiitGsCp`Q^j$b5~Os+B* zITlcb3d!+<|H1EW&BEk_n-op}T`#u%F#>e&0?oQ@e;?a+^8^Sy16NwxUu^(0pQP8@ zTI>ku-v%zO+nT%wTuDQLn_Hp_EWT>mu4RCM> zj20<--Q(S%&ffk#)9UXB_@8oic;ups00006VoOIv0RI600RN!9r;`8x010qNS#tmY zE+YT{E+YYWr9XB6000McNliru<^&1}6CMJ^Q{?~v02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00E6jL_t(Y$L*D`lEN?$#=kO%JjTkCNMvMXWF-=btmILU z$Vg-)5_vL@aW1E}Y_@4G2Q%E<44-Q!&2PW`SKzJxs>{WsmqUl4FG5#;5!sqy&H>H= z#+c+i(Dz>*M@vKifEXjr=S%l2w9`&YFs)YjF!ZT(E_-2&NzPd;!59dj2`Wt#Ysy8h zxnp3foGZUy4MU&Cai3aY-iM~H9mjoI>fkkBYOTfLu$!fkwc7=pPIh|UZa=2WuGi05 zAA}I5d&Y5}WFK_Kn6%m4T}sE^2RLVoP#F{v&C0@10I)XyZmQ|+7Bg2`3@oKL#)uF? z76^kh5CB%v<-8H>q7x|&Si>18`@;KhcPioRO2ddKtGm|mIEOP#W^biGXmLhFu=YOB zI$O0;IcM|TyHuarlE|Gd2P&JeldaED>TZ^EX>4Tx04R}tkv&MmKpe$iKg22(DRvNX$WWauh>AFB6^c+H)C#RSm|Xe=O&XFE z7e~Rh;NZt%)xpJCR|i)?5c~jfbaGO3krMxx6k5c1aNLh~_a1le0Dq&xR5LgZsG4P@ zlL;Z4TNS!r5kv?P^dKTJQ=b#X6ge3JS*_Gq>z@3D;k>r8%ypW>NMaF75FtQD4P{hdAx^7CiitGsCp`Q^j$b5~Os+B* zITlcb3d!+<|H1EW&BEk_n-op}T`#u%F#>e&0?oQ@e;?a+^8^Sy16NwxUu^(0pQP8@ zTI>ku-v%zO+nT%wTuDQLn_Hp_EWT>mu4RCM> zj20<--Q(S%&ffk#)9UXB_@8oic;ups00006VoOIv0RI600RN!9r;`8x010qNS#tmY zE+YT{E+YYWr9XB6000McNliru<^&23J2CMF){_7L02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00L%7L_t(Y$HiBzma8xj{?b!X$s>$hiCh`Eva%9+W#pAS zLLMRV%F4<}B(gG(P>Jgm5Hh3*8|a?1WB42L{md{Y0KXVw&rAQlPEP=+DBOFvaDKTB z;>W>bP!8DGaBgH73GptdkAAwRT{#KyE@-S;Q8|hE;_t`lNfqE8Ks_z+9q5|s6bW6A z$3NXj;^=|l00)5TS75KqD=1OzK~p3a$~`zH@Tv;n*5+;8quM=dyggZ;viXf$&fu%Ha&cQ1*sR=R> zsB3~^4aM!Qs*o!sU}*%6Lu<2iQZ+clHs?rqC5nF;k?OW55Yg%=wzdhjpCguWt=rZm zNf;2!G#uKV(Dt;bX&g#5dE3utxoJb;6?#w)?Cp(36xqH;B3>{B`dw_aomNSt6s_6L z^JnEb#eE-YzUE52c$~XxdkH|QG$}+XUB&l|{8cmmvs65F}8vC`dtES^Np_AQ{h|J67NBH9J|){i0p ge13jB_k#NP4?=62m^@7uMgRZ+07*qoM6N<$f;8t2Q~&?~ literal 0 HcmV?d00001 diff --git a/assets-cg/base_3.png b/assets-cg/base_3.png new file mode 100644 index 0000000000000000000000000000000000000000..6f7b5ee320a2e0b7a1e93c774772a29b1569d48b GIT binary patch literal 1137 zcmV-%1djWOP)EX>4Tx04R}tkv&MmKpe$iKg22(DRvNX$WWauh>AFB6^c+H)C#RSm|Xe=O&XFE z7e~Rh;NZt%)xpJCR|i)?5c~jfbaGO3krMxx6k5c1aNLh~_a1le0Dq&xR5LgZsG4P@ zlL;Z4TNS!r5kv?P^dKTJQ=b#X6ge3JS*_Gq>z@3D;k>r8%ypW>NMaF75FtQD4P{hdAx^7CiitGsCp`Q^j$b5~Os+B* zITlcb3d!+<|H1EW&BEk_n-op}T`#u%F#>e&0?oQ@e;?a+^8^Sy16NwxUu^(0pQP8@ zTI>ku-v%zO+nT%wTuDQLn_Hp_EWT>mu4RCM> zj20<--Q(S%&ffk#)9UXB_@8oic;ups00006VoOIv0RI600RN!9r;`8x010qNS#tmY zE+YT{E+YYWr9XB6000McNliru<^&2400@(F+mrwR02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00K!#L_t(Y$E}v3ma8xjhJSiiAoB<#E0M^^N@OJxSy{QV zva%An5?L8}CGyHV0`fYpNQZ_YfwbE>heMi@e3PGbso;B)k5%i&T@4&=OE3eiwTtycG;<*P#EB)4A->7~zEZ(<0MG+UJ;t6H zV*mhJ>*#t0vIEKB6kBsZ%Ks7YNx4n01I-)=$zCh-IWPmI10i5L=s5UzJd6PF^K;LB z;{X7t$C!Zt&~;`BJ{}KaC@SA^003wpbe%y};c?ffDvez%2tv6Sv%a_ZF5IUS;lL0ALMq<-&(*1)ND>2s=Q^7g&ml=`It(kTs61O!6i^l+lOw$oU?zPF?{ zq>+-}K9ox)&-o`$|2kOu)Y`X`-9eQ)xS6P^n>kMB)9 zS7Q!@fT1AE&U?4HtQ~`W22|G>boH}1g!0svpKCJT<@_w2jB(dspU~QbD-xH*l(dn* zS&<2R_4Z(3s}bEOc5?X~2c!(nughQ8w6Ww2?37*&o)FXr5)q6s6I5oxluqoxu{ex! zaZwPyd%#~So3~K<_^HS9++0LE4DLc0uC-n?b3Vjc3$67`THjesSYE0?c8iliR{%cgKk(wO}R>oapxf(We)00000NkvXXu0mjf D?H&8} literal 0 HcmV?d00001 diff --git a/assets-cg/dot.png b/assets-cg/dot.png new file mode 100644 index 0000000000000000000000000000000000000000..f635d6f03def3178b665ed28967f57721e709969 GIT binary patch literal 589 zcmV-T0EX>4Tx04R}tkv&MmKpe$iKg22(DRvNX$WWauh>AFB6^c+H)C#RSm|Xe=O&XFE z7e~Rh;NZt%)xpJCR|i)?5c~jfbaGO3krMxx6k5c1aNLh~_a1le0Dq&xR5LgZsG4P@ zlL;Z4TNS!r5kv?P^dKTJQ=b#X6ge3JS*_Gq>z@3D;k>r8%ypW>NMaF75FtQD4P{hdAx^7CiitGsCp`Q^j$b5~Os+B* zITlcb3d!+<|H1EW&BEk_n-op}T`#u%F#>e&0?oQ@e;?a+^8^Sy16NwxUu^(0pQP8@ zTI>ku-v%zO+nT%wTuDQLn_Hp_EWT>mu4RCM> zj20<--Q(S%&ffk#)9UXB_@8oic;ups00006VoOIv0RI600RN!9r;`8x010qNS#tmY zE+YT{E+YYWr9XB6000McNliru<^%{5Ef5~U;e`MI02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{001RPL_t&t*JEU0U|{&q!0?{|3K$s}7#WcTV1S7M3&1MK bh|Np@hDinn=pS}e00000NkvXXu0mjf)!FUg literal 0 HcmV?d00001 diff --git a/assets-cg/expl_1.png b/assets-cg/expl_1.png new file mode 100644 index 0000000000000000000000000000000000000000..f4d838ada64931cb5c15fafeec7d74bd3b06f545 GIT binary patch literal 1157 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 TOxSMk00000NkvXXu0mjfOjF@T literal 0 HcmV?d00001 diff --git a/assets-cg/expl_4.png b/assets-cg/expl_4.png new file mode 100644 index 0000000000000000000000000000000000000000..b96d0d5dc26afe1905b2e21784ee9bd4def48b08 GIT binary patch literal 1377 zcmV-n1)lneP)EX>4Tx04R}tkv&MmKpe$iKg22(DRvNX$WWauh>AFB6^c+H)C#RSm|Xe=O&XFE z7e~Rh;NZt%)xpJCR|i)?5c~jfbaGO3krMxx6k5c1aNLh~_a1le0Dq&xR5LgZsG4P@ zlL;Z4TNS!r5kv?P^dKTJQ=b#X6ge3JS*_Gq>z@3D;k>r8%ypW>NMaF75FtQD4P{hdAx^7CiitGsCp`Q^j$b5~Os+B* zITlcb3d!+<|H1EW&BEk_n-op}T`#u%F#>e&0?oQ@e;?a+^8^Sy16NwxUu^(0pQP8@ zTI>ku-v%zO+nT%wTuDQLn_Hp_EWT>mu4RCM> zj20<--Q(S%&ffk#)9UXB_@8oic;ups00006VoOIv0RI600RN!9r;`8x010qNS#tmY zE+YT{E+YYWr9XB6000McNliru<^&24DIL1#pp*ar02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00TKmL_t(&-pyM}uEQV<1jIqR=n=Z;5xVFRy68b8^=%#% zFE56%Jw}PtMVkWtF}8;!r+{m_T|WKy`h4efvMVzcYWM5&owwWNb4G>Liffc+Mh!Aw zScGl2%cp&!J+WU$vu^tzGA1)Ek9tJw9g7TZxxHSNtlxVlN)A_)t_bgNCA_GvF-`=c zTQY?-rbc)N6;AS?G@fb9!d4gst3CJ}jnjtq(RK)}B{Tv?r^V6S(7LPg_DX>3#}*_E zS7m}(>TB|73KeOtqDlJbLcM_7whgO3sVeNh_ww{}?+u{=-6Gxe)Z$Iylu}CfYZ?jI z0I>%~O>sj3FE=oNAv^mWQG8#|YvnExN=u$V^Rp$jjcS6Udj$;}&Aw;lPHDS&8l7+? zX0?y3zF|K$voOJB^@BOIb8EL?@p@KMrhdOXUei$Cqde2wZt&3b(kQE<{aurl4(~I{ zQ;%_wy1=BE28%q_Wh-yt+>l&fEFnKq`e@(IFkm!iRB9|Nknjlp1u-FHp){mlfYVqmn4sd(M5!+WfwRn3KZ)k6~%Lh$0QN@VG zP{nJ-`h4eEs)^{Qy{u26S$g92ma4p7WPA}4r_ypv2=L1<7sVWFU`loZ1_e>gpaOhEP*%B}xPJ&#w~QgSn@ z+I~H2y04j$w8~omicLF*1DFS(SXeVXANLxjH5-WXjDrZve@mngDF3BdZ`8q29s|)J zGg=F13nb>*+9ADcJ?$KY?(AT2#Ik(HC~A8O=f+GO`}41vA$o;tt=aRe>y!kwgp}k_ zM#wT-+3DJ=j(JvHuuNA literal 0 HcmV?d00001 diff --git a/assets-cg/font.png b/assets-cg/font.png new file mode 100644 index 0000000000000000000000000000000000000000..3cf37764785927c158e2adde476388eb4e9201bb GIT binary patch literal 19394 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 literal 0 HcmV?d00001 diff --git a/assets-cg/fxconv-metadata.txt b/assets-cg/fxconv-metadata.txt new file mode 100644 index 0000000..ca55401 --- /dev/null +++ b/assets-cg/fxconv-metadata.txt @@ -0,0 +1,7 @@ +*.png: + type: bopti-image + name_regex: (.*)\.png img_\1 + profile: p8_rgb565a + +minimap.png: + profile: rgb565a diff --git a/assets-cg/icon-sel.png b/assets-cg/icon-sel.png new file mode 100644 index 0000000000000000000000000000000000000000..e5f1cf0dc7df44aa142c07decccec86a5b57889a GIT binary patch literal 19293 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

LDO?uVB0R|9JcKxbX{!tsjSfP> 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 P_?KBtMnY3`!}0$BgVMz? literal 0 HcmV?d00001 diff --git a/assets-cg/minimap.png b/assets-cg/minimap.png new file mode 100644 index 0000000000000000000000000000000000000000..3d9be3a7634f8a2003bd3a0a1195589e297f4781 GIT binary patch literal 827 zcmV-B1H}A^P)EX>4Tx04R}tkv&MmKpe$iKg22(DRvNX$WWauh>AFB6^c+H)C#RSm|Xe=O&XFE z7e~Rh;NZt%)xpJCR|i)?5c~jfbaGO3krMxx6k5c1aNLh~_a1le0Dq&xR5LgZsG4P@ zlL;Z4TNS!r5kv?P^dKTJQ=b#X6ge3JS*_Gq>z@3D;k>r8%ypW>NMaF75FtQD4P{hdAx^7CiitGsCp`Q^j$b5~Os+B* zITlcb3d!+<|H1EW&BEk_n-op}T`#u%F#>e&0?oQ@e;?a+^8^Sy16NwxUu^(0pQP8@ zTI>ku-v%zO+nT%wTuDQLn_Hp_EWT>mu4RCM> zj20<--Q(S%&ffk#)9UXB_@8oic;ups00006VoOIv0RI600RN!9r;`8x010qNS#tmY zE+YT{E+YYWr9XB6000McNliru<^&23DH*&+4!QsU02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{009$8L_t(&-tAdI4ul{OYmHYfzQ?!tIN#!rd;oi46IR6~ ztAnuVBpv``rZa7z2q^a9L!`!cMrRGgx>%+FNScG_j0;4PXc+(iut^<u*|JGZ7peB|!BCye-oO9F)SxT8>Pzyp2iGcR8{ zz|BjnwF5Cr`U8X{>1*1F&)Co>EG8=xF}0BifR58TPD=vEX>4Tx04R}tkv&MmKpe$iKg22(DRvNX$WWauh>AFB6^c+H)C#RSm|Xe=O&XFE z7e~Rh;NZt%)xpJCR|i)?5c~jfbaGO3krMxx6k5c1aNLh~_a1le0Dq&xR5LgZsG4P@ zlL;Z4TNS!r5kv?P^dKTJQ=b#X6ge3JS*_Gq>z@3D;k>r8%ypW>NMaF75FtQD4P{hdAx^7CiitGsCp`Q^j$b5~Os+B* zITlcb3d!+<|H1EW&BEk_n-op}T`#u%F#>e&0?oQ@e;?a+^8^Sy16NwxUu^(0pQP8@ zTI>ku-v%zO+nT%wTuDQLn_Hp_EWT>mu4RCM> zj20<--Q(S%&ffk#)9UXB_@8oic;ups000DWNklcxK0$D zzh!jrdPtw-ec!xql6K+)gCtAxe)2ru=SNZl`>=Ygiu2>g@xJ|&HRdNVGCqc<4_5#H z?oJl~jAzeZ>}LrK?Fa6{h0}Q7{>c^=$`~0R0|1mSO@z+-?||gF&CSoOF+Uk~DjN(d zuz@|uhgD_SbPA5`!m(XeoSyoBfc07x=gu5~4z9V>Qe8GMWZ!^iGX_3>`lAfpT5~1p z(t#lR0<71nILu5oH2 zSrp5&0we%zx3^$f`s;J~tiRn4q?z=@L#Bb~dK17L+vN{207lYv6M3*+t73PL z`8AJ1YFQAxRw#(qkroM(_u@YTq|gFTmX$&QD3*h;&&{NgteaSii8Z*ePzC^HLXeH- zyf()}X4Bp_d9PnDMrx3}*O;FaG)ExhJsx~CH-U~L7VB&}r7As%w5au)3$qLhK+v8>cS`F)QjrzBs`FsvF!1WF9SH{@ z7LE%!la2{%3xK@#bhfo1|-no4Rj_oqQt7DXz zGz_SE?RI+$%_R?xl15IB0nOLuP`y^gy}LK?;lnTX=y3ycGbvy2d{LIgPjISij)K$u zPnYc`0Bml4W|R~J$D1lM=@@yHYM%~(Mz1Jss`|AFPIG+06|=M@+?@Ap+=8;jWw(5d z34|NG=ZzIXbNo#^j#!9kTB(%saI{N@Ar+%o$UbG58hjG;nxfgvN7L-zrTMoj-D81 zKoD9ToGJ_XYb*=eWa)Qc9s2U+tC7wO8;4CXUph_;%_Z-TpH>P5PlxgoFFq0bbET9I z!Bk3lUx!J}eEIr~zf+u^5B+IK^znsW7R4q^`B5(=up5zpyj{Iq!8`Y(Fi@%%Awbfje4eHdYXOr?)Bu2;AAgiED&xN* z$mjJwWX6K24o1EgzkK}$#px*n0MeOZAmJz7K6^Cpu@GeJmxZEX>4Tx04R}tkv&MmKpe$iKg22(DRvNX$WWauh>AFB6^c+H)C#RSm|Xe=O&XFE z7e~Rh;NZt%)xpJCR|i)?5c~jfbaGO3krMxx6k5c1aNLh~_a1le0Dq&xR5LgZsG4P@ zlL;Z4TNS!r5kv?P^dKTJQ=b#X6ge3JS*_Gq>z@3D;k>r8%ypW>NMaF75FtQD4P{hdAx^7CiitGsCp`Q^j$b5~Os+B* zITlcb3d!+<|H1EW&BEk_n-op}T`#u%F#>e&0?oQ@e;?a+^8^Sy16NwxUu^(0pQP8@ zTI>ku-v%zO+nT%wTuDQLn_Hp_EWT>mu4RCM> zj20<--Q(S%&ffk#)9UXB_@8oic;ups00006VoOIv0RI600RN!9r;`8x010qNS#tmY zE+YT{E+YYWr9XB6000McNliru<^&246cnl4P>uiq02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{012E)L_t(|+U;G4#5aj4{(RT?E?sau_?z_q@8@RVTP+`!Lp8V~jE5ICe)E!NwS4{`_`F zA?lZ@pS*g_V0!BF4NXL&#cB7hGDrs3`5gX znYDIlcdNv~c)E7a_94oo*--#T1NPg3o>kqSpMo_67L)D&L&@?i^Q#Lf$W? zjTo&jIVqYxX+7PWX1bynjYlQLZi>;)J$LM%?ZfO5?~#N^^(RClYMQiHL;957CUU>8vufsQD!qkZ3Z8to#v<&;*C$c8lw z_dNWJ^gbTB)NgWR;P47@-^X$6pxsE}>}z{Je6EaVEOXfQd7hsOw$vI3)oABd<8>cw zoMGdb`b^C%x21nCLzC)<%9sSkDtx|{%MRUc+($|_xF1lqocuQ{1RDEdCD;&8g%4+n7JU9khpxF~%)^d_msDX(E&qc#G1~FmB9SsJ~VBX}O`v!n2)9mP^ z@z@8HcOiOFjcVUi5~*z9Y7M=v3)@_V>~$Rz+uJolF0FG2^5kAMe+Lrg&I5YuftdnU zb%U({F0p-kS6q@hdYB5+%p{7z=ro3%516x&CZ+$UBM%7*u?BNCL!0XJCUuv2|NUJ_ zVlkCk(LRHn6Jq~REBn+UXGs#>LOYdIBt|4C!9*l$t?B!|8^^I*mDRrPorOH(ICgF! z(&6Cl!MZmN&lJ+`I!IGJ0fRzO}k%+4qTJ3cMvhY%N%h-)TUmwreKK`|hAy7^C7-9x9JRzui8#N{0tB9^{zk2JBO-v1|!_%;-p6e!Z=Iy zW>vEwrLqlNuT?X?AZ`3eMQ6D<6jjLR+XrXQ0ij*<+IY0gfRAZI7eyMw7t&AnpH(Sx z={Z$f=Pi{IyZ5eB4mQZjzIhPAkJx<-BbY#!Bg-O|Mb6sZB8eafW*Nt>E;E?CvT z#qGyO5ud9`_uZ#TdYej87V_4r zN3-srASG>{eKLHS>Tj&zYD9HQM7v93_!i9GW9~lMmC~ovmT!l2e6yVJW|uGb<|0p| z%~AUdu95@_Nzgh~y_UwRr@%P_aX12LTwFcJNiG%yPI$;RmRJ)z^1cG+r?EAiRBMGNio{2b;8xD6eo#*+vN|CK_c6yf6 z@`8o?_3>QtM_&0QY~uFL#8Gb(oB7#T0xh^8+}xJNyUQ;3v=wBu0pe^rUzj*Yo5DEa zEPn?qlT<&T7IrSCG1PgO7ja|)NtlHaCl%u?n#b8FJ_1oGtkStqwQP&Mp2ec_`bAH&Djk#;uf*LS+%XogI7Z3R5fOU8%72BxTBX!RUJ_6#=ob@H?CD~LlKC| zDVJoCtKEIB&fy9J7q5ZG7CHkDR#?D;P-4rD-2q}j_R`C^bnY(pJfsuvrfK?LQtgaX zg{Omqf3cn##0nprhq0#4e{W{WS^yOCeSI1zrHYX|Bu>;G!sY<@`?v$>xJ*J#HAhTJ z3742JY8)Z|I!6LqmxVY$n@BMm&gg@qB&D{AA{}}3CHl=p`zYJ(`svQ^g#C~XvQ70Q zC2bzk?p6I(LU1t)d3VR4fbWEn)#DT(s0I%#KMTkHm114Pw(%``+k!8C7umM=#VZuc;4_sftrL0(m z*IwkrVEf8$Lq;?8TR94`6`tneWbik?aU)*HF*a zA`K@SNlQ`#JXAJa3TZfFOqxboic3-91}%A78x$IwPm)s%i8^gVG+U5S>s+mY%4s1V zfhgj%qHVN;#K}T3w58;;AcGl#%aMlqftCrw0TsHy81qbxi;@MD85cl=!9|WlU8tsc zx7BVb0S$sDC1$-79$Y^uP1O0oJX30fZ>0I&e`idn!SWZ+PATY=*-PC30000EX>4Tx04R}tkv&MmKpe$iKg22(DRvNX$WWauh>AFB6^c+H)C#RSm|Xe=O&XFE z7e~Rh;NZt%)xpJCR|i)?5c~jfbaGO3krMxx6k5c1aNLh~_a1le0Dq&xR5LgZsG4P@ zlL;Z4TNS!r5kv?P^dKTJQ=b#X6ge3JS*_Gq>z@3D;k>r8%ypW>NMaF75FtQD4P{hdAx^7CiitGsCp`Q^j$b5~Os+B* zITlcb3d!+<|H1EW&BEk_n-op}T`#u%F#>e&0?oQ@e;?a+^8^Sy16NwxUu^(0pQP8@ zTI>ku-v%zO+nT%wTuDQLn_Hp_EWT>mu4RCM> zj20<--Q(S%&ffk#)9UXB_@8oic;ups00006VoOIv0RI600RN!9r;`8x010qNS#tmY zE+YT{E+YYWr9XB6000McNliru<^&1}J1%)a&Eo(702y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00It4L_t(Y$L&@s6r5nM#t_KR#T31vTs=jR-1fbsLwOgBkS*PF&$%BM z_;3GUiHubuW99qKPXX8!VdEKo3Z`MF{dJ&;jMZ=;5Jf-}TYl=?9tD&fQL&hNI`jOD z^dvF67$dIB#QnxrN5ADP0F)2Nl(Q>BCv&g7zVePDU^oy?$y~s|+xhu&|GY_t zzc)AO@~`W*&gXm0Z%-u?P=mVWrMaIk>w{JZJ8ds81?v0GKhhRW=5ic%+Ix5H)Ny*j z;5RX$>gp)&$RS`3_yWRMl<%5uGq!pxppjWul9oKicrgYFz^Y4 zjFcb;*>v;fIBl-#GOx5sc40QQ46!Zq3T0kt34Jyh%~&F1MfZF&ogMpcft0axN^I9z wfun<(c6WcHvgtEX>4Tx04R}tkv&MmKpe$iKg22(DRvNX$WWauh>AFB6^c+H)C#RSm|Xe=O&XFE z7e~Rh;NZt%)xpJCR|i)?5c~jfbaGO3krMxx6k5c1aNLh~_a1le0Dq&xR5LgZsG4P@ zlL;Z4TNS!r5kv?P^dKTJQ=b#X6ge3JS*_Gq>z@3D;k>r8%ypW>NMaF75FtQD4P{hdAx^7CiitGsCp`Q^j$b5~Os+B* zITlcb3d!+<|H1EW&BEk_n-op}T`#u%F#>e&0?oQ@e;?a+^8^Sy16NwxUu^(0pQP8@ zTI>ku-v%zO+nT%wTuDQLn_Hp_EWT>mu4RCM> zj20<--Q(S%&ffk#)9UXB_@8oic;ups00006VoOIv0RI600RN!9r;`8x010qNS#tmY zE+YT{E+YYWr9XB6000McNliru<^&1}J2M(bp*jEn02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00C`DL_t(I%jJ|YYQr!ThJQKs0WxNZArS~Pcrk^}9SQ~G zp+mjo0=YxROgTd@khNsW39@+cU=XAQd+F!{m|7i-R9le~cPxF2EeU=9`=6u-{Oce? z3t_;eZ9dI%atrDX0oN<4$3g%_RjZ-ACE(ry0svzGga^g|F{Kv*CfqTB4|jBQ?UvNH zmdZ7OjP*AP(SD1n283q?zn_ci_7%hy9oqnK0n_%wfXgu8a!GwKhKo$c#>?}Q>}6^m zj4i9*)Rw&NVreTuQPp_7_sr`wXj_2+z&)PY&1OxzbN2iAbv1@s;opvL7hbN|ASaAV00000NkvXXu0mjf{gR2o literal 0 HcmV?d00001 diff --git a/assets-cg/square.png b/assets-cg/square.png new file mode 100644 index 0000000000000000000000000000000000000000..cbef44cd4bfcc9a5c21007ebe421c10f6fd221dd GIT binary patch literal 952 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 literal 0 HcmV?d00001 diff --git a/assets-cg/tree_3.png b/assets-cg/tree_3.png new file mode 100644 index 0000000000000000000000000000000000000000..2d1b94898b3037a183386ead13c53dff8e1d7916 GIT binary patch literal 8555 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 +#include +#include +#include +#include +#include +#include +using namespace libnum; + +//--- +// Horizon shader +//--- + +/* Render the horizon line with a palette. The horizon line passes through + (x,y) and has the specified angle. This is an Azur shader, the function only + queues a command and delays the rendering to azrp_update(). */ +void ab_horizon(int x, int y, float alpha, uint16_t *palette); +/* Configuration call, to be performed at every scale update */ +void ab_horizon_configure(void); + +//--- +// Image utilities +//--- + +/* Allocate a new image from a rotation + upscaling of a square image img. + The source image must be RGB565A, P8_RGB565A or P4_RGB565A. */ +bopti_image_t *image_rotate_scale_square + (bopti_image_t const *img, float alpha, num gamma); + +/* Same but optimized for P8_RGB565A. *anchor_x and *anchor_y should be set to + the coordinates of the "anchor" within the source image, and are updated to + reflect the coordinates of that same spot in the rotated image. */ +bopti_image_t *image_rotate_scale_square_p8_rgb565a + (bopti_image_t const *img, + float alpha, num gamma, + int *anchor_x, int *anchor_y); + +/* An image with all upscaled variations from 0.125x to 2x. */ +class DynamicImage +{ +public: + DynamicImage(bopti_image_t const *source, float m_alpha, prof_t *prof_ctx, + int anchor_x, int anchor_y); + ~DynamicImage(); + + /* Get the closest scaled version, generating it on demand. */ + bopti_image_t *getAtScale(num scale, int *anchor_x, int *anchor_y); + +private: + bopti_image_t *m_images[16]; + int m_anchor_x, m_anchor_y; + int8_t m_ax[16], m_ay[16]; + + bopti_image_t const *m_source; + float m_alpha; + prof_t *m_prof; +}; + +//--- +// 3D transforms and projections +//--- + +struct plane +{ + plane() { + memset(this, 0, sizeof *this); + } + + /* Position in space */ + vec3 pos; + /* Euler angles with aviation terminology */ + float roll, pitch, yaw; + /* Air speed */ + num air_speed; + + // The following are automatically set by plane_update() + + /* Sine and cosine of every angle */ + num cos_r, sin_r, cos_p, sin_p, cos_y, sin_y; + /* Vector looking forward and to the right */ + vec3 forward, right; + /* Vector looking forward but parallel to the ground */ + vec3 forward_ground; +}; + +struct mat3 +{ + num x11, x12, x13; + num x21, x22, x23; + num x31, x32, x33; +}; + +mat3 operator * (mat3 const &A, mat3 const &B); +vec3 operator * (mat3 const &M, vec3 const &u); +num distance2(vec3 u, vec3 v); +num64 distance2_64(vec3 u, vec3 v); + +/* Update computationally-derived parameters of a plane */ +void plane_update(struct plane *plane); + +/* Pixels per world unit (primitive control for FOV) */ +#define SCREEN_SCALING 200 +/* Minimum and maximum distance of visible objects */ +#define NEAR_CLIP_PLANE 8 +#define FAR_CLIP_PLANE 50 +#define SIDE_CLIP_PLANE 25 +/* Margin around the screen where a center point is considered visible */ +#define SCREEN_MARGIN 20 + +/* A 3D point with a handle to some sprite/information/etc. */ +struct ref_point +{ + /* Position of the point in 3D space (evolves during transformations) */ + vec3 pos; + /* Unique identifier (used to identify point properties in world map) */ + int id; +}; + +void transform_world2camera(struct plane const *plane, vec3 camera_pos, + struct ref_point *points, int size); + +void transform_camera2screen(struct ref_point *points, int size); + +//--- +// World map +//--- + +/* Needs to be larger than FAR_CLIP_PLANE for culling optimisations to work */ +#define CHUNK_SIZE 64 + +struct chunk +{ + struct ref_point *points; + int count; +}; + +struct world +{ + int w, h; + int chunks_x, chunks_y; + struct chunk *chunks; +}; + +/* Make a world structure using the specified set of points. The points are + split into chunks, which allows faster lookup later on. The points are all + copied, to the original array is safe to free after this call. */ +struct world *world_make(int w, int h, struct ref_point *points, int count); + +/* Destroy a world structure and its copied points. */ +void world_destroy(struct world *w); + +/* Select the points in the world that are "close" to the specified anchor. + These are points from the anchor's chunk and up to 8 neighboring chunks. The + size of the array is returned in *size. free() the array after use. + + This function rotates chunks, so that for instance if position.x is 0.5 and + forward.x < 0 (ie. we look at a region of space with x<0), chunks from the + other side of the map are rotated in to provide a wrap-around effect. + + extra_size entries are left uninitialized at the end of the array so that + any dynamic sprites can be added. *size does *not* count extra_size. */ +struct ref_point *world_select(struct world const *w, vec3 position, + vec3 forward, int *size, int extra_size); + +//--- +// Game mechanics +//--- + +/* Distance forward where bombs drop */ +#define BOMB_DISTANCE 15 + +struct objective +{ + vec3 pos; + bool destroyed; + int type; +}; + +struct explosion +{ + vec3 pos; + num time; /* if negative, no explosion */ +}; + +#endif /* AFTERBURNER_H */ diff --git a/src/camera.cpp b/src/camera.cpp new file mode 100644 index 0000000..c268026 --- /dev/null +++ b/src/camera.cpp @@ -0,0 +1,112 @@ +#define __BSD_VISIBLE 1 +#include "afterburner.h" +#include +#include + +mat3 operator *(mat3 const &A, mat3 const &B) +{ + mat3 C; + + C.x11 = A.x11 * B.x11 + A.x12 * B.x21 + A.x13 * B.x31; + C.x12 = A.x11 * B.x12 + A.x12 * B.x22 + A.x13 * B.x32; + C.x13 = A.x11 * B.x13 + A.x12 * B.x23 + A.x13 * B.x33; + + C.x21 = A.x21 * B.x11 + A.x22 * B.x21 + A.x23 * B.x31; + C.x22 = A.x21 * B.x12 + A.x22 * B.x22 + A.x23 * B.x32; + C.x23 = A.x21 * B.x13 + A.x22 * B.x23 + A.x23 * B.x33; + + C.x31 = A.x31 * B.x11 + A.x32 * B.x21 + A.x33 * B.x31; + C.x32 = A.x31 * B.x12 + A.x32 * B.x22 + A.x33 * B.x32; + C.x33 = A.x31 * B.x13 + A.x32 * B.x23 + A.x33 * B.x33; + + return C; +} + +vec3 operator * (mat3 const &M, vec3 const &u) +{ + vec3 v; + + v.x = M.x11 * u.x + M.x12 * u.y + M.x13 * u.z; + v.y = M.x21 * u.x + M.x22 * u.y + M.x23 * u.z; + v.z = M.x31 * u.x + M.x32 * u.y + M.x33 * u.z; + + return v; +} + +num distance2(vec3 u, vec3 v) +{ + u -= v; + return u.x * u.x + u.y * u.y + u.z * u.z; +} + +num64 distance2_64(vec3 u, vec3 v) +{ + u -= v; + return num32::dmul(u.x,u.x) + num32::dmul(u.y,u.y) + num32::dmul(u.z,u.z); +} + +void plane_update(struct plane *plane) +{ + float c, s; + + sincosf(plane->roll, &s, &c); + plane->sin_r = s; + plane->cos_r = c; + + sincosf(plane->pitch, &s, &c); + plane->sin_p = s; + plane->cos_p = c; + + sincosf(plane->yaw, &s, &c); + plane->sin_y = s; + plane->cos_y = c; + + plane->forward = vec3( + -plane->sin_y * plane->cos_p, + plane->cos_y * plane->cos_p, + plane->sin_p); + plane->forward_ground = vec3(-plane->sin_y, plane->cos_y, 0); + plane->right = vec3(plane->cos_y, plane->sin_y, 0); +} + +void transform_world2camera(struct plane const *plane, vec3 camera_pos, + struct ref_point *points, int size) +{ + num cos_r = plane->cos_r; + num sin_r = plane->sin_r; + num cos_p = plane->cos_p; + num sin_p = plane->sin_p; + num cos_y = plane->cos_y; + num sin_y = plane->sin_y; + + mat3 m_roll = { + cos_r, 0, sin_r, + 0, 1, 0, + -sin_r, 0, cos_r, + }; + mat3 m_pitch = { + 1, 0, 0, + 0, cos_p, +sin_p, + 0, -sin_p, cos_p, + }; + mat3 m_yaw = { + cos_y, +sin_y, 0, + -sin_y, cos_y, 0, + 0, 0, 1, + }; + + mat3 M = m_roll * (m_pitch * m_yaw); + + for(int i = 0; i < size; i++) + points[i].pos = M * (points[i].pos - camera_pos); +} + +void transform_camera2screen(struct ref_point *points, int size) +{ + for(int i = 0; i < size; i++) { + vec3 r = points[i].pos; + r.x = num(azrp_width / 2) + r.x * SCREEN_SCALING / r.y; + r.z = num(azrp_height / 2) - r.z * SCREEN_SCALING / r.y; + points[i].pos = r; + } +} diff --git a/src/horizon.cpp b/src/horizon.cpp new file mode 100644 index 0000000..01425db --- /dev/null +++ b/src/horizon.cpp @@ -0,0 +1,60 @@ +#include "afterburner.h" +#include +#include +#include +using namespace libnum; + +uint8_t AB_HORIZON_SHADER_ID = -1; + +struct ab_horizon_cmd +{ + /* Shader ID for Azur */ + uint8_t shader_id; + uint8_t _[3]; + + /* Variation in value for a single move of +1x */ + num dx; + /* Variation in value for a row move of -198x and +1y */ + num drow; + /* Palette, supporting indexes 0...255 */ + uint16_t *palette; + /* Current horizon value */ + num current; +}; + +extern "C" { + extern azrp_shader_t ab_horizon_shader; +} + +GCONSTRUCTOR +static void register_shader(void) +{ + AB_HORIZON_SHADER_ID = azrp_register_shader(ab_horizon_shader); +} + +void ab_horizon(int cx, int cy, float alpha, uint16_t *palette) +{ + prof_enter(azrp_perf_cmdgen); + + num sin_alpha = sinf(alpha); + num cos_alpha = cosf(alpha); + + num dx = -sin_alpha; + num dy = -cos_alpha; + + struct ab_horizon_cmd cmd; + cmd.shader_id = AB_HORIZON_SHADER_ID; + cmd.current = dx * -cx + dy *-cy; + cmd.dx = dx; + cmd.drow = -198*dx + dy; + cmd.palette = palette; + + azrp_queue_command(&cmd, sizeof cmd, 0, azrp_frag_count); + prof_leave(azrp_perf_cmdgen); +} + +void ab_horizon_configure(void) +{ + uint32_t value = (azrp_width << 16) | azrp_frag_height; + azrp_set_uniforms(AB_HORIZON_SHADER_ID, (void *)value); +} diff --git a/src/horizon.s b/src/horizon.s new file mode 100644 index 0000000..93bf6c8 --- /dev/null +++ b/src/horizon.s @@ -0,0 +1,94 @@ +/* Horizon shader + + r0: (temporary) + r1: cmd.dx + r2: cmd.drow + r3: palette + r4: azrp_width + r5: cmd + r6: azrp_frag + r7: azrp_frag_height + r8: cmd.current + r9: a constant + r10: mask for color index access + */ + +.global _ab_horizon_shader +.balign 4 + +_ab_horizon_shader: + /* r4: azrp_width (top 16 bits) | azrp_frag_height (bottom 16 bits) + r5: struct ab_horizon_cmd *cmd + r6: uint16_t *fragment */ + + mov.l r8, @-r15 + add #4, r5 + + mov.l r9, @-r15 + extu.w r4, r7 + + mov.l @r5+, r1 /* cmd.dx */ + shlr16 r4 + + mov.l @r5+, r2 /* cmd.drow */ + add #-2, r6 + + mov.l @r5+, r3 /* cmd.palette */ + nop + + mov.l @r5, r8 /* num.current */ + nop + + mov.l r10, @-r15 /* r10 = 0x1ff (511 colors) */ + mov #-1, r10 + + ldrs 1f + mov #-23, r0 + + ldre 2f + shld r0, r10 + + mov.l .round, r9 + nop + +.row: + ldrc r4 + nop + +1: add #2, r6 + mov r8, r0 + + add r9, r0 + nop + + shlr16 r0 + nop + + and r10, r0 + nop + + shll r0 + mov.w @(r0, r3), r0 + + mov.w r0, @r6 +2: add r1, r8 + + dt r7 + nop + + bf.s .row + add r2, r8 + + /* Update cmd.current for the next fragment */ + mov.l r8, @r5 + nop + + mov.l @r15+, r10 + mov.l @r15+, r9 + rts + mov.l @r15+, r8 + +.balign 4 +.round: + /* Add 256 and also round up the next unit */ + .long 0x01008000 diff --git a/src/image.cpp b/src/image.cpp new file mode 100644 index 0000000..b79e2bd --- /dev/null +++ b/src/image.cpp @@ -0,0 +1,56 @@ +#include "afterburner.h" +#include +#include +#include +#include +#include + +//--- +// Dynamic images +//--- + +DynamicImage::DynamicImage(bopti_image_t const *source, float alpha, + prof_t *prof_ctx, int anchor_x, int anchor_y): + m_anchor_x {anchor_x}, m_anchor_y {anchor_y}, m_source {source}, + m_alpha {alpha}, m_prof {prof_ctx} +{ + for(int i = 0; i < 16; i++) { + m_images[i] = NULL; + m_ax[i] = -1; + m_ay[i] = -1; + } +} + +bopti_image_t *DynamicImage::getAtScale(num scale, int *ax, int *ay) +{ + if(m_prof) + prof_enter(*m_prof); + + /* Round to the closest multiple of 0.125 */ + int i = (int)(8 * scale + num(0.5)); + if(i < 0) i = 0; + if(i > 15) i = 15; + + if(!m_images[i]) { + int ax_i=m_anchor_x, ay_i=m_anchor_y; + struct image_linear_map map; + image_rotate_around_scale(m_source, m_alpha, scale.v, false, &ax_i, + &ay_i, &map); + m_images[i] = image_linear_alloc(m_source, &map); + m_ax[i] = ax_i; + m_ay[i] = ay_i; + } + + if(m_prof) + prof_leave(*m_prof); + + *ax = m_ax[i]; + *ay = m_ay[i]; + return m_images[i]; +} + +DynamicImage::~DynamicImage() +{ + for(int i = 0; i < 16; i++) + image_free(m_images[i]); +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..a27e789 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,722 @@ +#include "afterburner.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static char logbuf[512]; + +GUNUSED static void logger(char const *fmt, ...) +{ + if(!usb_is_open()) + return; + + va_list args; + va_start(args, fmt); + + vsnprintf(logbuf, sizeof logbuf, fmt, args); + va_end(args); + + usb_fxlink_text(logbuf, 0); +} + +#define RGB24(hex) \ + (((hex & 0xf80000) >> 8) | \ + ((hex & 0x00fc00) >> 5) | \ + ((hex & 0x0000f8) >> 3)) + +/* Generate the gradient used for the horizon. This is an array of 512 colors, + 256 of which represent the ground, while the other 256 represent the sky. */ +static void generate_horizon_gradient(uint16_t *horizon_colors) +{ + int hc = 0; + + for(int i = 0; i < 256; i++) { + horizon_colors[hc++] = RGB24(0x438747); + } + for(int i = 0; i < 32; i++) { + int r = 0x68 + (0x3f - 0x68) * i / 32; + int g = 0x8c + (0x66 - 0x8c) * i / 32; + int b = 0xd6 + (0xb3 - 0xd6) * i / 32; + horizon_colors[hc++] = RGB24(((r << 16) | (g << 8) | b)); + } + for(int i = 0; i < 64; i++) { + horizon_colors[hc++] = RGB24(0x3f66b3); + }; + for(int i = 0; i < 64; i++) { + int r = 0x3f + (0x0c - 0x3f) * i / 64; + int g = 0x66 + (0x29 - 0x66) * i / 64; + int b = 0xb3 + (0x61 - 0xb3) * i / 64; + horizon_colors[hc++] = RGB24(((r << 16) | (g << 8) | b)); + } + for(int i = 0; i < 96; i++) { + horizon_colors[hc++] = RGB24(0x0c2961); + } +} + +/* A plane controller that individually controls each direction. + * Up/Down: move forward/backward (including pitch) + * Left/Right: move left/right (ignoring roll; parallel to the ground) + * SHIFT+Up/Down: pitch down/up + * SHIFT+Left/Right: turn left/right (yaw) + * ALPHA+Up/Down: move forward/backward (ignoring pitch) + * ALPHA+Left/Right: roll */ +GUNUSED static void controller_debug(struct plane *plane, num dt) +{ + int sh = keydown(KEY_SHIFT); + int al = keydown(KEY_ALPHA); + + float dt_f = (float)dt; + + /* Movement speed for movement controlled here */ + num debug_speed = 6.0; + + if(keydown(KEY_UP) && !sh && !al) + plane->pos += plane->forward * debug_speed * dt; + if(keydown(KEY_DOWN) && !sh && !al) + plane->pos -= plane->forward * debug_speed * dt; + if(keydown(KEY_RIGHT) && !sh && !al) + plane->pos += plane->right * debug_speed * dt; + if(keydown(KEY_LEFT) && !sh && !al) + plane->pos -= plane->right * debug_speed * dt; + + if(keydown(KEY_UP) && sh && !al) + plane->pitch -= 0.3 * dt_f; + if(keydown(KEY_DOWN) && sh && !al) + plane->pitch += 0.3 * dt_f; + if(keydown(KEY_RIGHT) && sh && !al) + plane->yaw -= 0.5 * dt_f; + if(keydown(KEY_LEFT) && sh && !al) + plane->yaw += 0.5 * dt_f; + + if(keydown(KEY_UP) && !sh && al) + plane->pos += plane->forward_ground * debug_speed * dt; + if(keydown(KEY_DOWN) && !sh && al) + plane->pos -= plane->forward_ground * debug_speed * dt; + if(keydown(KEY_LEFT) && !sh && al) + plane->roll += 0.45 * dt_f; + if(keydown(KEY_RIGHT) && !sh && al) + plane->roll -= 0.45 * dt_f; + + if(keydown(KEY_MINUS)) + plane->pos.z -= 2.0 * dt; + if(keydown(KEY_PLUS)) + plane->pos.z += 2.0 * dt; +} + +/* A simple plane controller for players. + * Up/Down: pitch down/up + * Left/Right: turn left/right (yaw) + * SHIFT/ALPHA: accelerate/decelerate + TODO: Automatic roll effect when turning */ +static void controller_simple(struct plane *plane, num dt) +{ + float dt_f = (float)dt; + + if(keydown(KEY_UP)) + plane->pitch -= 0.3 * dt_f; + if(keydown(KEY_DOWN)) + plane->pitch += 0.3 * dt_f; + + if(keydown(KEY_RIGHT)) + plane->yaw -= 0.5 * dt_f; + if(keydown(KEY_LEFT)) + plane->yaw += 0.5 * dt_f; + + if(keydown(KEY_SHIFT)) + plane->air_speed += num(4.0) * dt; + if(keydown(KEY_ALPHA)) + plane->air_speed -= num(4.0) * dt; + + /* Lock roll to player turning, for more immersion and visual effects + while keeping very simple controls */ + float target_roll = 0; + if(keydown(KEY_LEFT)) + target_roll = 0.5; + if(keydown(KEY_RIGHT)) + target_roll = -0.5; + + if(plane->roll < target_roll) { + plane->roll += 0.3 * dt_f; + if(plane->roll > target_roll) + plane->roll = target_roll; + } + if(plane->roll > target_roll) { + plane->roll -= 0.3 * dt_f; + if(plane->roll < target_roll) + plane->roll = target_roll; + } +} + +/* Sort to have closer objects last */ +static int compare_depth(void const *p1, void const *p2) +{ + struct ref_point const *u = (struct ref_point const *)p1; + struct ref_point const *v = (struct ref_point const *)p2; + /* Return v < u */ + return v->pos.y.v - u->pos.y.v; +} + +#define remap(point, _x, _y) { \ + *(_x) = wx + (int)((point).x) * ww / world->w; \ + *(_y) = wy + wh-1 - (int)((point).y) * wh / world->h; \ +} + +/* Generate the minimap as a bopti image (Azur can't easily draw like in the + VRAM, and I don't have time to write a full shader for that) */ +static bopti_image_t *generate_minimap(struct world const *world, + struct plane const *plane, struct objective *objectives, + int objective_count) +{ + extern bopti_image_t img_minimap; + + /* Duplicate the empty minimap (which is full RGB565) */ + image_t *img = image_copy_alloc(&img_minimap, IMAGE_RGB565A); + + /* Location of world map within image */ + int wx=9, wy=9, ww=30, wh=30, x, y; + + for(int i = 0; i < objective_count; i++) { + remap(objectives[i].pos, &x, &y); + int color = objectives[i].destroyed ? C_RGB(15,3,31) : C_RGB(31,2,5); + image_set_pixel(img, x, y, color); + } + + for(int i = 0; i < 32; i++) { + remap(plane->pos + num(i) * plane->forward, &x, &y); + image_set_pixel(img, x, y, C_RGB(0, 18, 4)); + } + + remap(plane->pos, &x, &y); + image_set_pixel(img, x, y, C_RGB(0, 31, 10)); + + return img; +} + +/* Draw player's plane at (x,y) */ +static void draw_player(int x, int y, struct plane *plane, + bool orient_with_angles, bool orient_with_keyboard) +{ + /* Rows: 25 (anchor 17), 27 (anchor 20), 26 (anchor 21) + Columns: all 48 (anchor 23) */ + extern bopti_image_t img_plane; + + int row = 1, col = 1; + if(orient_with_angles) { + if(plane->pitch > 0.08) + row = 0; + if(plane->pitch < -0.08) + row = 2; + if(plane->roll > 0.1) + col = 0; + if(plane->roll < -0.1) + col = 2; + } + if(orient_with_keyboard) { + if(keydown(KEY_DOWN)) + row = 0; + if(keydown(KEY_UP)) + row = 2; + if(keydown(KEY_LEFT)) + col = 0; + if(keydown(KEY_RIGHT)) + col = 2; + } + + int top=0, height=0, ay=0; + if(row == 0) top = 0, height = 25, ay = 17; + if(row == 1) top = 25, height = 27, ay = 20; + if(row == 2) top = 52, height = 26, ay = 21; + + azrp_subimage(x-23, y-ay, &img_plane, 48 * col, top, 48, height, + DIMAGE_NONE); +} + +/* Distance between two points, with wrap-around */ +num64 distance2_wrap_64(struct world const *world, vec3 u, vec3 v) +{ + num dx = u.x - v.x; + num dy = u.y - v.y; + num dz = u.z - v.z; + + /* Choose the closest x distance possible */ + if(dx < num(-world->w)) + dx += num(world->w); + if(dx > num(world->w)) + dx -= num(world->w); + /* Choose the closest y distance possible */ + if(dy < num(-world->h)) + dy += num(world->h); + if(dy > num(world->h)) + dy -= num(world->h); + + return num32::dmul(dx, dx) + num32::dmul(dy, dy) + num32::dmul(dz, dz); +} + +/* Render text with Azur images - quite bad, but I don't have time lol. */ +static void draw_text(int x, int y, char const *fmt, ...) +{ + char str[128]; + va_list args; + va_start(args, fmt); + vsnprintf(str, 128, fmt, args); + va_end(args); + + extern bopti_image_t img_font; + + for(int i = 0; str[i]; i++) { + if(str[i] < 32 || str[i] >= 0x7f) continue; + + int row = (str[i] - 32) >> 4; + int col = (str[i] - 32) & 15; + azrp_subimage(x + 5 * i, y, &img_font, 7 * col + 1, 9 * row + 1, 6, 8, + DIMAGE_NONE); + } +} + +int main(void) +{ + azrp_config_scale(2); + azrp_shader_clear_configure(); + azrp_shader_image_rgb16_configure(); + azrp_shader_image_p8_configure(); + azrp_shader_image_p4_configure(); + ab_horizon_configure(); + + __printf_enable_fp(); + + srand(rtc_ticks()); + + prof_init(); + + usb_interface_t const *intf[] = { &usb_ff_bulk, NULL }; + usb_open(intf, GINT_CALL_NULL); + + int volatile timer_flag = 1; + int timer = timer_configure(TIMER_ANY, 33000, GINT_CALL_SET(&timer_flag)); + timer_start(timer); + + bool lost = false; + + //--- + // Horizon colors + //--- + + uint16_t horizon_colors[512]; + generate_horizon_gradient(horizon_colors); + + //--- + // World map + //--- + + /* Stuff starts to blow up at the density of 1/16 sprite/unit² (4096 + sprites for a 256x256 world): even with chunk-based selection, we end up + with > 2000 points to consider each frame, which makes the 3D transform + blow up to 16-20 ms per frame. That's the obvious next optimization. */ + int point_count = 2048; + struct ref_point *points = (struct ref_point *) + malloc(point_count * sizeof *points); + + /* We give 12 bases to destroy */ + struct objective objectives[12]; + int objective_count = 12; + + /* We allow 3 explosions at the same time */ + struct explosion explosions[3]; + int explosion_count = 3; + + for(int i = 0; i < explosion_count; i++) { + explosions[i].pos = vec3(); + explosions[i].time = num(-1); + } + + for(int i = 0; i < point_count; i++) { + num x, y; + x.v = rand() % num(256).v; + y.v = rand() % num(256).v; + points[i].pos = vec3(x, y, 0); + + if(i < objective_count) { + objectives[i].pos = points[i].pos; + objectives[i].destroyed = false; + objectives[i].type = rand() % 3; + points[i].id = 100 + i; + } + else { + points[i].id = rand() % 6; + } + } + + struct world *world = world_make(256, 256, points, point_count); + if(!world) return 0; + + //--- + // Cached explosion images + //--- + + prof_t perf_scaling; + + /* Unlike normal sprites, we don't rotate exlosions, so we can keep them + cached for the whole execution */ + extern bopti_image_t img_expl_1, img_expl_2, img_expl_3, img_expl_4; + DynamicImage dyn_expl_1(&img_expl_1, 0.0, &perf_scaling, 24, 24); + DynamicImage dyn_expl_2(&img_expl_2, 0.0, &perf_scaling, 24, 24); + DynamicImage dyn_expl_3(&img_expl_3, 0.0, &perf_scaling, 24, 24); + DynamicImage dyn_expl_4(&img_expl_4, 0.0, &perf_scaling, 31, 31); + + DynamicImage *dyn_expl[4] = { + &dyn_expl_1, &dyn_expl_2, &dyn_expl_3, &dyn_expl_4, + }; + + //--- + // HUD and messages + //--- + + char const *message = NULL; + num message_time = num(0.0); + + //--- + // Camera + //--- + + struct plane plane; + plane.pos = vec3(world->w / 2, world->h / 2, 5); + plane.roll = 0.0f; + plane.pitch = 0.0f; + plane.yaw = 0.0f; + plane.air_speed = 12.0; + + int plane_screen_x = 198/2; + int plane_screen_y = 112/2; + + while(1) { + /* Wait until a new frame to cap at the desired FPS */ + while(!timer_flag) sleep(); + + //--- Total frame time + prof_t perf_frame = prof_make(); + prof_enter(perf_frame); + + /* Time difference for this frame (assumes consistant FPS) */ + num dt = 33e-3; + + //--- Physics/modeling time (split in two parts) + prof_t perf_physics = prof_make(); + prof_enter(perf_physics); + + plane_update(&plane); + vec3 camera_pos = plane.pos - num(10) * plane.forward; + + /* Compute horizon reference */ + struct ref_point far_forward; + far_forward.pos = plane.pos + plane.forward * num(FAR_CLIP_PLANE); + far_forward.pos.z = 0; + transform_world2camera(&plane, camera_pos, &far_forward, 1); + transform_camera2screen(&far_forward, 1); + + int visible_explosions = 0; + for(int i = 0; i < explosion_count; i++) + visible_explosions += (explosions[i].time >= num(0)); + + /* Gather world points in chunks close to the plane */ + int sprite_count; + struct ref_point *sprites = world_select(world, plane.pos, + plane.forward, &sprite_count, 1 + visible_explosions); + + /* Extra sprite for the target under the plane */ + sprites[sprite_count].pos = + plane.pos + num(BOMB_DISTANCE) * plane.forward; + sprites[sprite_count].pos.z = 0; + sprites[sprite_count].id = -1; + sprite_count++; + + /* Extra sprites for visible explosions */ + for(int i = 0; i < explosion_count; i++) { + if(explosions[i].time < num(0)) + continue; + + int n = (int)(explosions[i].time * 6) % 4; + sprites[sprite_count].pos = explosions[i].pos; + sprites[sprite_count].id = 9 + n; + sprite_count++; + } + + transform_world2camera(&plane, camera_pos, sprites, sprite_count); + + /* Prune sprites that are definitely not visible */ + int close_point_count = 0; + for(int i = 0; i < sprite_count; i++) { + if(sprites[i].pos.y < num(NEAR_CLIP_PLANE) || + sprites[i].pos.y >= num(FAR_CLIP_PLANE) || + abs((int)sprites[i].pos.x) >= SIDE_CLIP_PLANE) + continue; + sprites[close_point_count++] = sprites[i]; + } + + transform_camera2screen(sprites, close_point_count); + + /* Prune points that are also too far awar from the screen */ + int visible_point_count = 0; + for(int i = 0; i < close_point_count; i++) { + int x = (int)sprites[i].pos.x; + int z = (int)sprites[i].pos.z; + if(x < -SCREEN_MARGIN || x >= azrp_width + SCREEN_MARGIN + || z < -SCREEN_MARGIN || z >= azrp_height + SCREEN_MARGIN) + continue; + sprites[visible_point_count++] = sprites[i]; + } + + /* Sort sprites furthest to closest */ + qsort(sprites, visible_point_count, sizeof *sprites, compare_depth); + + prof_leave(perf_physics); + + extern bopti_image_t img_dot; + extern bopti_image_t img_tree_1, img_tree_2, img_tree_3; + extern bopti_image_t img_mountain_1; + extern bopti_image_t img_rock_1, img_rock_2; + extern bopti_image_t img_base_1, img_base_2, img_base_3; + + /* Resize images for all visible sprites */ + perf_scaling = prof_make(); + + DynamicImage dyn_tree_1(&img_tree_1, + -plane.roll, &perf_scaling, 16, 31); + DynamicImage dyn_tree_2(&img_tree_2, + -plane.roll, &perf_scaling, 16, 31); + DynamicImage dyn_tree_3(&img_tree_3, + -plane.roll, &perf_scaling, 16, 31); + DynamicImage dyn_mountain_1(&img_mountain_1, + -plane.roll, &perf_scaling, 24, 34); + DynamicImage dyn_rock_1(&img_rock_1, + -plane.roll, &perf_scaling, 15, 20); + DynamicImage dyn_rock_2(&img_rock_2, + -plane.roll, &perf_scaling, 11, 15); + DynamicImage dyn_base_1(&img_base_1, + -plane.roll, &perf_scaling, 16, 20); + DynamicImage dyn_base_2(&img_base_2, + -plane.roll, &perf_scaling, 16, 20); + DynamicImage dyn_base_3(&img_base_3, + -plane.roll, &perf_scaling, 16, 20); + + DynamicImage *dyn_images[9] = { + &dyn_tree_1, &dyn_tree_2, &dyn_tree_3, + &dyn_mountain_1, + &dyn_rock_1, &dyn_rock_2, + &dyn_base_1, &dyn_base_2, &dyn_base_3, + }; + + /* Generate the minimap */ + image_t *minimap = generate_minimap(world, &plane, objectives, + objective_count); + + //--- + + azrp_perf_clear(); + ab_horizon((int)far_forward.pos.x, (int)far_forward.pos.z, -plane.roll, + horizon_colors); + + for(int i = 0; i < visible_point_count; i++) { + int ax, ay; + + num scale = 15.0; + /* Make targeted buildings larger */ + if(sprites[i].id >= 6 && sprites[i].id < 9) + scale = 25.0; + scale = scale / sprites[i].pos.y; + + bopti_image_t *img = NULL; + if(sprites[i].id == -1) { + img = &img_dot; + ax = 2; + ay = 2; + } + else if(sprites[i].id < 6) { + img = dyn_images[sprites[i].id]->getAtScale(scale,&ax,&ay); + } + else if(sprites[i].id >= 9 && sprites[i].id < 13) { + img = dyn_expl[sprites[i].id - 9]->getAtScale(scale, &ax, &ay); + } + else if(sprites[i].id >= 100) { + struct objective *o = &objectives[sprites[i].id - 100]; + if(!o->destroyed) + img = dyn_images[6+o->type]->getAtScale(scale,&ax,&ay); + } + + if(img) azrp_image((int)sprites[i].pos.x - ax, + (int)sprites[i].pos.z - ay, img); + } + if(!lost) + draw_player(plane_screen_x, plane_screen_y, &plane, false, !lost); + if(message) + draw_text(2, 2, "%s", message); + else + draw_text(2, 2, "Speed:%d", (int)(plane.air_speed * 40)); + azrp_image(azrp_width - minimap->width - 2, 2, minimap); + + /* Shows a dot the forward direction on the ground */ + // azrp_image((int)far_forward.x-2, (int)far_forward.z-2, &img_dot); + azrp_update(); + + free(sprites); + image_free(minimap); + + //--- + // Event handling and physics + //--- + + bool explosion_trigger = false; + key_event_t ev; + while((ev = pollevent()).type != KEYEV_NONE) { + if(ev.type == KEYEV_DOWN && ev.key == KEY_F1) + explosion_trigger = true; + } + + clearevents(); + if(keydown(KEY_MENU) || keydown(KEY_EXIT)) + break; + + prof_enter(perf_physics); + + if(!lost) + controller_simple(&plane, dt); + + /* Limit pitch to reasonable ranges (extreme values cause graphical + glitches with the horizon, and accentuate the lens problem */ + if(plane.pitch < -0.2) + plane.pitch = -0.2; + if(plane.pitch > 0.2) + plane.pitch = 0.2; + + /* Limit air speed to "reasonable" defaults */ + if(plane.air_speed < num(6.0)) + plane.air_speed = 6.0; + if(plane.air_speed > num(24.0)) + plane.air_speed = 24.0; + + if(!lost) + plane.pos += plane.air_speed * dt * plane.forward; + + /* Wrap the plane around the world map */ + if((int)plane.pos.x < 0) + plane.pos.x += world->w; + if((int)plane.pos.x >= world->w) + plane.pos.x -= world->w; + if((int)plane.pos.y < 0) + plane.pos.y += world->h; + if((int)plane.pos.y >= world->h) + plane.pos.y -= world->h; + + /* Add new explosions */ + if(!lost && explosion_trigger) { + int id = -1; + for(int i = 0; i < explosion_count; i++) { + if(explosions[i].time < num(0)) { + id = i; + break; + } + } + if(id >= 0) { + explosions[id].pos = + plane.pos + num(BOMB_DISTANCE) * plane.forward; + explosions[id].pos.z = 0; + explosions[id].time = 0.0; + + bool hit_someone = false; + + /* Destroy bases that are close! */ + for(int i = 0; i < objective_count; i++) { + if(distance2_64(objectives[i].pos, + explosions[id].pos) < num64(20)) { + objectives[i].destroyed = true; + hit_someone = true; + } + } + + message = hit_someone ? "Hit!" : "Missed!"; + message_time = num(2.0); + } + } + + /* Maintain the time of current explosions */ + for(int i = 0; i < explosion_count; i++) { + if(explosions[i].time >= num(0)) { + explosions[i].time += dt; + if(explosions[i].time >= num(4.0/6.0)) + explosions[i].time = -1; + } + } + + /* Maintain the message time */ + if(message != NULL) { + message_time -= dt; + if(message_time < num(0)) { + message = NULL; + message_time = num(0.0); + } + } + + prof_leave(perf_physics); + prof_leave(perf_frame); +#if 0 + logger("Frame total: %d µs\n - Rendering: update %d µs, shaders %d µs" + "\n - Physics: %d µs\n - Sprite scaling: %d µs\n" + "Current info:\n Position: x=%g y=%g z=%g\n Angles: roll=%g " + "pitch=%g yaw=%g\n Air speed: %g\n" + "Points: %d selected, %d close, %d visible\n" + "Explosions: %d\n", + prof_time(perf_frame), + prof_time(azrp_perf_render), + prof_time(azrp_perf_shaders), + prof_time(perf_physics), + prof_time(perf_scaling), + (double)plane.pos.x, + (double)plane.pos.y, + (double)plane.pos.z, + (double)plane.roll, + (double)plane.pitch, + (double)plane.yaw, + (double)plane.air_speed, + sprite_count, + close_point_count, + visible_point_count, + visible_explosions); +#endif + + /* Victory condition */ + bool all_objectives_destroyed = true; + for(int i = 0; i < objective_count; i++) + all_objectives_destroyed &= objectives[i].destroyed; + + if(all_objectives_destroyed) { + message = "Mission accomplished!"; + message_time = num(32000); + } + + /* Death condition */ + if(!lost && plane.pos.z <= num(0.5)) { + message = "Unfortunate."; + message_time = num(32000); + lost = true; + + /* Hijack one of the explosions */ + explosions[0].pos = plane.pos; + explosions[0].time = num(0.0); + } + } + + timer_stop(timer); + return 1; +} diff --git a/src/world.cpp b/src/world.cpp new file mode 100644 index 0000000..2eecc5f --- /dev/null +++ b/src/world.cpp @@ -0,0 +1,163 @@ +#include "afterburner.h" +#include + +int debug = 0; +void *debug2 = NULL; + +struct world *world_make(int width, int height, struct ref_point *points, + int count) +{ + if(!points) + return NULL; + + struct world *w = (struct world *)malloc(sizeof *w); + int *js = NULL; + size_t s; + if(!w) goto fail; + + w->w = width; + w->h = height; + w->chunks_x = (width + CHUNK_SIZE - 1) / CHUNK_SIZE; + w->chunks_y = (height + CHUNK_SIZE - 1) / CHUNK_SIZE; + + s = w->chunks_x * w->chunks_y * sizeof *w->chunks; + w->chunks = (struct chunk *)malloc(s); + if(!w->chunks) goto fail; + for(int i = 0; i < w->chunks_x * w->chunks_y; i++) { + w->chunks[i].points = NULL; + w->chunks[i].count = 0; + } + + /* Do a first pass to count the number of points in each chunks */ + for(int i = 0; i < count; i++) { + int cx = (int)points[i].pos.x / CHUNK_SIZE; + int cy = (int)points[i].pos.y / CHUNK_SIZE; + w->chunks[cy * w->chunks_x + cx].count++; + } + + /* Allocate all chunks' data */ + for(int i = 0; i < w->chunks_x * w->chunks_y; i++) { + if(w->chunks[i].count == 0) + continue; + w->chunks[i].points = (struct ref_point *)malloc( + w->chunks[i].count * sizeof(struct ref_point)); + if(!w->chunks[i].points) goto fail; + } + + /* Do a second pass to actually fill the chunks */ + s = w->chunks_x * w->chunks_y * sizeof(int); + js = (int *)malloc(s); + if(!js) goto fail; + for(int i = 0; i < w->chunks_x * w->chunks_y; i++) { + js[i] = 0; + } + + for(int i = 0; i < count; i++) { + int cx = (int)points[i].pos.x / CHUNK_SIZE; + int cy = (int)points[i].pos.y / CHUNK_SIZE; + int j = cy * w->chunks_x + cx; + w->chunks[j].points[js[j]++] = points[i]; + } + + free(js); + return w; + +fail: + world_destroy(w); + return NULL; +} + +void world_destroy(struct world *w) +{ + if(!w) return; + + if(w->chunks) { + for(int i = 0; i < w->chunks_x * w->chunks_y; i++) + free(w->chunks[i].points); + free(w->chunks); + } + + free(w); +} + +struct ref_point *world_select(struct world const *world, vec3 position, + GUNUSED vec3 forward, int *size, int extra_size) +{ + int cx1 = (int)position.x / CHUNK_SIZE; + int cy1 = (int)position.y / CHUNK_SIZE; + +// int fx = (int)forward.x >= 0 ? +1 : -1; +// int fy = (int)forward.y >= 0 ? +1 : -1; + + int chunks[9]; + int chunk_count = 0; + + for(int dy = -1; dy <= +1; dy++) + for(int dx = -1; dx <= +1; dx++) { + + int cy = cy1 + dy; + int cx = cx1 + dx; + + if(cx < 0) + cx = world->chunks_x - 1; + if(cx >= world->chunks_x) + cx = 0; + if(cy < 0) + cy = world->chunks_y - 1; + if(cy >= world->chunks_y) + cy = 0; + + chunks[chunk_count++] = cy * world->chunks_x + cx; + } + + /* Count the points to be allocated */ + int total_point_count = 0; + for(int i = 0; i < chunk_count; i++) + total_point_count += world->chunks[chunks[i]].count; + + /* Allocate the array */ + struct ref_point *output = (struct ref_point *)malloc( + (total_point_count + extra_size) * sizeof *output); + + /* Fill the points in selected chunks. While doing this, we also rotate the + points. The player is constrained to be within the world boundary, but + it can still be on the edge and look outside. In this situation, we + rotate the points to give the illusion that the world wraps around. */ + num world_size_x14 = num(world->w / 4); + num world_size_x34 = num(3 * world->w / 4); + num world_size_y14 = num(world->h / 4); + num world_size_y34 = num(3 * world->h / 4); + + /* This simple test only works if the map is at least 4x as large as + the depth of view of the plane, (here 256 > 4×50). */ + num wrap_x_minus = (position.x < world_size_x14) + ? world_size_x34 : num(world->w); + num wrap_x_plus = (position.x > world_size_x34) + ? world_size_x14 : num(0); + num wrap_y_minus = (position.y < world_size_y14) + ? world_size_y34 : num(world->h); + num wrap_y_plus = (position.y > world_size_y34) + ? world_size_y14 : num(0); + + int k = 0; + for(int i = 0; i < chunk_count; i++) { + struct chunk *c = &world->chunks[chunks[i]]; + for(int j = 0; j < c->count; j++) { + struct ref_point p = c->points[j]; + + if(p.pos.x < wrap_x_plus) + p.pos.x += num(world->w); + else if(p.pos.x > wrap_x_minus) + p.pos.x -= num(world->w); + if(p.pos.y < wrap_y_plus) + p.pos.y += num(world->h); + else if(p.pos.y > wrap_y_minus) + p.pos.y -= num(world->h); + + output[k++] = p; + } + } + + *size = total_point_count; + return output; +}