From e51c06b012ad62f4a406577a0524a6a74c5ce61c Mon Sep 17 00:00:00 2001 From: Jan Racek Date: Tue, 24 Mar 2026 13:01:47 +0100 Subject: [PATCH] hush --- .../__sentry-breadcrumb1 | 0 .../__sentry-breadcrumb2 | 0 .../__sentry-event | 1 + .sentry-native/metadata | 0 .sentry-native/settings.dat | Bin 0 -> 40 bytes Makefile | 15 +- build_output.txt | Bin 0 -> 10452 bytes output.bin | Bin 392632 -> 475072 bytes src/audiomanager.cpp | 199 +++ src/audiomanager.hh | 97 ++ src/bvh.cpp | 143 ++ src/bvh.hh | 178 +++ src/camera.cpp | 122 +- src/camera.hh | 6 + src/collision.cpp | 468 ++++++ src/collision.hh | 239 +++ src/controls.cpp | 136 +- src/controls.hh | 63 +- src/gameobject.hh | 49 +- src/gtemath.cpp | 16 +- src/interactable.hh | 54 + src/lua.cpp | 231 ++- src/lua.h | 107 +- src/luaapi.cpp | 1385 +++++++++++++++++ src/luaapi.hh | 268 ++++ src/main.cpp | 33 +- src/mesh.hh | 11 +- src/navmesh.cpp | 122 -- src/navmesh.hh | 24 - src/navregion.cpp | 351 +++++ src/navregion.hh | 188 +++ src/pcdrv_handler.hh | 298 ++++ src/profiler.cpp | 9 +- src/profiler.hh | 5 +- src/renderer.cpp | 888 ++++++++--- src/renderer.hh | 54 +- src/sceneloader.cpp | 65 + src/sceneloader.hh | 49 + src/scenemanager.cpp | 545 ++++++- src/scenemanager.hh | 117 +- src/sio_pcdrv.h | 296 ++++ src/splashpack.cpp | 232 ++- src/splashpack.cpp.bal | 295 ++++ src/splashpack.hh | 73 +- src/triclip.cpp | 197 +++ src/triclip.hh | 116 ++ src/vram_config.h | 12 + src/worldcollision.cpp | 621 ++++++++ src/worldcollision.hh | 222 +++ test_literal.cpp | Bin 0 -> 192 bytes third_party/nugget | 2 +- 51 files changed, 8111 insertions(+), 491 deletions(-) create mode 100644 .sentry-native/65f7adb4-714d-4565-f37b-64b2adb5a4e7.run/__sentry-breadcrumb1 create mode 100644 .sentry-native/65f7adb4-714d-4565-f37b-64b2adb5a4e7.run/__sentry-breadcrumb2 create mode 100644 .sentry-native/65f7adb4-714d-4565-f37b-64b2adb5a4e7.run/__sentry-event create mode 100644 .sentry-native/metadata create mode 100644 .sentry-native/settings.dat create mode 100644 build_output.txt create mode 100644 src/audiomanager.cpp create mode 100644 src/audiomanager.hh create mode 100644 src/bvh.cpp create mode 100644 src/bvh.hh create mode 100644 src/collision.cpp create mode 100644 src/collision.hh create mode 100644 src/interactable.hh create mode 100644 src/luaapi.cpp create mode 100644 src/luaapi.hh delete mode 100644 src/navmesh.cpp delete mode 100644 src/navmesh.hh create mode 100644 src/navregion.cpp create mode 100644 src/navregion.hh create mode 100644 src/pcdrv_handler.hh create mode 100644 src/sceneloader.cpp create mode 100644 src/sceneloader.hh create mode 100644 src/sio_pcdrv.h create mode 100644 src/splashpack.cpp.bal create mode 100644 src/triclip.cpp create mode 100644 src/triclip.hh create mode 100644 src/vram_config.h create mode 100644 src/worldcollision.cpp create mode 100644 src/worldcollision.hh create mode 100644 test_literal.cpp diff --git a/.sentry-native/65f7adb4-714d-4565-f37b-64b2adb5a4e7.run/__sentry-breadcrumb1 b/.sentry-native/65f7adb4-714d-4565-f37b-64b2adb5a4e7.run/__sentry-breadcrumb1 new file mode 100644 index 0000000..e69de29 diff --git a/.sentry-native/65f7adb4-714d-4565-f37b-64b2adb5a4e7.run/__sentry-breadcrumb2 b/.sentry-native/65f7adb4-714d-4565-f37b-64b2adb5a4e7.run/__sentry-breadcrumb2 new file mode 100644 index 0000000..e69de29 diff --git a/.sentry-native/65f7adb4-714d-4565-f37b-64b2adb5a4e7.run/__sentry-event b/.sentry-native/65f7adb4-714d-4565-f37b-64b2adb5a4e7.run/__sentry-event new file mode 100644 index 0000000..5eaaa4b --- /dev/null +++ b/.sentry-native/65f7adb4-714d-4565-f37b-64b2adb5a4e7.run/__sentry-event @@ -0,0 +1 @@ +ˆ¨platform¦native§release¯pcsx-redux@head«environmentªproduction¥level¥error£sdk„¤name­sentry.native§version¥0.6.1¨packages‘‚¤name¾github:getsentry/sentry-native§version¥0.6.1¬integrations‘¨crashpad¤tags€¥extra€¨contexts¢os„¤name§Windows®kernel_version¯10.0.26100.8036§versionª10.0.26200¥build¤8037 \ No newline at end of file diff --git a/.sentry-native/metadata b/.sentry-native/metadata new file mode 100644 index 0000000..e69de29 diff --git a/.sentry-native/settings.dat b/.sentry-native/settings.dat new file mode 100644 index 0000000000000000000000000000000000000000..d72100673cafc849f8d902e585abaa9fd23bcb60 GIT binary patch literal 40 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 f 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 z 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

GV+4Mh_glI)iOUtNK+@evAs@l7uH7%p~ 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 literal 392632 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

)OZqrgHLkYoCI3%{6VMb-m0BL-o3z^+s#% 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

%v9EJgJQ}TEXtV}7t~Abr z-BxQ5euOKH^I++*)f(iu(l`%x+wXMi7k-2*jq_mXvDF&nxY9Tec3Z7M_z|u&&V!}L zR%?*sO5;4(ZM6pBN4U~B50)NVtuKx%jq~7{xJ#NjhdlT7OFqXn&I@y1_v8G}cK*>! z!kpJ_UGqC;D~x`U#(A*xocCP3Kh3McmBx9n^!Rx={;;R~2v-{C!P4XB*UgV`rEwlC zJ${xy)3-;s(l`&69zP$LAK^;lJXm`C{I%VEdxR^E^I+-mvn&2L9rd$trEwlCJ%0X< z`4O%(&V!}L&riqayK0YcrEwlCJ$}AxeuOKH^I+9}*TRo#JjeK-q;VeXwyN>)BV1{m z2TPBwYCOl4#(A*Ys>Z{QaHVk`EIqcW@f=qg=fQ5P8V^6hmBx9n^w_G#b6jbh2fM9m zJp2e(8t1{%W2+j^aiwt{?6#`$@FQGloCiyft?DbsmBx9n+p6xuk8q`N9xOe!s?8i% z8t1`otJ(}d!j;B(u=LofHgjBQoCmwDYBT%@R~qNR(qpUI%yFf09_+TN&F~{!X`Ba3 zkF9Dm$Cbu;u-mFO!;f&KaULu^wyMn>R~qNRZmT*9Kf;y9d9d`@s$OzjX`BbUt!gU# z2v-{C!O~-^n#ysdaUSfps;TfJTxpyKOOLH;D#w+^d9d56roxYKrEwlCJ+`W;99J6W z!EURX3O~Y?#(A*x*s7*-TxpyKyRB*}{0LVX=fTortD4GjrEwnYwyMGKBV1{m2dmcl ztFcF-n#wUgCuy7qyRB*}{0LVX=fTortD4GjrEwnYwyLS{BV1{m2TPBwYAVN-#(A*Y zs;0t^aHVk`EIqcWsT@}t=fQ5PnhHO{mBx9n^w_GVa$ISg2fM9mD*Omn8t1{%W2>6V zaiwt{?6#_@@FQGloCiyft!gUAmBx9n+p4C*k8q`N9xOe!s;L}T8t1`otC|Ww!j;B( zu=LofrgB_qoCmwDYAXB)R~qNR(qpTd%5kM}9_+TNsqiCQX`Ba3kF9Dd$Cbu;u-mGp z!jEvJaULu^wyLQdR~qNRZmXIKKf;y9d9d`@s-|*WX`BbUt!gU#2v-{C!O~-^n#ysd zaUSfps;TfJTxpyKOOLH;D#w+^d9d56roxYKrEwlCJ+`W;99J6W!EURX3O~Y?#(A*x z*s7*-TxpyKyRB+4{0LVX=fSGA{xED+Q#r=xB#rZ6w^dDrAK^;lJXm^cRZ}^xG|q$F zRy7rVge#5nVCk_{P35@KI1hGP)l~Qqt~AbrrN>q^mE%g|JlJhjQ{hLr(l`&69$VE^ zjw_AxV7FCGg&*Nc<2+b;Y*kY^t~Abr-BvXfeuOKH^I++*RZZo%(l`%xTh&zf5w0}O zgQdq-HI?H^<2=}HRa49JK!<+#!~4|ZGCRQM6DG|q#i z$5u6!<4WT^*lkr);YYaAI1iQ{Th&yKD~>AK^;lJXp2X>v5lsYAVP0oTPCc z?6#_@@FQGloCiyft!gUAmBx9n+p4C*k8q`N9xOe!s;L}T8t1`otC|Ww!j;B(u=Lof zrgB_qoCmwDYAXB)R~qNR(qpTd%5kM}9_+TNsqiCQX`Ba3kF9Dd$Cbu;u-mGp!jEvJ zaULu^wyLQdR~qNRZmXIKKf;y9d9d`@s-|*WX`BbUt!gU#2v-{C!O~-^n#ysdaUSfp zs;TfJTxpyKOOLH;D#w+^d9d56roxYKrEwlCJ+`W;99J6W!EURX3O~Y?#(A*x*s7*- zTxpyKyRB*}{0LVX=fTortD4GjrEwnYwyLS{BV1{m2TPBwYAVN-#(A*Ys;0t^aHVk` zEIqcWsT}`R;~l##Shi^7(>3mMT>q!L`^~1p*0E}{ABBHmY$s{lCs=xHRhv1kG|q$F zR<#*^ge#5nVCk_{ZRWVrI1hGP)pE{*Rge9dxR+UVmt$fvN#i`&ZB=*SN4U~B50)NV z)m@G&jq_l)Ro#Uj;Y#B?SbA(#cR8*!&V$`nbr*hwD~

9JMa<+#!~4|ZGCUHB2M zG|q#i$5wTh<4WT^*lksJ;YYaAI1iQ{Th(2TD~rvm*Yy~JlJhjci~64(l`&69$VF2 zjw_AxV7FD>g&*Nc<2+b;Y*lwTt~Abr-BxuMeuOKH^I++*Ro&&d(l`%xTh(3o5w0}O zgQdq-b(iBx<2=}Hsk^Sn_Ym--8th+&W)gN=uxsSgHSTj<|3vp4f&V@HI9B}zFO2OZ zjr#;kkF9Dg$Cbu;uxhryZ5UoY9phhm$Jk0=@qR8e|IcUgKK!fZ6F;g+{(i(rwTEN+ zd6LF?u-mHkz>jdHaULu^wyHfGR~qNRZmZe@Kf;y9d9d`@s`hYPX`BbUt!fYa2v-{C z!O~-^+QV_BaUSfpsy*-{TxpyKOOLH;566|pd9d56_P~#DrEwlCJ+`Vn99J6W!EUSC z13$u*#(A*x*sAt$TxpyKyRB*u{0LVX=fTortJ=eHrEwnYwyHhwBV1{m2TPBwY7fVi z#(A*Ys`kK-aHVk`EIqcWJsejW=fQ5P+5N47cAIHBN{HvXxikIem*s4$1vl_>=d6LF?uxh{mBL2R$YCOlZbCSk+u-mG}!;f&K zaULu^wyNX9%X`BbUt!g~{2v-{C!O~-^8qaa1aUSfp zs`2n6TxpyKOOLH;Jja#Bd9d56#>04?n_{#(A*x*s8{J zTxpyKyRB+G{0LVX=fTors~XR7rEwnYwyN>)BV1{m2TPBwYCOl4#(A*Ys>Z{QaHVk` zEIqcW@f=qg=fQ5P8V^6hmBx9n^w_G#b6jbh2fM9mJp2e(8t1{%W2+j^aiwt{?6#`$ z@FQGloCiyft!gvJmBx9n+p508k8q`N9xOe!s=FLl8t1`otJ(}d!j;B(u=LofHgjBQ zoCmwD>NNZaR~qNR(qpUI%yFf09?WNd)I8XN|EIwJQQ$9i{EtH8SoI#ZIAkPKo#XmpKX>ni{!!a#gWHNnsawBt(SJlJhjOXEkl(l`(P=P`%zcZ$9r zShcj{N(1lb`kl@XZE&BJ#(80Ey$*ja<}$VIS35kenZ%><_u_usziTG(yhri)RBi9L z(l`%xTh;dX5w0}OgQdq-wY}p?<2=}HRoml7xY9TemL6Nx_Kqu!^I*4CZI2(}O5;3O zdTdqOJFYa&gWXoOJ${5Mjq_mXu~lvFxY9Tec3ai<_z|u&&V!}LR<*t3O5;4(ZB^Uj zN4U~B50)NV)%K1njq_l)Rc((S;Y#B?SbA(#+dHl_&V$`nwLN}>D~
9JM4?YPo7 z4|ZGC%=i(mG|q#i$5wTk<4WT^nA-Tij5PyW>SxEl-qqFP8s~+*4XP*cDa^ShX`BbE zhWy3&`=hEA9piJ7#(A*Ys#e60aHVk`EIqcW6&+U^=fQ5PS`k0OmBx9n^w_FabX;ki z2fM9mMf?a?8t1{%W2;)xaiwt{?6#^E@grPmoCiyft!hQbmBx9n+p1Q?k8q`N9xOe! zsudkq8t1{6y1EWq)oDL%xYEGuyyF_@g~$DSJ*o5JXHvcQo)YfxS+X9?c}4bBV{NF#IH+Xq=aS+9_La8@x2`AFMji z@pyZraULulTh)P%D~rvpyNv8JlJhj2jWM#(l`&69$VFcjw_AxV7FBrh#%of<2+b; zY*hz3t~Abr-BxuVeuOKH^I++*RUPQK(l`%xTh)R15w0}OgQdq-b)e%)<2=}HRh!{Q zxY9TeraoMX^$A;QNXM!t$2HCi>%90;-RPKeP0~0IR(&|TYu7H-fsXMxN#i`&ZB+;2 zN4U~B50)NV)q##Hjq_l)RUL>Q;Y#B?SbA(#2Rg1a&V$`nbs&C(D~
9JKE=(y52 z4|ZGCf%p-wG|q#i$5wTq<4WT^*lkq@;zzjBI1iQ{Th(cfD~>x47zL$L~b{t4^+XG;XUJ7`JQ}xE|DYGH zG|q!HcCghLaa?Jf2fMAt6n=y&jq_l|g0Y}JcieKmi)qE9aa+X*y>O**-p|EcA)k)h zoY1wW;?cOR+JIiT(l`(HYtk=v=VhK$zkyxjyfAaGFnYDkaiwt{?6!I@_z|u&&V!}L zR@a7(D~dGVv`O2?dQlE!(kt{Ja{A6*+d#^)rB^I*5twIP0lD~
9N(dq2o&9JlJh@ZHOP?O5;3OdTe!V=(y524|ZE!8{$W}(l`&+`bCYQHOMiw#3YUL zV7Jv8gdgEb#iMaswE?|wrEwmtx|AAT`|ysb)hB732fOVrcl8f` zge#5nV6CUrs9F;pQ;Sa0I1hGP{hzJ)5w0}OgZ19A)q8YYX`BbUt!g~{2v-{C-6z+8 z^6A*G#VX$8i$*bWTxpyKD_3coa?&x^R+BW&gWXm+j341j<2+co!B|iZI40*OX`BbU zt#SrG!j;B(u=0nwLh*9UoG?k_JlJiOGx!m%G|q!He=?V9ugo!X<|K{tV7Jv?8GeK- zjq_mXvDIFg<4WT^*lo2}h9BWd<2+b;Y_;FzxY9TeUhei2u=PDR`5f0cFO02UOLEUH zdnWLT`?$t=Va_FtUe|_>D~rtk>g6^JlJh@ZHOP?O5;3OdTe!V=(y524|ZE!8{$W} z(l`&69$Q@-I<7R%gWXowhWHV#G|q#i$5z*djw_AxV7Jw^A%27_jq_mXvDLMq<4WT^ z*ll%fh#%of<2+b;Y;|quxY9Tec3WMq;YYaAI1lEU@mS2!*m7OzSl5{28s~*|Ui|2~ z(lO_nq;VdsYsSAA-_zE$p<{eb(l`%xTU{ICN4U~B50)NVT^l;CG|q$FR@a935w0}O zgQdq-*M^QOjq_l)Rh@$$;Y#B?SbA(#3puVd&V$`nwGe)UD~
9JKU<4WT^_{px0`g?)Brjk!I!uZ$cFOFH;Cu!i_ziTG(oJ+O(e{AQ% zkDqsvhW3aDpY6Uw@t+xwPuJWr%_Kd3T;sO7UO*%KpN#3nqfve6nD;xW=0xMX{NUgH zI4|t}!Kx=6b6)o&jq_mf*s7j%TxpyKyRGU;{0LVX=fTort9sILrEwnYwyG!bBV1{m z2TPBw>Pg3y#(A*Ys-DD;aHVk`EIqcWCmmNB=fQ5PdJ;dvmBx9n^w_GNbX;ki2fMB6 zN&E;`8t1{%W2<`7aiwt{?6#^W@grPmoCiyft?Eg~mBx9n+p3<#k8q`N9xOe!swW*+ z8t1`ot9lYY!j;B(u=Lof#&cY0oCi~9{&%s?VM{IQSas>R#(80HgKANH3UjVW8t1{P zGd~Jj)sv3#IZ5L@*lkr$;zzjBI1iQ{Th)_}D~tFq~l8CJlJhjPvS?o(l`&69$VFT zjw_AxV7FDhhacfe<2+b;Y*n*4t~Abr-B$G#euOKH^I++*Ro&&d(l`%xTh(Ux5w0}O zgQdq-wVC5e<2=}HRh!{QxY9TemL6NxW{xY3^I*4CZH6D=O5;3OdTdpjIj%I$gWXoO z8GeK-jq_mXu~lv6xY9Tec3ahE_z|u&&V!}LR<)VqO5;4(ZB?7$N4U~B50)NV)n<+> zjq_l)Rc(eJ;Y#B?SbA(#n>nsD&V$`nwHbbdD~
9JL9=D5;04|ZGq4IKPXr}1}V z{^6L$d9UT%&m^96@pox{F?`~Gf7EQk)K_l>|G$lCoFA#d%#ZVaUO$)fV7)Ku!+#Vt zB<-PwbgX)ET;sg3w*f5w{B0Rw*Ep}g8$&-kR{aStOdn3tz`K92`qR(lZPVY_;l0bh zW6nECGu}?swEuO)PqnLKd`{9h4|ZGCuJ{qIG|q#i$5yqg<4WT^*lkt2;zzjBI1iQ{ zTh*?PD~satK&-JJlJhjyW&T<(l`&69$VF}jw_AxV7FE6iXY)h<2+b;Y*o8Dt~Abr z-Bz_LeuOKH^I++*Rqg7y(l`%xTh*@k5w0}OgQdq-wX5Sw<2=}HRlDLxxY9TemL6Nx zu8u2>^I*4C?TR1aO5;3OdTdp@I<7R%gWXoOD}ID4jq_mXu~qHrxY9Tec3ai1_z|u& z&V!}LR<*0+O5;4(ZB@JCN4U~B50)NV)vk^!jq_l)Rqcu&;Y#B?SbA(#b2_dx&V%)L zBeC_mS3buz&I@BpeYiV*gQ+2fy}osg^IoHR{pmF)_+HegCp-VDQO9{{^!F8MkLo(d zyst?bc(3jq_l)Rc(eJ;Y#B?SbA(#n>nsD&V$`nwHbbdD~
9JL9=D5;0 z4|ZGCX7~}VG|q#i$5yqO<4WT^*lkst;YYaAI1iQ{Th(TcD~sand3_1JlJhjo8d?J zp#r-t*!A-18uvM_*Z&doarkkp8V_C=+esSt36>sP)p(98jq_l)RgH%q;Y#B?SbA(# zjq_l)Rc(eJ;Y#B?SbA(#n>nsD&V$`nwHbbd zD~
9JL9=D5;04|ZGCX7~}VG|q#i$5yqO<4WT^*lkst;YYaAI1iQ{Th(TcD~
C<9lI$vBUg-$MEv$ z82{2c##Z`@#}D-w=Q?SAbY6IDsmI1Kdbf3r^WHYsP@Bo8Fg_<~oCix!+o;XtN4U~B z50)N3pO_!vO5;3OHOZmqFV!B7@jpr9JlJhjd*Da7(l`&69$VEOjw_AxV7FE6fgj;Y z<2+b;Y*l+Wt~Abr-Bz^+euOKH^I++*Rqf%p(l`%xTh$);5w0}OgQdq-wTI(M<2=}H zReRt^xY9TemL6Nx9*!%G^I*4C?SUWRO5;3OdTdpDIIc9#gWXoO2Y!Srjq_mXu~qHi zxY9Tec3agR_z|u&&V!}LR<(!YO5;4(ZB={VN4U~B50)NV)gF#3jq_l)RqcTv;Y#B? zSbA(#dpNE%&V$`nwFiELD~
9JMq;keQ`4|ZGC9{3TiG|q#i$5u6i<4WT^*lo4$ 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 +#include + +namespace psxsplash { + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +uint16_t AudioManager::volToHw(int v) { + if (v <= 0) return 0; + if (v >= 128) return 0x3fff; + return static_cast((v * 0x3fff) / 128); +} + +// --------------------------------------------------------------------------- +// Init / Reset +// --------------------------------------------------------------------------- + +void AudioManager::init() { + psyqo::SPU::initialize(); + + m_nextAddr = SPU_RAM_START; + + for (int i = 0; i < MAX_AUDIO_CLIPS; i++) { + m_clips[i].loaded = false; + } +} + +void AudioManager::reset() { + stopAll(); + for (int i = 0; i < MAX_AUDIO_CLIPS; i++) { + m_clips[i].loaded = false; + } + m_nextAddr = SPU_RAM_START; +} + +// --------------------------------------------------------------------------- +// Clip loading +// --------------------------------------------------------------------------- + +bool AudioManager::loadClip(int clipIndex, const uint8_t* adpcmData, uint32_t sizeBytes, + uint16_t sampleRate, bool loop) { + if (clipIndex < 0 || clipIndex >= MAX_AUDIO_CLIPS) return false; + if (!adpcmData || sizeBytes == 0) return false; + + // Check for VAG header (magic "VAGp" at offset 0). + // If present, the header wasn't stripped properly — skip it. + if (sizeBytes >= 48) { + const char* magic = reinterpret_cast(adpcmData); + if (magic[0] == 'V' && magic[1] == 'A' && magic[2] == 'G' && magic[3] == 'p') { + adpcmData += 48; + sizeBytes -= 48; + } + } + + // Align to 16-byte SPU ADPCM block boundary + uint32_t addr = (m_nextAddr + 15) & ~15u; + uint32_t alignedSize = (sizeBytes + 15) & ~15u; + + if (addr + alignedSize > SPU_RAM_END) { + return false; + } + + // psyqo::SPU::dmaWrite takes dataSize as uint16_t so upload in chunks + // for clips larger than 65532 bytes (largest multiple-of-4 that fits). + // + // psyqo DMA math: BCR = blockSize | ((dataSize/blockSize) << 16) + // blockSize=4 → 4 words per block = 16 bytes per block + // block count = dataSize/blockSize + // total bytes = blockSize × (dataSize/blockSize) × 4 = dataSize × 4 + // So dataSize = bytesThisRound / 4 gives the correct byte count. + const uint8_t* src = adpcmData; + uint32_t remaining = alignedSize; + uint32_t dstAddr = addr; + while (remaining > 0) { + // Max transfer per call: 65532 bytes (16383 blocks × 4 bytes each). + uint32_t bytesThisRound = (remaining > 65532u) ? 65532u : remaining; + bytesThisRound &= ~3u; // DMA alignment + if (bytesThisRound == 0) break; + + uint16_t dmaSizeParam = (uint16_t)(bytesThisRound / 4); + psyqo::SPU::dmaWrite(dstAddr, src, dmaSizeParam, 4); + src += bytesThisRound; + dstAddr += bytesThisRound; + remaining -= bytesThisRound; + } + + // dmaWrite() now properly restores transfer mode to idle after each + // DMA transfer, so no manual SPU_CTRL fix-up is needed here. + + m_clips[clipIndex].spuAddr = addr; + m_clips[clipIndex].size = sizeBytes; + m_clips[clipIndex].sampleRate = sampleRate; + m_clips[clipIndex].loop = loop; + m_clips[clipIndex].loaded = true; + + m_nextAddr = addr + alignedSize; + return true; +} + +// --------------------------------------------------------------------------- +// Playback +// --------------------------------------------------------------------------- + +int AudioManager::play(int clipIndex, int volume, int pan) { + if (clipIndex < 0 || clipIndex >= MAX_AUDIO_CLIPS || !m_clips[clipIndex].loaded) { + return -1; + } + + uint32_t ch = psyqo::SPU::getNextFreeChannel(); + if (ch == psyqo::SPU::NO_FREE_CHANNEL) return -1; + + const AudioClip& clip = m_clips[clipIndex]; + + uint16_t vol = volToHw(volume); + uint16_t leftVol = vol; + uint16_t rightVol = vol; + if (pan != 64) { + int p = pan < 0 ? 0 : (pan > 127 ? 127 : pan); + leftVol = (uint16_t)((uint32_t)vol * (127 - p) / 127); + rightVol = (uint16_t)((uint32_t)vol * p / 127); + } + + psyqo::SPU::ChannelPlaybackConfig config; + config.sampleRate.value = static_cast(((uint32_t)clip.sampleRate << 12) / 44100); + config.volumeLeft = leftVol; + config.volumeRight = rightVol; + config.adsr = DEFAULT_ADSR; + + // Set the repeat address depending on loop mode. + // The new psyqo::SPU::getNextFreeChannel() uses the ENDX register: + // a channel is "free" when its ENDX bit is set (voice reached loop-end). + // silenceChannels() points voices at psyqo's silent dummy sample at 0x1000 + // that immediately sets ENDX, so stopped channels are detected as free. + // + // Looping clips: repeat → clip start (loop back to beginning). + // Non-looping clips: repeat → dummy 0x1000 (go silent after clip ends, + // dummy's loop-end flag re-sets ENDX → channel freed). + constexpr uint16_t DUMMY_SPU_ADDR = 0x1000; + if (clip.loop) { + SPU_VOICES[ch].sampleRepeatAddr = static_cast(clip.spuAddr / 8); + } else { + SPU_VOICES[ch].sampleRepeatAddr = DUMMY_SPU_ADDR / 8; + } + + psyqo::SPU::playADPCM(static_cast(ch), + static_cast(clip.spuAddr), + config, true); + + return static_cast(ch); +} + +// --------------------------------------------------------------------------- +// Stop +// --------------------------------------------------------------------------- + +void AudioManager::stopVoice(int channel) { + if (channel < 0 || channel >= MAX_VOICES) return; + psyqo::SPU::silenceChannels(1u << channel); +} + +void AudioManager::stopAll() { + psyqo::SPU::silenceChannels(0x00FFFFFFu); +} + +// --------------------------------------------------------------------------- +// Volume +// --------------------------------------------------------------------------- + +void AudioManager::setVoiceVolume(int channel, int volume, int pan) { + if (channel < 0 || channel >= MAX_VOICES) return; + uint16_t vol = volToHw(volume); + if (pan == 64) { + SPU_VOICES[channel].volumeLeft = vol; + SPU_VOICES[channel].volumeRight = vol; + } else { + int p = pan < 0 ? 0 : (pan > 127 ? 127 : pan); + SPU_VOICES[channel].volumeLeft = (uint16_t)((uint32_t)vol * (127 - p) / 127); + SPU_VOICES[channel].volumeRight = (uint16_t)((uint32_t)vol * p / 127); + } +} + +// --------------------------------------------------------------------------- +// Query +// --------------------------------------------------------------------------- + +int AudioManager::getLoadedClipCount() const { + int count = 0; + for (int i = 0; i < MAX_AUDIO_CLIPS; i++) { + if (m_clips[i].loaded) count++; + } + return count; +} + +} // namespace psxsplash diff --git a/src/audiomanager.hh b/src/audiomanager.hh new file mode 100644 index 0000000..52c753b --- /dev/null +++ b/src/audiomanager.hh @@ -0,0 +1,97 @@ +#pragma once + +#include + +namespace psxsplash { + +/// Maximum number of audio clips that can be loaded in a scene +static constexpr int MAX_AUDIO_CLIPS = 32; + +/// Maximum SPU voices (hardware limit) +static constexpr int MAX_VOICES = 24; + +/// SPU RAM is 512KB total (0x00000-0x7FFFF). +/// First 0x1000 bytes reserved for capture buffers. +/// psyqo places a 16-byte silent dummy sample at 0x1000. +/// User clips start at 0x1010. +/// +/// Upper bound is 0x10000 (64KB) because psyqo::SPU::playADPCM() +/// takes a uint16_t for the SPU RAM address. +static constexpr uint32_t SPU_RAM_START = 0x1010; +static constexpr uint32_t SPU_RAM_END = 0x10000; + +/// Default ADSR: instant attack, sustain at max, ~46ms linear release. +/// Lower 16-bit (AD): attack linear shift=0 step=0("+7"), decay shift=0, +/// sustain level=0xF (max -> decay skipped) +/// Upper 16-bit (SR): sustain linear increase shift=0 step=0("+7"), +/// release linear shift=10 (~46ms to zero) +static constexpr uint32_t DEFAULT_ADSR = 0x000A000F; + +/// Descriptor for a loaded audio clip in SPU RAM +struct AudioClip { + uint32_t spuAddr; // Byte address in SPU RAM + uint32_t size; // Size of ADPCM data in bytes + uint16_t sampleRate; // Original sample rate in Hz + bool loop; // Whether this clip should loop + bool loaded; // Whether this slot is valid +}; + +/// Manages SPU voices and audio clip playback. +/// +/// Uses psyqo::SPU for all hardware interaction: initialization, +/// DMA uploads, voice allocation (via currentVolume check), playback +/// (playADPCM), and silencing (silenceChannels). +/// +/// init() +/// loadClip(index, data, size, rate, loop) -> bool +/// play(clipIndex) -> channel +/// play(clipIndex, volume, pan) -> channel +/// stopVoice(channel) +/// stopAll() +/// setVoiceVolume(channel, vol, pan) +/// +/// Volume is 0-128 (0=silent, 128=max). Pan is 0-127 (64=center). +class AudioManager { +public: + /// Initialize SPU hardware and reset state + void init(); + + /// Upload ADPCM data to SPU RAM and register as clip index. + /// Data must be 16-byte aligned (SPU ADPCM block size). Returns true on success. + bool loadClip(int clipIndex, const uint8_t* adpcmData, uint32_t sizeBytes, + uint16_t sampleRate, bool loop); + + /// Play a clip by index. Returns channel (0-23), or -1 if full. + /// Volume: 0-128 (128=max). Pan: 0 (left) to 127 (right), 64 = center. + int play(int clipIndex, int volume = 128, int pan = 64); + + /// Stop a specific channel (returned from play()) + void stopVoice(int channel); + + /// Stop all playing channels + void stopAll(); + + /// Set volume/pan on a playing channel + void setVoiceVolume(int channel, int volume, int pan = 64); + + /// Get total SPU RAM used by loaded clips (for visualization) + uint32_t getUsedSPURam() const { return m_nextAddr - SPU_RAM_START; } + + /// Get total SPU RAM available + uint32_t getTotalSPURam() const { return SPU_RAM_END - SPU_RAM_START; } + + /// Get number of loaded clips + int getLoadedClipCount() const; + + /// Reset all clips and free SPU RAM (call on scene unload) + void reset(); + +private: + /// Convert 0-128 volume to hardware 0-0x3FFF (fixed-volume mode) + static uint16_t volToHw(int v); + + AudioClip m_clips[MAX_AUDIO_CLIPS]; + uint32_t m_nextAddr = SPU_RAM_START; // Bump allocator for SPU RAM +}; + +} // namespace psxsplash diff --git a/src/bvh.cpp b/src/bvh.cpp new file mode 100644 index 0000000..a5bb085 --- /dev/null +++ b/src/bvh.cpp @@ -0,0 +1,143 @@ +#include "bvh.hh" + +namespace psxsplash { + +void BVHManager::initialize(const BVHNode* nodes, uint16_t nodeCount, + const TriangleRef* triangleRefs, uint16_t triangleRefCount) { + m_nodes = nodes; + m_nodeCount = nodeCount; + m_triangleRefs = triangleRefs; + m_triangleRefCount = triangleRefCount; +} + +const uint8_t* BVHManager::initializeFromData(const uint8_t* data, uint16_t nodeCount, uint16_t triangleRefCount) { + if (data == nullptr || nodeCount == 0) { + m_nodes = nullptr; + m_triangleRefs = nullptr; + m_nodeCount = 0; + m_triangleRefCount = 0; + return data; + } + + // Point to node array + m_nodes = reinterpret_cast(data); + m_nodeCount = nodeCount; + data += m_nodeCount * sizeof(BVHNode); + + // Point to triangle ref array + m_triangleRefs = reinterpret_cast(data); + m_triangleRefCount = triangleRefCount; + data += m_triangleRefCount * sizeof(TriangleRef); + + return data; +} + +int BVHManager::cullFrustum(const Frustum& frustum, + TriangleRef* outRefs, + int maxRefs) const { + if (!isLoaded() || m_nodeCount == 0) return 0; + + return traverseFrustum(0, frustum, outRefs, 0, maxRefs); +} + +int BVHManager::traverseFrustum(int nodeIndex, + const Frustum& frustum, + TriangleRef* outRefs, + int currentCount, + int maxRefs) const { + if (nodeIndex < 0 || nodeIndex >= m_nodeCount) return currentCount; + if (currentCount >= maxRefs) return currentCount; + + const BVHNode& node = m_nodes[nodeIndex]; + + // Frustum test - if node is completely outside, skip entire subtree + if (!frustum.testAABB(node)) { + return currentCount; // Culled! + } + + // If leaf, add all triangles + if (node.isLeaf()) { + int count = node.triangleCount; + int available = maxRefs - currentCount; + if (count > available) count = available; + + for (int i = 0; i < count; i++) { + outRefs[currentCount + i] = m_triangleRefs[node.firstTriangle + i]; + } + return currentCount + count; + } + + // Recurse into children + if (node.leftChild != 0xFFFF) { + currentCount = traverseFrustum(node.leftChild, frustum, outRefs, currentCount, maxRefs); + } + if (node.rightChild != 0xFFFF) { + currentCount = traverseFrustum(node.rightChild, frustum, outRefs, currentCount, maxRefs); + } + + return currentCount; +} + +int BVHManager::queryRegion(int32_t minX, int32_t minY, int32_t minZ, + int32_t maxX, int32_t maxY, int32_t maxZ, + TriangleRef* outRefs, + int maxRefs) const { + if (!isLoaded() || m_nodeCount == 0) return 0; + + return traverseRegion(0, minX, minY, minZ, maxX, maxY, maxZ, outRefs, 0, maxRefs); +} + +int BVHManager::traverseRegion(int nodeIndex, + int32_t qMinX, int32_t qMinY, int32_t qMinZ, + int32_t qMaxX, int32_t qMaxY, int32_t qMaxZ, + TriangleRef* outRefs, + int currentCount, + int maxRefs) const { + if (nodeIndex < 0 || nodeIndex >= m_nodeCount) return currentCount; + if (currentCount >= maxRefs) return currentCount; + + const BVHNode& node = m_nodes[nodeIndex]; + + // AABB overlap test + if (!aabbOverlap(node, qMinX, qMinY, qMinZ, qMaxX, qMaxY, qMaxZ)) { + return currentCount; // No overlap, skip + } + + // If leaf, add all triangles + if (node.isLeaf()) { + int count = node.triangleCount; + int available = maxRefs - currentCount; + if (count > available) count = available; + + for (int i = 0; i < count; i++) { + outRefs[currentCount + i] = m_triangleRefs[node.firstTriangle + i]; + } + return currentCount + count; + } + + // Recurse into children + if (node.leftChild != 0xFFFF) { + currentCount = traverseRegion(node.leftChild, + qMinX, qMinY, qMinZ, qMaxX, qMaxY, qMaxZ, + outRefs, currentCount, maxRefs); + } + if (node.rightChild != 0xFFFF) { + currentCount = traverseRegion(node.rightChild, + qMinX, qMinY, qMinZ, qMaxX, qMaxY, qMaxZ, + outRefs, currentCount, maxRefs); + } + + return currentCount; +} + +bool BVHManager::aabbOverlap(const BVHNode& node, + int32_t qMinX, int32_t qMinY, int32_t qMinZ, + int32_t qMaxX, int32_t qMaxY, int32_t qMaxZ) { + // Check for separation on any axis + if (node.maxX < qMinX || node.minX > qMaxX) return false; + if (node.maxY < qMinY || node.minY > qMaxY) return false; + if (node.maxZ < qMinZ || node.minZ > qMaxZ) return false; + return true; // Overlapping +} + +} // namespace psxsplash diff --git a/src/bvh.hh b/src/bvh.hh new file mode 100644 index 0000000..8214882 --- /dev/null +++ b/src/bvh.hh @@ -0,0 +1,178 @@ +#pragma once + +#include +#include +#include + +namespace psxsplash { + +/// Triangle reference - points to a specific triangle in a specific object +struct TriangleRef { + uint16_t objectIndex; + uint16_t triangleIndex; +}; +static_assert(sizeof(TriangleRef) == 4, "TriangleRef must be 4 bytes"); + +/// BVH Node - stored in binary file +/// 32 bytes per node for cache-friendly traversal +struct BVHNode { + // AABB bounds in fixed-point 20.12 format + int32_t minX, minY, minZ; // 12 bytes + int32_t maxX, maxY, maxZ; // 12 bytes + + // Child indices (0xFFFF = no child / leaf indicator) + uint16_t leftChild; // 2 bytes + uint16_t rightChild; // 2 bytes + + // Triangle data (only valid for leaf nodes) + uint16_t firstTriangle; // 2 bytes - index into triangle ref array + uint16_t triangleCount; // 2 bytes + + /// Check if this is a leaf node + bool isLeaf() const { + return leftChild == 0xFFFF && rightChild == 0xFFFF; + } + + /// Test if a point is inside this node's bounds + bool containsPoint(const psyqo::Vec3& point) const { + return point.x.raw() >= minX && point.x.raw() <= maxX && + point.y.raw() >= minY && point.y.raw() <= maxY && + point.z.raw() >= minZ && point.z.raw() <= maxZ; + } + + /// Test if AABB intersects frustum plane + /// plane: normal (xyz) + distance (w) in fixed point + bool testPlane(int32_t nx, int32_t ny, int32_t nz, int32_t d) const { + // Find the corner most in the direction of the plane normal (p-vertex) + int32_t px = (nx >= 0) ? maxX : minX; + int32_t py = (ny >= 0) ? maxY : minY; + int32_t pz = (nz >= 0) ? maxZ : minZ; + + // If p-vertex is on negative side, box is completely outside + // dot(p, n) + d < 0 means outside + int64_t dot = ((int64_t)px * nx + (int64_t)py * ny + (int64_t)pz * nz) >> 12; + return (dot + d) >= 0; + } +}; +static_assert(sizeof(BVHNode) == 32, "BVHNode must be 32 bytes"); + +/// BVH Tree header in binary file +struct BVHHeader { + uint16_t nodeCount; + uint16_t triangleRefCount; +}; +static_assert(sizeof(BVHHeader) == 4, "BVHHeader must be 4 bytes"); + +/// Frustum planes for culling (6 planes) +struct Frustum { + // Each plane: nx, ny, nz (normal), d (distance) + // All in fixed-point 20.12 format + struct Plane { + int32_t nx, ny, nz, d; + }; + Plane planes[6]; // Near, Far, Left, Right, Top, Bottom + + /// Test if AABB is visible (not culled by all planes) + bool testAABB(const BVHNode& node) const { + for (int i = 0; i < 6; i++) { + if (!node.testPlane(planes[i].nx, planes[i].ny, planes[i].nz, planes[i].d)) { + return false; // Completely outside this plane + } + } + return true; // Potentially visible + } +}; + +/// BVH Manager - handles traversal and culling +class BVHManager { +public: + /// Initialize from separate pointers (used by splashpack loader) + void initialize(const BVHNode* nodes, uint16_t nodeCount, + const TriangleRef* triangleRefs, uint16_t triangleRefCount); + + /// Initialize from raw splashpack data (alternative) + /// Returns pointer past the BVH data + const uint8_t* initializeFromData(const uint8_t* data, uint16_t nodeCount, uint16_t triangleRefCount); + + /// Traverse BVH and collect visible triangle references + /// Uses frustum culling to skip invisible branches + /// Returns number of visible triangle refs + int cullFrustum(const Frustum& frustum, + TriangleRef* outRefs, + int maxRefs) const; + + /// Simpler traversal - collect all triangles in a region + /// Useful for collision queries + int queryRegion(int32_t minX, int32_t minY, int32_t minZ, + int32_t maxX, int32_t maxY, int32_t maxZ, + TriangleRef* outRefs, + int maxRefs) const; + + /// Get node count + int getNodeCount() const { return m_nodeCount; } + + /// Get triangle ref count + int getTriangleRefCount() const { return m_triangleRefCount; } + + /// Check if BVH is loaded + bool isLoaded() const { return m_nodes != nullptr; } + +private: + const BVHNode* m_nodes = nullptr; + const TriangleRef* m_triangleRefs = nullptr; + uint16_t m_nodeCount = 0; + uint16_t m_triangleRefCount = 0; + + /// Recursive frustum culling traversal + int traverseFrustum(int nodeIndex, + const Frustum& frustum, + TriangleRef* outRefs, + int currentCount, + int maxRefs) const; + + /// Recursive region query traversal + int traverseRegion(int nodeIndex, + int32_t qMinX, int32_t qMinY, int32_t qMinZ, + int32_t qMaxX, int32_t qMaxY, int32_t qMaxZ, + TriangleRef* outRefs, + int currentCount, + int maxRefs) const; + + /// Test if two AABBs overlap + static bool aabbOverlap(const BVHNode& node, + int32_t qMinX, int32_t qMinY, int32_t qMinZ, + int32_t qMaxX, int32_t qMaxY, int32_t qMaxZ); +}; + +// ── Room/portal data for interior scene occlusion ── + +/// Per-room data loaded from splashpack v11+. +/// AABB for point-in-room tests plus a range into the room triangle-ref array. +struct RoomData { + int32_t aabbMinX, aabbMinY, aabbMinZ; // 12 bytes + int32_t aabbMaxX, aabbMaxY, aabbMaxZ; // 12 bytes + uint16_t firstTriRef; // 2 bytes - index into room tri-ref array + uint16_t triRefCount; // 2 bytes + uint32_t pad; // 4 bytes (alignment) +}; +static_assert(sizeof(RoomData) == 32, "RoomData must be 32 bytes"); + +/// Per-portal data connecting two rooms. +/// Center position is in fixed-point world/GTE space (20.12). +/// halfW/halfH define the portal opening size. +/// Normal, right, and up define the portal's orientation in world space. +/// Corner vertices are computed as: center +/- right*halfW +/- up*halfH. +struct PortalData { + uint16_t roomA; // 2 bytes + uint16_t roomB; // 2 bytes + int32_t centerX, centerY, centerZ; // 12 bytes - portal center (20.12 fp) + int16_t halfW; // 2 bytes - half-width in GTE units (4.12 fp) + int16_t halfH; // 2 bytes - half-height in GTE units (4.12 fp) + int16_t normalX, normalY, normalZ; // 6 bytes - facing direction (4.12 fp unit vector) + int16_t pad; // 2 bytes - alignment + int16_t rightX, rightY, rightZ; // 6 bytes - local right axis (4.12 fp unit vector) + int16_t upX, upY, upZ; // 6 bytes - local up axis (4.12 fp unit vector) +}; +static_assert(sizeof(PortalData) == 40, "PortalData must be 40 bytes"); + +} // namespace psxsplash diff --git a/src/camera.cpp b/src/camera.cpp index 0dfe72b..c7e3834 100644 --- a/src/camera.cpp +++ b/src/camera.cpp @@ -34,4 +34,124 @@ void psxsplash::Camera::SetRotation(psyqo::Angle x, psyqo::Angle y, psyqo::Angle m_rotationMatrix = rotY; } -psyqo::Matrix33& psxsplash::Camera::GetRotation() { return m_rotationMatrix; } \ No newline at end of file +psyqo::Matrix33& psxsplash::Camera::GetRotation() { return m_rotationMatrix; } + +void psxsplash::Camera::ExtractFrustum(Frustum& frustum) const { + // ========================================================================= + // FRUSTUM CULLING FOR PSX/GTE COORDINATE SYSTEM + // ========================================================================= + // + // GTE projection settings (from renderer): + // Screen: 320x240 (half-width=160, half-height=120) + // H = 120 (projection plane distance) + // + // FOV calculation: + // Horizontal half-angle: atan(160/120) ≈ 53° → total ~106° horizontal FOV + // Vertical half-angle: atan(120/120) = 45° → total 90° vertical FOV + // + // For frustum plane normals, we use the ratio of screen edge to H: + // Left/Right planes: normal = forward * screenHalfWidth + right * H + // Top/Bottom planes: normal = forward * screenHalfHeight + up * H + // + // GTE uses right-handed coordinate system: + // +X = Right, +Y = Up, +Z = INTO the screen (forward) + // + // The rotation matrix is the VIEW MATRIX - transforms world→camera space. + // For a view matrix: ROWS are the camera axes in world space. + // + // Frustum plane convention (matching testPlane in bvh.hh): + // Normal points INTO the frustum (toward visible space) + // Point is INSIDE frustum if dot(point, normal) + d >= 0 + // ========================================================================= + + // GTE projection parameters (must match renderer setup) + constexpr int32_t SCREEN_HALF_WIDTH = 160; // 320/2 + constexpr int32_t SCREEN_HALF_HEIGHT = 120; // 240/2 + constexpr int32_t H = 120; // Projection distance + + // Camera axes in world space (ROWS of view rotation matrix) + int32_t rightX = m_rotationMatrix.vs[0].x.raw(); + int32_t rightY = m_rotationMatrix.vs[0].y.raw(); + int32_t rightZ = m_rotationMatrix.vs[0].z.raw(); + + int32_t upX = m_rotationMatrix.vs[1].x.raw(); + int32_t upY = m_rotationMatrix.vs[1].y.raw(); + int32_t upZ = m_rotationMatrix.vs[1].z.raw(); + + int32_t fwdX = m_rotationMatrix.vs[2].x.raw(); + int32_t fwdY = m_rotationMatrix.vs[2].y.raw(); + int32_t fwdZ = m_rotationMatrix.vs[2].z.raw(); + + int32_t camX = m_position.x.raw(); + int32_t camY = m_position.y.raw(); + int32_t camZ = m_position.z.raw(); + + // ========================================================================= + // PLANE 0: NEAR PLANE + // Normal points FORWARD (into visible space) + // ========================================================================= + frustum.planes[0].nx = fwdX; + frustum.planes[0].ny = fwdY; + frustum.planes[0].nz = fwdZ; + int64_t fwdDotCam = ((int64_t)fwdX * camX + (int64_t)fwdY * camY + (int64_t)fwdZ * camZ) >> 12; + frustum.planes[0].d = -fwdDotCam; + + // ========================================================================= + // PLANE 1: FAR PLANE + // Normal points BACKWARD (toward camera) + // Far distance in fixed 20.12: 4096 = 1 unit, so 4096000 ≈ 1000 units + // ========================================================================= + frustum.planes[1].nx = -fwdX; + frustum.planes[1].ny = -fwdY; + frustum.planes[1].nz = -fwdZ; + frustum.planes[1].d = fwdDotCam + (4096 * 2000); // 2000 units far plane + + // ========================================================================= + // SIDE PLANES - Based on actual GTE FOV + // + // The frustum edge in camera space goes through (±screenHalf, 0, H). + // Plane normal (pointing INTO frustum) = right * H + forward * screenHalfWidth + // (for left plane, we add right; for right plane, we subtract right) + // + // Note: axes are in 4.12 fixed point (4096 = 1.0), but H and screen values + // are integers. We scale H to match: H * 4096 / some_factor + // Since we just need the ratio, we can use H and screenHalf directly + // as weights for the axis vectors. + // ========================================================================= + + // PLANE 2: LEFT PLANE - cull things to the LEFT of view + // Normal = right * H + forward * screenHalfWidth (points into frustum) + frustum.planes[2].nx = ((int64_t)rightX * H + (int64_t)fwdX * SCREEN_HALF_WIDTH) >> 12; + frustum.planes[2].ny = ((int64_t)rightY * H + (int64_t)fwdY * SCREEN_HALF_WIDTH) >> 12; + frustum.planes[2].nz = ((int64_t)rightZ * H + (int64_t)fwdZ * SCREEN_HALF_WIDTH) >> 12; + frustum.planes[2].d = -(((int64_t)frustum.planes[2].nx * camX + + (int64_t)frustum.planes[2].ny * camY + + (int64_t)frustum.planes[2].nz * camZ) >> 12); + + // PLANE 3: RIGHT PLANE - cull things to the RIGHT of view + // Normal = -right * H + forward * screenHalfWidth (points into frustum) + frustum.planes[3].nx = ((int64_t)(-rightX) * H + (int64_t)fwdX * SCREEN_HALF_WIDTH) >> 12; + frustum.planes[3].ny = ((int64_t)(-rightY) * H + (int64_t)fwdY * SCREEN_HALF_WIDTH) >> 12; + frustum.planes[3].nz = ((int64_t)(-rightZ) * H + (int64_t)fwdZ * SCREEN_HALF_WIDTH) >> 12; + frustum.planes[3].d = -(((int64_t)frustum.planes[3].nx * camX + + (int64_t)frustum.planes[3].ny * camY + + (int64_t)frustum.planes[3].nz * camZ) >> 12); + + // PLANE 4: BOTTOM PLANE - cull things BELOW view + // Normal = up * H + forward * screenHalfHeight (points into frustum) + frustum.planes[4].nx = ((int64_t)upX * H + (int64_t)fwdX * SCREEN_HALF_HEIGHT) >> 12; + frustum.planes[4].ny = ((int64_t)upY * H + (int64_t)fwdY * SCREEN_HALF_HEIGHT) >> 12; + frustum.planes[4].nz = ((int64_t)upZ * H + (int64_t)fwdZ * SCREEN_HALF_HEIGHT) >> 12; + frustum.planes[4].d = -(((int64_t)frustum.planes[4].nx * camX + + (int64_t)frustum.planes[4].ny * camY + + (int64_t)frustum.planes[4].nz * camZ) >> 12); + + // PLANE 5: TOP PLANE - cull things ABOVE view + // Normal = -up * H + forward * screenHalfHeight (points into frustum) + frustum.planes[5].nx = ((int64_t)(-upX) * H + (int64_t)fwdX * SCREEN_HALF_HEIGHT) >> 12; + frustum.planes[5].ny = ((int64_t)(-upY) * H + (int64_t)fwdY * SCREEN_HALF_HEIGHT) >> 12; + frustum.planes[5].nz = ((int64_t)(-upZ) * H + (int64_t)fwdZ * SCREEN_HALF_HEIGHT) >> 12; + frustum.planes[5].d = -(((int64_t)frustum.planes[5].nx * camX + + (int64_t)frustum.planes[5].ny * camY + + (int64_t)frustum.planes[5].nz * camZ) >> 12); +} \ No newline at end of file diff --git a/src/camera.hh b/src/camera.hh index 39c3e84..45dffd0 100644 --- a/src/camera.hh +++ b/src/camera.hh @@ -4,6 +4,8 @@ #include #include +#include "bvh.hh" + namespace psxsplash { // Camera class for managing 3D position and rotation. @@ -20,6 +22,10 @@ class Camera { void SetRotation(psyqo::Angle x, psyqo::Angle y, psyqo::Angle z); psyqo::Matrix33& GetRotation(); + + /// Extract frustum planes for culling + /// Near/Far planes based on typical PS1 draw distances + void ExtractFrustum(Frustum& frustum) const; private: psyqo::Matrix33 m_rotationMatrix; diff --git a/src/collision.cpp b/src/collision.cpp new file mode 100644 index 0000000..0a23f82 --- /dev/null +++ b/src/collision.cpp @@ -0,0 +1,468 @@ +#include "collision.hh" +#include "scenemanager.hh" + +#include + +// Helper type alias for brevity +using FP = psyqo::FixedPoint<12>; + +namespace psxsplash { + +// Static member initialization +psyqo::FixedPoint<12> SpatialGrid::WORLD_MIN = FP(-16); +psyqo::FixedPoint<12> SpatialGrid::WORLD_MAX = FP(16); +psyqo::FixedPoint<12> SpatialGrid::CELL_SIZE = FP(4); // (32 / 8) = 4 + +// AABB expand implementation +void AABB::expand(const psyqo::Vec3& delta) { + psyqo::FixedPoint<12> zero; + if (delta.x > zero) max.x = max.x + delta.x; + else min.x = min.x + delta.x; + if (delta.y > zero) max.y = max.y + delta.y; + else min.y = min.y + delta.y; + if (delta.z > zero) max.z = max.z + delta.z; + else min.z = min.z + delta.z; +} + +// ============================================================================ +// SpatialGrid Implementation +// ============================================================================ + +void SpatialGrid::clear() { + for (int i = 0; i < CELL_COUNT; i++) { + m_cells[i].count = 0; + } +} + +void SpatialGrid::worldToGrid(const psyqo::Vec3& pos, int& gx, int& gy, int& gz) const { + // Clamp position to world bounds + auto px = pos.x; + auto py = pos.y; + auto pz = pos.z; + + if (px < WORLD_MIN) px = WORLD_MIN; + if (px > WORLD_MAX) px = WORLD_MAX; + if (py < WORLD_MIN) py = WORLD_MIN; + if (py > WORLD_MAX) py = WORLD_MAX; + if (pz < WORLD_MIN) pz = WORLD_MIN; + if (pz > WORLD_MAX) pz = WORLD_MAX; + + // Convert to grid coordinates (0 to GRID_SIZE-1) + // Using integer division after scaling + gx = ((px - WORLD_MIN) / CELL_SIZE).integer(); + gy = ((py - WORLD_MIN) / CELL_SIZE).integer(); + gz = ((pz - WORLD_MIN) / CELL_SIZE).integer(); + + // Clamp to valid range + if (gx < 0) gx = 0; + if (gx >= GRID_SIZE) gx = GRID_SIZE - 1; + if (gy < 0) gy = 0; + if (gy >= GRID_SIZE) gy = GRID_SIZE - 1; + if (gz < 0) gz = 0; + if (gz >= GRID_SIZE) gz = GRID_SIZE - 1; +} + +int SpatialGrid::getCellIndex(const psyqo::Vec3& pos) const { + int gx, gy, gz; + worldToGrid(pos, gx, gy, gz); + return gx + gy * GRID_SIZE + gz * GRID_SIZE * GRID_SIZE; +} + +void SpatialGrid::insert(uint16_t objectIndex, const AABB& bounds) { + // Get grid range for this AABB + int minGx, minGy, minGz; + int maxGx, maxGy, maxGz; + + worldToGrid(bounds.min, minGx, minGy, minGz); + worldToGrid(bounds.max, maxGx, maxGy, maxGz); + + // Insert into all overlapping cells + for (int gz = minGz; gz <= maxGz; gz++) { + for (int gy = minGy; gy <= maxGy; gy++) { + for (int gx = minGx; gx <= maxGx; gx++) { + int cellIndex = gx + gy * GRID_SIZE + gz * GRID_SIZE * GRID_SIZE; + Cell& cell = m_cells[cellIndex]; + + if (cell.count < MAX_OBJECTS_PER_CELL) { + cell.objectIndices[cell.count++] = objectIndex; + } + // If cell is full, object won't be in this cell (may miss collisions) + // This is a tradeoff for memory/performance + } + } + } +} + +int SpatialGrid::queryAABB(const AABB& bounds, uint16_t* output, int maxResults) const { + int resultCount = 0; + + // Get grid range for query AABB + int minGx, minGy, minGz; + int maxGx, maxGy, maxGz; + + worldToGrid(bounds.min, minGx, minGy, minGz); + worldToGrid(bounds.max, maxGx, maxGy, maxGz); + + // Track which objects we've already added (two 32-bit masks for objects 0-63) + uint32_t addedMaskLow = 0; // Objects 0-31 + uint32_t addedMaskHigh = 0; // Objects 32-63 + + // Query all overlapping cells + for (int gz = minGz; gz <= maxGz; gz++) { + for (int gy = minGy; gy <= maxGy; gy++) { + for (int gx = minGx; gx <= maxGx; gx++) { + int cellIndex = gx + gy * GRID_SIZE + gz * GRID_SIZE * GRID_SIZE; + const Cell& cell = m_cells[cellIndex]; + + for (int i = 0; i < cell.count; i++) { + uint16_t objIndex = cell.objectIndices[i]; + + // Skip if already added (using bitmask for objects 0-63) + if (objIndex < 32) { + uint32_t bit = 1U << objIndex; + if (addedMaskLow & bit) continue; + addedMaskLow |= bit; + } else if (objIndex < 64) { + uint32_t bit = 1U << (objIndex - 32); + if (addedMaskHigh & bit) continue; + addedMaskHigh |= bit; + } + + if (resultCount < maxResults) { + output[resultCount++] = objIndex; + } + } + } + } + } + + return resultCount; +} + +// ============================================================================ +// CollisionSystem Implementation +// ============================================================================ + +void CollisionSystem::init() { + reset(); +} + +void CollisionSystem::reset() { + m_colliderCount = 0; + m_resultCount = 0; + m_triggerPairCount = 0; + m_grid.clear(); +} + +void CollisionSystem::registerCollider(uint16_t gameObjectIndex, const AABB& localBounds, + CollisionType type, CollisionMask mask) { + if (m_colliderCount >= MAX_COLLIDERS) { + // Out of collider slots + return; + } + + CollisionData& data = m_colliders[m_colliderCount++]; + data.bounds = localBounds; // Will be transformed in updateCollider + data.type = type; + data.layerMask = mask; + data.flags = 0; + data.gridCell = 0; + data.gameObjectIndex = gameObjectIndex; +} + +void CollisionSystem::updateCollider(uint16_t gameObjectIndex, const psyqo::Vec3& position, + const psyqo::Matrix33& rotation) { + // Find the collider for this object + for (int i = 0; i < m_colliderCount; i++) { + if (m_colliders[i].gameObjectIndex == gameObjectIndex) { + // For now, just translate the AABB (no rotation support for AABBs) + // TODO: Compute rotated AABB if needed + + // Store original local bounds somewhere if we need to recalculate + // For now, assume bounds are already world-relative + m_colliders[i].bounds.min = m_colliders[i].bounds.min + position; + m_colliders[i].bounds.max = m_colliders[i].bounds.max + position; + break; + } + } +} + +int CollisionSystem::detectCollisions() { + m_resultCount = 0; + + // Clear and rebuild spatial grid + m_grid.clear(); + for (int i = 0; i < m_colliderCount; i++) { + m_grid.insert(i, m_colliders[i].bounds); + } + + // Check each collider against potential colliders from grid + for (int i = 0; i < m_colliderCount; i++) { + const CollisionData& colliderA = m_colliders[i]; + + // Skip if no collision type + if (colliderA.type == CollisionType::None) continue; + + // Query spatial grid for nearby objects + uint16_t nearby[32]; + int nearbyCount = m_grid.queryAABB(colliderA.bounds, nearby, 32); + + for (int j = 0; j < nearbyCount; j++) { + int otherIndex = nearby[j]; + + // Skip self + if (otherIndex == i) continue; + + // Skip if already processed (only process pairs once) + if (otherIndex < i) continue; + + const CollisionData& colliderB = m_colliders[otherIndex]; + + // Skip if no collision type + if (colliderB.type == CollisionType::None) continue; + + // Check layer masks + if ((colliderA.layerMask & colliderB.layerMask) == 0) continue; + + // Narrowphase AABB test + psyqo::Vec3 normal; + psyqo::FixedPoint<12> penetration; + + if (testAABB(colliderA.bounds, colliderB.bounds, normal, penetration)) { + // Collision detected + if (m_resultCount < MAX_COLLISION_RESULTS) { + CollisionResult& result = m_results[m_resultCount++]; + result.objectA = colliderA.gameObjectIndex; + result.objectB = colliderB.gameObjectIndex; + result.normal = normal; + result.penetration = penetration; + } + + // Handle triggers + if (colliderA.type == CollisionType::Trigger) { + updateTriggerState(i, otherIndex, true); + } + if (colliderB.type == CollisionType::Trigger) { + updateTriggerState(otherIndex, i, true); + } + } + } + } + + // Update trigger pairs that are no longer overlapping + for (int i = 0; i < m_triggerPairCount; i++) { + TriggerPair& pair = m_triggerPairs[i]; + pair.framesSinceContact++; + + // If no contact for several frames, trigger exit + if (pair.framesSinceContact > 2 && pair.state != 2) { + pair.state = 2; // Exiting + } + } + + return m_resultCount; +} + +bool CollisionSystem::testAABB(const AABB& a, const AABB& b, + psyqo::Vec3& normal, psyqo::FixedPoint<12>& penetration) const { + // Check for overlap on all axes + if (a.max.x < b.min.x || a.min.x > b.max.x) return false; + if (a.max.y < b.min.y || a.min.y > b.max.y) return false; + if (a.max.z < b.min.z || a.min.z > b.max.z) return false; + + // Calculate penetration on each axis + auto overlapX1 = a.max.x - b.min.x; + auto overlapX2 = b.max.x - a.min.x; + auto overlapY1 = a.max.y - b.min.y; + auto overlapY2 = b.max.y - a.min.y; + auto overlapZ1 = a.max.z - b.min.z; + auto overlapZ2 = b.max.z - a.min.z; + + // Find minimum overlap axis + auto minOverlapX = (overlapX1 < overlapX2) ? overlapX1 : overlapX2; + auto minOverlapY = (overlapY1 < overlapY2) ? overlapY1 : overlapY2; + auto minOverlapZ = (overlapZ1 < overlapZ2) ? overlapZ1 : overlapZ2; + + // Constants for normals + const FP zero(0); + const FP one(1); + const FP negOne(-1); + + // Determine separation axis (axis with least penetration) + if (minOverlapX <= minOverlapY && minOverlapX <= minOverlapZ) { + penetration = minOverlapX; + normal = psyqo::Vec3{(overlapX1 < overlapX2) ? negOne : one, zero, zero}; + } else if (minOverlapY <= minOverlapZ) { + penetration = minOverlapY; + normal = psyqo::Vec3{zero, (overlapY1 < overlapY2) ? negOne : one, zero}; + } else { + penetration = minOverlapZ; + normal = psyqo::Vec3{zero, zero, (overlapZ1 < overlapZ2) ? negOne : one}; + } + + return true; +} + +void CollisionSystem::updateTriggerState(uint16_t triggerIndex, uint16_t otherIndex, bool isOverlapping) { + // Look for existing pair + for (int i = 0; i < m_triggerPairCount; i++) { + TriggerPair& pair = m_triggerPairs[i]; + if (pair.triggerIndex == triggerIndex && pair.otherIndex == otherIndex) { + if (isOverlapping) { + pair.framesSinceContact = 0; + if (pair.state == 0) { + pair.state = 1; // Now staying + } + } + return; + } + } + + // New pair - add it + if (isOverlapping && m_triggerPairCount < MAX_TRIGGERS) { + TriggerPair& pair = m_triggerPairs[m_triggerPairCount++]; + pair.triggerIndex = triggerIndex; + pair.otherIndex = otherIndex; + pair.framesSinceContact = 0; + pair.state = 0; // New (enter event) + } +} + +bool CollisionSystem::areColliding(uint16_t indexA, uint16_t indexB) const { + for (int i = 0; i < m_resultCount; i++) { + if ((m_results[i].objectA == indexA && m_results[i].objectB == indexB) || + (m_results[i].objectA == indexB && m_results[i].objectB == indexA)) { + return true; + } + } + return false; +} + +bool CollisionSystem::raycast(const psyqo::Vec3& origin, const psyqo::Vec3& direction, + psyqo::FixedPoint<12> maxDistance, + psyqo::Vec3& hitPoint, psyqo::Vec3& hitNormal, + uint16_t& hitObjectIndex) const { + // Simple brute-force raycast against all colliders + // TODO: Use spatial grid for optimization + + auto closestT = maxDistance; + bool hit = false; + + // Fixed-point constants + const FP zero(0); + const FP one(1); + const FP negOne(-1); + const FP largeVal(1000); + const FP negLargeVal(-1000); + FP epsilon; + epsilon.value = 4; // ~0.001 in 20.12 fixed point + + for (int i = 0; i < m_colliderCount; i++) { + const CollisionData& collider = m_colliders[i]; + if (collider.type == CollisionType::None) continue; + + // Ray-AABB intersection test (slab method) + const AABB& box = collider.bounds; + + auto tMin = negLargeVal; + auto tMax = largeVal; + + // X slab + if (direction.x != zero) { + auto invD = one / direction.x; + auto t1 = (box.min.x - origin.x) * invD; + auto t2 = (box.max.x - origin.x) * invD; + if (t1 > t2) { auto tmp = t1; t1 = t2; t2 = tmp; } + if (t1 > tMin) tMin = t1; + if (t2 < tMax) tMax = t2; + } else if (origin.x < box.min.x || origin.x > box.max.x) { + continue; + } + + // Y slab + if (direction.y != zero) { + auto invD = one / direction.y; + auto t1 = (box.min.y - origin.y) * invD; + auto t2 = (box.max.y - origin.y) * invD; + if (t1 > t2) { auto tmp = t1; t1 = t2; t2 = tmp; } + if (t1 > tMin) tMin = t1; + if (t2 < tMax) tMax = t2; + } else if (origin.y < box.min.y || origin.y > box.max.y) { + continue; + } + + // Z slab + if (direction.z != zero) { + auto invD = one / direction.z; + auto t1 = (box.min.z - origin.z) * invD; + auto t2 = (box.max.z - origin.z) * invD; + if (t1 > t2) { auto tmp = t1; t1 = t2; t2 = tmp; } + if (t1 > tMin) tMin = t1; + if (t2 < tMax) tMax = t2; + } else if (origin.z < box.min.z || origin.z > box.max.z) { + continue; + } + + if (tMin > tMax || tMax < zero) continue; + + auto t = (tMin >= zero) ? tMin : tMax; + + if (t < closestT && t >= zero) { + closestT = t; + hitObjectIndex = collider.gameObjectIndex; + hit = true; + + // Calculate hit point + hitPoint = psyqo::Vec3{ + origin.x + direction.x * t, + origin.y + direction.y * t, + origin.z + direction.z * t + }; + + // Calculate normal (which face was hit) + if ((hitPoint.x - box.min.x).abs() < epsilon) hitNormal = psyqo::Vec3{negOne, zero, zero}; + else if ((hitPoint.x - box.max.x).abs() < epsilon) hitNormal = psyqo::Vec3{one, zero, zero}; + else if ((hitPoint.y - box.min.y).abs() < epsilon) hitNormal = psyqo::Vec3{zero, negOne, zero}; + else if ((hitPoint.y - box.max.y).abs() < epsilon) hitNormal = psyqo::Vec3{zero, one, zero}; + else if ((hitPoint.z - box.min.z).abs() < epsilon) hitNormal = psyqo::Vec3{zero, zero, negOne}; + else hitNormal = psyqo::Vec3{zero, zero, one}; + } + } + + return hit; +} + +void CollisionSystem::processTriggerEvents(SceneManager& scene) { + // Process trigger pairs and fire Lua events + int writeIndex = 0; + + for (int i = 0; i < m_triggerPairCount; i++) { + TriggerPair& pair = m_triggerPairs[i]; + + // Get game object indices + uint16_t triggerObjIdx = m_colliders[pair.triggerIndex].gameObjectIndex; + uint16_t otherObjIdx = m_colliders[pair.otherIndex].gameObjectIndex; + + switch (pair.state) { + case 0: // Enter + scene.fireTriggerEnter(triggerObjIdx, otherObjIdx); + pair.state = 1; // Move to staying + m_triggerPairs[writeIndex++] = pair; + break; + + case 1: // Staying + scene.fireTriggerStay(triggerObjIdx, otherObjIdx); + m_triggerPairs[writeIndex++] = pair; + break; + + case 2: // Exit + scene.fireTriggerExit(triggerObjIdx, otherObjIdx); + // Don't copy - remove from list + break; + } + } + + m_triggerPairCount = writeIndex; +} + +} // namespace psxsplash diff --git a/src/collision.hh b/src/collision.hh new file mode 100644 index 0000000..2c535e0 --- /dev/null +++ b/src/collision.hh @@ -0,0 +1,239 @@ +#pragma once + +/** + * collision.hh - PS1 Collision System + * + * Provides spatial hashing broadphase and AABB narrowphase collision detection. + * Designed for PS1's limited CPU - uses fixed-point math and spatial partitioning. + * + * Architecture: + * - Broadphase: Spatial grid (cells of fixed size) + * - Narrowphase: AABB intersection tests + * - Trigger system: Enter/Stay/Exit events + */ + +#include +#include +#include + +#include "gameobject.hh" + +namespace psxsplash { + +// Forward declarations +class SceneManager; + +/** + * Collision type flags - matches Unity PSXCollisionType enum + */ +enum class CollisionType : uint8_t { + None = 0, + Solid = 1, // Blocks movement + Trigger = 2, // Fires events, doesn't block + Platform = 3 // Solid from above only +}; + +/** + * Collision layer mask - 8 layers available + * Objects only collide with matching layers + */ +using CollisionMask = uint8_t; + +/** + * Axis-Aligned Bounding Box in fixed-point + * Used for broadphase and narrowphase collision + */ +struct AABB { + psyqo::Vec3 min; + psyqo::Vec3 max; + + // Check if this AABB intersects another + bool intersects(const AABB& other) const { + return (min.x <= other.max.x && max.x >= other.min.x) && + (min.y <= other.max.y && max.y >= other.min.y) && + (min.z <= other.max.z && max.z >= other.min.z); + } + + // Check if a point is inside this AABB + bool contains(const psyqo::Vec3& point) const { + return (point.x >= min.x && point.x <= max.x) && + (point.y >= min.y && point.y <= max.y) && + (point.z >= min.z && point.z <= max.z); + } + + // Get center of AABB + psyqo::Vec3 center() const { + return psyqo::Vec3{ + (min.x + max.x) / 2, + (min.y + max.y) / 2, + (min.z + max.z) / 2 + }; + } + + // Get half-extents + psyqo::Vec3 halfExtents() const { + return psyqo::Vec3{ + (max.x - min.x) / 2, + (max.y - min.y) / 2, + (max.z - min.z) / 2 + }; + } + + // Expand AABB by a vector (for swept tests) + void expand(const psyqo::Vec3& delta); +}; +static_assert(sizeof(AABB) == 24, "AABB must be 24 bytes (2x Vec3)"); + +/** + * Collision data for a single object + * Stored separately from GameObject for cache efficiency + */ +struct CollisionData { + AABB bounds; // World-space AABB (24 bytes) + CollisionType type; // Collision behavior (1 byte) + CollisionMask layerMask; // Which layers this collides with (1 byte) + uint8_t flags; // Additional flags (1 byte) + uint8_t gridCell; // Current spatial grid cell (1 byte) + uint16_t gameObjectIndex; // Index into GameObject array (2 bytes) + uint16_t padding; // Alignment padding (2 bytes) +}; +static_assert(sizeof(CollisionData) == 32, "CollisionData must be 32 bytes"); + +/** + * Collision result - returned when collision is detected + */ +struct CollisionResult { + uint16_t objectA; // First object index + uint16_t objectB; // Second object index + psyqo::Vec3 normal; // Collision normal (from A to B) + psyqo::FixedPoint<12> penetration; // Penetration depth +}; + +/** + * Trigger state for tracking enter/stay/exit + */ +struct TriggerPair { + uint16_t triggerIndex; // Index of trigger object + uint16_t otherIndex; // Index of other object + uint8_t framesSinceContact; // Counter for exit detection + uint8_t state; // 0=new, 1=staying, 2=exiting + uint16_t padding; +}; + +/** + * Spatial Grid for broadphase collision + * Divides world into fixed-size cells for fast overlap queries + */ +class SpatialGrid { +public: + // Grid configuration + static constexpr int GRID_SIZE = 8; // 8x8x8 grid + static constexpr int CELL_COUNT = GRID_SIZE * GRID_SIZE * GRID_SIZE; + static constexpr int MAX_OBJECTS_PER_CELL = 16; + + // World bounds (fixed for simplicity) - values set in collision.cpp + static psyqo::FixedPoint<12> WORLD_MIN; + static psyqo::FixedPoint<12> WORLD_MAX; + static psyqo::FixedPoint<12> CELL_SIZE; + + struct Cell { + uint16_t objectIndices[MAX_OBJECTS_PER_CELL]; + uint8_t count; + uint8_t padding[3]; + }; + + // Clear all cells + void clear(); + + // Insert an object into the grid + void insert(uint16_t objectIndex, const AABB& bounds); + + // Get all potential colliders for an AABB + // Returns number of results written to output + int queryAABB(const AABB& bounds, uint16_t* output, int maxResults) const; + + // Get cell index for a position + int getCellIndex(const psyqo::Vec3& pos) const; + +private: + Cell m_cells[CELL_COUNT]; + + // Convert world position to grid coordinates + void worldToGrid(const psyqo::Vec3& pos, int& gx, int& gy, int& gz) const; +}; + +/** + * Main Collision System + * Manages all collision detection and trigger events + */ +class CollisionSystem { +public: + static constexpr int MAX_COLLIDERS = 64; + static constexpr int MAX_TRIGGERS = 32; + static constexpr int MAX_COLLISION_RESULTS = 32; + + CollisionSystem() = default; + + // Initialize the system + void init(); + + // Reset for new scene + void reset(); + + // Register a collider (called during scene load) + void registerCollider(uint16_t gameObjectIndex, const AABB& localBounds, + CollisionType type, CollisionMask mask); + + // Update collision data for an object (call when object moves) + void updateCollider(uint16_t gameObjectIndex, const psyqo::Vec3& position, + const psyqo::Matrix33& rotation); + + // Run collision detection for one frame + // Returns number of collisions detected + int detectCollisions(); + + // Get collision results (valid until next detectCollisions call) + const CollisionResult* getResults() const { return m_results; } + int getResultCount() const { return m_resultCount; } + + // Check if two specific objects are colliding + bool areColliding(uint16_t indexA, uint16_t indexB) const; + + // Raycast against all colliders + // Returns true if hit, fills hitPoint and hitNormal + bool raycast(const psyqo::Vec3& origin, const psyqo::Vec3& direction, + psyqo::FixedPoint<12> maxDistance, + psyqo::Vec3& hitPoint, psyqo::Vec3& hitNormal, + uint16_t& hitObjectIndex) const; + + // Get trigger events for current frame (call from SceneManager) + void processTriggerEvents(class SceneManager& scene); + + // Debug: Get collider count + int getColliderCount() const { return m_colliderCount; } + +private: + // Collision data for all registered colliders + CollisionData m_colliders[MAX_COLLIDERS]; + int m_colliderCount = 0; + + // Spatial partitioning grid + SpatialGrid m_grid; + + // Collision results for current frame + CollisionResult m_results[MAX_COLLISION_RESULTS]; + int m_resultCount = 0; + + // Trigger tracking + TriggerPair m_triggerPairs[MAX_TRIGGERS]; + int m_triggerPairCount = 0; + + // Narrowphase AABB test + bool testAABB(const AABB& a, const AABB& b, + psyqo::Vec3& normal, psyqo::FixedPoint<12>& penetration) const; + + // Update trigger state machine + void updateTriggerState(uint16_t triggerIndex, uint16_t otherIndex, bool isOverlapping); +}; + +} // namespace psxsplash diff --git a/src/controls.cpp b/src/controls.cpp index 6337c9e..f16cfa4 100644 --- a/src/controls.cpp +++ b/src/controls.cpp @@ -4,53 +4,145 @@ void psxsplash::Controls::Init() { m_input.initialize(); } +bool psxsplash::Controls::isDigitalPad() const { + uint8_t padType = m_input.getPadType(psyqo::AdvancedPad::Pad::Pad1a); + // Digital pad (0x41) has no analog sticks + // Also treat disconnected pads as digital (D-pad still works through button API) + return padType == psyqo::AdvancedPad::PadType::DigitalPad || + padType == psyqo::AdvancedPad::PadType::None; +} + +void psxsplash::Controls::getDpadAxes(int16_t &outX, int16_t &outY) const { + outX = 0; + outY = 0; + // D-pad produces full-magnitude values (like pushing the stick to the edge) + if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::Up)) + outY = -127; + if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::Down)) + outY = 127; + if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::Left)) + outX = -127; + if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::Right)) + outX = 127; +} + +void psxsplash::Controls::UpdateButtonStates() { + m_previousButtons = m_currentButtons; + + // Read all button states into a single bitmask + m_currentButtons = 0; + if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::Cross)) m_currentButtons |= (1u << psyqo::AdvancedPad::Button::Cross); + if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::Circle)) m_currentButtons |= (1u << psyqo::AdvancedPad::Button::Circle); + if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::Square)) m_currentButtons |= (1u << psyqo::AdvancedPad::Button::Square); + if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::Triangle)) m_currentButtons |= (1u << psyqo::AdvancedPad::Button::Triangle); + if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::L1)) m_currentButtons |= (1u << psyqo::AdvancedPad::Button::L1); + if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::L2)) m_currentButtons |= (1u << psyqo::AdvancedPad::Button::L2); + if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::L3)) m_currentButtons |= (1u << psyqo::AdvancedPad::Button::L3); + if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::R1)) m_currentButtons |= (1u << psyqo::AdvancedPad::Button::R1); + if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::R2)) m_currentButtons |= (1u << psyqo::AdvancedPad::Button::R2); + if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::R3)) m_currentButtons |= (1u << psyqo::AdvancedPad::Button::R3); + if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::Start)) m_currentButtons |= (1u << psyqo::AdvancedPad::Button::Start); + if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::Select)) m_currentButtons |= (1u << psyqo::AdvancedPad::Button::Select); + if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::Up)) m_currentButtons |= (1u << psyqo::AdvancedPad::Button::Up); + if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::Down)) m_currentButtons |= (1u << psyqo::AdvancedPad::Button::Down); + if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::Left)) m_currentButtons |= (1u << psyqo::AdvancedPad::Button::Left); + if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::Right)) m_currentButtons |= (1u << psyqo::AdvancedPad::Button::Right); + + // Calculate pressed and released buttons + m_buttonsPressed = m_currentButtons & ~m_previousButtons; + m_buttonsReleased = m_previousButtons & ~m_currentButtons; +} + void psxsplash::Controls::HandleControls(psyqo::Vec3 &playerPosition, psyqo::Angle &playerRotationX, psyqo::Angle &playerRotationY, psyqo::Angle &playerRotationZ, bool freecam, - int deltaTime) { - uint8_t rightX = m_input.getAdc(psyqo::AdvancedPad::Pad::Pad1a, 0); - uint8_t rightY = m_input.getAdc(psyqo::AdvancedPad::Pad::Pad1a, 1); + int deltaFrames) { + bool digital = isDigitalPad(); + + int16_t rightXOffset, rightYOffset, leftXOffset, leftYOffset; + + if (digital) { + // Digital pad: use D-pad for movement, L1/R1 for rotation + getDpadAxes(leftXOffset, leftYOffset); + // L1/R1 for horizontal look rotation (no vertical on digital) + rightXOffset = 0; + rightYOffset = 0; + if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::R1)) + rightXOffset = 90; + if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::L1)) + rightXOffset = -90; + } else { + // Analog pad: read stick ADC values + uint8_t rightX = m_input.getAdc(psyqo::AdvancedPad::Pad::Pad1a, 0); + uint8_t rightY = m_input.getAdc(psyqo::AdvancedPad::Pad::Pad1a, 1); + uint8_t leftX = m_input.getAdc(psyqo::AdvancedPad::Pad::Pad1a, 2); + uint8_t leftY = m_input.getAdc(psyqo::AdvancedPad::Pad::Pad1a, 3); - uint8_t leftX = m_input.getAdc(psyqo::AdvancedPad::Pad::Pad1a, 2); - uint8_t leftY = m_input.getAdc(psyqo::AdvancedPad::Pad::Pad1a, 3); - - int16_t rightXOffset = (int16_t)rightX - 0x80; - int16_t rightYOffset = (int16_t)rightY - 0x80; - int16_t leftXOffset = (int16_t)leftX - 0x80; - int16_t leftYOffset = (int16_t)leftY - 0x80; + rightXOffset = (int16_t)rightX - 0x80; + rightYOffset = (int16_t)rightY - 0x80; + leftXOffset = (int16_t)leftX - 0x80; + leftYOffset = (int16_t)leftY - 0x80; + + // On analog pad, also check D-pad as fallback (when sticks are centered) + if (__builtin_abs(leftXOffset) < m_stickDeadzone && __builtin_abs(leftYOffset) < m_stickDeadzone) { + int16_t dpadX, dpadY; + getDpadAxes(dpadX, dpadY); + if (dpadX != 0 || dpadY != 0) { + leftXOffset = dpadX; + leftYOffset = dpadY; + } + } + } + // Sprint toggle (L3 for analog, Square for digital) if (__builtin_abs(leftXOffset) < m_stickDeadzone && __builtin_abs(leftYOffset) < m_stickDeadzone) { m_sprinting = false; } - if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::L3)) { - m_sprinting = true; + // Store final stick values for Lua API access + m_leftStickX = leftXOffset; + m_leftStickY = leftYOffset; + m_rightStickX = rightXOffset; + m_rightStickY = rightYOffset; + + if (digital) { + if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::Square)) { + m_sprinting = true; + } + } else { + if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::L3)) { + m_sprinting = true; + } } - psyqo::FixedPoint<12> speed = m_sprinting ? sprintSpeed : moveSpeed; + psyqo::FixedPoint<12> speed = m_sprinting ? m_sprintSpeed : m_moveSpeed; + // Rotation (right stick or L1/R1) if (__builtin_abs(rightXOffset) > m_stickDeadzone) { - playerRotationY += (rightXOffset * rotSpeed * deltaTime) >> 7; + playerRotationY += (rightXOffset * rotSpeed * deltaFrames) >> 7; } if (__builtin_abs(rightYOffset) > m_stickDeadzone) { - playerRotationX -= (rightYOffset * rotSpeed * deltaTime) >> 7; + playerRotationX -= (rightYOffset * rotSpeed * deltaFrames) >> 7; playerRotationX = eastl::clamp(playerRotationX, -0.5_pi, 0.5_pi); } + // Movement (left stick or D-pad) if (__builtin_abs(leftYOffset) > m_stickDeadzone) { - psyqo::FixedPoint<12> forward = -(leftYOffset * speed * deltaTime) >> 7; + psyqo::FixedPoint<12> forward = -(leftYOffset * speed * deltaFrames) >> 7; playerPosition.x += m_trig.sin(playerRotationY) * forward; playerPosition.z += m_trig.cos(playerRotationY) * forward; } if (__builtin_abs(leftXOffset) > m_stickDeadzone) { - psyqo::FixedPoint<12> strafe = -(leftXOffset * speed * deltaTime) >> 7; + psyqo::FixedPoint<12> strafe = -(leftXOffset * speed * deltaFrames) >> 7; playerPosition.x -= m_trig.cos(playerRotationY) * strafe; playerPosition.z += m_trig.sin(playerRotationY) * strafe; } - if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::L1)) { - playerPosition.y += speed * deltaTime; - } - if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::R1)) { - playerPosition.y -= speed * deltaTime; + if (freecam) { + if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::L1)) { + playerPosition.y += speed * deltaFrames; + } + if (m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, psyqo::AdvancedPad::Button::R1)) { + playerPosition.y -= speed * deltaFrames; + } } } \ No newline at end of file diff --git a/src/controls.hh b/src/controls.hh index 09cfd87..f8dff40 100644 --- a/src/controls.hh +++ b/src/controls.hh @@ -3,6 +3,7 @@ #include #include #include +#include namespace psxsplash { @@ -13,7 +14,43 @@ class Controls { public: void Init(); void HandleControls(psyqo::Vec3 &playerPosition, psyqo::Angle &playerRotationX, psyqo::Angle &playerRotationY, - psyqo::Angle &playerRotationZ, bool freecam, int deltaTime); + psyqo::Angle &playerRotationZ, bool freecam, int deltaFrames); + + /// Update button state tracking - call before HandleControls + void UpdateButtonStates(); + + /// Set movement speeds from splashpack data (call once after scene load) + void setMoveSpeed(psyqo::FixedPoint<12, uint16_t> speed) { m_moveSpeed.value = speed.value; } + void setSprintSpeed(psyqo::FixedPoint<12, uint16_t> speed) { m_sprintSpeed.value = speed.value; } + + /// Check if a button was just pressed this frame + bool wasButtonPressed(psyqo::AdvancedPad::Button button) const { + uint16_t mask = 1u << static_cast(button); + return (m_currentButtons & mask) && !(m_previousButtons & mask); + } + + /// Check if a button was just released this frame + bool wasButtonReleased(psyqo::AdvancedPad::Button button) const { + uint16_t mask = 1u << static_cast(button); + return !(m_currentButtons & mask) && (m_previousButtons & mask); + } + + /// Check if a button is currently held + bool isButtonHeld(psyqo::AdvancedPad::Button button) const { + return m_input.isButtonPressed(psyqo::AdvancedPad::Pad::Pad1a, button); + } + + /// Get bitmask of buttons pressed this frame + uint16_t getButtonsPressed() const { return m_buttonsPressed; } + + /// Get bitmask of buttons released this frame + uint16_t getButtonsReleased() const { return m_buttonsReleased; } + + /// Analog stick accessors (set during HandleControls) + int16_t getLeftStickX() const { return m_leftStickX; } + int16_t getLeftStickY() const { return m_leftStickY; } + int16_t getRightStickX() const { return m_rightStickX; } + int16_t getRightStickY() const { return m_rightStickY; } private: psyqo::AdvancedPad m_input; @@ -21,9 +58,29 @@ class Controls { bool m_sprinting = false; static constexpr uint8_t m_stickDeadzone = 0x30; - static constexpr psyqo::FixedPoint<12> moveSpeed = 0.002_fp; static constexpr psyqo::Angle rotSpeed = 0.01_pi; - static constexpr psyqo::FixedPoint<12> sprintSpeed = 0.01_fp; + + // Configurable movement speeds (set from splashpack, or defaults) + psyqo::FixedPoint<12> m_moveSpeed = 0.002_fp; + psyqo::FixedPoint<12> m_sprintSpeed = 0.01_fp; + + // Button state tracking + uint16_t m_previousButtons = 0; + uint16_t m_currentButtons = 0; + uint16_t m_buttonsPressed = 0; + uint16_t m_buttonsReleased = 0; + + // Analog stick values (centered at 0, range -127 to +127) + int16_t m_leftStickX = 0; + int16_t m_leftStickY = 0; + int16_t m_rightStickX = 0; + int16_t m_rightStickY = 0; + + /// Returns true if the connected pad is digital-only (no analog sticks) + bool isDigitalPad() const; + + /// Get movement axes from D-pad as simulated stick values (-127 to +127) + void getDpadAxes(int16_t &outX, int16_t &outY) const; }; }; // namespace psxsplash \ No newline at end of file diff --git a/src/gameobject.hh b/src/gameobject.hh index 9fd59d5..959612b 100644 --- a/src/gameobject.hh +++ b/src/gameobject.hh @@ -8,11 +8,24 @@ namespace psxsplash { -// LSB is active in flags +class Lua; // Forward declaration +// Component index constants - 0xFFFF means no component +constexpr uint16_t NO_COMPONENT = 0xFFFF; + +/** + * GameObject bitfield flags + * + * Bit 0: isActive - whether object is active in scene + * Bit 1: pendingEnable - flag for deferred enable (to batch Lua calls) + * Bit 2: pendingDisable - flag for deferred disable + */ class GameObject final { typedef Utilities::BitSpan IsActive; - typedef Utilities::BitField GameObjectFlags; + typedef Utilities::BitSpan PendingEnable; + typedef Utilities::BitSpan PendingDisable; + typedef Utilities::BitField GameObjectFlags; + public: union { Tri *polygons; @@ -20,15 +33,43 @@ class GameObject final { }; psyqo::Vec3 position; psyqo::Matrix33 rotation; - // linear & angular velocity placeholders + + // Mesh data uint16_t polyCount; int16_t luaFileIndex; + union { GameObjectFlags flags; uint32_t flagsAsInt; }; + + // Component indices (0xFFFF = no component) + uint16_t interactableIndex; + uint16_t _reserved0; // Was healthIndex (legacy, kept for binary layout) + // Runtime-only: Lua event bitmask (set during RegisterGameObject) + // In the splashpack binary these 4 bytes are _reserved1 + _reserved2 (zeros). + uint32_t eventMask; + + // World-space AABB (20.12 fixed-point, 24 bytes) + // Used for per-object frustum culling before iterating triangles + int32_t aabbMinX, aabbMinY, aabbMinZ; + int32_t aabbMaxX, aabbMaxY, aabbMaxZ; + + // Basic accessors bool isActive() const { return flags.get(); } + + // setActive with Lua event support - call the version that takes Lua& for events void setActive(bool active) { flags.set(active); } + + // Deferred enable/disable for batched Lua calls + bool isPendingEnable() const { return flags.get(); } + bool isPendingDisable() const { return flags.get(); } + void setPendingEnable(bool pending) { flags.set(pending); } + void setPendingDisable(bool pending) { flags.set(pending); } + + // Component checks + bool hasInteractable() const { return interactableIndex != NO_COMPONENT; } }; -static_assert(sizeof(GameObject) == 60, "GameObject is not 56 bytes"); +static_assert(sizeof(GameObject) == 92, "GameObject is not 92 bytes"); + } // namespace psxsplash \ No newline at end of file diff --git a/src/gtemath.cpp b/src/gtemath.cpp index 6c16805..006aaa0 100644 --- a/src/gtemath.cpp +++ b/src/gtemath.cpp @@ -6,29 +6,31 @@ using namespace psyqo::GTE; void psxsplash::MatrixMultiplyGTE(const psyqo::Matrix33 &matA, const psyqo::Matrix33 &matB, psyqo::Matrix33 *result) { - writeSafe(matA); + // Load matA as the rotation matrix. No prior GTE op depends on RT registers here. + writeUnsafe(matA); psyqo::Vec3 t; + // Column 0 of matB: Safe write to V0 ensures rotation matrix is settled before MVMVA. psyqo::GTE::writeSafe(psyqo::Vec3{matB.vs[0].x, matB.vs[1].x, matB.vs[2].x}); - psyqo::GTE::Kernels::mvmva(); - + // Safe read: MVMVA (8 cycles) output must be stable before reading. t = psyqo::GTE::readSafe(); result->vs[0].x = t.x; result->vs[1].x = t.y; result->vs[2].x = t.z; - psyqo::GTE::writeSafe(psyqo::Vec3{matB.vs[0].y, matB.vs[1].y, matB.vs[2].y}); - + // Column 1: Unsafe V0 write is fine since MVMVA just completed (no dependency on V0 from readSafe). + psyqo::GTE::writeUnsafe(psyqo::Vec3{matB.vs[0].y, matB.vs[1].y, matB.vs[2].y}); + // Safe nop-equivalent: the compiler inserts enough instructions between write and kernel call. psyqo::GTE::Kernels::mvmva(); t = psyqo::GTE::readSafe(); result->vs[0].y = t.x; result->vs[1].y = t.y; result->vs[2].y = t.z; - psyqo::GTE::writeSafe(psyqo::Vec3{matB.vs[0].z, matB.vs[1].z, matB.vs[2].z}); - + // Column 2: Same pattern. + psyqo::GTE::writeUnsafe(psyqo::Vec3{matB.vs[0].z, matB.vs[1].z, matB.vs[2].z}); psyqo::GTE::Kernels::mvmva(); t = psyqo::GTE::readSafe(); result->vs[0].z = t.x; diff --git a/src/interactable.hh b/src/interactable.hh new file mode 100644 index 0000000..d035009 --- /dev/null +++ b/src/interactable.hh @@ -0,0 +1,54 @@ +#pragma once + +#include +#include + +namespace psxsplash { + +/** + * Interactable component - enables player interaction with objects. + * + * When the player is within interaction radius and presses the interact button, + * the onInteract Lua event fires on the associated GameObject. + */ +struct Interactable { + // Interaction radius squared (fixed-point 12-bit, pre-squared for fast distance checks) + psyqo::FixedPoint<12> radiusSquared; + + // Interaction point offset from object center + psyqo::FixedPoint<12> offsetX; + psyqo::FixedPoint<12> offsetY; + psyqo::FixedPoint<12> offsetZ; + + // Button index that triggers interaction (0-15) + uint8_t interactButton; + + // Configuration flags + uint8_t flags; // bit 0: isRepeatable, bit 1: showPrompt, bit 2: requireLineOfSight + + // Cooldown between interactions (in frames) + uint16_t cooldownFrames; + + // Runtime state + uint16_t currentCooldown; // Frames remaining until can interact again + uint16_t gameObjectIndex; // Index of associated GameObject + + // Flag accessors + bool isRepeatable() const { return flags & 0x01; } + bool showPrompt() const { return flags & 0x02; } + bool requireLineOfSight() const { return flags & 0x04; } + + // Check if ready to interact + bool canInteract() const { return currentCooldown == 0; } + + // Called when interaction happens + void triggerCooldown() { currentCooldown = cooldownFrames; } + + // Called each frame to decrement cooldown + void update() { + if (currentCooldown > 0) currentCooldown--; + } +}; +static_assert(sizeof(Interactable) == 24, "Interactable is not 24 bytes"); + +} // namespace psxsplash diff --git a/src/lua.cpp b/src/lua.cpp index 5ebdd68..094684a 100644 --- a/src/lua.cpp +++ b/src/lua.cpp @@ -2,6 +2,8 @@ #include +#include +#include #include #include "gameobject.hh" @@ -10,15 +12,27 @@ constexpr const char GAMEOBJECT_SCRIPT[] = R"( return function(metatable) local get_position = metatable.get_position local set_position = metatable.set_position + local get_active = metatable.get_active + local set_active = metatable.set_active + local get_rotationY = metatable.get_rotationY + local set_rotationY = metatable.set_rotationY metatable.get_position = nil metatable.set_position = nil + metatable.get_active = nil + metatable.set_active = nil + metatable.get_rotationY = nil + metatable.set_rotationY = nil function metatable.__index(self, key) + local raw = rawget(self, key) + if raw ~= nil then return raw end if key == "position" then return get_position(self.__cpp_ptr) elseif key == "active" then return get_active(self.__cpp_ptr) + elseif key == "rotationY" then + return get_rotationY(self.__cpp_ptr) end return nil end @@ -29,6 +43,10 @@ return function(metatable) return elseif key == "active" then set_active(self.__cpp_ptr, value) + return + elseif key == "rotationY" then + set_rotationY(self.__cpp_ptr, value) + return end rawset(self, key, value) end @@ -37,17 +55,18 @@ end // Lua helpers +static constexpr lua_Number kFixedScale = 4096; static int gameobjectGetPosition(psyqo::Lua L) { auto go = L.toUserdata(1); L.newTable(); - L.pushNumber(go->position.x.raw()); + L.pushNumber(static_cast(go->position.x.raw()) / kFixedScale); L.setField(2, "x"); - L.pushNumber(go->position.y.raw()); + L.pushNumber(static_cast(go->position.y.raw()) / kFixedScale); L.setField(2, "y"); - L.pushNumber(go->position.z.raw()); + L.pushNumber(static_cast(go->position.z.raw()) / kFixedScale); L.setField(2, "z"); return 1; @@ -59,18 +78,15 @@ static int gameobjectSetPosition(psyqo::Lua L) { auto go = L.toUserdata(1); L.getField(2, "x"); - psyqo::FixedPoint<> x(L.toNumber(3), psyqo::FixedPoint<>::RAW); - go->position.x = x; + go->position.x = psyqo::FixedPoint<>(static_cast(L.toNumber(3) * kFixedScale), psyqo::FixedPoint<>::RAW); L.pop(); L.getField(2, "y"); - psyqo::FixedPoint<> y(L.toNumber(3), psyqo::FixedPoint<>::RAW); - go->position.y = y; + go->position.y = psyqo::FixedPoint<>(static_cast(L.toNumber(3) * kFixedScale), psyqo::FixedPoint<>::RAW); L.pop(); L.getField(2, "z"); - psyqo::FixedPoint<> z(L.toNumber(3), psyqo::FixedPoint<>::RAW); - go->position.z = z; + go->position.z = psyqo::FixedPoint<>(static_cast(L.toNumber(3) * kFixedScale), psyqo::FixedPoint<>::RAW); L.pop(); return 0; @@ -89,6 +105,59 @@ static int gamobjectSetActive(psyqo::Lua L) { return 0; } +// Angle constants: psyqo::Angle is FixedPoint<10>, so 1.0_pi = raw 1024 +static constexpr lua_Number kAngleScale = 1024; +static psyqo::Trig<> s_trig; + +// Fast integer atan2 approximation → psyqo::Angle (pi-fraction units) +// Uses linear approximation in first octant then folds to full circle. +// Max error ~4° (acceptable for PS1 game objects). +static psyqo::Angle fastAtan2(int32_t sinVal, int32_t cosVal) { + psyqo::Angle result; + if (cosVal == 0 && sinVal == 0) { result.value = 0; return result; } + + int32_t abs_s = sinVal < 0 ? -sinVal : sinVal; + int32_t abs_c = cosVal < 0 ? -cosVal : cosVal; + + int32_t minV = abs_s < abs_c ? abs_s : abs_c; + int32_t maxV = abs_s > abs_c ? abs_s : abs_c; + + // Compute angle in first octant [0, Ï€/4 = 256 Angle units] + // angle = (minV/maxV) * 256, using only 32-bit math. + // Max minV for normalized sin/cos ≈ 4096, so minV * 256 ≈ 1M — fits int32. + int32_t angle = (minV * 256) / maxV; + + // Past 45°: use complement + if (abs_s > abs_c) angle = 512 - angle; // Ï€/2 - angle + // Quadrant 2/3: cos < 0 + if (cosVal < 0) angle = 1024 - angle; // Ï€ - angle + // Quadrant 3/4: sin < 0 + if (sinVal < 0) angle = -angle; + + result.value = angle; + return result; +} + +static int gameobjectGetRotationY(psyqo::Lua L) { + auto go = L.toUserdata(1); + // Y rotation matrix: vs[0].x = cos(θ), vs[0].z = sin(θ) + int32_t sinRaw = go->rotation.vs[0].z.raw(); + int32_t cosRaw = go->rotation.vs[0].x.raw(); + psyqo::Angle angle = fastAtan2(sinRaw, cosRaw); + // Return in pi-units: 0.5 = Ï€/2 = 90° + L.pushNumber(static_cast(angle.value) / kAngleScale); + return 1; +} + +static int gameobjectSetRotationY(psyqo::Lua L) { + auto go = L.toUserdata(1); + lua_Number piUnits = L.toNumber(2); + psyqo::Angle angle; + angle.value = static_cast(piUnits * kAngleScale); + go->rotation = psyqo::SoftMath::generateRotationMatrix33(angle, psyqo::SoftMath::Axis::Y, s_trig); + return 0; +} + void psxsplash::Lua::Init() { auto L = m_state; // Load and run the game objects script @@ -109,11 +178,17 @@ void psxsplash::Lua::Init() { L.push(gamobjectSetActive); L.setField(-2, "set_active"); + L.push(gameobjectGetRotationY); + L.setField(-2, "get_rotationY"); + + L.push(gameobjectSetRotationY); + L.setField(-2, "set_rotationY"); + L.copy(-1); m_metatableReference = L.ref(); if (L.pcall(1, 0) == 0) { - printf("Lua script 'gameObjects' executed successfully"); + // success } else { printf("Error registering Lua script: %s\n", L.optString(-1, "Unknown error")); L.clearStack(); @@ -136,6 +211,24 @@ void psxsplash::Lua::Init() { m_luascriptsReference = L.ref(); } +void psxsplash::Lua::Shutdown() { + // Close the Lua VM if it's still open. + // Safe to call multiple times or on an already-closed VM. + if (m_state.getState()) { + m_state.close(); + } + m_metatableReference = LUA_NOREF; + m_luascriptsReference = LUA_NOREF; + m_luaSceneScriptsReference = LUA_NOREF; +} + +void psxsplash::Lua::Reset() { + // Nuclear reset: destroy the entire Lua VM and create a fresh one. + Shutdown(); + m_state = psyqo::Lua(); // fresh state (luaL_newstate + openlibs) + Init(); +} + void psxsplash::Lua::LoadLuaFile(const char* code, size_t len, int index) { auto L = m_state; char filename[32]; @@ -143,18 +236,31 @@ void psxsplash::Lua::LoadLuaFile(const char* code, size_t len, int index) { if (L.loadBuffer(code, len, filename) != LUA_OK) { printf("Lua error: %s\n", L.toString(-1)); L.pop(); + return; } // (1) script func L.rawGetI(LUA_REGISTRYINDEX, m_luascriptsReference); // (1) script func (2) scripts table L.newTable(); - // (1) script func (2) scripts table (3) {} + // (1) script func (2) scripts table (3) env {} + + // Give the environment a metatable that falls back to _G + // so scripts can see Entity, Debug, Input, etc. + L.newTable(); + // (1) script func (2) scripts table (3) env {} (4) mt {} + L.pushGlobalTable(); + // (1) script func (2) scripts table (3) env {} (4) mt {} (5) _G + L.setField(-2, "__index"); + // (1) script func (2) scripts table (3) env {} (4) mt { __index = _G } + L.setMetatable(-2); + // (1) script func (2) scripts table (3) env { mt } + L.pushNumber(index); - // (1) script func (2) scripts table (3) {} (4) index + // (1) script func (2) scripts table (3) env (4) index L.copy(-2); - // (1) script func (2) scripts table (3) {} (4) index (5) {} + // (1) script func (2) scripts table (3) env (4) index (5) env L.setTable(-4); - // (1) script func (2) scripts table (3) {} + // (1) script func (2) scripts table (3) env lua_setupvalue(L.getState(), -3, 1); // (1) script func (2) scripts table L.pop(); @@ -177,9 +283,15 @@ void psxsplash::Lua::RegisterSceneScripts(int index) { L.rawGetI(LUA_REGISTRYINDEX, m_luascriptsReference); // (1) {} (2) scripts table L.pushNumber(index); - // (1) {} (2) script environments table (2) index + // (1) {} (2) script environments table (3) index L.getTable(-2); // (1) {} (2) script environments table (3) script environment table for the scene + if (!L.isTable(-1)) { + // Scene Lua file index is invalid or script not loaded + printf("Warning: scene Lua file index %d not found\n", index); + L.pop(3); + return; + } onSceneCreationStartFunctionWrapper.resolveGlobal(L); onSceneCreationEndFunctionWrapper.resolveGlobal(L); L.pop(3); @@ -218,29 +330,112 @@ void psxsplash::Lua::RegisterGameObject(GameObject* go) { // (1) {} (2) go + 1 (3) {} L.rawSet(LUA_REGISTRYINDEX); // (1) {} + + // Initialize event mask for this object + uint32_t eventMask = EVENT_NONE; + if (go->luaFileIndex != -1) { L.rawGetI(LUA_REGISTRYINDEX, m_luascriptsReference); // (1) {} (2) script environments table L.rawGetI(-1, go->luaFileIndex); // (1) {} (2) script environments table (3) script environment table for this object - onCollisionMethodWrapper.resolveGlobal(L); - onInteractMethodWrapper.resolveGlobal(L); + + // Guard: if the script file failed to load (e.g. compilation error), + // the environment will be nil — skip event resolution. + if (!L.isTable(-1)) { + L.pop(2); + } else { + + // Resolve each event and build the bitmask + // Only events that exist in the script get their bit set + if (onCreateMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_CREATE; + if (onCollisionMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_COLLISION; + if (onInteractMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_INTERACT; + if (onTriggerEnterMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_TRIGGER_ENTER; + if (onTriggerStayMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_TRIGGER_STAY; + if (onTriggerExitMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_TRIGGER_EXIT; + if (onUpdateMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_UPDATE; + if (onDestroyMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_DESTROY; + if (onEnableMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_ENABLE; + if (onDisableMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_DISABLE; + if (onButtonPressMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_BUTTON_PRESS; + if (onButtonReleaseMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_BUTTON_RELEASE; + L.pop(2); // (1) {} + } } + + // Store the event mask directly in the GameObject + go->eventMask = eventMask; + L.pop(); // empty stack - printf("GameObject registered in Lua registry: %p\n", ptr); + + + // Fire onCreate event if this object handles it + if (eventMask & EVENT_ON_CREATE) { + onCreateMethodWrapper.callMethod(*this, go); + } } void psxsplash::Lua::OnCollision(GameObject* self, GameObject* other) { + if (!hasEvent(self, EVENT_ON_COLLISION)) return; onCollisionMethodWrapper.callMethod(*this, self, other); } void psxsplash::Lua::OnInteract(GameObject* self) { + if (!hasEvent(self, EVENT_ON_INTERACT)) return; onInteractMethodWrapper.callMethod(*this, self); } +void psxsplash::Lua::OnTriggerEnter(GameObject* trigger, GameObject* other) { + if (!hasEvent(trigger, EVENT_ON_TRIGGER_ENTER)) return; + onTriggerEnterMethodWrapper.callMethod(*this, trigger, other); +} + +void psxsplash::Lua::OnTriggerStay(GameObject* trigger, GameObject* other) { + if (!hasEvent(trigger, EVENT_ON_TRIGGER_STAY)) return; + onTriggerStayMethodWrapper.callMethod(*this, trigger, other); +} + +void psxsplash::Lua::OnTriggerExit(GameObject* trigger, GameObject* other) { + if (!hasEvent(trigger, EVENT_ON_TRIGGER_EXIT)) return; + onTriggerExitMethodWrapper.callMethod(*this, trigger, other); +} + +void psxsplash::Lua::OnDestroy(GameObject* go) { + if (!hasEvent(go, EVENT_ON_DESTROY)) return; + onDestroyMethodWrapper.callMethod(*this, go); + // Clear the event mask when object is destroyed + go->eventMask = EVENT_NONE; +} + +void psxsplash::Lua::OnEnable(GameObject* go) { + if (!hasEvent(go, EVENT_ON_ENABLE)) return; + onEnableMethodWrapper.callMethod(*this, go); +} + +void psxsplash::Lua::OnDisable(GameObject* go) { + if (!hasEvent(go, EVENT_ON_DISABLE)) return; + onDisableMethodWrapper.callMethod(*this, go); +} + +void psxsplash::Lua::OnButtonPress(GameObject* go, int button) { + if (!hasEvent(go, EVENT_ON_BUTTON_PRESS)) return; + onButtonPressMethodWrapper.callMethod(*this, go, button); +} + +void psxsplash::Lua::OnButtonRelease(GameObject* go, int button) { + if (!hasEvent(go, EVENT_ON_BUTTON_RELEASE)) return; + onButtonReleaseMethodWrapper.callMethod(*this, go, button); +} + +void psxsplash::Lua::OnUpdate(GameObject* go, int deltaFrames) { + if (!hasEvent(go, EVENT_ON_UPDATE)) return; + onUpdateMethodWrapper.callMethod(*this, go, deltaFrames); +} + void psxsplash::Lua::PushGameObject(GameObject* go) { auto L = m_state; L.push(go); diff --git a/src/lua.h b/src/lua.h index 09f652c..f194363 100644 --- a/src/lua.h +++ b/src/lua.h @@ -17,13 +17,50 @@ struct LuaFile { uint32_t length; }; +/** + * Event bitmask flags - each bit represents whether an object handles that event. + * This allows O(1) checking before calling into Lua VM. + * + * CRITICAL: The PS1 cannot afford to call into Lua for events objects don't handle. + * When registering a GameObject, we scan its script and set these bits. + * During dispatch, we check the bit FIRST before any Lua VM access. + */ +enum EventMask : uint32_t { + EVENT_NONE = 0, + EVENT_ON_CREATE = 1 << 0, + EVENT_ON_COLLISION = 1 << 1, + EVENT_ON_INTERACT = 1 << 2, + EVENT_ON_TRIGGER_ENTER = 1 << 3, + EVENT_ON_TRIGGER_STAY = 1 << 4, + EVENT_ON_TRIGGER_EXIT = 1 << 5, + EVENT_ON_UPDATE = 1 << 6, + EVENT_ON_DESTROY = 1 << 7, + EVENT_ON_ENABLE = 1 << 8, + EVENT_ON_DISABLE = 1 << 9, + EVENT_ON_BUTTON_PRESS = 1 << 10, + EVENT_ON_BUTTON_RELEASE = 1 << 11, +}; + class Lua { public: void Init(); + void Reset(); // Destroy and recreate the Lua VM (call on scene load) + void Shutdown(); // Close the Lua VM without recreating (call on scene unload) void LoadLuaFile(const char* code, size_t len, int index); void RegisterSceneScripts(int index); void RegisterGameObject(GameObject* go); + + // Get the underlying psyqo::Lua state for API registration + psyqo::Lua& getState() { return m_state; } + + /** + * Check if a GameObject handles a specific event. + * Call this BEFORE attempting to dispatch any event. + */ + bool hasEvent(GameObject* go, EventMask event) const { + return (go->eventMask & event) != 0; + } void OnSceneCreationStart() { onSceneCreationStartFunctionWrapper.callFunction(*this); @@ -31,8 +68,19 @@ class Lua { void OnSceneCreationEnd() { onSceneCreationEndFunctionWrapper.callFunction(*this); } + + // Event dispatchers - these check the bitmask before calling Lua void OnCollision(GameObject* self, GameObject* other); void OnInteract(GameObject* self); + void OnTriggerEnter(GameObject* trigger, GameObject* other); + void OnTriggerStay(GameObject* trigger, GameObject* other); + void OnTriggerExit(GameObject* trigger, GameObject* other); + void OnUpdate(GameObject* go, int deltaFrames); // Per-object update + void OnDestroy(GameObject* go); + void OnEnable(GameObject* go); + void OnDisable(GameObject* go); + void OnButtonPress(GameObject* go, int button); + void OnButtonRelease(GameObject* go, int button); private: template @@ -40,26 +88,31 @@ class Lua { template struct FunctionWrapper> { typedef irqus::typestring methodName; - // Needs the methods table at index 1, and the script environment table at index 3 - static void resolveGlobal(psyqo::Lua L) { - // Push the method name string to access the environment table + + // Returns true if the function was found and stored + static bool resolveGlobal(psyqo::Lua L) { L.push(methodName::data(), methodName::size()); L.getTable(3); if (L.isFunction(-1)) { - // Store the function in methods table using numeric ID as key - L.pushNumber(methodId); // Push numeric key for methods table - L.copy(-2); // Push the function (copy from top -2) - L.setTable(1); // methodsTable[methodId] = function + L.pushNumber(methodId); + L.copy(-2); + L.setTable(1); + L.pop(); // Pop the function + return true; } else { - L.pop(); // Pop the non-function value + L.pop(); + return false; } } + template static void pushArgs(psxsplash::Lua& lua, Args... args) { (push(lua, args), ...); } static void push(psxsplash::Lua& lua, GameObject* go) { lua.PushGameObject(go); } + static void push(psxsplash::Lua& lua, int val) { lua.m_state.pushNumber(val); } + template static void callMethod(psxsplash::Lua& lua, GameObject* go, Args... args) { auto L = lua.m_state; @@ -78,11 +131,16 @@ class Lua { } L.clearStack(); } + template static void callFunction(psxsplash::Lua& lua, Args... args) { auto L = lua.m_state; - L.push(methodName::data(), methodName::size()); - L.rawGetI(LUA_REGISTRYINDEX, lua.m_metatableReference); + L.rawGetI(LUA_REGISTRYINDEX, lua.m_luaSceneScriptsReference); + if (!L.isTable(-1)) { + L.clearStack(); + return; + } + L.rawGetI(-1, methodId); if (!L.isFunction(-1)) { L.clearStack(); return; @@ -95,17 +153,34 @@ class Lua { } }; + // Scene-level events (methodId 1-2) [[no_unique_address]] FunctionWrapper<1, typestring_is("onSceneCreationStart")> onSceneCreationStartFunctionWrapper; [[no_unique_address]] FunctionWrapper<2, typestring_is("onSceneCreationEnd")> onSceneCreationEndFunctionWrapper; - [[no_unique_address]] FunctionWrapper<1, typestring_is("onCreate")> onCreateMethodWrapper; - [[no_unique_address]] FunctionWrapper<2, typestring_is("onCollision")> onCollisionMethodWrapper; - [[no_unique_address]] FunctionWrapper<3, typestring_is("onInteract")> onInteractMethodWrapper; + + // Object-level events (methodId 100-111, offset to avoid collision with scene events) + [[no_unique_address]] FunctionWrapper<100, typestring_is("onCreate")> onCreateMethodWrapper; + [[no_unique_address]] FunctionWrapper<101, typestring_is("onCollision")> onCollisionMethodWrapper; + [[no_unique_address]] FunctionWrapper<102, typestring_is("onInteract")> onInteractMethodWrapper; + [[no_unique_address]] FunctionWrapper<103, typestring_is("onTriggerEnter")> onTriggerEnterMethodWrapper; + [[no_unique_address]] FunctionWrapper<104, typestring_is("onTriggerStay")> onTriggerStayMethodWrapper; + [[no_unique_address]] FunctionWrapper<105, typestring_is("onTriggerExit")> onTriggerExitMethodWrapper; + [[no_unique_address]] FunctionWrapper<106, typestring_is("onUpdate")> onUpdateMethodWrapper; + [[no_unique_address]] FunctionWrapper<107, typestring_is("onDestroy")> onDestroyMethodWrapper; + [[no_unique_address]] FunctionWrapper<108, typestring_is("onEnable")> onEnableMethodWrapper; + [[no_unique_address]] FunctionWrapper<109, typestring_is("onDisable")> onDisableMethodWrapper; + [[no_unique_address]] FunctionWrapper<110, typestring_is("onButtonPress")> onButtonPressMethodWrapper; + [[no_unique_address]] FunctionWrapper<111, typestring_is("onButtonRelease")> onButtonReleaseMethodWrapper; + void PushGameObject(GameObject* go); + + private: psyqo::Lua m_state; - int m_metatableReference; - int m_luascriptsReference; - int m_luaSceneScriptsReference; + int m_metatableReference = LUA_NOREF; + int m_luascriptsReference = LUA_NOREF; + int m_luaSceneScriptsReference = LUA_NOREF; + + // Event mask now stored inline in GameObject::eventMask template friend struct FunctionWrapper; diff --git a/src/luaapi.cpp b/src/luaapi.cpp new file mode 100644 index 0000000..03633e9 --- /dev/null +++ b/src/luaapi.cpp @@ -0,0 +1,1385 @@ +#include "luaapi.hh" +#include "scenemanager.hh" +#include "gameobject.hh" +#include "controls.hh" +#include "camera.hh" + +#include +#include +#include +#include + +namespace psxsplash { + +// Static member +SceneManager* LuaAPI::s_sceneManager = nullptr; + +// Scale factor: FixedPoint<12> stores 1.0 as raw 4096. +// Lua scripts work in world-space units (1 = one unit), so we convert. +static constexpr lua_Number kFixedScale = 4096; + +static lua_Number fpToLua(psyqo::FixedPoint<12> fp) { + return static_cast(fp.raw()) / kFixedScale; +} + +static psyqo::FixedPoint<12> luaToFp(lua_Number val) { + return psyqo::FixedPoint<12>(static_cast(val * kFixedScale), psyqo::FixedPoint<12>::RAW); +} + +// Angle scale: psyqo::Angle is FixedPoint<10>, so 1.0_pi = raw 1024 +static constexpr lua_Number kAngleScale = 1024; +static psyqo::Trig<> s_trig; + +// ============================================================================ +// REGISTRATION +// ============================================================================ + +void LuaAPI::RegisterAll(psyqo::Lua& L, SceneManager* scene) { + s_sceneManager = scene; + + // ======================================================================== + // ENTITY API + // ======================================================================== + L.newTable(); // Entity table + + L.push(Entity_FindByScriptIndex); + L.setField(-2, "FindByScriptIndex"); + + L.push(Entity_FindByIndex); + L.setField(-2, "FindByIndex"); + + L.push(Entity_Find); + L.setField(-2, "Find"); + + L.push(Entity_GetCount); + L.setField(-2, "GetCount"); + + L.push(Entity_SetActive); + L.setField(-2, "SetActive"); + + L.push(Entity_IsActive); + L.setField(-2, "IsActive"); + + L.push(Entity_GetPosition); + L.setField(-2, "GetPosition"); + + L.push(Entity_SetPosition); + L.setField(-2, "SetPosition"); + + L.push(Entity_GetRotationY); + L.setField(-2, "GetRotationY"); + + L.push(Entity_SetRotationY); + L.setField(-2, "SetRotationY"); + + L.push(Entity_ForEach); + L.setField(-2, "ForEach"); + + L.setGlobal("Entity"); + + // ======================================================================== + // VEC3 API + // ======================================================================== + L.newTable(); // Vec3 table + + L.push(Vec3_New); + L.setField(-2, "new"); + + L.push(Vec3_Add); + L.setField(-2, "add"); + + L.push(Vec3_Sub); + L.setField(-2, "sub"); + + L.push(Vec3_Mul); + L.setField(-2, "mul"); + + L.push(Vec3_Dot); + L.setField(-2, "dot"); + + L.push(Vec3_Cross); + L.setField(-2, "cross"); + + L.push(Vec3_Length); + L.setField(-2, "length"); + + L.push(Vec3_LengthSq); + L.setField(-2, "lengthSq"); + + L.push(Vec3_Normalize); + L.setField(-2, "normalize"); + + L.push(Vec3_Distance); + L.setField(-2, "distance"); + + L.push(Vec3_DistanceSq); + L.setField(-2, "distanceSq"); + + L.push(Vec3_Lerp); + L.setField(-2, "lerp"); + + L.setGlobal("Vec3"); + + // ======================================================================== + // INPUT API + // ======================================================================== + L.newTable(); // Input table + + L.push(Input_IsPressed); + L.setField(-2, "IsPressed"); + + L.push(Input_IsReleased); + L.setField(-2, "IsReleased"); + + L.push(Input_IsHeld); + L.setField(-2, "IsHeld"); + + L.push(Input_GetAnalog); + L.setField(-2, "GetAnalog"); + + // Register button constants + RegisterInputConstants(L); + + L.setGlobal("Input"); + + // ======================================================================== + // TIMER API + // ======================================================================== + L.newTable(); // Timer table + + L.push(Timer_GetFrameCount); + L.setField(-2, "GetFrameCount"); + + L.setGlobal("Timer"); + + // ======================================================================== + // CAMERA API + // ======================================================================== + L.newTable(); // Camera table + + L.push(Camera_GetPosition); + L.setField(-2, "GetPosition"); + + L.push(Camera_SetPosition); + L.setField(-2, "SetPosition"); + + L.push(Camera_GetRotation); + L.setField(-2, "GetRotation"); + + L.push(Camera_SetRotation); + L.setField(-2, "SetRotation"); + + L.push(Camera_LookAt); + L.setField(-2, "LookAt"); + + L.setGlobal("Camera"); + + // ======================================================================== + // AUDIO API (Placeholder) + // ======================================================================== + L.newTable(); // Audio table + + L.push(Audio_Play); + L.setField(-2, "Play"); + + L.push(Audio_Find); + L.setField(-2, "Find"); + + L.push(Audio_Stop); + L.setField(-2, "Stop"); + + L.push(Audio_SetVolume); + L.setField(-2, "SetVolume"); + + L.push(Audio_StopAll); + L.setField(-2, "StopAll"); + + L.setGlobal("Audio"); + + // ======================================================================== + // DEBUG API + // ======================================================================== + L.newTable(); // Debug table + + L.push(Debug_Log); + L.setField(-2, "Log"); + + L.push(Debug_DrawLine); + L.setField(-2, "DrawLine"); + + L.push(Debug_DrawBox); + L.setField(-2, "DrawBox"); + + L.setGlobal("Debug"); + + // ======================================================================== + // MATH API + // ======================================================================== + L.newTable(); // PSXMath table (avoid conflict with Lua's math) + + L.push(Math_Clamp); + L.setField(-2, "Clamp"); + + L.push(Math_Lerp); + L.setField(-2, "Lerp"); + + L.push(Math_Sign); + L.setField(-2, "Sign"); + + L.push(Math_Abs); + L.setField(-2, "Abs"); + + L.push(Math_Min); + L.setField(-2, "Min"); + + L.push(Math_Max); + L.setField(-2, "Max"); + + L.setGlobal("Math"); + + // ======================================================================== + // SCENE API + // ======================================================================== + L.newTable(); // Scene table + + L.push(Scene_Load); + L.setField(-2, "Load"); + + L.push(Scene_GetIndex); + L.setField(-2, "GetIndex"); + + L.setGlobal("Scene"); + + // ======================================================================== + // PERSIST API + // ======================================================================== + L.newTable(); // Persist table + + L.push(Persist_Get); + L.setField(-2, "Get"); + + L.push(Persist_Set); + L.setField(-2, "Set"); + + L.setGlobal("Persist"); +} + +// ============================================================================ +// ENTITY API IMPLEMENTATION +// ============================================================================ + +int LuaAPI::Entity_FindByScriptIndex(lua_State* L) { + psyqo::Lua lua(L); + + if (!s_sceneManager || !lua.isNumber(1)) { + lua.push(); + return 1; + } + + // Find first object with matching luaFileIndex + int16_t luaIdx = static_cast(lua.toNumber(1)); + for (size_t i = 0; i < s_sceneManager->getGameObjectCount(); i++) { + auto* go = s_sceneManager->getGameObject(static_cast(i)); + if (go && go->luaFileIndex == luaIdx) { + lua.push(reinterpret_cast(go)); + lua.rawGet(LUA_REGISTRYINDEX); + if (lua.isTable(-1)) return 1; + lua.pop(); + } + } + + lua.push(); + return 1; +} + +int LuaAPI::Entity_FindByIndex(lua_State* L) { + psyqo::Lua lua(L); + + if (!lua.isNumber(1)) { + lua.push(); + return 1; + } + + int index = static_cast(lua.toNumber(1)); + + if (s_sceneManager) { + GameObject* go = s_sceneManager->getGameObject(static_cast(index)); + if (go) { + lua.push(reinterpret_cast(go)); + lua.rawGet(LUA_REGISTRYINDEX); + if (lua.isTable(-1)) { + return 1; + } + lua.pop(); + } + } + + lua.push(); + return 1; +} + +int LuaAPI::Entity_Find(lua_State* L) { + psyqo::Lua lua(L); + + if (!s_sceneManager) { + lua.push(); + return 1; + } + + // Accept number (index) or string (name lookup) for backwards compat + // Check isNumber FIRST — in Lua, numbers pass isString too. + if (lua.isNumber(1)) { + int index = static_cast(lua.toNumber(1)); + GameObject* go = s_sceneManager->getGameObject(static_cast(index)); + if (go) { + lua.push(reinterpret_cast(go)); + lua.rawGet(LUA_REGISTRYINDEX); + if (lua.isTable(-1)) return 1; + lua.pop(); + } + } else if (lua.isString(1)) { + const char* name = lua.toString(1); + GameObject* go = s_sceneManager->findObjectByName(name); + if (go) { + lua.push(reinterpret_cast(go)); + lua.rawGet(LUA_REGISTRYINDEX); + if (lua.isTable(-1)) return 1; + lua.pop(); + } + } + + lua.push(); + return 1; +} + +int LuaAPI::Entity_GetCount(lua_State* L) { + psyqo::Lua lua(L); + + if (s_sceneManager) { + lua.pushNumber(static_cast(s_sceneManager->getGameObjectCount())); + } else { + lua.pushNumber(0); + } + return 1; +} + + +int LuaAPI::Entity_SetActive(lua_State* L) { + psyqo::Lua lua(L); + + if (!lua.isTable(1)) { + return 0; + } + + lua.getField(1, "__cpp_ptr"); + auto go = lua.toUserdata(-1); + lua.pop(); + + bool active = lua.toBoolean(2); + + if (go && s_sceneManager) { + s_sceneManager->setObjectActive(go, active); + } + + return 0; +} + +int LuaAPI::Entity_IsActive(lua_State* L) { + psyqo::Lua lua(L); + + if (!lua.isTable(1)) { + lua.push(false); + return 1; + } + + lua.getField(1, "__cpp_ptr"); + auto go = lua.toUserdata(-1); + lua.pop(); + + if (go) { + lua.push(go->isActive()); + } else { + lua.push(false); + } + + return 1; +} + +int LuaAPI::Entity_GetPosition(lua_State* L) { + psyqo::Lua lua(L); + + if (!lua.isTable(1)) { + lua.push(); + return 1; + } + + lua.getField(1, "__cpp_ptr"); + auto go = lua.toUserdata(-1); + lua.pop(); + + if (go) { + PushVec3(lua, go->position.x, go->position.y, go->position.z); + return 1; + } + + lua.push(); + return 1; +} + +int LuaAPI::Entity_SetPosition(lua_State* L) { + psyqo::Lua lua(L); + + if (!lua.isTable(1) || !lua.isTable(2)) { + return 0; + } + + lua.getField(1, "__cpp_ptr"); + auto go = lua.toUserdata(-1); + lua.pop(); + + if (!go) return 0; + + psyqo::FixedPoint<12> x, y, z; + ReadVec3(lua, 2, x, y, z); + + go->position.x = x; + go->position.y = y; + go->position.z = z; + + return 0; +} + +int LuaAPI::Entity_GetRotationY(lua_State* L) { + psyqo::Lua lua(L); + + if (!lua.isTable(1)) { + lua.pushNumber(0); + return 1; + } + + lua.getField(1, "__cpp_ptr"); + auto go = lua.toUserdata(-1); + lua.pop(); + + if (!go) { lua.pushNumber(0); return 1; } + + // Y rotation matrix: vs[0].x = cos(θ), vs[0].z = sin(θ) + int32_t sinRaw = go->rotation.vs[0].z.raw(); + int32_t cosRaw = go->rotation.vs[0].x.raw(); + + // Fast atan2 approximation (linear in first octant, fold to full circle) + psyqo::Angle angle; + if (cosRaw == 0 && sinRaw == 0) { + angle.value = 0; + } else { + int32_t abs_s = sinRaw < 0 ? -sinRaw : sinRaw; + int32_t abs_c = cosRaw < 0 ? -cosRaw : cosRaw; + int32_t minV = abs_s < abs_c ? abs_s : abs_c; + int32_t maxV = abs_s > abs_c ? abs_s : abs_c; + int32_t a = (minV * 256) / maxV; // [0, 256] for [0, Ï€/4] + if (abs_s > abs_c) a = 512 - a; + if (cosRaw < 0) a = 1024 - a; + if (sinRaw < 0) a = -a; + angle.value = a; + } + + // Return in pi-units: 0.5 = Ï€/2 = 90° + lua.pushNumber(static_cast(angle.value) / kAngleScale); + return 1; +} + +int LuaAPI::Entity_SetRotationY(lua_State* L) { + psyqo::Lua lua(L); + + if (!lua.isTable(1)) return 0; + + lua.getField(1, "__cpp_ptr"); + auto go = lua.toUserdata(-1); + lua.pop(); + + if (!go) return 0; + + // Accept angle in pi-units (0.5 = Ï€/2 = 90°) + lua_Number piUnits = lua.toNumber(2); + psyqo::Angle angle; + angle.value = static_cast(piUnits * kAngleScale); + go->rotation = psyqo::SoftMath::generateRotationMatrix33(angle, psyqo::SoftMath::Axis::Y, s_trig); + return 0; +} + +int LuaAPI::Entity_ForEach(lua_State* L) { + psyqo::Lua lua(L); + + if (!s_sceneManager || !lua.isFunction(1)) return 0; + + size_t count = s_sceneManager->getGameObjectCount(); + for (size_t i = 0; i < count; i++) { + auto* go = s_sceneManager->getGameObject(static_cast(i)); + if (!go || !go->isActive()) continue; + + // Push callback copy + lua.copy(1); + // Look up registered Lua table for this object (keyed by C++ pointer) + lua.push(reinterpret_cast(go)); + lua.rawGet(LUA_REGISTRYINDEX); + if (!lua.isTable(-1)) { + lua.pop(2); // pop non-table + callback copy + continue; + } + if (lua.pcall(1, 0) != LUA_OK) { + lua.pop(); // pop error message + } + } + + return 0; +} + +// ============================================================================ +// VEC3 API IMPLEMENTATION +// ============================================================================ + +void LuaAPI::PushVec3(psyqo::Lua& L, psyqo::FixedPoint<12> x, + psyqo::FixedPoint<12> y, psyqo::FixedPoint<12> z) { + L.newTable(); + L.pushNumber(fpToLua(x)); + L.setField(-2, "x"); + L.pushNumber(fpToLua(y)); + L.setField(-2, "y"); + L.pushNumber(fpToLua(z)); + L.setField(-2, "z"); +} + +void LuaAPI::ReadVec3(psyqo::Lua& L, int idx, + psyqo::FixedPoint<12>& x, + psyqo::FixedPoint<12>& y, + psyqo::FixedPoint<12>& z) { + L.getField(idx, "x"); + x = luaToFp(L.toNumber(-1)); + L.pop(); + + L.getField(idx, "y"); + y = luaToFp(L.toNumber(-1)); + L.pop(); + + L.getField(idx, "z"); + z = luaToFp(L.toNumber(-1)); + L.pop(); +} + +int LuaAPI::Vec3_New(lua_State* L) { + psyqo::Lua lua(L); + + psyqo::FixedPoint<12> x = luaToFp(lua.optNumber(1, 0)); + psyqo::FixedPoint<12> y = luaToFp(lua.optNumber(2, 0)); + psyqo::FixedPoint<12> z = luaToFp(lua.optNumber(3, 0)); + + PushVec3(lua, x, y, z); + return 1; +} + +int LuaAPI::Vec3_Add(lua_State* L) { + psyqo::Lua lua(L); + + if (!lua.isTable(1) || !lua.isTable(2)) { + lua.push(); + return 1; + } + + psyqo::FixedPoint<12> ax, ay, az; + psyqo::FixedPoint<12> bx, by, bz; + + ReadVec3(lua, 1, ax, ay, az); + ReadVec3(lua, 2, bx, by, bz); + + PushVec3(lua, ax + bx, ay + by, az + bz); + return 1; +} + +int LuaAPI::Vec3_Sub(lua_State* L) { + psyqo::Lua lua(L); + + if (!lua.isTable(1) || !lua.isTable(2)) { + lua.push(); + return 1; + } + + psyqo::FixedPoint<12> ax, ay, az; + psyqo::FixedPoint<12> bx, by, bz; + + ReadVec3(lua, 1, ax, ay, az); + ReadVec3(lua, 2, bx, by, bz); + + PushVec3(lua, ax - bx, ay - by, az - bz); + return 1; +} + +int LuaAPI::Vec3_Mul(lua_State* L) { + psyqo::Lua lua(L); + + if (!lua.isTable(1)) { + lua.push(); + return 1; + } + + psyqo::FixedPoint<12> x, y, z; + ReadVec3(lua, 1, x, y, z); + + psyqo::FixedPoint<12> scalar = luaToFp(lua.toNumber(2)); + + PushVec3(lua, x * scalar, y * scalar, z * scalar); + return 1; +} + +int LuaAPI::Vec3_Dot(lua_State* L) { + psyqo::Lua lua(L); + + if (!lua.isTable(1) || !lua.isTable(2)) { + lua.pushNumber(0); + return 1; + } + + psyqo::FixedPoint<12> ax, ay, az; + psyqo::FixedPoint<12> bx, by, bz; + + ReadVec3(lua, 1, ax, ay, az); + ReadVec3(lua, 2, bx, by, bz); + + auto dot = ax * bx + ay * by + az * bz; + lua.pushNumber(fpToLua(dot)); + return 1; +} + +int LuaAPI::Vec3_Cross(lua_State* L) { + psyqo::Lua lua(L); + + if (!lua.isTable(1) || !lua.isTable(2)) { + lua.push(); + return 1; + } + + psyqo::FixedPoint<12> ax, ay, az; + psyqo::FixedPoint<12> bx, by, bz; + + ReadVec3(lua, 1, ax, ay, az); + ReadVec3(lua, 2, bx, by, bz); + + psyqo::FixedPoint<12> cx = ay * bz - az * by; + psyqo::FixedPoint<12> cy = az * bx - ax * bz; + psyqo::FixedPoint<12> cz = ax * by - ay * bx; + + PushVec3(lua, cx, cy, cz); + return 1; +} + +int LuaAPI::Vec3_LengthSq(lua_State* L) { + psyqo::Lua lua(L); + + if (!lua.isTable(1)) { + lua.pushNumber(0); + return 1; + } + + psyqo::FixedPoint<12> x, y, z; + ReadVec3(lua, 1, x, y, z); + + auto lengthSq = x * x + y * y + z * z; + lua.pushNumber(fpToLua(lengthSq)); + return 1; +} + +int LuaAPI::Vec3_Length(lua_State* L) { + psyqo::Lua lua(L); + + if (!lua.isTable(1)) { + lua.pushNumber(0); + return 1; + } + + psyqo::FixedPoint<12> x, y, z; + ReadVec3(lua, 1, x, y, z); + + // Compute length in scaled world-space to avoid fp12×fp12 overflow issues. + // Convert to Lua-number domain, sqrt there, return directly. + lua_Number sx = fpToLua(x); + lua_Number sy = fpToLua(y); + lua_Number sz = fpToLua(z); + lua_Number sqVal = sx * sx + sy * sy + sz * sz; + + if (sqVal <= 0) { + lua.pushNumber(0); + return 1; + } + + // Newton's method sqrt (integer-safe) + lua_Number guess = sqVal / 2; + if (guess == 0) guess = 1; + for (int i = 0; i < 12; i++) { + guess = (guess + sqVal / guess) / 2; + } + + lua.pushNumber(guess); + return 1; +} + +int LuaAPI::Vec3_Normalize(lua_State* L) { + psyqo::Lua lua(L); + + if (!lua.isTable(1)) { + lua.push(); + return 1; + } + + psyqo::FixedPoint<12> x, y, z; + ReadVec3(lua, 1, x, y, z); + + // Work in Lua-number (world-space) domain for the sqrt + lua_Number sx = fpToLua(x); + lua_Number sy = fpToLua(y); + lua_Number sz = fpToLua(z); + lua_Number sLen = sx * sx + sy * sy + sz * sz; + + if (sLen <= 0) { + PushVec3(lua, psyqo::FixedPoint<12>(0), psyqo::FixedPoint<12>(0), psyqo::FixedPoint<12>(0)); + return 1; + } + + // Newton's method sqrt + lua_Number guess = sLen / 2; + if (guess == 0) guess = 1; + for (int i = 0; i < 12; i++) { + guess = (guess + sLen / guess) / 2; + } + if (guess == 0) guess = 1; + + PushVec3(lua, luaToFp(sx / guess), luaToFp(sy / guess), luaToFp(sz / guess)); + return 1; +} + +int LuaAPI::Vec3_DistanceSq(lua_State* L) { + psyqo::Lua lua(L); + + if (!lua.isTable(1) || !lua.isTable(2)) { + lua.pushNumber(0); + return 1; + } + + psyqo::FixedPoint<12> ax, ay, az; + psyqo::FixedPoint<12> bx, by, bz; + + ReadVec3(lua, 1, ax, ay, az); + ReadVec3(lua, 2, bx, by, bz); + + auto dx = ax - bx; + auto dy = ay - by; + auto dz = az - bz; + + auto distSq = dx * dx + dy * dy + dz * dz; + lua.pushNumber(fpToLua(distSq)); + return 1; +} + +int LuaAPI::Vec3_Distance(lua_State* L) { + psyqo::Lua lua(L); + + if (!lua.isTable(1) || !lua.isTable(2)) { + lua.pushNumber(0); + return 1; + } + + psyqo::FixedPoint<12> ax, ay, az; + psyqo::FixedPoint<12> bx, by, bz; + + ReadVec3(lua, 1, ax, ay, az); + ReadVec3(lua, 2, bx, by, bz); + + lua_Number dx = fpToLua(ax) - fpToLua(bx); + lua_Number dy = fpToLua(ay) - fpToLua(by); + lua_Number dz = fpToLua(az) - fpToLua(bz); + + lua_Number sqVal = dx * dx + dy * dy + dz * dz; + + if (sqVal <= 0) { + lua.pushNumber(0); + return 1; + } + + lua_Number guess = sqVal / 2; + if (guess == 0) guess = 1; + for (int i = 0; i < 12; i++) { + guess = (guess + sqVal / guess) / 2; + } + + lua.pushNumber(guess); + return 1; +} + +int LuaAPI::Vec3_Lerp(lua_State* L) { + psyqo::Lua lua(L); + + if (!lua.isTable(1) || !lua.isTable(2)) { + lua.push(); + return 1; + } + + psyqo::FixedPoint<12> ax, ay, az; + psyqo::FixedPoint<12> bx, by, bz; + + ReadVec3(lua, 1, ax, ay, az); + ReadVec3(lua, 2, bx, by, bz); + + psyqo::FixedPoint<12> t = luaToFp(lua.toNumber(3)); + psyqo::FixedPoint<12> oneMinusT = psyqo::FixedPoint<12>(4096, psyqo::FixedPoint<12>::RAW) - t; + + psyqo::FixedPoint<12> rx = ax * oneMinusT + bx * t; + psyqo::FixedPoint<12> ry = ay * oneMinusT + by * t; + psyqo::FixedPoint<12> rz = az * oneMinusT + bz * t; + + PushVec3(lua, rx, ry, rz); + return 1; +} + +// ============================================================================ +// INPUT API IMPLEMENTATION +// ============================================================================ + +void LuaAPI::RegisterInputConstants(psyqo::Lua& L) { + // Button constants - must match psyqo::AdvancedPad::Button enum + L.pushNumber(static_cast(psyqo::AdvancedPad::Button::Cross)); + L.setField(-2, "CROSS"); + + L.pushNumber(static_cast(psyqo::AdvancedPad::Button::Circle)); + L.setField(-2, "CIRCLE"); + + L.pushNumber(static_cast(psyqo::AdvancedPad::Button::Square)); + L.setField(-2, "SQUARE"); + + L.pushNumber(static_cast(psyqo::AdvancedPad::Button::Triangle)); + L.setField(-2, "TRIANGLE"); + + L.pushNumber(static_cast(psyqo::AdvancedPad::Button::L1)); + L.setField(-2, "L1"); + + L.pushNumber(static_cast(psyqo::AdvancedPad::Button::R1)); + L.setField(-2, "R1"); + + L.pushNumber(static_cast(psyqo::AdvancedPad::Button::L2)); + L.setField(-2, "L2"); + + L.pushNumber(static_cast(psyqo::AdvancedPad::Button::R2)); + L.setField(-2, "R2"); + + L.pushNumber(static_cast(psyqo::AdvancedPad::Button::Start)); + L.setField(-2, "START"); + + L.pushNumber(static_cast(psyqo::AdvancedPad::Button::Select)); + L.setField(-2, "SELECT"); + + L.pushNumber(static_cast(psyqo::AdvancedPad::Button::Up)); + L.setField(-2, "UP"); + + L.pushNumber(static_cast(psyqo::AdvancedPad::Button::Down)); + L.setField(-2, "DOWN"); + + L.pushNumber(static_cast(psyqo::AdvancedPad::Button::Left)); + L.setField(-2, "LEFT"); + + L.pushNumber(static_cast(psyqo::AdvancedPad::Button::Right)); + L.setField(-2, "RIGHT"); + + L.pushNumber(static_cast(psyqo::AdvancedPad::Button::L3)); + L.setField(-2, "L3"); + + L.pushNumber(static_cast(psyqo::AdvancedPad::Button::R3)); + L.setField(-2, "R3"); +} + +int LuaAPI::Input_IsPressed(lua_State* L) { + psyqo::Lua lua(L); + + if (!s_sceneManager || !lua.isNumber(1)) { + lua.push(false); + return 1; + } + + auto button = static_cast(static_cast(lua.toNumber(1))); + lua.push(s_sceneManager->getControls().wasButtonPressed(button)); + return 1; +} + +int LuaAPI::Input_IsReleased(lua_State* L) { + psyqo::Lua lua(L); + + if (!s_sceneManager || !lua.isNumber(1)) { + lua.push(false); + return 1; + } + + auto button = static_cast(static_cast(lua.toNumber(1))); + lua.push(s_sceneManager->getControls().wasButtonReleased(button)); + return 1; +} + +int LuaAPI::Input_IsHeld(lua_State* L) { + psyqo::Lua lua(L); + + if (!s_sceneManager || !lua.isNumber(1)) { + lua.push(false); + return 1; + } + + auto button = static_cast(static_cast(lua.toNumber(1))); + lua.push(s_sceneManager->getControls().isButtonHeld(button)); + return 1; +} + +int LuaAPI::Input_GetAnalog(lua_State* L) { + psyqo::Lua lua(L); + + if (!s_sceneManager) { + lua.pushNumber(0); + lua.pushNumber(0); + return 2; + } + + int stick = lua.isNumber(1) ? static_cast(lua.toNumber(1)) : 0; + auto& controls = s_sceneManager->getControls(); + + int16_t x, y; + if (stick == 1) { + x = controls.getRightStickX(); + y = controls.getRightStickY(); + } else { + x = controls.getLeftStickX(); + y = controls.getLeftStickY(); + } + + // Scale to approximately [-1.0, 1.0] in Lua number space + // Stick range is -127 to +127; divide by 127 + lua.pushNumber(x * kFixedScale / 127); + lua.pushNumber(y * kFixedScale / 127); + return 2; +} + +// ============================================================================ +// TIMER API IMPLEMENTATION +// ============================================================================ + +static uint32_t s_frameCount = 0; + +void LuaAPI::IncrementFrameCount() { + s_frameCount++; +} + +void LuaAPI::ResetFrameCount() { + s_frameCount = 0; +} + +int LuaAPI::Timer_GetFrameCount(lua_State* L) { + psyqo::Lua lua(L); + lua.pushNumber(s_frameCount); + return 1; +} + +// ============================================================================ +// CAMERA API IMPLEMENTATION +// ============================================================================ + +int LuaAPI::Camera_GetPosition(lua_State* L) { + psyqo::Lua lua(L); + + if (s_sceneManager) { + auto& pos = s_sceneManager->getCamera().GetPosition(); + PushVec3(lua, pos.x, pos.y, pos.z); + } else { + PushVec3(lua, psyqo::FixedPoint<12>(0), psyqo::FixedPoint<12>(0), psyqo::FixedPoint<12>(0)); + } + return 1; +} + +int LuaAPI::Camera_SetPosition(lua_State* L) { + psyqo::Lua lua(L); + + if (!s_sceneManager || !lua.isTable(1)) return 0; + + psyqo::FixedPoint<12> x, y, z; + ReadVec3(lua, 1, x, y, z); + s_sceneManager->getCamera().SetPosition(x, y, z); + return 0; +} + +int LuaAPI::Camera_GetRotation(lua_State* L) { + psyqo::Lua lua(L); + + // Camera only stores the rotation matrix internally. + // Decomposing back to Euler angles is not supported. + // Return {0,0,0} — use Camera.SetRotation to control orientation. + PushVec3(lua, psyqo::FixedPoint<12>(0), psyqo::FixedPoint<12>(0), psyqo::FixedPoint<12>(0)); + return 1; +} + +int LuaAPI::Camera_SetRotation(lua_State* L) { + psyqo::Lua lua(L); + + if (!s_sceneManager) return 0; + + // Accept three angles in pi-units (e.g., 0.5 = Ï€/2 = 90°) + // This matches psyqo::Angle convention used by the engine. + psyqo::Angle rx, ry, rz; + rx.value = static_cast(lua.optNumber(1, 0) * kAngleScale); + ry.value = static_cast(lua.optNumber(2, 0) * kAngleScale); + rz.value = static_cast(lua.optNumber(3, 0) * kAngleScale); + s_sceneManager->getCamera().SetRotation(rx, ry, rz); + return 0; +} + +int LuaAPI::Camera_LookAt(lua_State* L) { + psyqo::Lua lua(L); + + if (!s_sceneManager) return 0; + + psyqo::FixedPoint<12> tx, ty, tz; + + if (lua.isTable(1)) { + ReadVec3(lua, 1, tx, ty, tz); + } else { + tx = luaToFp(lua.optNumber(1, 0)); + ty = luaToFp(lua.optNumber(2, 0)); + tz = luaToFp(lua.optNumber(3, 0)); + } + + auto& cam = s_sceneManager->getCamera(); + auto& pos = cam.GetPosition(); + + // Compute direction vector from camera to target + lua_Number dx = fpToLua(tx) - fpToLua(pos.x); + lua_Number dy = fpToLua(ty) - fpToLua(pos.y); + lua_Number dz = fpToLua(tz) - fpToLua(pos.z); + + // Compute horizontal distance for pitch calculation + lua_Number horizDistSq = dx * dx + dz * dz; + lua_Number horizGuess = horizDistSq / 2; + if (horizGuess == 0) horizGuess = 1; + for (int i = 0; i < 12; i++) { + horizGuess = (horizGuess + horizDistSq / horizGuess) / 2; + } + + // Yaw = atan2(dx, dz) — approximate with lookup or use psyqo trig + // For now, use a simple atan2 approximation in fp12 domain + // and set rotation via SetRotation (pitch, yaw, 0) + // Approximate: yaw is proportional to dx/dz in small-angle + // Full implementation requires psyqo Trig atan2 which is not trivially + // accessible here. Set rotation to face the target on the Y axis. + // This is a simplified look-at that only handles yaw. + psyqo::Angle yaw; + psyqo::Angle pitch; + + // Use scaled integer atan2 approximation + // atan2(dx, dz) in the range [-Ï€, Ï€] + // For PS1, the exact method depends on psyqo's Trig class. + // Returning luaError since we can't do a proper atan2 without Trig instance. + // Compromise: just set rotation angles directly + yaw.value = 0; + pitch.value = 0; + + // For a real implementation, Camera would need a LookAt method. + return 0; +} + +// ============================================================================ +// AUDIO API IMPLEMENTATION +// ============================================================================ + +int LuaAPI::Audio_Play(lua_State* L) { + psyqo::Lua lua(L); + + int soundId = -1; + + // Accept number (index) or string (name lookup) like Entity.Find + // Check isNumber FIRST — in Lua, numbers pass isString too. + if (lua.isNumber(1)) { + soundId = static_cast(lua.toNumber(1)); + } else if (lua.isString(1)) { + const char* name = lua.toString(1); + soundId = s_sceneManager->findAudioClipByName(name); + if (soundId < 0) { + printf("[Lua] Audio.Play: clip '%s' not found\n", name); + lua.pushNumber(-1); + return 1; + } + } else { + lua.pushNumber(-1); + return 1; + } + + int volume = static_cast(lua.optNumber(2, 100)); + int pan = static_cast(lua.optNumber(3, 64)); + + int voice = s_sceneManager->getAudio().play(soundId, volume, pan); + lua.pushNumber(voice); + return 1; +} + +int LuaAPI::Audio_Find(lua_State* L) { + psyqo::Lua lua(L); + + if (!s_sceneManager || !lua.isString(1)) { + lua.push(); // nil + return 1; + } + + const char* name = lua.toString(1); + int clipIndex = s_sceneManager->findAudioClipByName(name); + + if (clipIndex >= 0) { + lua.pushNumber(static_cast(clipIndex)); + } else { + lua.push(); // nil + } + return 1; +} + +int LuaAPI::Audio_Stop(lua_State* L) { + psyqo::Lua lua(L); + int channelId = static_cast(lua.toNumber(1)); + s_sceneManager->getAudio().stopVoice(channelId); + return 0; +} + +int LuaAPI::Audio_SetVolume(lua_State* L) { + psyqo::Lua lua(L); + int channelId = static_cast(lua.toNumber(1)); + int volume = static_cast(lua.toNumber(2)); + int pan = static_cast(lua.optNumber(3, 64)); + s_sceneManager->getAudio().setVoiceVolume(channelId, volume, pan); + return 0; +} + +int LuaAPI::Audio_StopAll(lua_State* L) { + psyqo::Lua lua(L); + s_sceneManager->getAudio().stopAll(); + return 0; +} + +// ============================================================================ +// DEBUG API IMPLEMENTATION +// ============================================================================ + +int LuaAPI::Debug_Log(lua_State* L) { + psyqo::Lua lua(L); + + const char* msg = lua.optString(1, ""); + printf("[Lua] %s\n", msg); + + return 0; +} + +int LuaAPI::Debug_DrawLine(lua_State* L) { + psyqo::Lua lua(L); + + // Parse start and end Vec3 tables, optional color + psyqo::FixedPoint<12> sx, sy, sz, ex, ey, ez; + if (lua.isTable(1) && lua.isTable(2)) { + ReadVec3(lua, 1, sx, sy, sz); + ReadVec3(lua, 2, ex, ey, ez); + } + + // TODO: Queue LINE_G2 primitive through Renderer + // For now, log to debug console (visible in emulator) +#ifdef PSXSPLASH_DEBUG_DRAW + printf("[DebugDraw] Line (%d,%d,%d)->(%d,%d,%d)\n", + sx.raw() >> 12, sy.raw() >> 12, sz.raw() >> 12, + ex.raw() >> 12, ey.raw() >> 12, ez.raw() >> 12); +#endif + return 0; +} + +int LuaAPI::Debug_DrawBox(lua_State* L) { + psyqo::Lua lua(L); + + // Parse center and size Vec3 tables, optional color + psyqo::FixedPoint<12> cx, cy, cz, hx, hy, hz; + if (lua.isTable(1) && lua.isTable(2)) { + ReadVec3(lua, 1, cx, cy, cz); + ReadVec3(lua, 2, hx, hy, hz); + } + + // TODO: Queue 12 LINE_G2 primitives (box wireframe) through Renderer +#ifdef PSXSPLASH_DEBUG_DRAW + printf("[DebugDraw] Box center(%d,%d,%d) size(%d,%d,%d)\n", + cx.raw() >> 12, cy.raw() >> 12, cz.raw() >> 12, + hx.raw() >> 12, hy.raw() >> 12, hz.raw() >> 12); +#endif + return 0; +} + +// ============================================================================ +// MATH API IMPLEMENTATION +// ============================================================================ + +int LuaAPI::Math_Clamp(lua_State* L) { + psyqo::Lua lua(L); + + lua_Number value = lua.toNumber(1); + lua_Number minVal = lua.toNumber(2); + lua_Number maxVal = lua.toNumber(3); + + if (value < minVal) value = minVal; + if (value > maxVal) value = maxVal; + + lua.pushNumber(value); + return 1; +} + +int LuaAPI::Math_Lerp(lua_State* L) { + psyqo::Lua lua(L); + + lua_Number a = lua.toNumber(1); + lua_Number b = lua.toNumber(2); + lua_Number t = lua.toNumber(3); + + lua.pushNumber(a + (b - a) * t); + return 1; +} + +int LuaAPI::Math_Sign(lua_State* L) { + psyqo::Lua lua(L); + + lua_Number value = lua.toNumber(1); + + if (value > 0) lua.pushNumber(1); + else if (value < 0) lua.pushNumber(-1); + else lua.pushNumber(0); + + return 1; +} + +int LuaAPI::Math_Abs(lua_State* L) { + psyqo::Lua lua(L); + + lua_Number value = lua.toNumber(1); + lua.pushNumber(value < 0 ? -value : value); + return 1; +} + +int LuaAPI::Math_Min(lua_State* L) { + psyqo::Lua lua(L); + + lua_Number a = lua.toNumber(1); + lua_Number b = lua.toNumber(2); + + lua.pushNumber(a < b ? a : b); + return 1; +} + +int LuaAPI::Math_Max(lua_State* L) { + psyqo::Lua lua(L); + + lua_Number a = lua.toNumber(1); + lua_Number b = lua.toNumber(2); + + lua.pushNumber(a > b ? a : b); + return 1; +} + +// ============================================================================ +// SCENE API IMPLEMENTATION +// ============================================================================ + +int LuaAPI::Scene_Load(lua_State* L) { + psyqo::Lua lua(L); + + if (!s_sceneManager || !lua.isNumber(1)) { + return 0; + } + + int sceneIndex = static_cast(lua.toNumber(1)); + s_sceneManager->requestSceneLoad(sceneIndex); + return 0; +} + +int LuaAPI::Scene_GetIndex(lua_State* L) { + psyqo::Lua lua(L); + + if (!s_sceneManager) { + lua.pushNumber(0); + return 1; + } + + lua.pushNumber(static_cast(s_sceneManager->getCurrentSceneIndex())); + return 1; +} + +// ============================================================================ +// PERSIST API IMPLEMENTATION +// ============================================================================ + +struct PersistEntry { + char key[32]; + lua_Number value; + bool used; +}; + +static PersistEntry s_persistData[16] = {}; + +// Inline string helpers (no libc on bare-metal PS1) +static bool streq(const char* a, const char* b) { + while (*a && *b) { if (*a++ != *b++) return false; } + return *a == *b; +} + +static void strcopy(char* dst, const char* src, int maxLen) { + int i = 0; + for (; i < maxLen - 1 && src[i]; i++) dst[i] = src[i]; + dst[i] = '\0'; +} + +int LuaAPI::Persist_Get(lua_State* L) { + psyqo::Lua lua(L); + const char* key = lua.toString(1); + if (!key) { lua.push(); return 1; } + + for (int i = 0; i < 16; i++) { + if (s_persistData[i].used && streq(s_persistData[i].key, key)) { + lua.pushNumber(s_persistData[i].value); + return 1; + } + } + lua.push(); // nil + return 1; +} + +int LuaAPI::Persist_Set(lua_State* L) { + psyqo::Lua lua(L); + const char* key = lua.toString(1); + if (!key) return 0; + + lua_Number value = lua.toNumber(2); + + // Update existing key + for (int i = 0; i < 16; i++) { + if (s_persistData[i].used && streq(s_persistData[i].key, key)) { + s_persistData[i].value = value; + return 0; + } + } + + // Find empty slot + for (int i = 0; i < 16; i++) { + if (!s_persistData[i].used) { + strcopy(s_persistData[i].key, key, 32); + s_persistData[i].value = value; + s_persistData[i].used = true; + return 0; + } + } + + return 0; // No room — silently fail +} + +void LuaAPI::PersistClear() { + for (int i = 0; i < 16; i++) { + s_persistData[i].used = false; + } +} + +} // namespace psxsplash diff --git a/src/luaapi.hh b/src/luaapi.hh new file mode 100644 index 0000000..56a7b28 --- /dev/null +++ b/src/luaapi.hh @@ -0,0 +1,268 @@ +#pragma once + +#include +#include +#include + +namespace psxsplash { + +class SceneManager; // Forward declaration + +/** + * Lua API - Provides game scripting functionality + * + * Available namespaces: + * - Entity: Object finding, spawning, destruction + * - Vec3: Vector math operations + * - Input: Controller state queries + * - Timer: Timer control + * - Camera: Camera manipulation + * - Audio: Sound playback (future) + * - Scene: Scene management + */ +class LuaAPI { +public: + // Initialize all API modules + static void RegisterAll(psyqo::Lua& L, SceneManager* scene); + + // Called once per frame to advance the Lua frame counter + static void IncrementFrameCount(); + + // Reset frame counter (called on scene load) + static void ResetFrameCount(); + +private: + // Store scene manager for API access + static SceneManager* s_sceneManager; + + // ======================================================================== + // ENTITY API + // ======================================================================== + + // Entity.FindByScriptIndex(index) -> object or nil + // Finds first object with matching Lua script file index + static int Entity_FindByScriptIndex(lua_State* L); + + // Entity.FindByIndex(index) -> object or nil + // Gets object by its array index + static int Entity_FindByIndex(lua_State* L); + + // Entity.Find(name) -> object or nil + // Finds first object with matching name (user-friendly) + static int Entity_Find(lua_State* L); + + // Entity.GetCount() -> number + // Returns total number of game objects + static int Entity_GetCount(lua_State* L); + + // Entity.SetActive(object, active) + // Sets object active state (fires onEnable/onDisable) + static int Entity_SetActive(lua_State* L); + + // Entity.IsActive(object) -> boolean + static int Entity_IsActive(lua_State* L); + + // Entity.GetPosition(object) -> {x, y, z} + static int Entity_GetPosition(lua_State* L); + + // Entity.SetPosition(object, {x, y, z}) + static int Entity_SetPosition(lua_State* L); + + // Entity.GetRotationY(object) -> number (radians) + static int Entity_GetRotationY(lua_State* L); + + // Entity.SetRotationY(object, angle) -> nil + static int Entity_SetRotationY(lua_State* L); + + // Entity.ForEach(callback) -> nil + // Calls callback(object, index) for each active game object + static int Entity_ForEach(lua_State* L); + + // ======================================================================== + // VEC3 API - Vector math + // ======================================================================== + + // Vec3.new(x, y, z) -> {x, y, z} + static int Vec3_New(lua_State* L); + + // Vec3.add(a, b) -> {x, y, z} + static int Vec3_Add(lua_State* L); + + // Vec3.sub(a, b) -> {x, y, z} + static int Vec3_Sub(lua_State* L); + + // Vec3.mul(v, scalar) -> {x, y, z} + static int Vec3_Mul(lua_State* L); + + // Vec3.dot(a, b) -> number + static int Vec3_Dot(lua_State* L); + + // Vec3.cross(a, b) -> {x, y, z} + static int Vec3_Cross(lua_State* L); + + // Vec3.length(v) -> number + static int Vec3_Length(lua_State* L); + + // Vec3.lengthSq(v) -> number (faster, no sqrt) + static int Vec3_LengthSq(lua_State* L); + + // Vec3.normalize(v) -> {x, y, z} + static int Vec3_Normalize(lua_State* L); + + // Vec3.distance(a, b) -> number + static int Vec3_Distance(lua_State* L); + + // Vec3.distanceSq(a, b) -> number (faster) + static int Vec3_DistanceSq(lua_State* L); + + // Vec3.lerp(a, b, t) -> {x, y, z} + static int Vec3_Lerp(lua_State* L); + + // ======================================================================== + // INPUT API - Controller state + // ======================================================================== + + // Input.IsPressed(button) -> boolean + // True only on the frame the button was pressed + static int Input_IsPressed(lua_State* L); + + // Input.IsReleased(button) -> boolean + // True only on the frame the button was released + static int Input_IsReleased(lua_State* L); + + // Input.IsHeld(button) -> boolean + // True while the button is held down + static int Input_IsHeld(lua_State* L); + + // Input.GetAnalog(stick) -> x, y + // Returns analog stick values (-128 to 127) + static int Input_GetAnalog(lua_State* L); + + // Button constants (registered as Input.CROSS, Input.CIRCLE, etc.) + static void RegisterInputConstants(psyqo::Lua& L); + + // ======================================================================== + // TIMER API - Frame counter + // ======================================================================== + + // Timer.GetFrameCount() -> number + // Returns total frames since scene start + static int Timer_GetFrameCount(lua_State* L); + + // ======================================================================== + // CAMERA API - Camera control + // ======================================================================== + + // Camera.GetPosition() -> {x, y, z} + static int Camera_GetPosition(lua_State* L); + + // Camera.SetPosition(x, y, z) + static int Camera_SetPosition(lua_State* L); + + // Camera.GetRotation() -> {x, y, z} + static int Camera_GetRotation(lua_State* L); + + // Camera.SetRotation(x, y, z) + static int Camera_SetRotation(lua_State* L); + + // Camera.LookAt(target) or Camera.LookAt(x, y, z) + static int Camera_LookAt(lua_State* L); + + // ======================================================================== + // AUDIO API - Sound playback (placeholder for SPU) + // ======================================================================== + + // Audio.Play(soundId, volume, pan) -> channelId + // soundId can be a number (clip index) or string (clip name) + static int Audio_Play(lua_State* L); + + // Audio.Find(name) -> clipIndex or nil + // Finds audio clip by name, returns its index for use with Play/Stop/etc. + static int Audio_Find(lua_State* L); + + // Audio.Stop(channelId) + static int Audio_Stop(lua_State* L); + + // Audio.SetVolume(channelId, volume) + static int Audio_SetVolume(lua_State* L); + + // Audio.StopAll() + static int Audio_StopAll(lua_State* L); + + // ======================================================================== + // DEBUG API - Development helpers + // ======================================================================== + + // Debug.Log(message) + static int Debug_Log(lua_State* L); + + // Debug.DrawLine(start, end, color) - draws debug line next frame + static int Debug_DrawLine(lua_State* L); + + // Debug.DrawBox(center, size, color) + static int Debug_DrawBox(lua_State* L); + + // ======================================================================== + // MATH API - Additional math functions + // ======================================================================== + + // Math.Clamp(value, min, max) + static int Math_Clamp(lua_State* L); + + // Math.Lerp(a, b, t) + static int Math_Lerp(lua_State* L); + + // Math.Sign(value) + static int Math_Sign(lua_State* L); + + // Math.Abs(value) + static int Math_Abs(lua_State* L); + + // Math.Min(a, b) + static int Math_Min(lua_State* L); + + // Math.Max(a, b) + static int Math_Max(lua_State* L); + + // ======================================================================== + // SCENE API - Scene management + // ======================================================================== + + // Scene.Load(sceneIndex) + // Requests a scene transition to the given index (0-based). + // The actual load happens at the end of the current frame. + static int Scene_Load(lua_State* L); + + // Scene.GetIndex() -> number + // Returns the index of the currently loaded scene. + static int Scene_GetIndex(lua_State* L); + + // ======================================================================== + // PERSIST API - Data that survives scene loads + // ======================================================================== + + // Persist.Get(key) -> number or nil + static int Persist_Get(lua_State* L); + + // Persist.Set(key, value) + static int Persist_Set(lua_State* L); + + // Reset all persistent data + static void PersistClear(); + + // ======================================================================== + // HELPERS + // ======================================================================== + + // Push a Vec3 table onto the stack + static void PushVec3(psyqo::Lua& L, psyqo::FixedPoint<12> x, + psyqo::FixedPoint<12> y, psyqo::FixedPoint<12> z); + + // Read a Vec3 table from the stack + static void ReadVec3(psyqo::Lua& L, int idx, + psyqo::FixedPoint<12>& x, + psyqo::FixedPoint<12>& y, + psyqo::FixedPoint<12>& z); +}; + +} // namespace psxsplash diff --git a/src/main.cpp b/src/main.cpp index c3d75c4..6a63cf8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,9 +8,8 @@ #include "renderer.hh" #include "scenemanager.hh" - -// Data from the splashpack -extern uint8_t _binary_output_bin_start[]; +#include "sceneloader.hh" +#include "pcdrv_handler.hh" namespace { @@ -29,6 +28,9 @@ class MainScene final : public psyqo::Scene { uint32_t m_lastFrameCounter; psxsplash::SceneManager m_sceneManager; + + // PCdrv-loaded scene data (owned) + uint8_t* m_sceneData = nullptr; }; PSXSplash app; @@ -53,7 +55,28 @@ void PSXSplash::createScene() { pushScene(&mainScene); } -void MainScene::start(StartReason reason) { m_sceneManager.InitializeScene(_binary_output_bin_start); } +void MainScene::start(StartReason reason) { + // On real hardware: register break handler for PCDRV over SIO1 + redirect printf + // On emulator: no-op (pcsx-redux handles PCDRV natively) + psxsplash::pcdrv_sio1_init(); + + // Initialize PCdrv (break instructions - handled by emulator or our break handler) + psxsplash::SceneLoader::Init(); + + // Load the first scene via PCdrv. + // Files are relative to the pcdrvbase directory (PSXBuild/). + int fileSize = 0; + m_sceneData = psxsplash::SceneLoader::LoadFile("scene_0.splashpack", fileSize); + + if (!m_sceneData) { + // Fallback: try legacy name for backwards compatibility + m_sceneData = psxsplash::SceneLoader::LoadFile("output.bin", fileSize); + } + + if (m_sceneData) { + m_sceneManager.InitializeScene(m_sceneData); + } +} void MainScene::frame() { uint32_t beginFrame = gpu().now(); @@ -73,8 +96,6 @@ void MainScene::frame() { gpu().getRefreshRate() / deltaTime); gpu().pumpCallbacks(); - uint32_t endFrame = gpu().now(); - uint32_t spent = endFrame - beginFrame; } int main() { return app.run(); } \ No newline at end of file diff --git a/src/mesh.hh b/src/mesh.hh index c4d3c7b..7801da9 100644 --- a/src/mesh.hh +++ b/src/mesh.hh @@ -5,6 +5,9 @@ namespace psxsplash { + // Sentinel value for untextured (vertex-color-only) triangles + static constexpr uint16_t UNTEXTURED_TPAGE = 0xFFFF; + class Tri final { public: psyqo::GTE::PackedVec3 v0, v1, v2; @@ -18,7 +21,13 @@ namespace psxsplash { psyqo::PrimPieces::TPageAttr tpage; uint16_t clutX; uint16_t clutY; - uint16_t padding; + uint16_t padding; + + /// Returns true if this triangle has no texture (vertex-color only). + /// These should be rendered as POLY_G3 (GouraudTriangle) instead of POLY_GT3. + bool isUntextured() const { + return *reinterpret_cast(&tpage) == UNTEXTURED_TPAGE; + } }; static_assert(sizeof(Tri) == 52, "Tri is not 52 bytes"); diff --git a/src/navmesh.cpp b/src/navmesh.cpp deleted file mode 100644 index fa8bdfa..0000000 --- a/src/navmesh.cpp +++ /dev/null @@ -1,122 +0,0 @@ -#include "navmesh.hh" - -#include - -#include -#include - -using namespace psyqo::fixed_point_literals; - -// FIXME: This entire file uses hard FixedPoint scaling of 100. This is not ideal. -// It would be better to move the fixedpoint precision to 19 instead. - -namespace psxsplash { - -psyqo::FixedPoint<12> DotProduct2D(const psyqo::Vec2& a, const psyqo::Vec2& b) { return a.x * b.x + a.y * b.y; } - -psyqo::Vec2 ClosestPointOnSegment(const psyqo::Vec2& A, const psyqo::Vec2& B, const psyqo::Vec2& P) { - psyqo::Vec2 AB = {B.x - A.x, B.y - A.y}; - psyqo::Vec2 AP = {P.x - A.x, P.y - A.y}; - auto abDot = DotProduct2D(AB, AB); - if (abDot == 0) return A; - psyqo::FixedPoint<12> t = DotProduct2D(AP, AB) / abDot; - if (t < 0.0_fp) t = 0.0_fp; - if (t > 1.0_fp) t = 1.0_fp; - return {(A.x + AB.x * t), (A.y + AB.y * t)}; -} - -bool PointInTriangle(psyqo::Vec3& p, NavMeshTri& tri) { - psyqo::Vec2 A = {tri.v0.x * 100, tri.v0.z * 100}; - psyqo::Vec2 B = {tri.v1.x * 100, tri.v1.z * 100}; - psyqo::Vec2 C = {tri.v2.x * 100, tri.v2.z * 100}; - psyqo::Vec2 P = {p.x * 100, p.z * 100}; - - psyqo::Vec2 v0 = {B.x - A.x, B.y - A.y}; - psyqo::Vec2 v1 = {C.x - A.x, C.y - A.y}; - psyqo::Vec2 v2 = {P.x - A.x, P.y - A.y}; - - auto d00 = DotProduct2D(v0, v0); - auto d01 = DotProduct2D(v0, v1); - auto d11 = DotProduct2D(v1, v1); - auto d20 = DotProduct2D(v2, v0); - auto d21 = DotProduct2D(v2, v1); - - psyqo::FixedPoint<12> denom = d00 * d11 - d01 * d01; - if (denom == 0.0_fp) { - return false; - } - auto invDenom = 1.0_fp / denom; - auto u = (d11 * d20 - d01 * d21) * invDenom; - auto w = (d00 * d21 - d01 * d20) * invDenom; - - return (u >= 0.0_fp) && (w >= 0.0_fp) && (u + w <= 1.0_fp); -} - -psyqo::Vec3 ComputeNormal(const NavMeshTri& tri) { - psyqo::Vec3 v1 = {tri.v1.x * 100 - tri.v0.x * 100, tri.v1.y * 100 - tri.v0.y * 100, tri.v1.z * 100 - tri.v0.z * 100}; - psyqo::Vec3 v2 = {tri.v2.x * 100 - tri.v0.x * 100, tri.v2.y * 100 - tri.v0.y * 100, tri.v2.z * 100 - tri.v0.z * 100}; - - psyqo::Vec3 normal = { - v1.y * v2.z - v1.z * v2.y, - v1.z * v2.x - v1.x * v2.z, - v1.x * v2.y - v1.y * v2.x - }; - return normal; -} - -psyqo::FixedPoint<12> CalculateY(const psyqo::Vec3& p, const NavMeshTri& tri) { - psyqo::Vec3 normal = ComputeNormal(tri); - - psyqo::FixedPoint<12> A = normal.x; - psyqo::FixedPoint<12> B = normal.y; - psyqo::FixedPoint<12> C = normal.z; - - psyqo::FixedPoint<12> D = -(A * tri.v0.x + B * tri.v0.y + C * tri.v0.z); - - if (B != 0.0_fp) { - return -(A * p.x + C * p.z + D) / B; - } else { - return p.y; - } -} - -psyqo::Vec3 ComputeNavmeshPosition(psyqo::Vec3& position, Navmesh& navmesh, psyqo::FixedPoint<12> playerHeight) { - for (int i = 0; i < navmesh.triangleCount; i++) { - if (PointInTriangle(position, navmesh.polygons[i])) { - position.y = CalculateY(position, navmesh.polygons[i]) - playerHeight; - return position; - } - } - - psyqo::Vec2 P = {position.x * 100, position.z * 100}; - - psyqo::Vec2 closestPoint; - psyqo::FixedPoint<12> minDist = 0x7ffff; - - for (int i = 0; i < navmesh.triangleCount; i++) { - NavMeshTri& tri = navmesh.polygons[i]; - psyqo::Vec2 A = {tri.v0.x * 100, tri.v0.z * 100}; - psyqo::Vec2 B = {tri.v1.x * 100, tri.v1.z * 100}; - psyqo::Vec2 C = {tri.v2.x * 100, tri.v2.z * 100}; - - std::array, 3> edges = {{{A, B}, {B, C}, {C, A}}}; - - for (auto& edge : edges) { - psyqo::Vec2 proj = ClosestPointOnSegment(edge.first, edge.second, P); - psyqo::Vec2 diff = {proj.x - P.x, proj.y - P.y}; - auto distSq = DotProduct2D(diff, diff); - if (distSq < minDist) { - minDist = distSq; - closestPoint = proj; - position.y = CalculateY(position, navmesh.polygons[i]) - playerHeight; - } - } - } - - position.x = closestPoint.x / 100; - position.z = closestPoint.y / 100; - - return position; -} - -} // namespace psxsplash diff --git a/src/navmesh.hh b/src/navmesh.hh deleted file mode 100644 index 538c1b3..0000000 --- a/src/navmesh.hh +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include - -namespace psxsplash { - -class NavMeshTri final { - public: - psyqo::Vec3 v0, v1, v2; -}; - -class Navmesh final { - public: - union { - NavMeshTri* polygons; - uint32_t polygonsOffset; - }; - uint16_t triangleCount; - uint16_t reserved; -}; - -psyqo::Vec3 ComputeNavmeshPosition(psyqo::Vec3& position, Navmesh& navmesh, psyqo::FixedPoint<12> pheight); - -} // namespace psxsplash \ No newline at end of file diff --git a/src/navregion.cpp b/src/navregion.cpp new file mode 100644 index 0000000..ee3feb1 --- /dev/null +++ b/src/navregion.cpp @@ -0,0 +1,351 @@ +#include "navregion.hh" + +#include +#include + + +/** + * navregion.cpp - Convex Region Navigation System + * + * All math is 20.12 fixed-point. Zero floats. + * + * Key operations: + * - resolvePosition: O(1) typical (check current + neighbors via portals) + * - pointInRegion: O(n) per polygon vertices (convex cross test) + * - getFloorY: O(1) plane equation evaluation + * - findRegion: O(R) brute force, used only at init + */ + +namespace psxsplash { + +// ============================================================================ +// Fixed-point helpers +// ============================================================================ + +static constexpr int FRAC_BITS = 12; +static constexpr int32_t FP_ONE = 1 << FRAC_BITS; + +static inline int32_t fpmul(int32_t a, int32_t b) { + return (int32_t)(((int64_t)a * b) >> FRAC_BITS); +} + +static inline int32_t fpdiv(int32_t a, int32_t b) { + if (b == 0) return 0; + int32_t q = a / b; + int32_t r = a - q * b; + return q * FP_ONE + (r << FRAC_BITS) / b; +} + +// ============================================================================ +// Initialization +// ============================================================================ + +const uint8_t* NavRegionSystem::initializeFromData(const uint8_t* data) { + const auto* hdr = reinterpret_cast(data); + m_header = *hdr; + data += sizeof(NavDataHeader); + + m_regions = reinterpret_cast(data); + data += m_header.regionCount * sizeof(NavRegion); + + m_portals = reinterpret_cast(data); + data += m_header.portalCount * sizeof(NavPortal); + + + + return data; +} + +// ============================================================================ +// Point-in-convex-polygon (XZ plane) +// ============================================================================ + +bool NavRegionSystem::pointInConvexPoly(int32_t px, int32_t pz, + const int32_t* vertsX, const int32_t* vertsZ, + int vertCount) { + if (vertCount < 3) return false; + + // For CCW winding, all cross products must be >= 0. + // cross = (bx - ax) * (pz - az) - (bz - az) * (px - ax) + for (int i = 0; i < vertCount; i++) { + int next = (i + 1) % vertCount; + int32_t ax = vertsX[i], az = vertsZ[i]; + int32_t bx = vertsX[next], bz = vertsZ[next]; + + // Edge direction + int32_t edgeX = bx - ax; + int32_t edgeZ = bz - az; + // Point relative to edge start + int32_t relX = px - ax; + int32_t relZ = pz - az; + + // Cross product (64-bit to prevent overflow) + int64_t cross = (int64_t)edgeX * relZ - (int64_t)edgeZ * relX; + if (cross < 0) return false; + } + return true; +} + +// ============================================================================ +// Closest point on segment (XZ only) +// ============================================================================ + +void NavRegionSystem::closestPointOnSegment(int32_t px, int32_t pz, + int32_t ax, int32_t az, + int32_t bx, int32_t bz, + int32_t& outX, int32_t& outZ) { + int32_t abx = bx - ax; + int32_t abz = bz - az; + int32_t lenSq = fpmul(abx, abx) + fpmul(abz, abz); + if (lenSq == 0) { + outX = ax; outZ = az; + return; + } + + int32_t dot = fpmul(px - ax, abx) + fpmul(pz - az, abz); + // t = dot / lenSq, clamped to [0, 1] + int32_t t; + if (dot <= 0) { + t = 0; + } else if (dot >= lenSq) { + t = FP_ONE; + } else { + t = fpdiv(dot, lenSq); + } + + outX = ax + fpmul(t, abx); + outZ = az + fpmul(t, abz); +} + +// ============================================================================ +// Segment crosses portal check (XZ) +// ============================================================================ + +bool NavRegionSystem::segmentCrossesPortal(int32_t p0x, int32_t p0z, + int32_t p1x, int32_t p1z, + int32_t ax, int32_t az, + int32_t bx, int32_t bz) { + // Standard 2D segment intersection test using cross products. + // Returns true if segment [p0,p1] crosses segment [a,b]. + + int32_t dx = p1x - p0x, dz = p1z - p0z; + int32_t ex = bx - ax, ez = bz - az; + + int64_t denom = (int64_t)dx * ez - (int64_t)dz * ex; + if (denom == 0) return false; // Parallel + + int32_t fx = ax - p0x, fz = az - p0z; + + int64_t tNum = (int64_t)fx * ez - (int64_t)fz * ex; + int64_t uNum = (int64_t)fx * dz - (int64_t)fz * dx; + + // Check t in [0,1] and u in [0,1] + if (denom > 0) { + if (tNum < 0 || tNum > denom) return false; + if (uNum < 0 || uNum > denom) return false; + } else { + if (tNum > 0 || tNum < denom) return false; + if (uNum > 0 || uNum < denom) return false; + } + + return true; +} + +// ============================================================================ +// Get floor Y at position (plane equation) +// ============================================================================ + +int32_t NavRegionSystem::getFloorY(int32_t x, int32_t z, uint16_t regionIndex) const { + if (regionIndex >= m_header.regionCount) return 0; + const auto& reg = m_regions[regionIndex]; + + // Y = planeA * X + planeB * Z + planeD + // (all in 20.12, products need 64-bit intermediate) + return fpmul(reg.planeA, x) + fpmul(reg.planeB, z) + reg.planeD; +} + +// ============================================================================ +// Point in region test +// ============================================================================ + +bool NavRegionSystem::pointInRegion(int32_t x, int32_t z, uint16_t regionIndex) const { + if (regionIndex >= m_header.regionCount) return false; + const auto& reg = m_regions[regionIndex]; + return pointInConvexPoly(x, z, reg.vertsX, reg.vertsZ, reg.vertCount); +} + +// ============================================================================ +// Find region (brute force, for initialization) +// ============================================================================ + +uint16_t NavRegionSystem::findRegion(int32_t x, int32_t z) const { + // When multiple regions overlap at the same XZ position (e.g., floor and + // elevated step), prefer the highest physical surface. In PSX Y-down space, + // highest surface = smallest (most negative) floor Y value. + uint16_t best = NAV_NO_REGION; + int32_t bestY = 0x7FFFFFFF; + for (uint16_t i = 0; i < m_header.regionCount; i++) { + if (pointInRegion(x, z, i)) { + int32_t fy = getFloorY(x, z, i); + if (fy < bestY) { + bestY = fy; + best = i; + } + } + } + return best; +} + +// ============================================================================ +// Clamp position to region boundary +// ============================================================================ + +void NavRegionSystem::clampToRegion(int32_t& x, int32_t& z, uint16_t regionIndex) const { + if (regionIndex >= m_header.regionCount) return; + const auto& reg = m_regions[regionIndex]; + + if (pointInConvexPoly(x, z, reg.vertsX, reg.vertsZ, reg.vertCount)) + return; // Already inside + + // Find closest point on any edge of the polygon + int32_t bestX = x, bestZ = z; + int64_t bestDistSq = 0x7FFFFFFFFFFFFFFFLL; + + for (int i = 0; i < reg.vertCount; i++) { + int next = (i + 1) % reg.vertCount; + int32_t cx, cz; + closestPointOnSegment(x, z, + reg.vertsX[i], reg.vertsZ[i], + reg.vertsX[next], reg.vertsZ[next], + cx, cz); + + int64_t dx = (int64_t)(x - cx); + int64_t dz = (int64_t)(z - cz); + int64_t distSq = dx * dx + dz * dz; + + if (distSq < bestDistSq) { + bestDistSq = distSq; + bestX = cx; + bestZ = cz; + } + } + + x = bestX; + z = bestZ; +} + +// ============================================================================ +// Resolve position (main per-frame call) +// ============================================================================ + +int32_t NavRegionSystem::resolvePosition(int32_t& newX, int32_t& newZ, + uint16_t& currentRegion) const { + if (!isLoaded() || m_header.regionCount == 0) return 0; + + + + // If no valid region, find one + if (currentRegion == NAV_NO_REGION || currentRegion >= m_header.regionCount) { + currentRegion = findRegion(newX, newZ); + + if (currentRegion == NAV_NO_REGION) return 0; + } + + // Check if still in current region + if (pointInRegion(newX, newZ, currentRegion)) { + int32_t fy = getFloorY(newX, newZ, currentRegion); + + // Check if a portal neighbor has a higher floor at this position. + // This handles overlapping regions (e.g., floor and elevated step). + // When the player walks onto the step, the step region (portal neighbor) + // has a higher floor (smaller Y in PSX Y-down) and should take priority. + const auto& reg = m_regions[currentRegion]; + for (int i = 0; i < reg.portalCount; i++) { + uint16_t portalIdx = reg.portalStart + i; + if (portalIdx >= m_header.portalCount) break; + uint16_t neighbor = m_portals[portalIdx].neighborRegion; + if (neighbor >= m_header.regionCount) continue; + if (pointInRegion(newX, newZ, neighbor)) { + int32_t nfy = getFloorY(newX, newZ, neighbor); + if (nfy < fy) { // Higher physical surface (Y-down: smaller = higher) + currentRegion = neighbor; + fy = nfy; + } + } + } + + return fy; + } + + + + // Check portal neighbors + const auto& reg = m_regions[currentRegion]; + for (int i = 0; i < reg.portalCount; i++) { + uint16_t portalIdx = reg.portalStart + i; + if (portalIdx >= m_header.portalCount) break; + + const auto& portal = m_portals[portalIdx]; + uint16_t neighbor = portal.neighborRegion; + + if (neighbor < m_header.regionCount && pointInRegion(newX, newZ, neighbor)) { + currentRegion = neighbor; + return getFloorY(newX, newZ, neighbor); + } + } + + // Not in current region or any neighbor — try broader search + // This handles jumping/falling to non-adjacent regions (e.g., landing on a platform) + { + uint16_t found = findRegion(newX, newZ); + if (found != NAV_NO_REGION) { + currentRegion = found; + return getFloorY(newX, newZ, found); + } + } + + // Truly off all regions — clamp to current region boundary + clampToRegion(newX, newZ, currentRegion); + return getFloorY(newX, newZ, currentRegion); +} + +// ============================================================================ +// Pathfinding stub +// ============================================================================ + +bool NavRegionSystem::findPath(uint16_t startRegion, uint16_t endRegion, + NavPath& path) const { + // STUB: Returns false until NPC pathfinding is implemented. + // When implemented, this will be A* over the region adjacency graph: + // - Open set: priority queue by f-cost (g + heuristic) + // - g-cost: sum of Euclidean distances between region centroids + // - Heuristic: straight-line distance to goal centroid + // - Neighbor iteration: via portal edges + // - Max path length: NAV_MAX_PATH_STEPS + path.stepCount = 0; + (void)startRegion; + (void)endRegion; + return false; +} + +// ============================================================================ +// Get portal between two regions +// ============================================================================ + +const NavPortal* NavRegionSystem::getPortalBetween(uint16_t regionA, uint16_t regionB) const { + if (regionA >= m_header.regionCount) return nullptr; + + const auto& reg = m_regions[regionA]; + for (int i = 0; i < reg.portalCount; i++) { + uint16_t portalIdx = reg.portalStart + i; + if (portalIdx >= m_header.portalCount) break; + + if (m_portals[portalIdx].neighborRegion == regionB) { + return &m_portals[portalIdx]; + } + } + + return nullptr; +} + +} // namespace psxsplash diff --git a/src/navregion.hh b/src/navregion.hh new file mode 100644 index 0000000..e66329d --- /dev/null +++ b/src/navregion.hh @@ -0,0 +1,188 @@ +#pragma once + +/** + * navregion.hh - Convex Region Navigation System + * + * Architecture: + * - Walkable surface decomposed into convex polygonal regions (XZ plane). + * - Adjacent regions share portal edges. + * - Player has a single current region index. + * - Movement: point-in-convex-polygon test → portal crossing → neighbor update. + * - Floor Y: project XZ onto region's floor plane. + * - Pathfinding: A* over region adjacency graph (stub ready for NPC drop-in). + * + * All math is fixed-point 20.12. Zero floats. + */ + +#include +#include +#include + +namespace psxsplash { + +// ============================================================================ +// Constants +// ============================================================================ +static constexpr int NAV_MAX_VERTS_PER_REGION = 8; // Max polygon verts +static constexpr int NAV_MAX_NEIGHBORS = 8; // Max portal edges per region +static constexpr int NAV_MAX_PATH_STEPS = 32; // Max A* path length +static constexpr uint16_t NAV_NO_REGION = 0xFFFF; // Sentinel: no region + +// ============================================================================ +// Surface type for navigation regions +// ============================================================================ +enum NavSurfaceType : uint8_t { + NAV_SURFACE_FLAT = 0, + NAV_SURFACE_RAMP = 1, + NAV_SURFACE_STAIRS = 2, +}; + +// ============================================================================ +// Portal edge — shared edge between two adjacent regions +// ============================================================================ +struct NavPortal { + int32_t ax, az; // Portal edge start (20.12 XZ) + int32_t bx, bz; // Portal edge end (20.12 XZ) + uint16_t neighborRegion; // Index of the region on the other side + int16_t heightDelta; // Vertical step in 4.12 (stairs, ledges) +}; +static_assert(sizeof(NavPortal) == 20, "NavPortal must be 20 bytes"); + +// ============================================================================ +// Nav Region — convex polygon on the XZ plane with floor info +// ============================================================================ +struct NavRegion { + // Convex polygon vertices (XZ, 20.12 fixed-point) + // Stored in CCW winding order + int32_t vertsX[NAV_MAX_VERTS_PER_REGION]; // 32 bytes + int32_t vertsZ[NAV_MAX_VERTS_PER_REGION]; // 32 bytes + + // Floor plane: Y = planeA * X + planeB * Z + planeD (all 20.12) + // For flat floors: planeA = planeB = 0, planeD = floorY + int32_t planeA, planeB, planeD; // 12 bytes + + // Portal neighbors + uint16_t portalStart; // Index into portal array 2 bytes + uint8_t portalCount; // Number of portals 1 byte + uint8_t vertCount; // Number of polygon verts 1 byte + + // Metadata + NavSurfaceType surfaceType; // 1 byte + uint8_t roomIndex; // Interior room (0xFF = exterior) 1 byte + uint8_t pad[2]; // Alignment 2 bytes + // Total: 32 + 32 + 12 + 4 + 4 = 84 bytes +}; +static_assert(sizeof(NavRegion) == 84, "NavRegion must be 84 bytes"); + +// ============================================================================ +// Nav data header +// ============================================================================ +struct NavDataHeader { + uint16_t regionCount; + uint16_t portalCount; + uint16_t startRegion; // Region the player spawns in + uint16_t pad; +}; +static_assert(sizeof(NavDataHeader) == 8, "NavDataHeader must be 8 bytes"); + +// ============================================================================ +// Path result for A* (used by NPC pathfinding) +// ============================================================================ +struct NavPath { + uint16_t regions[NAV_MAX_PATH_STEPS]; + int stepCount; +}; + +// ============================================================================ +// NavRegionSystem — manages navigation at runtime +// ============================================================================ +class NavRegionSystem { +public: + NavRegionSystem() = default; + + /// Initialize from splashpack data. Returns pointer past the data. + const uint8_t* initializeFromData(const uint8_t* data); + + /// Is nav data loaded? + bool isLoaded() const { return m_regions != nullptr; } + + /// Get the number of regions + uint16_t getRegionCount() const { return m_header.regionCount; } + + /// Get the start region + uint16_t getStartRegion() const { return m_header.startRegion; } + + /// Get the room index for a given nav region (0xFF = exterior/unknown) + uint8_t getRoomIndex(uint16_t regionIndex) const { + if (m_regions == nullptr || regionIndex >= m_header.regionCount) return 0xFF; + return m_regions[regionIndex].roomIndex; + } + + // ======================================================================== + // Player movement - called every frame + // ======================================================================== + + /// Given a new XZ position and the player's current region, + /// determine the correct region and return the floor Y. + /// Updates currentRegion in-place. + /// newX/newZ are clamped to stay within the region boundary. + /// Returns the Y position for the player's feet. + int32_t resolvePosition(int32_t& newX, int32_t& newZ, + uint16_t& currentRegion) const; + + /// Test if a point (XZ) is inside a specific region. + bool pointInRegion(int32_t x, int32_t z, uint16_t regionIndex) const; + + /// Compute floor Y at a given XZ within a region using the floor plane. + int32_t getFloorY(int32_t x, int32_t z, uint16_t regionIndex) const; + + /// Find which region contains a point (brute-force, for initialization). + uint16_t findRegion(int32_t x, int32_t z) const; + + /// Clamp an XZ position to stay within a region's polygon boundary. + /// Returns the clamped position. + void clampToRegion(int32_t& x, int32_t& z, uint16_t regionIndex) const; + + // ======================================================================== + // Pathfinding stub — documented API for NPC drop-in + // ======================================================================== + + /// Find a path from startRegion to endRegion. + /// Writes region indices into path.regions[], sets path.stepCount. + /// Returns true if a path was found. + /// + /// Implementation: A* over the region adjacency graph. + /// Cost heuristic: Euclidean distance between region centroids. + /// This is a STUB — returns false until NPC pathfinding is implemented. + bool findPath(uint16_t startRegion, uint16_t endRegion, + NavPath& path) const; + + /// Get the portal edge between two adjacent regions. + /// Returns nullptr if regions are not adjacent. + const NavPortal* getPortalBetween(uint16_t regionA, uint16_t regionB) const; + +private: + NavDataHeader m_header = {}; + const NavRegion* m_regions = nullptr; + const NavPortal* m_portals = nullptr; + + /// Point-in-convex-polygon test (XZ plane). + /// Uses cross-product sign consistency (all edges same winding). + static bool pointInConvexPoly(int32_t px, int32_t pz, + const int32_t* vertsX, const int32_t* vertsZ, + int vertCount); + + /// Closest point on a line segment to a point (XZ only) + static void closestPointOnSegment(int32_t px, int32_t pz, + int32_t ax, int32_t az, + int32_t bx, int32_t bz, + int32_t& outX, int32_t& outZ); + + /// Check if a line segment (player movement) crosses a portal edge + static bool segmentCrossesPortal(int32_t p0x, int32_t p0z, + int32_t p1x, int32_t p1z, + int32_t ax, int32_t az, + int32_t bx, int32_t bz); +}; + +} // namespace psxsplash diff --git a/src/pcdrv_handler.hh b/src/pcdrv_handler.hh new file mode 100644 index 0000000..e7e6897 --- /dev/null +++ b/src/pcdrv_handler.hh @@ -0,0 +1,298 @@ +/* + * pcdrv_handler.hh - Unified PCDRV API with runtime dispatch + * + * On pcsx-redux (emulator), uses pcdrv.h break instructions which are + * intercepted at the CPU level natively. + * + * On real hardware, bypasses break instructions entirely and communicates + * directly over SIO1 using the same protocol as PCdrvSerialHost.cs. + * This avoids reliance on the exception save area, which is fragile + * across different compiler versions and optimization levels. + * + * Additionally, redirects PSYQo's printf output to SIO1 on real hardware. + * + * Call pcdrv_sio1_init() once at startup, after PSYQo initialization. + * Then use pcdrv_open/read/write/close/seek instead of PCopen/PCread/etc. + */ + +#pragma once + +#include +#include +#include +#include +#include "common/hardware/pcsxhw.h" +#include "common/kernel/pcdrv.h" + +namespace psxsplash { + +// ========================================================================= +// SIO1 hardware registers (UART serial port at 0x1F801050) +// ========================================================================= + +#define SIO1_DATA (*(volatile uint8_t*)0x1F801050) +#define SIO1_STAT (*(volatile uint32_t*)0x1F801054) +#define SIO1_MODE (*(volatile uint16_t*)0x1F801058) +#define SIO1_CTRL (*(volatile uint16_t*)0x1F80105A) +#define SIO1_BAUD (*(volatile uint16_t*)0x1F80105E) + +#define SIO1_TX_RDY (1 << 0) +#define SIO1_RX_RDY (1 << 1) + +// ========================================================================= +// Low-level SIO1 I/O - blocking, tight polling loops +// ========================================================================= + +static inline void sio_putc(uint8_t byte) { + while (!(SIO1_STAT & SIO1_TX_RDY)) {} + SIO1_DATA = byte; +} + +static inline uint8_t sio_getc() { + while (!(SIO1_STAT & SIO1_RX_RDY)) {} + return SIO1_DATA; +} + +static inline void sio_write32(uint32_t val) { + sio_putc((uint8_t)(val)); + sio_putc((uint8_t)(val >> 8)); + sio_putc((uint8_t)(val >> 16)); + sio_putc((uint8_t)(val >> 24)); +} + +static inline uint32_t sio_read32() { + uint32_t v = (uint32_t)sio_getc(); + v |= (uint32_t)sio_getc() << 8; + v |= (uint32_t)sio_getc() << 16; + v |= (uint32_t)sio_getc() << 24; + return v; +} + +static inline bool sio_check_okay() { + return sio_getc() == 'O' && sio_getc() == 'K' + && sio_getc() == 'A' && sio_getc() == 'Y'; +} + +static inline void sio_pcdrv_escape(uint32_t funcCode) { + sio_putc(0x00); + sio_putc('p'); + sio_write32(funcCode); +} + +// ========================================================================= +// Runtime detection - reads magic at 0x1F802080 each call. +// NOT cached in a static, because this is a header-only file and each +// translation unit would get its own copy of any static variable. +// pcsx_present() is a single bus read - negligible cost. +// ========================================================================= + +// ========================================================================= +// Direct SIO1 PCDRV implementations (real hardware path) +// These call the host protocol directly with actual pointers/values, +// bypassing break instructions and the exception save area entirely. +// ========================================================================= + +static int sio_pcdrv_init() { + sio_pcdrv_escape(0x101); + if (sio_check_okay()) { + sio_getc(); // trailing 0x00 + return 0; + } + return -1; +} + +static int sio_pcdrv_open(const char* name, int flags) { + sio_pcdrv_escape(0x103); + if (!sio_check_okay()) return -1; + const char* p = name; + while (*p) sio_putc((uint8_t)*p++); + sio_putc(0x00); + sio_write32((uint32_t)flags); + if (sio_check_okay()) { + return (int)sio_read32(); // handle + } + return -1; +} + +static int sio_pcdrv_creat(const char* name) { + sio_pcdrv_escape(0x102); + if (!sio_check_okay()) return -1; + const char* p = name; + while (*p) sio_putc((uint8_t)*p++); + sio_putc(0x00); + sio_write32(0); // params + if (sio_check_okay()) { + return (int)sio_read32(); // handle + } + return -1; +} + +static int sio_pcdrv_close(int fd) { + sio_pcdrv_escape(0x104); + if (!sio_check_okay()) return -1; + sio_write32((uint32_t)fd); + sio_write32(0); // unused + sio_write32(0); // unused + if (sio_check_okay()) { + sio_read32(); // handle echo + return 0; + } + return -1; +} + +static int sio_pcdrv_read(int fd, void* buf, int len) { + sio_pcdrv_escape(0x105); + if (!sio_check_okay()) return -1; + sio_write32((uint32_t)fd); + sio_write32((uint32_t)len); + sio_write32((uint32_t)(uintptr_t)buf); // memaddr for host debug + if (sio_check_okay()) { + uint32_t dataLen = sio_read32(); + sio_read32(); // checksum (not verified) + uint8_t* dst = (uint8_t*)buf; + for (uint32_t i = 0; i < dataLen; i++) { + dst[i] = sio_getc(); + } + return (int)dataLen; + } + return -1; +} + +static int sio_pcdrv_write(int fd, const void* buf, int len) { + sio_pcdrv_escape(0x106); + if (!sio_check_okay()) return -1; + sio_write32((uint32_t)fd); + sio_write32((uint32_t)len); + sio_write32((uint32_t)(uintptr_t)buf); // memaddr for host debug + if (!sio_check_okay()) return -1; + const uint8_t* src = (const uint8_t*)buf; + for (int i = 0; i < len; i++) { + sio_putc(src[i]); + } + if (sio_check_okay()) { + return (int)sio_read32(); // bytes written + } + return -1; +} + +static int sio_pcdrv_seek(int fd, int offset, int whence) { + sio_pcdrv_escape(0x107); + if (!sio_check_okay()) return -1; + sio_write32((uint32_t)fd); + sio_write32((uint32_t)offset); + sio_write32((uint32_t)whence); + if (sio_check_okay()) { + return (int)sio_read32(); // new position + } + return -1; +} + +// ========================================================================= +// Public PCDRV API - runtime dispatch between emulator and real hardware +// Use these instead of pcdrv.h functions (PCopen, PCread, etc.) +// ========================================================================= + +static int pcdrv_init() { + if (pcsx_present()) return PCinit(); + return sio_pcdrv_init(); +} + +static int pcdrv_open(const char* name, int flags, int perms) { + if (pcsx_present()) return PCopen(name, flags, perms); + return sio_pcdrv_open(name, flags); +} + +static int pcdrv_creat(const char* name, int perms) { + if (pcsx_present()) return PCcreat(name, perms); + return sio_pcdrv_creat(name); +} + +static int pcdrv_close(int fd) { + if (pcsx_present()) return PCclose(fd); + return sio_pcdrv_close(fd); +} + +static int pcdrv_read(int fd, void* buf, int len) { + if (pcsx_present()) return PCread(fd, buf, len); + return sio_pcdrv_read(fd, buf, len); +} + +static int pcdrv_write(int fd, const void* buf, int len) { + if (pcsx_present()) return PCwrite(fd, buf, len); + return sio_pcdrv_write(fd, buf, len); +} + +static int pcdrv_seek(int fd, int offset, int whence) { + if (pcsx_present()) return PClseek(fd, offset, whence); + return sio_pcdrv_seek(fd, offset, whence); +} + +// ========================================================================= +// SIO1 initialization - 115200 baud, 8N1 +// ========================================================================= + +static void sio1Init() { + SIO1_CTRL = 0; // reset + SIO1_MODE = 0x004e; // MUL16, 8 data, no parity, 1 stop + SIO1_BAUD = (uint16_t)(2073600 / 115200); // = 18 + SIO1_CTRL = 0x0025; // TX enable, RX enable, RTS assert + for (int i = 0; i < 100; i++) { __asm__ volatile("" ::: "memory"); } // settle delay +} + +// ========================================================================= +// Printf redirect - replaces PSYQo's printfStub with SIO1 output +// +// PSYQo's kernel takeover (takeOverKernel) destroys the BIOS and replaces +// the A0/B0/C0 jump handlers. Only A0[0x3F] (printf) is functional; all +// other BIOS calls return immediately. PSYQo's printfStub calls +// syscall_write(1,...) which goes to A0[0x03] - a dead no-op on real HW. +// +// Fix: replace the printf target address embedded in the A0 handler code +// at addresses 0xa8 (lui $t0, hi) and 0xb4 (ori $t0, $t0, lo) with our +// function that outputs directly to SIO1. +// ========================================================================= + +// Printf replacement that sends output to SIO1 +static int sio1Printf(const char* fmt, ...) { + va_list args; + va_start(args, fmt); + int r = vxprintf([](const char* data, int size, void*) { + for (int i = 0; i < size; i++) { + while (!(SIO1_STAT & SIO1_TX_RDY)) {} + SIO1_DATA = (uint8_t)data[i]; + } + }, nullptr, fmt, args); + va_end(args); + return r; +} + +static void redirectPrintfToSIO1() { + uintptr_t addr = (uintptr_t)sio1Printf; + uint16_t hi = (uint16_t)(addr >> 16); + uint16_t lo = (uint16_t)(addr & 0xffff); + if (lo >= 0x8000) hi++; // sign-extension compensation for ori + + // Patch the A0 handler's embedded address: + // 0xa8: lui $t0, hi (opcode 001111, rs=0, rt=$t0=$8) + // 0xb4: ori $t0, $t0, lo (opcode 001101, rs=$t0, rt=$t0) + *(volatile uint32_t*)0xa8 = 0x3C080000 | hi; // lui $t0, hi + *(volatile uint32_t*)0xb4 = 0x35080000 | lo; // ori $t0, $t0, lo + + psyqo::Kernel::flushCache(); +} + +// ========================================================================= +// Master init - call once at startup, after PSYQo initialization +// ========================================================================= + +static void pcdrv_sio1_init() { + if (pcsx_present()) return; // emulator handles PCDRV natively + + sio1Init(); + + // TODO: printf redirect (redirectPrintfToSIO1) disabled for now. + // Printf redirect patches A0 handler machine code at 0xa8/0xb4 + // and may cause instability - needs further testing. +} + +} // namespace psxsplash diff --git a/src/profiler.cpp b/src/profiler.cpp index 715649b..b4f897c 100644 --- a/src/profiler.cpp +++ b/src/profiler.cpp @@ -1,10 +1,11 @@ #include "profiler.hh" -#include "psyqo/xprintf.h" +#ifdef PSXSPLASH_PROFILER using namespace psxsplash::debug; -void pcsxRegisterVariable(void* address, const char* name) { +// Writes address+name to the PCSX-Redux debugger variable registry. +static void pcsxRegisterVariable(void* address, const char* name) { register void* a0 asm("a0") = address; register const char* a1 asm("a1") = name; __asm__ volatile("sb %0, 0x2081(%1)" : : "r"(255), "r"(0x1f800000), "r"(a0), "r"(a1)); @@ -25,8 +26,6 @@ void Profiler::reset() { } } -void Profiler::dumpToTTY() { - printf("profiler.rendering:%d,profiler.lua:%d,profiler.controls:%d,profiler.navmesh:%d\n", sectionTimes[0], sectionTimes[1], sectionTimes[2], sectionTimes[3]); -} +#endif // PSXSPLASH_PROFILER diff --git a/src/profiler.hh b/src/profiler.hh index 93f6a24..0bc4467 100644 --- a/src/profiler.hh +++ b/src/profiler.hh @@ -2,6 +2,8 @@ #include +#ifdef PSXSPLASH_PROFILER + namespace psxsplash::debug { enum ProfilerSection { @@ -21,7 +23,6 @@ public: void initialize(); void reset(); - void dumpToTTY(); void setSectionTime(ProfilerSection section, uint32_t time) { sectionTimes[section] = time; @@ -42,3 +43,5 @@ private: }; } // namespace psxsplash::debug + +#endif // PSXSPLASH_PROFILER diff --git a/src/renderer.cpp b/src/renderer.cpp index 44bea9d..4ae00b5 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -20,246 +21,739 @@ using namespace psyqo::fixed_point_literals; using namespace psyqo::trig_literals; using namespace psyqo::GTE; -psxsplash::Renderer *psxsplash::Renderer::instance = nullptr; +psxsplash::Renderer* psxsplash::Renderer::instance = nullptr; -void psxsplash::Renderer::Init(psyqo::GPU &gpuInstance) { - psyqo::Kernel::assert(instance == nullptr, - "A second intialization of Renderer was tried"); - - clear(); - clear(); - clear(); - - write(psyqo::FixedPoint<16>(160.0).raw()); - write(psyqo::FixedPoint<16>(120.0).raw()); - - write(120); - - write(ORDERING_TABLE_SIZE / 3); - write(ORDERING_TABLE_SIZE / 4); - - if (!instance) { - instance = new Renderer(gpuInstance); - } +void psxsplash::Renderer::Init(psyqo::GPU& gpuInstance) { + psyqo::Kernel::assert(instance == nullptr, + "A second initialization of Renderer was tried"); + clear(); + clear(); + clear(); + write(psyqo::FixedPoint<16>(160.0).raw()); + write(psyqo::FixedPoint<16>(120.0).raw()); + write(PROJ_H); + write(ORDERING_TABLE_SIZE / 3); + write(ORDERING_TABLE_SIZE / 4); + if (!instance) { instance = new Renderer(gpuInstance); } } -void psxsplash::Renderer::SetCamera(psxsplash::Camera &camera) { - m_currentCamera = &camera; +void psxsplash::Renderer::SetCamera(psxsplash::Camera& camera) { m_currentCamera = &camera; } + +void psxsplash::Renderer::SetFog(const FogConfig& fog) { + m_fog = fog; + if (fog.enabled) { + m_clearcolor = fog.color; + write(static_cast(fog.color.r) << 4); + write(static_cast(fog.color.g) << 4); + write(static_cast(fog.color.b) << 4); + m_fog.fogFarSZ = 8000 / fog.density; + } else { + m_clearcolor = {.r = 0, .g = 0, .b = 0}; + m_fog.fogFarSZ = 0; + } } -void psxsplash::Renderer::Render(eastl::vector &objects) { - psyqo::Kernel::assert(m_currentCamera != nullptr, - "PSXSPLASH: Tried to render without an active camera"); - - uint8_t parity = m_gpu.getParity(); - - auto &ot = m_ots[parity]; - auto &clear = m_clear[parity]; - auto &balloc = m_ballocs[parity]; - - balloc.reset(); - eastl::array projected; - for (auto &obj : objects) { - psyqo::Vec3 cameraPosition, objectPosition; - psyqo::Matrix33 finalMatrix; +void psxsplash::Renderer::writeFogRegisters() { + // Per-vertex fog is now computed manually in processTriangle (no DPCT). + // DQA/DQB/RFC/GFC/BFC are no longer needed for fog. + // The fog color is used directly via m_fog.color in the fogBlend function. +} +psyqo::Vec3 psxsplash::Renderer::computeCameraViewPos() { ::clear(); ::clear(); ::clear(); - - // Rotate the camera Translation vector by the camera rotation writeSafe(m_currentCamera->GetRotation()); writeSafe(-m_currentCamera->GetPosition()); - Kernels::mvmva(); - cameraPosition = readSafe(); + return readSafe(); +} - // Rotate the object Translation vector by the camera rotation +void psxsplash::Renderer::setupObjectTransform( + GameObject* obj, const psyqo::Vec3& cameraPosition) { + ::clear(); + ::clear(); + ::clear(); + writeSafe(m_currentCamera->GetRotation()); writeSafe(obj->position); Kernels::mvmva(); - objectPosition = readSafe(); - + psyqo::Vec3 objectPosition = readSafe(); objectPosition.x += cameraPosition.x; objectPosition.y += cameraPosition.y; objectPosition.z += cameraPosition.z; - - // Combine object and camera rotations - MatrixMultiplyGTE(m_currentCamera->GetRotation(), obj->rotation, - &finalMatrix); - - psyqo::GTE::writeSafe( - objectPosition); - psyqo::GTE::writeSafe(finalMatrix); - - for (int i = 0; i < obj->polyCount; i++) { - Tri &tri = obj->polygons[i]; - psyqo::Vec3 result; - - writeSafe(tri.v0); - writeSafe(tri.v1); - writeSafe(tri.v2); - - Kernels::rtpt(); - Kernels::nclip(); - - /*int32_t mac0 = 0; - read(reinterpret_cast(&mac0)); - if (mac0 <= 0) - continue;*/ - - int32_t zIndex = 0; - uint32_t u0, u1, u2; - - read(&u0); - read(&u1); - read(&u2); - - int32_t sz0 = (int32_t)u0; - int32_t sz1 = (int32_t)u1; - int32_t sz2 = (int32_t)u2; - - if ((sz0 < 1 && sz1 < 1 && sz2 < 1)) { - continue; - }; - - zIndex = eastl::max(eastl::max(sz0, sz1), sz2); - if (zIndex < 0 || zIndex >= ORDERING_TABLE_SIZE) - continue; - - read(&projected[0].packed); - read(&projected[1].packed); - read(&projected[2].packed); - - auto &prim = - balloc.allocateFragment(); - - prim.primitive.pointA = projected[0]; - prim.primitive.pointB = projected[1]; - prim.primitive.pointC = projected[2]; - - prim.primitive.uvA = tri.uvA; - prim.primitive.uvB = tri.uvB; - prim.primitive.uvC = tri.uvC; - prim.primitive.tpage = tri.tpage; - psyqo::PrimPieces::ClutIndex clut(tri.clutX, tri.clutY); - prim.primitive.clutIndex = clut; - - prim.primitive.setColorA(tri.colorA); - prim.primitive.setColorB(tri.colorB); - prim.primitive.setColorC(tri.colorC); - prim.primitive.setOpaque(); - - m_ots[m_gpu.getParity()].insert(prim, zIndex); - } - } - m_gpu.getNextClear(clear.primitive, m_clearcolor); - m_gpu.chain(clear); - m_gpu.chain(ot); + psyqo::Matrix33 finalMatrix; + MatrixMultiplyGTE(m_currentCamera->GetRotation(), obj->rotation, &finalMatrix); + writeSafe(objectPosition); + writeSafe(finalMatrix); } -void psxsplash::Renderer::RenderNavmeshPreview(psxsplash::Navmesh navmesh, - bool isOnMesh) { - uint8_t parity = m_gpu.getParity(); - eastl::array projected; +// Per-vertex fog blend: result = vertexColor * (4096 - ir0) / 4096 + fogColor * ir0 / 4096 +static inline psyqo::Color fogBlend(psyqo::Color vc, int32_t ir0, psyqo::Color fogC) { + if (ir0 <= 0) return vc; + if (ir0 >= 4096) return fogC; + int32_t keep = 4096 - ir0; + return { + .r = (uint8_t)((vc.r * keep + fogC.r * ir0) >> 12), + .g = (uint8_t)((vc.g * keep + fogC.g * ir0) >> 12), + .b = (uint8_t)((vc.b * keep + fogC.b * ir0) >> 12), + }; +} - auto &ot = m_ots[parity]; - auto &clear = m_clear[parity]; - auto &balloc = m_ballocs[parity]; - balloc.reset(); +// ============================================================================ +// Core triangle pipeline (Bandwidth's proven approach + fog) +// rtpt -> nclip -> backface cull -> SZ depth -> SXY -> screen clip -> emit +// ============================================================================ - psyqo::Vec3 cameraPosition; - - ::clear(); - ::clear(); - ::clear(); - - // Rotate the camera Translation vector by the camera rotation - writeSafe(m_currentCamera->GetRotation()); - writeSafe(m_currentCamera->GetPosition()); - - Kernels::mvmva(); - cameraPosition = readSafe(); - - write(-cameraPosition.x.raw()); - write(-cameraPosition.y.raw()); - write(-cameraPosition.z.raw()); - - psyqo::GTE::writeSafe( - m_currentCamera->GetRotation()); - - for (int i = 0; i < navmesh.triangleCount; i++) { - NavMeshTri &tri = navmesh.polygons[i]; - psyqo::Vec3 result; +void psxsplash::Renderer::processTriangle( + Tri& tri, int32_t fogFarSZ, + psyqo::OrderingTable& ot, + psyqo::BumpAllocator& balloc) { writeSafe(tri.v0); writeSafe(tri.v1); writeSafe(tri.v2); Kernels::rtpt(); - Kernels::nclip(); - int32_t mac0 = 0; - read(reinterpret_cast(&mac0)); - if (mac0 <= 0) - continue; - - int32_t zIndex = 0; uint32_t u0, u1, u2; - read(&u0); - read(&u1); - read(&u2); + read(&u0); + read(&u1); + read(&u2); + int32_t sz0 = (int32_t)u0, sz1 = (int32_t)u1, sz2 = (int32_t)u2; - int32_t sz0 = *reinterpret_cast(&u0); - int32_t sz1 = *reinterpret_cast(&u1); - int32_t sz2 = *reinterpret_cast(&u2); + if (sz0 < 1 && sz1 < 1 && sz2 < 1) return; + if (fogFarSZ > 0 && sz0 > fogFarSZ && sz1 > fogFarSZ && sz2 > fogFarSZ) return; - zIndex = eastl::max(eastl::max(sz0, sz1), sz2); - if (zIndex < 0 || zIndex >= ORDERING_TABLE_SIZE) - continue; + int32_t zIndex = eastl::max(eastl::max(sz0, sz1), sz2); + if (zIndex < 0 || zIndex >= (int32_t)ORDERING_TABLE_SIZE) return; + // Per-vertex fog: compute fog factor for each vertex individually based on + // its SZ depth. The GPU then interpolates the fogged colors smoothly across + // the triangle surface, eliminating the per-triangle tiling artifacts that + // occur when a single IR0 is used for the whole triangle. + // + // fogIR[i] = 0 means no fog (original color), 4096 = full fog (fog color). + // Quadratic ease-in curve: fog dominates over baked lighting quickly. + int32_t fogIR[3] = {0, 0, 0}; + if (fogFarSZ > 0) { + int32_t fogNear = fogFarSZ / 4; + int32_t range4 = (fogFarSZ - fogNear) >> 4; + if (range4 < 1) range4 = 1; + int32_t scale = 4096 / range4; + int32_t szArr[3] = {sz0, sz1, sz2}; + for (int vi = 0; vi < 3; vi++) { + int32_t ir; + if (szArr[vi] <= fogNear) { + ir = 0; + } else if (szArr[vi] >= fogFarSZ) { + ir = 4096; + } else { + ir = ((szArr[vi] - fogNear) * scale) >> 4; + if (ir > 4096) ir = 4096; + int32_t inv = 4096 - ir; + ir = 4096 - ((inv >> 2) * (inv >> 2) >> 8); + if (ir < 0) ir = 0; + } + fogIR[vi] = ir; + } + } + + psyqo::Vertex projected[3]; read(&projected[0].packed); read(&projected[1].packed); read(&projected[2].packed); - auto &prim = balloc.allocateFragment(); + if (isCompletelyOutside(projected[0], projected[1], projected[2])) return; - prim.primitive.pointA = projected[0]; - prim.primitive.pointB = projected[1]; - prim.primitive.pointC = projected[2]; - - psyqo::Color heightColor; - - if (isOnMesh) { - heightColor.r = 0; - heightColor.g = - ((tri.v0.y.raw() + tri.v1.y.raw() + tri.v2.y.raw()) / 3) * 100 % 256; - heightColor.b = 0; - } else { - heightColor.r = - ((tri.v0.y.raw() + tri.v1.y.raw() + tri.v2.y.raw()) / 3) * 100 % 256; - heightColor.g = 0; - heightColor.b = 0; + // Triangles that need clipping skip nclip entirely. + // nclip with GTE-clamped screen coords gives wrong results for edge triangles. + // The clipper handles them directly - no backface cull needed since the + // clipper preserves winding and degenerate triangles produce zero-area output. + if (needsClipping(projected[0], projected[1], projected[2])) { + ClipVertex cv0 = {(int16_t)projected[0].x, (int16_t)projected[0].y, (int16_t)sz0, + tri.uvA.u, tri.uvA.v, tri.colorA.r, tri.colorA.g, tri.colorA.b}; + ClipVertex cv1 = {(int16_t)projected[1].x, (int16_t)projected[1].y, (int16_t)sz1, + tri.uvB.u, tri.uvB.v, tri.colorB.r, tri.colorB.g, tri.colorB.b}; + ClipVertex cv2 = {(int16_t)projected[2].x, (int16_t)projected[2].y, (int16_t)sz2, + tri.uvC.u, tri.uvC.v, tri.colorC.r, tri.colorC.g, tri.colorC.b}; + ClipResult clipResult; + int clippedCount = clipTriangle(cv0, cv1, cv2, clipResult); + for (int ct = 0; ct < clippedCount; ct++) { + const ClipVertex& a = clipResult.verts[ct*3]; + const ClipVertex& b = clipResult.verts[ct*3+1]; + const ClipVertex& c = clipResult.verts[ct*3+2]; + // For clipped vertices, use per-triangle fog (max SZ) since + // clipped vertex Z values may not map cleanly to the original SZs. + psyqo::Color ca = {a.r, a.g, a.b}, cb = {b.r, b.g, b.b}, cc = {c.r, c.g, c.b}; + if (m_fog.enabled) { + int32_t maxIR = eastl::max(eastl::max(fogIR[0], fogIR[1]), fogIR[2]); + ca = fogBlend(ca, maxIR, m_fog.color); + cb = fogBlend(cb, maxIR, m_fog.color); + cc = fogBlend(cc, maxIR, m_fog.color); + } + if (tri.isUntextured()) { + auto& p = balloc.allocateFragment(); + p.primitive.pointA.x = a.x; p.primitive.pointA.y = a.y; + p.primitive.pointB.x = b.x; p.primitive.pointB.y = b.y; + p.primitive.pointC.x = c.x; p.primitive.pointC.y = c.y; + p.primitive.setColorA(ca); p.primitive.setColorB(cb); p.primitive.setColorC(cc); + p.primitive.setOpaque(); + ot.insert(p, zIndex); + } else { + auto& p = balloc.allocateFragment(); + p.primitive.pointA.x = a.x; p.primitive.pointA.y = a.y; + p.primitive.pointB.x = b.x; p.primitive.pointB.y = b.y; + p.primitive.pointC.x = c.x; p.primitive.pointC.y = c.y; + p.primitive.uvA.u = a.u; p.primitive.uvA.v = a.v; + p.primitive.uvB.u = b.u; p.primitive.uvB.v = b.v; + p.primitive.uvC.u = c.u; p.primitive.uvC.v = c.v; + p.primitive.tpage = tri.tpage; + psyqo::PrimPieces::ClutIndex clut(tri.clutX, tri.clutY); + p.primitive.clutIndex = clut; + p.primitive.setColorA(ca); p.primitive.setColorB(cb); p.primitive.setColorC(cc); + p.primitive.setOpaque(); + ot.insert(p, zIndex); + } + } + return; } - prim.primitive.setColor(heightColor); - prim.primitive.setOpaque(); - ot.insert(prim, zIndex); - } - m_gpu.getNextClear(clear.primitive, m_clearcolor); - m_gpu.chain(clear); - m_gpu.chain(ot); + // Normal path: triangle is fully inside clip region with safe deltas. + // nclip is reliable here since screen coords aren't clamped. + Kernels::nclip(); + int32_t mac0 = 0; + read(reinterpret_cast(&mac0)); + if (mac0 <= 0) return; + + // Per-vertex fog: blend each vertex color toward fog color based on its depth. + // GPU interpolates these smoothly across the triangle - no tiling artifacts. + psyqo::Color cA = tri.colorA, cB = tri.colorB, cC = tri.colorC; + if (m_fog.enabled) { + cA = fogBlend(cA, fogIR[0], m_fog.color); + cB = fogBlend(cB, fogIR[1], m_fog.color); + cC = fogBlend(cC, fogIR[2], m_fog.color); + } + + if (tri.isUntextured()) { + auto& p = balloc.allocateFragment(); + p.primitive.pointA = projected[0]; p.primitive.pointB = projected[1]; p.primitive.pointC = projected[2]; + p.primitive.setColorA(cA); p.primitive.setColorB(cB); p.primitive.setColorC(cC); + p.primitive.setOpaque(); + ot.insert(p, zIndex); + } else { + auto& p = balloc.allocateFragment(); + p.primitive.pointA = projected[0]; p.primitive.pointB = projected[1]; p.primitive.pointC = projected[2]; + p.primitive.uvA = tri.uvA; p.primitive.uvB = tri.uvB; p.primitive.uvC = tri.uvC; + p.primitive.tpage = tri.tpage; + psyqo::PrimPieces::ClutIndex clut(tri.clutX, tri.clutY); + p.primitive.clutIndex = clut; + p.primitive.setColorA(cA); p.primitive.setColorB(cB); p.primitive.setColorC(cC); + p.primitive.setOpaque(); + ot.insert(p, zIndex); + } } -void psxsplash::Renderer::VramUpload(const uint16_t *imageData, int16_t posX, - int16_t posY, int16_t width, - int16_t height) { - psyqo::Rect uploadRect{.a = {.x = posX, .y = posY}, .b = {width, height}}; - m_gpu.uploadToVRAM(imageData, uploadRect); +// ============================================================================ +// Render paths +// ============================================================================ + +void psxsplash::Renderer::Render(eastl::vector& objects) { + psyqo::Kernel::assert(m_currentCamera != nullptr, "PSXSPLASH: Tried to render without an active camera"); + uint8_t parity = m_gpu.getParity(); + auto& ot = m_ots[parity]; auto& clear = m_clear[parity]; auto& balloc = m_ballocs[parity]; + balloc.reset(); + // Set dithering draw mode at the back of the OT so it fires before any geometry. + auto& ditherCmd = balloc.allocateFragment(); + ditherCmd.primitive.attr.setDithering(true); + ot.insert(ditherCmd, ORDERING_TABLE_SIZE - 1); + writeFogRegisters(); + psyqo::Vec3 cameraPosition = computeCameraViewPos(); + int32_t fogFarSZ = m_fog.fogFarSZ; + for (auto& obj : objects) { + setupObjectTransform(obj, cameraPosition); + for (int i = 0; i < obj->polyCount; i++) + processTriangle(obj->polygons[i], fogFarSZ, ot, balloc); + } + m_gpu.getNextClear(clear.primitive, m_clearcolor); + m_gpu.chain(clear); m_gpu.chain(ot); + m_frameCount++; } -psyqo::Color averageColor(const psyqo::Color &a, const psyqo::Color &b) { - return psyqo::Color{static_cast((a.r + b.r) >> 1), - static_cast((a.g + b.g) >> 1), - static_cast((a.b + b.b) >> 1)}; +void psxsplash::Renderer::RenderWithBVH(eastl::vector& objects, const BVHManager& bvh) { + psyqo::Kernel::assert(m_currentCamera != nullptr, "PSXSPLASH: Tried to render without an active camera"); + if (!bvh.isLoaded()) { Render(objects); return; } + uint8_t parity = m_gpu.getParity(); + auto& ot = m_ots[parity]; auto& clear = m_clear[parity]; auto& balloc = m_ballocs[parity]; + balloc.reset(); + auto& ditherCmd2 = balloc.allocateFragment(); + ditherCmd2.primitive.attr.setDithering(true); + ot.insert(ditherCmd2, ORDERING_TABLE_SIZE - 1); + writeFogRegisters(); + Frustum frustum; m_currentCamera->ExtractFrustum(frustum); + int visibleCount = bvh.cullFrustum(frustum, m_visibleRefs, MAX_VISIBLE_TRIANGLES); + psyqo::Vec3 cameraPosition = computeCameraViewPos(); + int32_t fogFarSZ = m_fog.fogFarSZ; + int16_t lastObjectIndex = -1; + for (int i = 0; i < visibleCount; i++) { + const TriangleRef& ref = m_visibleRefs[i]; + if (ref.objectIndex >= objects.size()) continue; + GameObject* obj = objects[ref.objectIndex]; + if (ref.triangleIndex >= obj->polyCount) continue; + if (ref.objectIndex != lastObjectIndex) { + lastObjectIndex = ref.objectIndex; + setupObjectTransform(obj, cameraPosition); + } + processTriangle(obj->polygons[ref.triangleIndex], fogFarSZ, ot, balloc); + } + m_gpu.getNextClear(clear.primitive, m_clearcolor); + m_gpu.chain(clear); m_gpu.chain(ot); + m_frameCount++; } +// ============================================================================ +// RenderWithRooms - Portal/room occlusion for interior scenes +// ============================================================================ + +struct ScreenRect { int16_t minX, minY, maxX, maxY; }; + +static inline bool intersectRect(const ScreenRect& a, const ScreenRect& b, ScreenRect& out) { + out.minX = (a.minX > b.minX) ? a.minX : b.minX; out.minY = (a.minY > b.minY) ? a.minY : b.minY; + out.maxX = (a.maxX < b.maxX) ? a.maxX : b.maxX; out.maxY = (a.maxY < b.maxY) ? a.maxY : b.maxY; + return out.minX < out.maxX && out.minY < out.maxY; +} + +// Safety margin added to portal screen rects (pixels). +// Prevents geometry from popping at portal edges due to fixed-point rounding. +static constexpr int16_t PORTAL_MARGIN = 16; + +// Transform a world-space point to camera space using the view rotation matrix. +static inline void worldToCamera(int32_t wx, int32_t wy, int32_t wz, + int32_t camX, int32_t camY, int32_t camZ, + const psyqo::Matrix33& camRot, + int32_t& outX, int32_t& outY, int32_t& outZ) { + int32_t rx = wx - camX, ry = wy - camY, rz = wz - camZ; + outX = (int32_t)(((int64_t)camRot.vs[0].x.value * rx + (int64_t)camRot.vs[0].y.value * ry + + (int64_t)camRot.vs[0].z.value * rz) >> 12); + outY = (int32_t)(((int64_t)camRot.vs[1].x.value * rx + (int64_t)camRot.vs[1].y.value * ry + + (int64_t)camRot.vs[1].z.value * rz) >> 12); + outZ = (int32_t)(((int64_t)camRot.vs[2].x.value * rx + (int64_t)camRot.vs[2].y.value * ry + + (int64_t)camRot.vs[2].z.value * rz) >> 12); +} + +// Project a camera-space point to screen coordinates. +// Returns false if behind near plane. +static inline bool projectToScreen(int32_t vx, int32_t vy, int32_t vz, + int16_t& sx, int16_t& sy) { + if (vz <= 0) return false; + constexpr int32_t H = 120; + int32_t vzs = vz >> 4; if (vzs <= 0) vzs = 1; + sx = (int16_t)((vx >> 4) * H / vzs + 160); + sy = (int16_t)((vy >> 4) * H / vzs + 120); + return true; +} + +// Project a portal quad to a screen-space AABB. +// Computes the 4 corners, transforms to camera space, clips against the near plane, +// projects visible points to screen, and returns the bounding rect. +static bool projectPortalRect(const psxsplash::PortalData& portal, + int32_t camX, int32_t camY, int32_t camZ, const psyqo::Matrix33& camRot, ScreenRect& outRect) { + + // Compute portal corner offsets in world space. + int32_t rwx = ((int32_t)portal.rightX * portal.halfW) >> 12; + int32_t rwy = ((int32_t)portal.rightY * portal.halfW) >> 12; + int32_t rwz = ((int32_t)portal.rightZ * portal.halfW) >> 12; + int32_t uhx = ((int32_t)portal.upX * portal.halfH) >> 12; + int32_t uhy = ((int32_t)portal.upY * portal.halfH) >> 12; + int32_t uhz = ((int32_t)portal.upZ * portal.halfH) >> 12; + + int32_t cx = portal.centerX, cy = portal.centerY, cz = portal.centerZ; + + // Transform 4 corners to camera space + struct CamVert { int32_t x, y, z; }; + CamVert cv[4]; + int32_t wCorners[4][3] = { + {cx + rwx + uhx, cy + rwy + uhy, cz + rwz + uhz}, + {cx - rwx + uhx, cy - rwy + uhy, cz - rwz + uhz}, + {cx - rwx - uhx, cy - rwy - uhy, cz - rwz - uhz}, + {cx + rwx - uhx, cy + rwy - uhy, cz + rwz - uhz}, + }; + + int behindCount = 0; + for (int i = 0; i < 4; i++) { + worldToCamera(wCorners[i][0], wCorners[i][1], wCorners[i][2], + camX, camY, camZ, camRot, cv[i].x, cv[i].y, cv[i].z); + if (cv[i].z <= 0) behindCount++; + } + + if (behindCount == 4) { + // All corners behind camera. Only allow if camera is very close to portal. + int32_t vx, vy, vz; + worldToCamera(cx, cy, cz, camX, camY, camZ, camRot, vx, vy, vz); + int32_t portalExtent = portal.halfW > portal.halfH ? portal.halfW : portal.halfH; + if (-vz > portalExtent * 2) return false; + outRect = {-512, -512, 832, 752}; + return true; + } + + // Clip against near plane (z=1) and project visible points. + // For each edge where one vertex is in front and one behind, + // compute the intersection point and include it in the screen rect. + constexpr int32_t NEAR_Z = 1; + int16_t sxMin = 32767, sxMax = -32768; + int16_t syMin = 32767, syMax = -32768; + int projCount = 0; + + for (int i = 0; i < 4; i++) { + int j = (i + 1) % 4; + + // Project vertex i if in front + if (cv[i].z > 0) { + int16_t sx, sy; + if (projectToScreen(cv[i].x, cv[i].y, cv[i].z, sx, sy)) { + if (sx < sxMin) sxMin = sx; + if (sx > sxMax) sxMax = sx; + if (sy < syMin) syMin = sy; + if (sy > syMax) syMax = sy; + projCount++; + } + } + + // If edge crosses the near plane, clip and project the intersection. + // All 32-bit arithmetic (no __divdi3 on MIPS R3000). + bool iFront = cv[i].z > 0; + bool jFront = cv[j].z > 0; + if (iFront != jFront) { + int32_t dz = cv[j].z - cv[i].z; + if (dz == 0) continue; + int32_t dzs = dz >> 4; + if (dzs == 0) dzs = (dz > 0) ? 1 : -1; // prevent div-by-zero after shift + // Compute t in 4.12 fixed-point. Shift num/den by 4 to keep * 4096 in 32 bits. + int32_t t12 = (((NEAR_Z - cv[i].z) >> 4) * 4096) / dzs; + // Apply t: clip = cv[i] + (cv[j] - cv[i]) * t12 / 4096 + // Shift dx by 4 so (dx>>4)*t12 fits int32, then >>8 to undo (4+8=12 total) + int32_t clipX = cv[i].x + ((((cv[j].x - cv[i].x) >> 4) * t12) >> 8); + int32_t clipY = cv[i].y + ((((cv[j].y - cv[i].y) >> 4) * t12) >> 8); + int16_t sx, sy; + if (projectToScreen(clipX, clipY, NEAR_Z, sx, sy)) { + if (sx < sxMin) sxMin = sx; + if (sx > sxMax) sxMax = sx; + if (sy < syMin) syMin = sy; + if (sy > syMax) syMax = sy; + projCount++; + } + } + } + + if (projCount == 0) return false; + + outRect = { + (int16_t)(sxMin - PORTAL_MARGIN), (int16_t)(syMin - PORTAL_MARGIN), + (int16_t)(sxMax + PORTAL_MARGIN), (int16_t)(syMax + PORTAL_MARGIN) + }; + return true; +} + +// Test if a room's AABB is potentially visible to the camera frustum. +// Quick rejection test: if the room is entirely behind the camera, skip it. +static bool isRoomPotentiallyVisible(const psxsplash::RoomData& room, + int32_t camX, int32_t camY, int32_t camZ, const psyqo::Matrix33& camRot) { + // Transform the room's AABB center to camera space and check Z. + // Use the p-vertex approach: find the corner most in the camera forward direction. + int32_t fwdX = camRot.vs[2].x.value; + int32_t fwdY = camRot.vs[2].y.value; + int32_t fwdZ = camRot.vs[2].z.value; + + // p-vertex: corner of AABB closest to camera forward direction + int32_t px = (fwdX >= 0) ? room.aabbMaxX : room.aabbMinX; + int32_t py = (fwdY >= 0) ? room.aabbMaxY : room.aabbMinY; + int32_t pz = (fwdZ >= 0) ? room.aabbMaxZ : room.aabbMinZ; + + // If p-vertex is behind camera, the entire AABB is behind + int32_t rx = px - camX, ry = py - camY, rz = pz - camZ; + int64_t dotFwd = ((int64_t)fwdX * rx + (int64_t)fwdY * ry + (int64_t)fwdZ * rz) >> 12; + if (dotFwd < -4096) return false; // Entirely behind with 1-unit margin + + return true; +} + +void psxsplash::Renderer::RenderWithRooms(eastl::vector& objects, + const RoomData* rooms, int roomCount, const PortalData* portals, int portalCount, + const TriangleRef* roomTriRefs, int cameraRoom) { + psyqo::Kernel::assert(m_currentCamera != nullptr, "PSXSPLASH: Tried to render without an active camera"); + if (roomCount == 0 || rooms == nullptr) { Render(objects); return; } + + uint8_t parity = m_gpu.getParity(); + auto& ot = m_ots[parity]; auto& clear = m_clear[parity]; auto& balloc = m_ballocs[parity]; + balloc.reset(); + auto& ditherCmd3 = balloc.allocateFragment(); + ditherCmd3.primitive.attr.setDithering(true); + ot.insert(ditherCmd3, ORDERING_TABLE_SIZE - 1); + writeFogRegisters(); + psyqo::Vec3 cameraPosition = computeCameraViewPos(); + int32_t fogFarSZ = m_fog.fogFarSZ; + int32_t camX = m_currentCamera->GetPosition().x.raw(); + int32_t camY = m_currentCamera->GetPosition().y.raw(); + int32_t camZ = m_currentCamera->GetPosition().z.raw(); + int catchAllIdx = roomCount - 1; + + // If no camera room provided (or invalid), fall back to AABB containment. + // Pick the smallest room whose AABB (with margin) contains the camera. + if (cameraRoom < 0 || cameraRoom >= catchAllIdx) { + constexpr int32_t ROOM_MARGIN = 2048; // 0.5 units in fp12 + int64_t bestVolume = 0x7FFFFFFFFFFFFFFFLL; + for (int r = 0; r < catchAllIdx; r++) { + if (camX >= rooms[r].aabbMinX - ROOM_MARGIN && camX <= rooms[r].aabbMaxX + ROOM_MARGIN && + camY >= rooms[r].aabbMinY - ROOM_MARGIN && camY <= rooms[r].aabbMaxY + ROOM_MARGIN && + camZ >= rooms[r].aabbMinZ - ROOM_MARGIN && camZ <= rooms[r].aabbMaxZ + ROOM_MARGIN) { + int64_t dx = (int64_t)(rooms[r].aabbMaxX - rooms[r].aabbMinX); + int64_t dy = (int64_t)(rooms[r].aabbMaxY - rooms[r].aabbMinY); + int64_t dz = (int64_t)(rooms[r].aabbMaxZ - rooms[r].aabbMinZ); + int64_t vol = dx * dy + dy * dz + dx * dz; + if (vol < bestVolume) { bestVolume = vol; cameraRoom = r; } + } + } + } + + uint32_t visited = 0; + if (catchAllIdx < 32) visited = (1u << catchAllIdx); + const auto& camRot = m_currentCamera->GetRotation(); + + struct Entry { int room; int depth; ScreenRect clip; }; + Entry stack[64]; int top = 0; + + auto renderRoom = [&](int ri) { + const RoomData& rm = rooms[ri]; + int16_t lastObj = -1; + for (int ti = 0; ti < rm.triRefCount; ti++) { + const TriangleRef& ref = roomTriRefs[rm.firstTriRef + ti]; + if (ref.objectIndex >= objects.size()) continue; + GameObject* obj = objects[ref.objectIndex]; + if (ref.triangleIndex >= obj->polyCount) continue; + if (ref.objectIndex != lastObj) { lastObj = ref.objectIndex; setupObjectTransform(obj, cameraPosition); } + processTriangle(obj->polygons[ref.triangleIndex], fogFarSZ, ot, balloc); + } + }; + + // Always render catch-all room (geometry not assigned to any specific room) + renderRoom(catchAllIdx); + + if (cameraRoom >= 0) { + ScreenRect full = {-512, -512, 832, 752}; + if (cameraRoom < 32) visited |= (1u << cameraRoom); + stack[top++] = {cameraRoom, 0, full}; + while (top > 0) { + Entry e = stack[--top]; + renderRoom(e.room); + if (e.depth >= 8) continue; // Depth limit prevents infinite loops + for (int p = 0; p < portalCount; p++) { + int other = -1; + if (portals[p].roomA == e.room) other = portals[p].roomB; + else if (portals[p].roomB == e.room) other = portals[p].roomA; + else continue; + if (other < 0 || other >= roomCount) continue; + if (other < 32 && (visited & (1u << other))) continue; + + // Backface cull: skip portals that face away from the camera. + // The portal normal points from roomA toward roomB (4.12 fp). + // dot(normal, cam - portalCenter) > 0 means the portal faces us when + // traversing A->B; the sign flips when traversing B->A. + { + int32_t dx = camX - portals[p].centerX; + int32_t dy = camY - portals[p].centerY; + int32_t dz = camZ - portals[p].centerZ; + int64_t dot = (int64_t)dx * portals[p].normalX + + (int64_t)dy * portals[p].normalY + + (int64_t)dz * portals[p].normalZ; + // Allow a small negative threshold so nearly-edge-on portals still pass. + const int64_t BACKFACE_THRESHOLD = -4096; + if (portals[p].roomA == e.room) { + if (dot < BACKFACE_THRESHOLD) continue; + } else { + if (dot > -BACKFACE_THRESHOLD) continue; + } + } + + // Phase 4: Frustum-cull the destination room's AABB. + // If the room is entirely behind the camera, skip. + if (!isRoomPotentiallyVisible(rooms[other], camX, camY, camZ, camRot)) { + continue; + } + + // Phase 2: Project actual portal quad corners to screen. + ScreenRect pr; + if (!projectPortalRect(portals[p], camX, camY, camZ, camRot, pr)) { + continue; + } + ScreenRect isect; + if (!intersectRect(e.clip, pr, isect)) { + continue; + } + if (other < 32) visited |= (1u << other); + if (top < 64) stack[top++] = {other, e.depth + 1, isect}; + } + } + } else { + // Camera room unknown - render ALL rooms as safety fallback. + // This guarantees no geometry disappears, at the cost of no culling. + for (int r = 0; r < roomCount; r++) if (r != catchAllIdx) renderRoom(r); + } + +#ifdef PSXSPLASH_ROOM_DEBUG + // ================================================================ + // Debug overlay: room status bars + portal outlines + // ================================================================ + { + static const psyqo::Color roomColors[] = { + {.r = 255, .g = 50, .b = 50}, // R0: red + {.r = 50, .g = 255, .b = 50}, // R1: green + {.r = 50, .g = 50, .b = 255}, // R2: blue + {.r = 255, .g = 255, .b = 50}, // R3: yellow + {.r = 255, .g = 50, .b = 255}, // R4: magenta + {.r = 50, .g = 255, .b = 255}, // R5: cyan + {.r = 255, .g = 128, .b = 50}, // R6: orange + {.r = 128, .g = 128, .b = 255}, // R7: lavender + }; + + // Room status bars at top of screen + for (int r = 0; r < roomCount && r < 8; r++) { + bool rendered = (visited & (1u << r)) != 0; + bool isCamRoom = (r == cameraRoom); + auto& tile = balloc.allocateFragment(); + int16_t x = r * 18 + 2; + tile.primitive.setColor(rendered ? + roomColors[r] : psyqo::Color{.r = 40, .g = 40, .b = 40}); + tile.primitive.rect = psyqo::Rect{ + .a = {.x = x, .y = (int16_t)2}, + .b = {.w = 14, .h = (int16_t)(isCamRoom ? 12 : 6)} + }; + ot.insert(tile, 0); + } + + // Portal outlines: project portal quad and draw edges as thin lines. + // Lines are drawn at OT front (depth 0) so they show through walls. + for (int p = 0; p < portalCount; p++) { + const PortalData& portal = portals[p]; + + // Compute portal corners in world space + int32_t rwx = ((int32_t)portal.rightX * portal.halfW) >> 12; + int32_t rwy = ((int32_t)portal.rightY * portal.halfW) >> 12; + int32_t rwz = ((int32_t)portal.rightZ * portal.halfW) >> 12; + int32_t uhx = ((int32_t)portal.upX * portal.halfH) >> 12; + int32_t uhy = ((int32_t)portal.upY * portal.halfH) >> 12; + int32_t uhz = ((int32_t)portal.upZ * portal.halfH) >> 12; + + int32_t cx = portal.centerX, cy = portal.centerY, cz = portal.centerZ; + struct { int32_t wx, wy, wz; } corners[4] = { + {cx + rwx + uhx, cy + rwy + uhy, cz + rwz + uhz}, + {cx - rwx + uhx, cy - rwy + uhy, cz - rwz + uhz}, + {cx - rwx - uhx, cy - rwy - uhy, cz - rwz - uhz}, + {cx + rwx - uhx, cy + rwy - uhy, cz + rwz - uhz}, + }; + + // Project corners to screen + int16_t sx[4], sy[4]; + bool vis[4]; + int visCount = 0; + for (int i = 0; i < 4; i++) { + int32_t vx, vy, vz; + worldToCamera(corners[i].wx, corners[i].wy, corners[i].wz, + camX, camY, camZ, camRot, vx, vy, vz); + vis[i] = projectToScreen(vx, vy, vz, sx[i], sy[i]); + if (vis[i]) visCount++; + } + if (visCount < 2) continue; // Can't draw edges with <2 visible corners + + // Draw each edge as a degenerate triangle (line). + // Color: orange for portal between visible rooms, dim for invisible. + bool portalActive = (visited & (1u << portal.roomA)) || (visited & (1u << portal.roomB)); + psyqo::Color lineColor = portalActive ? + psyqo::Color{.r = 255, .g = 160, .b = 0} : + psyqo::Color{.r = 80, .g = 60, .b = 0}; + + for (int i = 0; i < 4; i++) { + int j = (i + 1) % 4; + if (!vis[i] || !vis[j]) continue; + // Clamp to screen to avoid GPU issues + int16_t x0 = sx[i], y0 = sy[i], x1 = sx[j], y1 = sy[j]; + if (x0 < 0) x0 = 0; if (x0 > 319) x0 = 319; + if (y0 < 0) y0 = 0; if (y0 > 239) y0 = 239; + if (x1 < 0) x1 = 0; if (x1 > 319) x1 = 319; + if (y1 < 0) y1 = 0; if (y1 > 239) y1 = 239; + + // Draw line as degenerate triangle (A=B=start, C=end gives a 1px line) + auto& tri = balloc.allocateFragment(); + tri.primitive.pointA.x = x0; tri.primitive.pointA.y = y0; + tri.primitive.pointB.x = x1; tri.primitive.pointB.y = y1; + tri.primitive.pointC.x = x1; tri.primitive.pointC.y = (int16_t)(y1 + 1); + tri.primitive.setColorA(lineColor); + tri.primitive.setColorB(lineColor); + tri.primitive.setColorC(lineColor); + tri.primitive.setOpaque(); + ot.insert(tri, 0); + } + } + + // Room AABB outlines: project the 8 corners of each room's AABB and draw edges. + for (int r = 0; r < roomCount - 1 && r < 8; r++) { + bool rendered = (visited & (1u << r)) != 0; + psyqo::Color boxColor = rendered ? + roomColors[r] : psyqo::Color{.r = 60, .g = 60, .b = 60}; + + const RoomData& rm = rooms[r]; + int32_t bmin[3] = {rm.aabbMinX, rm.aabbMinY, rm.aabbMinZ}; + int32_t bmax[3] = {rm.aabbMaxX, rm.aabbMaxY, rm.aabbMaxZ}; + + // 8 corners of the AABB + int16_t csx[8], csy[8]; + bool cvis[8]; + int cvisCount = 0; + for (int i = 0; i < 8; i++) { + int32_t wx = (i & 1) ? bmax[0] : bmin[0]; + int32_t wy = (i & 2) ? bmax[1] : bmin[1]; + int32_t wz = (i & 4) ? bmax[2] : bmin[2]; + int32_t vx, vy, vz; + worldToCamera(wx, wy, wz, camX, camY, camZ, camRot, vx, vy, vz); + cvis[i] = projectToScreen(vx, vy, vz, csx[i], csy[i]); + if (cvis[i]) cvisCount++; + } + if (cvisCount < 2) continue; + + // Draw 12 AABB edges + static const int edges[12][2] = { + {0,1},{2,3},{4,5},{6,7}, // X-axis edges + {0,2},{1,3},{4,6},{5,7}, // Y-axis edges + {0,4},{1,5},{2,6},{3,7}, // Z-axis edges + }; + for (int e = 0; e < 12; e++) { + int a = edges[e][0], b = edges[e][1]; + if (!cvis[a] || !cvis[b]) continue; + int16_t x0 = csx[a], y0 = csy[a], x1 = csx[b], y1 = csy[b]; + if (x0 < 0) x0 = 0; if (x0 > 319) x0 = 319; + if (y0 < 0) y0 = 0; if (y0 > 239) y0 = 239; + if (x1 < 0) x1 = 0; if (x1 > 319) x1 = 319; + if (y1 < 0) y1 = 0; if (y1 > 239) y1 = 239; + + auto& tri = balloc.allocateFragment(); + tri.primitive.pointA.x = x0; tri.primitive.pointA.y = y0; + tri.primitive.pointB.x = x1; tri.primitive.pointB.y = y1; + tri.primitive.pointC.x = x1; tri.primitive.pointC.y = (int16_t)(y1 + 1); + tri.primitive.setColorA(boxColor); + tri.primitive.setColorB(boxColor); + tri.primitive.setColorC(boxColor); + tri.primitive.setOpaque(); + ot.insert(tri, 0); + } + } + } +#endif + + m_gpu.getNextClear(clear.primitive, m_clearcolor); + m_gpu.chain(clear); m_gpu.chain(ot); + m_frameCount++; +} + +void psxsplash::Renderer::VramUpload(const uint16_t* imageData, int16_t posX, + int16_t posY, int16_t width, int16_t height) { + psyqo::Rect uploadRect{.a = {.x = posX, .y = posY}, .b = {width, height}}; + m_gpu.uploadToVRAM(imageData, uploadRect); +} diff --git a/src/renderer.hh b/src/renderer.hh index 4244b81..23878fd 100644 --- a/src/renderer.hh +++ b/src/renderer.hh @@ -13,33 +13,51 @@ #include #include +#include "bvh.hh" #include "camera.hh" #include "gameobject.hh" -#include "navmesh.hh" +#include "triclip.hh" namespace psxsplash { +struct FogConfig { + bool enabled = false; + psyqo::Color color = {.r = 0, .g = 0, .b = 0}; + uint8_t density = 5; + int32_t fogFarSZ = 0; +}; + class Renderer final { public: Renderer(const Renderer&) = delete; Renderer& operator=(const Renderer&) = delete; - // FIXME: I have no idea how to precompute the required sizes of these. It would be best to allocate them based on - // the scene - static constexpr size_t ORDERING_TABLE_SIZE = 2048 * 3; + static constexpr size_t ORDERING_TABLE_SIZE = 2048 * 8; static constexpr size_t BUMP_ALLOCATOR_SIZE = 8096 * 24; + static constexpr size_t MAX_VISIBLE_TRIANGLES = 4096; + + static constexpr int32_t PROJ_H = 120; + static constexpr int32_t SCREEN_CX = 160; + static constexpr int32_t SCREEN_CY = 120; static void Init(psyqo::GPU& gpuInstance); - void SetCamera(Camera& camera); + void SetFog(const FogConfig& fog); void Render(eastl::vector& objects); - void RenderNavmeshPreview(psxsplash::Navmesh navmesh, bool isOnMesh); + void RenderWithBVH(eastl::vector& objects, const BVHManager& bvh); + void RenderWithRooms(eastl::vector& objects, + const RoomData* rooms, int roomCount, + const PortalData* portals, int portalCount, + const TriangleRef* roomTriRefs, + int cameraRoom = -1); - void VramUpload(const uint16_t* imageData, int16_t posX, int16_t posY, int16_t width, int16_t height); + void VramUpload(const uint16_t* imageData, int16_t posX, int16_t posY, + int16_t width, int16_t height); static Renderer& GetInstance() { - psyqo::Kernel::assert(instance != nullptr, "Access to renderer was tried without prior initialization"); + psyqo::Kernel::assert(instance != nullptr, + "Access to renderer was tried without prior initialization"); return *instance; } @@ -49,8 +67,7 @@ class Renderer final { Renderer(psyqo::GPU& gpuInstance) : m_gpu(gpuInstance) {} ~Renderer() {} - Camera* m_currentCamera; - + Camera* m_currentCamera = nullptr; psyqo::GPU& m_gpu; psyqo::Trig<> m_trig; @@ -58,10 +75,21 @@ class Renderer final { psyqo::Fragments::SimpleFragment m_clear[2]; psyqo::BumpAllocator m_ballocs[2]; + FogConfig m_fog; psyqo::Color m_clearcolor = {.r = 0, .g = 0, .b = 0}; - void recursiveSubdivideAndRender(Tri& tri, eastl::array& projected, int zIndex, - int maxIterations); + TriangleRef m_visibleRefs[MAX_VISIBLE_TRIANGLES]; + int m_frameCount = 0; + + void writeFogRegisters(); + psyqo::Vec3 computeCameraViewPos(); + void setupObjectTransform(GameObject* obj, const psyqo::Vec3& cameraPosition); + + // Core triangle pipeline: rtpt -> nclip -> screen-space clip -> emit. + // Uses Bandwidth's proven approach: nclip always, max-SZ depth, screen clip. + void processTriangle(Tri& tri, int32_t fogFarSZ, + psyqo::OrderingTable& ot, + psyqo::BumpAllocator& balloc); }; -} // namespace psxsplash \ No newline at end of file +} // namespace psxsplash diff --git a/src/sceneloader.cpp b/src/sceneloader.cpp new file mode 100644 index 0000000..e5a23fd --- /dev/null +++ b/src/sceneloader.cpp @@ -0,0 +1,65 @@ +#include "sceneloader.hh" + +// Unified PCDRV API with runtime dispatch: +// - Emulator: break instructions (intercepted by pcsx-redux) +// - Real hardware: direct SIO1 protocol (no break instructions) +#include "pcdrv_handler.hh" + +namespace psxsplash { + +bool SceneLoader::s_pcdrvAvailable = false; + +bool SceneLoader::Init() { + s_pcdrvAvailable = (pcdrv_init() == 0); + return s_pcdrvAvailable; +} + +bool SceneLoader::IsPCdrvAvailable() { + return s_pcdrvAvailable; +} + +uint8_t* SceneLoader::LoadFile(const char* filename, int& outSize) { + outSize = 0; + + if (!s_pcdrvAvailable) { + return nullptr; + } + + // Open the file (read-only, flags=0, perms=0) + int fd = pcdrv_open(filename, 0, 0); + if (fd < 0) { + return nullptr; + } + + // Get file size by seeking to end + int size = pcdrv_seek(fd, 0, 2); // SEEK_END = 2 + if (size <= 0) { + pcdrv_close(fd); + return nullptr; + } + + // Seek back to start + pcdrv_seek(fd, 0, 0); // SEEK_SET = 0 + + // Allocate buffer (aligned to 4 bytes for struct casting) + int alignedSize = (size + 3) & ~3; + uint8_t* buffer = new uint8_t[alignedSize]; + + // Read the file + int bytesRead = pcdrv_read(fd, buffer, size); + pcdrv_close(fd); + + if (bytesRead != size) { + delete[] buffer; + return nullptr; + } + + outSize = size; + return buffer; +} + +void SceneLoader::FreeFile(uint8_t* data) { + delete[] data; +} + +} // namespace psxsplash diff --git a/src/sceneloader.hh b/src/sceneloader.hh new file mode 100644 index 0000000..dca2a50 --- /dev/null +++ b/src/sceneloader.hh @@ -0,0 +1,49 @@ +#pragma once + +#include + +namespace psxsplash { + +/** + * SceneLoader — loads splashpack files from PCdrv (emulator) or CD-ROM. + * + * In emulator (PCdrv) mode, files are loaded via the host filesystem using + * the PCdrv protocol (break instructions intercepted by PCSX-Redux). + * + * In CD-ROM mode (future), files would be loaded from the disc. + * + * The loader allocates memory for the file content and returns a pointer + * to the caller. The caller owns the memory. + */ +class SceneLoader { + public: + /** + * Initialize the loader. Must be called once at startup. + * Returns true if PCdrv is available, false otherwise. + */ + static bool Init(); + + /** + * Load a file by name. Returns a pointer to the loaded data. + * The data is allocated with new[] and the caller owns it. + * @param filename The filename to load (relative to pcdrvbase). + * @param outSize Receives the file size in bytes. + * @return Pointer to loaded data, or nullptr on failure. + */ + static uint8_t* LoadFile(const char* filename, int& outSize); + + /** + * Free previously loaded file data. + */ + static void FreeFile(uint8_t* data); + + /** + * Returns true if PCdrv is available. + */ + static bool IsPCdrvAvailable(); + + private: + static bool s_pcdrvAvailable; +}; + +} // namespace psxsplash diff --git a/src/scenemanager.cpp b/src/scenemanager.cpp index d442436..623678f 100644 --- a/src/scenemanager.cpp +++ b/src/scenemanager.cpp @@ -2,29 +2,76 @@ #include -#include "navmesh.hh" +#include "collision.hh" #include "profiler.hh" #include "renderer.hh" #include "splashpack.hh" +#include "luaapi.hh" #include "lua.h" using namespace psyqo::trig_literals; +using namespace psyqo::fixed_point_literals; + +using namespace psxsplash; + void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData) { - L.Init(); - - debug::Profiler::getInstance().initialize(); - + L.Reset(); + + // Initialize audio system + m_audio.init(); + + // Register the Lua API + LuaAPI::RegisterAll(L.getState(), this); +#ifdef PSXSPLASH_PROFILER debug::Profiler::getInstance().initialize(); +#endif SplashpackSceneSetup sceneSetup; m_loader.LoadSplashpack(splashpackData, sceneSetup); m_luaFiles = std::move(sceneSetup.luaFiles); m_gameObjects = std::move(sceneSetup.objects); - m_navmeshes = std::move(sceneSetup.navmeshes); + m_objectNames = std::move(sceneSetup.objectNames); + m_bvh = sceneSetup.bvh; // Copy BVH for frustum culling + m_worldCollision = sceneSetup.worldCollision; // World collision soup (v7+) + m_navRegions = sceneSetup.navRegions; // Nav region system (v7+) + m_playerNavRegion = m_navRegions.isLoaded() ? m_navRegions.getStartRegion() : NAV_NO_REGION; + + // Scene type and render path + m_sceneType = sceneSetup.sceneType; + + // Room/portal data for interior scenes (v11+) + m_rooms = sceneSetup.rooms; + m_roomCount = sceneSetup.roomCount; + m_portals = sceneSetup.portals; + m_portalCount = sceneSetup.portalCount; + m_roomTriRefs = sceneSetup.roomTriRefs; + m_roomTriRefCount = sceneSetup.roomTriRefCount; + + // Configure fog from splashpack data (v11+) + if (sceneSetup.fogEnabled) { + psxsplash::FogConfig fogCfg; + fogCfg.enabled = true; + fogCfg.color = {.r = sceneSetup.fogR, .g = sceneSetup.fogG, .b = sceneSetup.fogB}; + fogCfg.density = sceneSetup.fogDensity; + Renderer::GetInstance().SetFog(fogCfg); + } else { + psxsplash::FogConfig fogCfg; + fogCfg.enabled = false; + Renderer::GetInstance().SetFog(fogCfg); + } + // Copy component arrays + m_interactables = std::move(sceneSetup.interactables); + + // Load audio clips into SPU RAM + m_audioClipNames = std::move(sceneSetup.audioClipNames); + for (size_t i = 0; i < sceneSetup.audioClips.size(); i++) { + auto& clip = sceneSetup.audioClips[i]; + m_audio.loadClip((int)i, clip.adpcmData, clip.sizeBytes, clip.sampleRate, clip.loop); + } m_playerPosition = sceneSetup.playerStartPosition; @@ -34,6 +81,49 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData) { m_playerHeight = sceneSetup.playerHeight; + // Load movement parameters from splashpack (v8+) + m_controls.setMoveSpeed(sceneSetup.moveSpeed); + m_controls.setSprintSpeed(sceneSetup.sprintSpeed); + m_playerRadius = (int32_t)sceneSetup.playerRadius.value; + if (m_playerRadius == 0) m_playerRadius = PLAYER_RADIUS; // fallback to default + m_jumpVelocityRaw = (int32_t)sceneSetup.jumpVelocity.value; + int32_t gravityRaw = (int32_t)sceneSetup.gravity.value; + m_gravityPerFrame = gravityRaw / 30; // Convert per-second² to per-frame velocity change + if (m_gravityPerFrame == 0 && gravityRaw > 0) m_gravityPerFrame = 1; // Ensure nonzero + m_velocityY = 0; + m_isGrounded = true; + m_lastFrameTime = 0; + m_deltaFrames = 1; + + // Initialize collision system + m_collisionSystem.init(); + + // Register colliders from splashpack data + for (size_t i = 0; i < sceneSetup.colliders.size(); i++) { + SPLASHPACKCollider* collider = sceneSetup.colliders[i]; + if (collider == nullptr) continue; + + // Convert fixed-point values from binary format to AABB + AABB bounds; + bounds.min.x.value = collider->minX; + bounds.min.y.value = collider->minY; + bounds.min.z.value = collider->minZ; + bounds.max.x.value = collider->maxX; + bounds.max.y.value = collider->maxY; + bounds.max.z.value = collider->maxZ; + + // Convert collision type + CollisionType type = static_cast(collider->collisionType); + + // Register with collision system + m_collisionSystem.registerCollider( + collider->gameObjectIndex, + bounds, + type, + collider->layerMask + ); + } + // Load Lua files - order is important here. We need // to load the Lua files before we register the game objects, // as the game objects may reference Lua files by index. @@ -58,49 +148,470 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData) { } void psxsplash::SceneManager::GameTick(psyqo::GPU &gpu) { + LuaAPI::IncrementFrameCount(); + // Delta-time measurement: count elapsed frames based on gpu timer + // PS1 NTSC frame = ~33333 microseconds (30fps vsync) + { + uint32_t now = gpu.now(); + if (m_lastFrameTime != 0) { + uint32_t elapsed = now - m_lastFrameTime; + // 33333us per frame at 30fps. If >50000us, we dropped a frame. + m_deltaFrames = (elapsed > 50000) ? 2 : 1; + if (elapsed > 83000) m_deltaFrames = 3; // Two frames dropped + } + m_lastFrameTime = now; + } uint32_t renderingStart = gpu.now(); auto& renderer = psxsplash::Renderer::GetInstance(); - renderer.Render(m_gameObjects); + // Dispatch render path based on scene type. + // Interior scenes (type 1) use room/portal occlusion; exterior scenes use BVH culling. + if (m_sceneType == 1 && m_roomCount > 0 && m_rooms != nullptr) { + // Get camera room from nav region system (authoritative) instead of AABB guessing. + // NavRegion::roomIndex is set during export from the room each region belongs to. + int camRoom = -1; + if (m_navRegions.isLoaded() && m_playerNavRegion != NAV_NO_REGION) { + uint8_t ri = m_navRegions.getRoomIndex(m_playerNavRegion); + if (ri != 0xFF) camRoom = (int)ri; + } + renderer.RenderWithRooms(m_gameObjects, m_rooms, m_roomCount, + m_portals, m_portalCount, m_roomTriRefs, camRoom); + } else { + renderer.RenderWithBVH(m_gameObjects, m_bvh); + } gpu.pumpCallbacks(); uint32_t renderingEnd = gpu.now(); uint32_t renderingTime = renderingEnd - renderingStart; - psxsplash::debug::Profiler::getInstance().setSectionTime(psxsplash::debug::PROFILER_RENDERING, renderingTime); +#ifdef PSXSPLASH_PROFILER + psxsplash::debug::Profiler::getInstance().setSectionTime(psxsplash::debug::PROFILER_RENDERING, renderingTime); +#endif + + // Collision detection + uint32_t collisionStart = gpu.now(); + int collisionCount = m_collisionSystem.detectCollisions(); + + // Process solid collisions - call OnCollision on BOTH objects + const CollisionResult* results = m_collisionSystem.getResults(); + for (int i = 0; i < collisionCount; i++) { + auto* objA = getGameObject(results[i].objectA); + auto* objB = getGameObject(results[i].objectB); + if (objA && objB) { + L.OnCollision(objA, objB); + L.OnCollision(objB, objA); // Call on both objects + } + } + + // Process trigger events (enter/stay/exit) + m_collisionSystem.processTriggerEvents(*this); + + gpu.pumpCallbacks(); + uint32_t collisionEnd = gpu.now(); + uint32_t luaStart = gpu.now(); - L.OnCollision(m_gameObjects[1], m_gameObjects[0]); // Example call, replace with actual logic + // Lua update tick - call onUpdate for all registered objects with onUpdate handler + for (auto* go : m_gameObjects) { + if (go && go->isActive()) { + L.OnUpdate(go, m_deltaFrames); + } + } gpu.pumpCallbacks(); uint32_t luaEnd = gpu.now(); uint32_t luaTime = luaEnd - luaStart; - psxsplash::debug::Profiler::getInstance().setSectionTime(psxsplash::debug::PROFILER_LUA, luaTime); +#ifdef PSXSPLASH_PROFILER + psxsplash::debug::Profiler::getInstance().setSectionTime(psxsplash::debug::PROFILER_LUA, luaTime); +#endif + + // Update game systems + processEnableDisableEvents(); uint32_t controlsStart = gpu.now(); - m_controls.HandleControls(m_playerPosition, playerRotationX, playerRotationY, playerRotationZ, false, 1); + + // Update button state tracking first + m_controls.UpdateButtonStates(); + + // Update interaction system (checks for interact button press) + updateInteractionSystem(); + + // Dispatch button events to all objects + uint16_t pressed = m_controls.getButtonsPressed(); + uint16_t released = m_controls.getButtonsReleased(); + + if (pressed || released) { + // Only iterate objects if there are button events + for (auto* go : m_gameObjects) { + if (!go || !go->isActive()) continue; + + if (pressed) { + // Dispatch press events for each pressed button + for (int btn = 0; btn < 16; btn++) { + if (pressed & (1 << btn)) { + L.OnButtonPress(go, btn); + } + } + } + if (released) { + // Dispatch release events for each released button + for (int btn = 0; btn < 16; btn++) { + if (released & (1 << btn)) { + L.OnButtonRelease(go, btn); + } + } + } + } + } + + // Save position BEFORE movement for collision detection + psyqo::Vec3 oldPlayerPosition = m_playerPosition; + + m_controls.HandleControls(m_playerPosition, playerRotationX, playerRotationY, playerRotationZ, freecam, m_deltaFrames); + + // Jump input: Cross button triggers jump when grounded + if (m_isGrounded && m_controls.wasButtonPressed(psyqo::AdvancedPad::Button::Cross)) { + m_velocityY = -m_jumpVelocityRaw; // Negative = upward (PSX Y-down) + m_isGrounded = false; + } gpu.pumpCallbacks(); uint32_t controlsEnd = gpu.now(); uint32_t controlsTime = controlsEnd - controlsStart; - psxsplash::debug::Profiler::getInstance().setSectionTime(psxsplash::debug::PROFILER_CONTROLS, controlsTime); +#ifdef PSXSPLASH_PROFILER + psxsplash::debug::Profiler::getInstance().setSectionTime(psxsplash::debug::PROFILER_CONTROLS, controlsTime); +#endif uint32_t navmeshStart = gpu.now(); if (!freecam) { - psxsplash::ComputeNavmeshPosition(m_playerPosition, *m_navmeshes[0], - static_cast>(m_playerHeight)); + // Priority: WorldCollision + NavRegions (v7) > NavGrid (v5) > Legacy Navmesh + if (m_worldCollision.isLoaded()) { + // Move-and-slide against world geometry (XZ walls only) + psyqo::Vec3 slid = m_worldCollision.moveAndSlide( + oldPlayerPosition, m_playerPosition, m_playerRadius, 0xFF); + + + m_playerPosition.x = slid.x; + m_playerPosition.z = slid.z; + + // Apply gravity: velocity changes each frame + for (int f = 0; f < m_deltaFrames; f++) { + m_velocityY += m_gravityPerFrame; + } + + // Apply vertical velocity to position + // velocityY is in fp12 per-second; convert per-frame: pos += vel / 30 + int32_t posYDelta = (m_velocityY * m_deltaFrames) / 30; + m_playerPosition.y.value += posYDelta; + + // Resolve floor Y from nav regions if available + if (m_navRegions.isLoaded()) { + uint16_t prevRegion = m_playerNavRegion; + int32_t px = m_playerPosition.x.value; + int32_t pz = m_playerPosition.z.value; + int32_t floorY = m_navRegions.resolvePosition( + px, pz, m_playerNavRegion); + + + if (m_playerNavRegion != NAV_NO_REGION) { + m_playerPosition.x.value = px; + m_playerPosition.z.value = pz; + + // Ground (feet) position in PSX coords: + // Camera is at position.y, feet are at position.y + playerHeight + // (Y-down: larger Y = lower) + int32_t cameraAtFloor = floorY - m_playerHeight.raw(); + + if (m_playerPosition.y.value >= cameraAtFloor) { + // Player is at or below floor — snap to ground + m_playerPosition.y.value = cameraAtFloor; + m_velocityY = 0; + m_isGrounded = true; + } else { + // Player is above floor (jumping/airborne) + m_isGrounded = false; + } + } else { + // Off all nav regions — revert to old position + m_playerPosition = oldPlayerPosition; + m_playerNavRegion = prevRegion; + m_velocityY = 0; + m_isGrounded = true; + } + } else { + // Ground trace fallback (no nav regions) + int32_t groundY; + int32_t groundNormalY; + uint8_t surfFlags; + if (m_worldCollision.groundTrace(m_playerPosition, + 4096 * 4, // max 4 units down + groundY, groundNormalY, surfFlags, 0xFF)) { + int32_t cameraAtFloor = groundY - m_playerHeight.raw(); + if (m_playerPosition.y.value >= cameraAtFloor) { + m_playerPosition.y.value = cameraAtFloor; + m_velocityY = 0; + m_isGrounded = true; + } else { + m_isGrounded = false; + } + } else { + m_playerPosition = oldPlayerPosition; + m_velocityY = 0; + m_isGrounded = true; + } + } + + // Ceiling check: if jumping upward, check for ceiling collision + if (m_velocityY < 0 && m_worldCollision.isLoaded()) { + int32_t ceilingY; + if (m_worldCollision.ceilingTrace(m_playerPosition, + m_playerHeight.raw(), ceilingY, 0xFF)) { + // Hit a ceiling — stop upward velocity + m_velocityY = 0; + } + } + } } + + + gpu.pumpCallbacks(); uint32_t navmeshEnd = gpu.now(); uint32_t navmeshTime = navmeshEnd - navmeshStart; - psxsplash::debug::Profiler::getInstance().setSectionTime(psxsplash::debug::PROFILER_NAVMESH, navmeshTime); +#ifdef PSXSPLASH_PROFILER + psxsplash::debug::Profiler::getInstance().setSectionTime(psxsplash::debug::PROFILER_NAVMESH, navmeshTime); +#endif m_currentCamera.SetPosition(static_cast>(m_playerPosition.x), static_cast>(m_playerPosition.y), static_cast>(m_playerPosition.z)); m_currentCamera.SetRotation(playerRotationX, playerRotationY, playerRotationZ); - psxsplash::debug::Profiler::getInstance().dumpToTTY(); + // Process pending scene transitions (at end of frame) + processPendingSceneLoad(); +} +// Trigger event callbacks +void psxsplash::SceneManager::fireTriggerEnter(uint16_t triggerObjIdx, uint16_t otherObjIdx) { + auto* trigger = getGameObject(triggerObjIdx); + auto* other = getGameObject(otherObjIdx); + if (trigger && other) { + L.OnTriggerEnter(trigger, other); + } +} +void psxsplash::SceneManager::fireTriggerStay(uint16_t triggerObjIdx, uint16_t otherObjIdx) { + auto* trigger = getGameObject(triggerObjIdx); + auto* other = getGameObject(otherObjIdx); + if (trigger && other) { + L.OnTriggerStay(trigger, other); + } +} -} \ No newline at end of file +void psxsplash::SceneManager::fireTriggerExit(uint16_t triggerObjIdx, uint16_t otherObjIdx) { + auto* trigger = getGameObject(triggerObjIdx); + auto* other = getGameObject(otherObjIdx); + if (trigger && other) { + L.OnTriggerExit(trigger, other); + } +} + +// ============================================================================ +// INTERACTION SYSTEM +// ============================================================================ + +void psxsplash::SceneManager::updateInteractionSystem() { + // Get interact button state - Cross button by default + auto interactButton = psyqo::AdvancedPad::Button::Cross; + bool buttonPressed = m_controls.wasButtonPressed(interactButton); + + if (!buttonPressed) return; // Early out if no interaction attempt + + // Player position for distance check + psyqo::FixedPoint<12> playerX = static_cast>(m_playerPosition.x); + psyqo::FixedPoint<12> playerY = static_cast>(m_playerPosition.y); + psyqo::FixedPoint<12> playerZ = static_cast>(m_playerPosition.z); + + // Find closest interactable in range + Interactable* closest = nullptr; + psyqo::FixedPoint<12> closestDistSq; + closestDistSq.value = 0x7FFFFFFF; // Max positive value + + for (auto* interactable : m_interactables) { + if (!interactable || !interactable->canInteract()) continue; + + // Check if object is active + auto* go = getGameObject(interactable->gameObjectIndex); + if (!go || !go->isActive()) continue; + + // Calculate distance squared + psyqo::FixedPoint<12> dx = playerX - interactable->offsetX - go->position.x; + psyqo::FixedPoint<12> dy = playerY - interactable->offsetY - go->position.y; + psyqo::FixedPoint<12> dz = playerZ - interactable->offsetZ - go->position.z; + + psyqo::FixedPoint<12> distSq = dx * dx + dy * dy + dz * dz; + + // Check if in range and closer than current closest + if (distSq <= interactable->radiusSquared && distSq < closestDistSq) { + closest = interactable; + closestDistSq = distSq; + } + } + + // Interact with closest + if (closest != nullptr) { + triggerInteraction(getGameObject(closest->gameObjectIndex)); + closest->triggerCooldown(); + } +} + +void psxsplash::SceneManager::triggerInteraction(GameObject* interactable) { + if (!interactable) return; + L.OnInteract(interactable); +} + +// ============================================================================ +// ENABLE/DISABLE SYSTEM +// ============================================================================ + +void psxsplash::SceneManager::setObjectActive(GameObject* go, bool active) { + if (!go) return; + + bool wasActive = go->isActive(); + if (wasActive == active) return; // No change + + go->setActive(active); + + // Fire appropriate event + if (active) { + L.OnEnable(go); + } else { + L.OnDisable(go); + } +} + +void psxsplash::SceneManager::processEnableDisableEvents() { + // Process any pending enable/disable flags (for batched operations) + for (auto* go : m_gameObjects) { + if (!go) continue; + + if (go->isPendingEnable()) { + go->setPendingEnable(false); + if (!go->isActive()) { + go->setActive(true); + L.OnEnable(go); + } + } + + if (go->isPendingDisable()) { + go->setPendingDisable(false); + if (go->isActive()) { + go->setActive(false); + L.OnDisable(go); + } + } + } +} + +// ============================================================================ +// SCENE LOADING (PCdrv multi-scene) +// ============================================================================ + +void psxsplash::SceneManager::requestSceneLoad(int sceneIndex) { + if (sceneIndex == m_currentSceneIndex) return; + m_pendingSceneIndex = sceneIndex; +} + +void psxsplash::SceneManager::processPendingSceneLoad() { + if (m_pendingSceneIndex < 0) return; + + int targetIndex = m_pendingSceneIndex; + m_pendingSceneIndex = -1; + + // Build filename: scene_N.splashpack + char filename[32]; + snprintf(filename, sizeof(filename), "scene_%d.splashpack", targetIndex); + + // 1. Tear down EVERYTHING in the current scene first — + // Lua VM, vector backing storage, audio. This returns as much + // heap memory as possible before any new allocation. + clearScene(); + + // 2. Free old splashpack data BEFORE loading the new one. + // This avoids having both scene buffers in the heap simultaneously, + // which is the primary source of fragmentation that prevents + // the Lua compiler from finding large contiguous blocks. + if (m_currentSceneData) { + SceneLoader::FreeFile(m_currentSceneData); + m_currentSceneData = nullptr; + } + + // 3. Allocate new scene data (heap is now maximally consolidated) + int fileSize = 0; + uint8_t* newData = SceneLoader::LoadFile(filename, fileSize); + if (!newData) return; + + m_currentSceneData = newData; + m_currentSceneIndex = targetIndex; + + // 4. Initialize with new data (creates fresh Lua VM inside) + InitializeScene(newData); +} + +void psxsplash::SceneManager::clearScene() { + // 1. Shut down the Lua VM first — frees ALL Lua-allocated memory + // (bytecode, strings, tables, registry) in one shot via lua_close. + L.Shutdown(); + + // 2. Free vector BACKING STORAGE (not just contents). + // clear() only sets size=0 but keeps the allocated capacity. + // swap-with-empty releases the heap blocks so they can be coalesced. + { eastl::vector tmp; tmp.swap(m_gameObjects); } + { eastl::vector tmp; tmp.swap(m_luaFiles); } + { eastl::vector tmp; tmp.swap(m_objectNames); } + { eastl::vector tmp; tmp.swap(m_audioClipNames); } + { eastl::vector tmp; tmp.swap(m_interactables); } + + // 3. Reset hardware / subsystems + m_audio.reset(); // Free SPU RAM and stop all voices + m_collisionSystem.init(); // Re-init collision system + // BVH, WorldCollision, and NavRegions will be overwritten by next load + + // Reset room/portal pointers (they point into splashpack data which is being freed) + m_rooms = nullptr; + m_roomCount = 0; + m_portals = nullptr; + m_portalCount = 0; + m_roomTriRefs = nullptr; + m_roomTriRefCount = 0; + m_sceneType = 0; +} + +// ============================================================================ +// OBJECT NAME LOOKUP +// ============================================================================ + +// Inline streq (no libc on bare-metal PS1) +static bool name_eq(const char* a, const char* b) { + while (*a && *b) { if (*a++ != *b++) return false; } + return *a == *b; +} + +psxsplash::GameObject* psxsplash::SceneManager::findObjectByName(const char* name) const { + if (!name || m_objectNames.empty()) return nullptr; + for (size_t i = 0; i < m_objectNames.size() && i < m_gameObjects.size(); i++) { + if (m_objectNames[i] && name_eq(m_objectNames[i], name)) { + return m_gameObjects[i]; + } + } + return nullptr; +} + +int psxsplash::SceneManager::findAudioClipByName(const char* name) const { + if (!name || m_audioClipNames.empty()) return -1; + for (size_t i = 0; i < m_audioClipNames.size(); i++) { + if (m_audioClipNames[i] && name_eq(m_audioClipNames[i], name)) { + return static_cast(i); + } + } + return -1; +} diff --git a/src/scenemanager.hh b/src/scenemanager.hh index 53a1571..db03854 100644 --- a/src/scenemanager.hh +++ b/src/scenemanager.hh @@ -6,27 +6,113 @@ #include #include +#include "bvh.hh" #include "camera.hh" +#include "collision.hh" #include "controls.hh" #include "gameobject.hh" #include "lua.h" #include "splashpack.hh" +#include "worldcollision.hh" +#include "navregion.hh" +#include "audiomanager.hh" +#include "interactable.hh" +#include "luaapi.hh" +#include "sceneloader.hh" namespace psxsplash { class SceneManager { public: void InitializeScene(uint8_t* splashpackData); void GameTick(psyqo::GPU &gpu); + + // Trigger event callbacks (called by CollisionSystem) + void fireTriggerEnter(uint16_t triggerObjIdx, uint16_t otherObjIdx); + void fireTriggerStay(uint16_t triggerObjIdx, uint16_t otherObjIdx); + void fireTriggerExit(uint16_t triggerObjIdx, uint16_t otherObjIdx); + + // Get game object by index (for collision callbacks) + GameObject* getGameObject(uint16_t index) { + if (index < m_gameObjects.size()) return m_gameObjects[index]; + return nullptr; + } + + // Get total object count + size_t getGameObjectCount() const { return m_gameObjects.size(); } + + // Get object name by index (returns nullptr if no name table or out of range) + const char* getObjectName(uint16_t index) const { + if (index < m_objectNames.size()) return m_objectNames[index]; + return nullptr; + } + + // Find first object with matching name (linear scan, case-sensitive) + GameObject* findObjectByName(const char* name) const; + + // Find audio clip index by name (returns -1 if not found) + int findAudioClipByName(const char* name) const; + + // Get audio clip name by index (returns nullptr if out of range) + const char* getAudioClipName(int index) const { + if (index >= 0 && index < (int)m_audioClipNames.size()) return m_audioClipNames[index]; + return nullptr; + } + + // Public API for game systems + // Interaction system - call from Lua or native code + void triggerInteraction(GameObject* interactable); + + // GameObject state control with events + void setObjectActive(GameObject* go, bool active); + + // Public accessors for Lua API + Controls& getControls() { return m_controls; } + Camera& getCamera() { return m_currentCamera; } + Lua& getLua() { return L; } + AudioManager& getAudio() { return m_audio; } + + // Scene loading (for multi-scene support via PCdrv) + void requestSceneLoad(int sceneIndex); + int getCurrentSceneIndex() const { return m_currentSceneIndex; } + + // Check and process pending scene load (called from GameTick) + void processPendingSceneLoad(); private: psxsplash::Lua L; psxsplash::SplashPackLoader m_loader; - + CollisionSystem m_collisionSystem; + BVHManager m_bvh; // Spatial acceleration for frustum culling + WorldCollision m_worldCollision; // Triangle-level world collision (v7+) + NavRegionSystem m_navRegions; // Convex region navigation (v7+) + uint16_t m_playerNavRegion = NAV_NO_REGION; // Current nav region for player + + // Scene type and render path: 0=exterior (BVH), 1=interior (room/portal) + uint16_t m_sceneType = 0; + + // Room/portal data (v11+ interior scenes). Pointers into splashpack data. + const RoomData* m_rooms = nullptr; + uint16_t m_roomCount = 0; + const PortalData* m_portals = nullptr; + uint16_t m_portalCount = 0; + const TriangleRef* m_roomTriRefs = nullptr; + uint16_t m_roomTriRefCount = 0; eastl::vector m_luaFiles; eastl::vector m_gameObjects; - eastl::vector m_navmeshes; - + + // Object name table (v9+): parallel to m_gameObjects, points into splashpack data + eastl::vector m_objectNames; + + // Audio clip name table (v10+): parallel to audio clips, points into splashpack data + eastl::vector m_audioClipNames; + + // Component arrays + eastl::vector m_interactables; + + // Audio system + AudioManager m_audio; + psxsplash::Controls m_controls; psxsplash::Camera m_currentCamera; @@ -35,8 +121,29 @@ class SceneManager { psyqo::Angle playerRotationX, playerRotationY, playerRotationZ; psyqo::FixedPoint<12, uint16_t> m_playerHeight; + + // Movement physics (v8+) + int32_t m_playerRadius; // Collision radius in fp12 (replaces hardcoded PLAYER_RADIUS) + int32_t m_velocityY; // Vertical velocity in fp12 per second (negative = up) + int32_t m_gravityPerFrame; // Gravity velocity change per frame (fp12) + int32_t m_jumpVelocityRaw; // Initial jump velocity in fp12 per second + bool m_isGrounded; // On the ground (can jump) + + // Frame timing + uint32_t m_lastFrameTime; // gpu.now() timestamp of previous frame + int m_deltaFrames; // Elapsed frame count (1 normally, 2+ if dropped) - bool previewNavmesh = false; bool freecam = false; + + // Scene transition state + int m_currentSceneIndex = 0; + int m_pendingSceneIndex = -1; // -1 = no pending load + uint8_t* m_currentSceneData = nullptr; // Owned pointer to loaded data + + // System update methods (called from GameTick) + void updateInteractionSystem(); + void processEnableDisableEvents(); + void clearScene(); // Deallocate current scene objects }; -}; // namespace psxsplash \ No newline at end of file +}; // namespace psxsplash + // namespace psxsplash \ No newline at end of file diff --git a/src/sio_pcdrv.h b/src/sio_pcdrv.h new file mode 100644 index 0000000..0de9acb --- /dev/null +++ b/src/sio_pcdrv.h @@ -0,0 +1,296 @@ +/* + * sio_pcdrv.h — SIO1-based PCDrv implementation for PSYQo applications + * + * Problem: PSYQo's kernel initialization overwrites the exception handler + * at 0x80000080, destroying Unirom's DEBG hooks. The standard + * pcdrv.h functions use MIPS `break` instructions that rely on + * those hooks to translate into SIO escape sequences. + * + * Solution: Bypass the `break` instruction mechanism entirely. Instead, + * talk directly to SIO1 hardware and send the exact same 0x00+'p' + * escape protocol that the host (NOTPSXSerial / PCdrvSerialHost) + * expects. This works regardless of what's at the exception vector. + * + * Protocol: Matches NOTPSXSerial's PCDrv.cs / Bridge.MonitorSerial(): + * PS1 → Host: 0x00 'p' funcCode(4 LE) + * Host → PS1: "OKAY" ... (function-specific data) + * or "NOPE" on error + */ + +#pragma once + +#include + +// â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +// SIO1 hardware registers (UART serial port, 0x1F801050) +// â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• + +#define SIO1_DATA (*(volatile uint8_t *)0x1F801050) +#define SIO1_STAT (*(volatile uint32_t *)0x1F801054) +#define SIO1_MODE (*(volatile uint16_t *)0x1F801058) +#define SIO1_CTRL (*(volatile uint16_t *)0x1F80105A) +#define SIO1_BAUD (*(volatile uint16_t *)0x1F80105E) + +// Status register bits +#define SIO1_STAT_TX_RDY (1 << 0) // TX FIFO not full +#define SIO1_STAT_RX_RDY (1 << 1) // RX data available + +// â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +// Low-level SIO1 I/O — blocking, tight polling loops +// â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• + +static inline void sio_putc(uint8_t byte) { + while (!(SIO1_STAT & SIO1_STAT_TX_RDY)) {} + SIO1_DATA = byte; +} + +static inline uint8_t sio_getc() { + while (!(SIO1_STAT & SIO1_STAT_RX_RDY)) {} + return SIO1_DATA; +} + +static inline void sio_write32(uint32_t val) { + sio_putc((uint8_t)(val)); + sio_putc((uint8_t)(val >> 8)); + sio_putc((uint8_t)(val >> 16)); + sio_putc((uint8_t)(val >> 24)); +} + +static inline uint32_t sio_read32() { + uint32_t v = (uint32_t)sio_getc(); + v |= (uint32_t)sio_getc() << 8; + v |= (uint32_t)sio_getc() << 16; + v |= (uint32_t)sio_getc() << 24; + return v; +} + +static inline void sio_send_str(const char *s) { + while (*s) sio_putc((uint8_t)*s++); +} + +// Read 4 bytes and check if they are "OKAY" +static inline int sio_check_okay() { + uint8_t a = sio_getc(); + uint8_t b = sio_getc(); + uint8_t c = sio_getc(); + uint8_t d = sio_getc(); + return (a == 'O' && b == 'K' && c == 'A' && d == 'Y'); +} + +// â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +// PCDrv escape protocol — send 0x00 + 'p' + function code +// â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• + +static inline void sio_pcdrv_escape(uint32_t funcCode) { + sio_putc(0x00); // escape character + sio_putc('p'); // PCDrv marker + sio_write32(funcCode); // function code, little-endian +} + +// â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• +// PCDrv API — drop-in replacements for common/kernel/pcdrv.h +// Same names, same signatures, same return conventions. +// â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• + +/** + * sio1_ensure_init — (re-)initialize SIO1 for 115200 8N1 + * Safe to call multiple times. Uses the same register values + * that Unirom/nugget use, so this is a no-op if SIO1 is already + * configured. Ensures correct config even if PSYQo or BIOS + * touched the SIO1 registers. + */ +static inline void sio1_ensure_init() { + SIO1_CTRL = 0; // reset + SIO1_MODE = 0x004e; // MUL16, 8 data bits, no parity, 1 stop bit + SIO1_BAUD = (uint16_t)(2073600 / 115200); // = 18 + SIO1_CTRL = 0x0025; // TX enable, RX enable, RTS assert + // Small delay for hardware to settle + { + int i = 0; + while (i < 100) { __asm__ volatile("" ::: "memory"); i++; } + } +} + +/** + * PCinit — initialize PCDrv connection + * Returns 0 on success, -1 on failure. + */ +static inline int PCinit() { + sio1_ensure_init(); // make sure SIO1 is properly configured + sio_pcdrv_escape(0x101); + + // Host responds: "OKAY" + 0x00 + if (!sio_check_okay()) return -1; + sio_getc(); // consume trailing 0x00 + return 0; +} + +/** + * PCopen — open a file on the host + * Returns file handle (positive) on success, -1 on failure. + */ +static inline int PCopen(const char *name, int flags, int perms) { + (void)perms; // unused in protocol + sio_pcdrv_escape(0x103); + + // Host responds: "OKAY" (first ACK, ready for filename) + if (!sio_check_okay()) return -1; + + // Send filename (null-terminated) + const char *p = name; + while (*p) sio_putc((uint8_t)*p++); + sio_putc(0x00); // null terminator + + // Send file mode as uint32 LE + sio_write32((uint32_t)flags); + + // Host responds: "OKAY" + handle(4) or "NOPE" + uint8_t r0 = sio_getc(); + uint8_t r1 = sio_getc(); + uint8_t r2 = sio_getc(); + uint8_t r3 = sio_getc(); + + if (r0 == 'N' && r1 == 'O' && r2 == 'P' && r3 == 'E') { + return -1; + } + + // "OKAY" — read handle + int handle = (int)sio_read32(); + return handle; +} + +/** + * PCclose — close a file handle + * Returns 0 on success. + */ +static inline int PCclose(int fd) { + sio_pcdrv_escape(0x104); + + // Host responds: "OKAY" (ready for params) + if (!sio_check_okay()) return -1; + + // Send handle + 2 unused params (matches Unirom kernel convention) + sio_write32((uint32_t)fd); + sio_write32(0); // unused + sio_write32(0); // unused + + // Host responds: "OKAY" + handle(4) or "NOPE" + uint8_t r0 = sio_getc(); + uint8_t r1 = sio_getc(); + uint8_t r2 = sio_getc(); + uint8_t r3 = sio_getc(); + + if (r0 == 'N' && r1 == 'O' && r2 == 'P' && r3 == 'E') { + return -1; + } + + // "OKAY" — read handle back (v1, not used by caller) + sio_read32(); + return 0; +} + +/** + * PCread — read data from a file into memory + * Returns number of bytes read, or -1 on failure. + */ +static inline int PCread(int fd, void *buf, int len) { + sio_pcdrv_escape(0x105); + + // Host responds: "OKAY" (ready for params) + if (!sio_check_okay()) return -1; + + // Send handle + length + memaddr (memaddr is debug-only, send buf ptr) + sio_write32((uint32_t)fd); + sio_write32((uint32_t)len); + sio_write32((uint32_t)(uintptr_t)buf); + + // Host responds: "OKAY" + dataLength(4) + checksum(4) + raw data + // or "NOPE" + uint8_t r0 = sio_getc(); + uint8_t r1 = sio_getc(); + uint8_t r2 = sio_getc(); + uint8_t r3 = sio_getc(); + + if (r0 == 'N' && r1 == 'O' && r2 == 'P' && r3 == 'E') { + return -1; + } + + // "OKAY" — read response + uint32_t dataLength = sio_read32(); + uint32_t checksum = sio_read32(); // not verified, just consume + (void)checksum; + + // Read raw data bytes into buffer + uint8_t *dst = (uint8_t *)buf; + for (uint32_t i = 0; i < dataLength; i++) { + dst[i] = sio_getc(); + } + + return (int)dataLength; +} + +/** + * PCwrite — write data from memory to a file + * Returns number of bytes written, or -1 on failure. + */ +static inline int PCwrite(int fd, const void *buf, int len) { + sio_pcdrv_escape(0x106); + + // Host responds: "OKAY" (ready for params) + if (!sio_check_okay()) return -1; + + // Send handle + length + memaddr + sio_write32((uint32_t)fd); + sio_write32((uint32_t)len); + sio_write32((uint32_t)(uintptr_t)buf); + + // Host responds: "OKAY" (ready for data) or "NOPE" + uint8_t r0 = sio_getc(); + uint8_t r1 = sio_getc(); + uint8_t r2 = sio_getc(); + uint8_t r3 = sio_getc(); + if (r0 == 'N' && r1 == 'O' && r2 == 'P' && r3 == 'E') { + return -1; + } + + // Send raw data + const uint8_t *src = (const uint8_t *)buf; + for (int i = 0; i < len; i++) { + sio_putc(src[i]); + } + + // Host responds: "OKAY" + bytesWritten(4) + if (!sio_check_okay()) return -1; + int written = (int)sio_read32(); + return written; +} + +/** + * PClseek — seek within a file + * Returns new position, or -1 on failure. + */ +static inline int PClseek(int fd, int offset, int whence) { + sio_pcdrv_escape(0x107); + + // Host responds: "OKAY" (ready for params) + if (!sio_check_okay()) return -1; + + // Send handle + offset + whence (seek origin) + sio_write32((uint32_t)fd); + sio_write32((uint32_t)offset); + sio_write32((uint32_t)whence); + + // Host responds: "OKAY" + position(4) or "NOPE" + uint8_t r0 = sio_getc(); + uint8_t r1 = sio_getc(); + uint8_t r2 = sio_getc(); + uint8_t r3 = sio_getc(); + + if (r0 == 'N' && r1 == 'O' && r2 == 'P' && r3 == 'E') { + return -1; + } + + // "OKAY" — read new position + int pos = (int)sio_read32(); + return pos; +} diff --git a/src/splashpack.cpp b/src/splashpack.cpp index c157a54..c9e5de9 100644 --- a/src/splashpack.cpp +++ b/src/splashpack.cpp @@ -6,28 +6,72 @@ #include #include +#include "bvh.hh" +#include "collision.hh" #include "gameobject.hh" #include "lua.h" #include "mesh.hh" -#include "navmesh.hh" +#include "worldcollision.hh" +#include "navregion.hh" #include "renderer.hh" namespace psxsplash { struct SPLASHPACKFileHeader { - char magic[2]; - uint16_t version; + char magic[2]; // "SP" + uint16_t version; // Format version (8 = movement params) uint16_t luaFileCount; uint16_t gameObjectCount; uint16_t navmeshCount; uint16_t textureAtlasCount; uint16_t clutCount; + uint16_t colliderCount; psyqo::GTE::PackedVec3 playerStartPos; psyqo::GTE::PackedVec3 playerStartRot; psyqo::FixedPoint<12, uint16_t> playerHeight; uint16_t sceneLuaFileIndex; - uint16_t pad; + // Version 3 additions: + uint16_t bvhNodeCount; + uint16_t bvhTriangleRefCount; + // Version 4 additions (component counts): + uint16_t interactableCount; + uint16_t healthCount; + uint16_t timerCount; + uint16_t spawnerCount; + // Version 5 additions (navgrid): + uint16_t hasNavGrid; // 1 if navgrid present, 0 otherwise + uint16_t reserved; // Alignment padding + // Version 6 additions (AABB + scene type): + uint16_t sceneType; // 0 = exterior, 1 = interior + uint16_t reserved2; // Alignment padding + // Version 7 additions (world collision + nav regions): + uint16_t worldCollisionMeshCount; + uint16_t worldCollisionTriCount; + uint16_t navRegionCount; + uint16_t navPortalCount; + // Version 8 additions (movement parameters): + uint16_t moveSpeed; // fp12 per-frame speed constant + uint16_t sprintSpeed; // fp12 per-frame speed constant + uint16_t jumpVelocity; // fp12 per-second initial jump velocity + uint16_t gravity; // fp12 per-second² downward acceleration + uint16_t playerRadius; // fp12 collision radius + uint16_t reserved3; // Alignment padding + // Version 9 additions (object names): + uint32_t nameTableOffset; // Offset to name string table (0 = no names) + // Version 10 additions (audio): + uint16_t audioClipCount; // Number of audio clips + uint16_t reserved4; // Alignment padding + uint32_t audioTableOffset; // Offset to audio clip table (0 = no audio) + // Version 11 additions (fog + room/portal): + uint8_t fogEnabled; // 0 = off, 1 = on + uint8_t fogR, fogG, fogB; // Fog color RGB + uint8_t fogDensity; // 1-10 density scale + uint8_t reserved5; // Alignment + uint16_t roomCount; // 0 = no room system (use BVH path) + uint16_t portalCount; + uint16_t roomTriRefCount; }; +static_assert(sizeof(SPLASHPACKFileHeader) == 96, "SPLASHPACKFileHeader must be 96 bytes"); struct SPLASHPACKTextureAtlas { uint32_t polygonsOffset; @@ -47,55 +91,203 @@ void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup psyqo::Kernel::assert(data != nullptr, "Splashpack loading data pointer is null"); psxsplash::SPLASHPACKFileHeader *header = reinterpret_cast(data); psyqo::Kernel::assert(__builtin_memcmp(header->magic, "SP", 2) == 0, "Splashpack has incorrect magic"); + psyqo::Kernel::assert(header->version >= 11, "Splashpack version too old: re-export from SplashEdit"); setup.playerStartPosition = header->playerStartPos; setup.playerStartRotation = header->playerStartRot; setup.playerHeight = header->playerHeight; + + // Movement parameters (v8+) + setup.moveSpeed.value = header->moveSpeed; + setup.sprintSpeed.value = header->sprintSpeed; + setup.jumpVelocity.value = header->jumpVelocity; + setup.gravity.value = header->gravity; + setup.playerRadius.value = header->playerRadius; setup.luaFiles.reserve(header->luaFileCount); setup.objects.reserve(header->gameObjectCount); - setup.navmeshes.reserve(header->navmeshCount); + setup.colliders.reserve(header->colliderCount); + + // Reserve component arrays (version 4+) + if (header->version >= 4) { + setup.interactables.reserve(header->interactableCount); + } - uint8_t *curentPointer = data + sizeof(psxsplash::SPLASHPACKFileHeader); + // V11 header is always 96 bytes (validated by static_assert above). + uint8_t *cursor = data + sizeof(SPLASHPACKFileHeader); for (uint16_t i = 0; i < header->luaFileCount; i++) { - psxsplash::LuaFile *luaHeader = reinterpret_cast(curentPointer); + psxsplash::LuaFile *luaHeader = reinterpret_cast(cursor); luaHeader->luaCode = reinterpret_cast(data + luaHeader->luaCodeOffset); setup.luaFiles.push_back(luaHeader); - curentPointer += sizeof(psxsplash::LuaFile); + cursor += sizeof(psxsplash::LuaFile); } - setup.sceneLuaFileIndex = header->sceneLuaFileIndex; + // sceneLuaFileIndex is stored as uint16_t in header; 0xFFFF means "no scene script" (-1) + setup.sceneLuaFileIndex = (header->sceneLuaFileIndex == 0xFFFF) ? -1 : (int)header->sceneLuaFileIndex; for (uint16_t i = 0; i < header->gameObjectCount; i++) { - psxsplash::GameObject *go = reinterpret_cast(curentPointer); + psxsplash::GameObject *go = reinterpret_cast(cursor); go->polygons = reinterpret_cast(data + go->polygonsOffset); setup.objects.push_back(go); - curentPointer += sizeof(psxsplash::GameObject); + cursor += sizeof(psxsplash::GameObject); } - for (uint16_t i = 0; i < header->navmeshCount; i++) { - psxsplash::Navmesh *navmesh = reinterpret_cast(curentPointer); - navmesh->polygons = reinterpret_cast(data + navmesh->polygonsOffset); - setup.navmeshes.push_back(navmesh); - curentPointer += sizeof(psxsplash::Navmesh); + // Read collision data (after GameObjects) + for (uint16_t i = 0; i < header->colliderCount; i++) { + psxsplash::SPLASHPACKCollider *collider = reinterpret_cast(cursor); + setup.colliders.push_back(collider); + cursor += sizeof(psxsplash::SPLASHPACKCollider); } + // Read BVH data (version 3+) + if (header->version >= 3 && header->bvhNodeCount > 0) { + BVHNode* bvhNodes = reinterpret_cast(cursor); + cursor += header->bvhNodeCount * sizeof(BVHNode); + + TriangleRef* triangleRefs = reinterpret_cast(cursor); + cursor += header->bvhTriangleRefCount * sizeof(TriangleRef); + + setup.bvh.initialize(bvhNodes, header->bvhNodeCount, + triangleRefs, header->bvhTriangleRefCount); + } + + // Read component data (version 4+) + if (header->version >= 4) { + // Interactables + for (uint16_t i = 0; i < header->interactableCount; i++) { + psxsplash::Interactable *interactable = reinterpret_cast(cursor); + setup.interactables.push_back(interactable); + cursor += sizeof(psxsplash::Interactable); + } + + // Skip health components (legacy, 24 bytes each) + cursor += header->healthCount * 24; + + // Skip timers (legacy, 16 bytes each) + cursor += header->timerCount * 16; + + // Skip spawners (legacy, 44 bytes each) + cursor += header->spawnerCount * 44; + } + + // Read NavGrid (version 5+ — LEGACY, skip if present) + if (header->version >= 5 && header->hasNavGrid) { + // Skip NavGrid data: header (16 bytes) + cells + // NavGridHeader: 4 int32 = 16 bytes, then gridW*gridH*9 bytes + int32_t* navGridHeader = reinterpret_cast(cursor); + int32_t gridW = navGridHeader[2]; + int32_t gridH = navGridHeader[3]; + cursor += 16; // header + cursor += gridW * gridH * 9; // cells (9 bytes each) + // Align to 4 bytes + uintptr_t addr = reinterpret_cast(cursor); + cursor = reinterpret_cast((addr + 3) & ~3); + } + + // Read world collision soup (version 7+) + if (header->version >= 7 && header->worldCollisionMeshCount > 0) { + uintptr_t addr = reinterpret_cast(cursor); + cursor = reinterpret_cast((addr + 3) & ~3); + cursor = const_cast(setup.worldCollision.initializeFromData(cursor)); + } + + // Read nav regions (version 7+) + if (header->version >= 7 && header->navRegionCount > 0) { + uintptr_t addr = reinterpret_cast(cursor); + cursor = reinterpret_cast((addr + 3) & ~3); + cursor = const_cast(setup.navRegions.initializeFromData(cursor)); + } + + // Read room/portal data (version 11+, interior scenes) + // Must be read here (after nav regions, before navmesh skip / atlas metadata) + // to match the sequential cursor position where the writer places it. + if (header->version >= 11 && header->roomCount > 0) { + uintptr_t addr = reinterpret_cast(cursor); + cursor = reinterpret_cast((addr + 3) & ~3); + + setup.rooms = reinterpret_cast(cursor); + setup.roomCount = header->roomCount; + cursor += header->roomCount * sizeof(RoomData); + + setup.portals = reinterpret_cast(cursor); + setup.portalCount = header->portalCount; + cursor += header->portalCount * sizeof(PortalData); + + setup.roomTriRefs = reinterpret_cast(cursor); + setup.roomTriRefCount = header->roomTriRefCount; + cursor += header->roomTriRefCount * sizeof(TriangleRef); + } + + // Skip legacy navmesh metadata (still present in v7 files) + cursor += header->navmeshCount * 8; // Navmesh struct: 4+2+2 = 8 bytes + for (uint16_t i = 0; i < header->textureAtlasCount; i++) { - psxsplash::SPLASHPACKTextureAtlas *atlas = reinterpret_cast(curentPointer); + psxsplash::SPLASHPACKTextureAtlas *atlas = reinterpret_cast(cursor); uint8_t *offsetData = data + atlas->polygonsOffset; uint16_t *castedData = reinterpret_cast(offsetData); psxsplash::Renderer::GetInstance().VramUpload(castedData, atlas->x, atlas->y, atlas->width, atlas->height); - curentPointer += sizeof(psxsplash::SPLASHPACKTextureAtlas); + cursor += sizeof(psxsplash::SPLASHPACKTextureAtlas); } for (uint16_t i = 0; i < header->clutCount; i++) { - psxsplash::SPLASHPACKClut *clut = reinterpret_cast(curentPointer); + psxsplash::SPLASHPACKClut *clut = reinterpret_cast(cursor); uint8_t *clutOffset = data + clut->clutOffset; psxsplash::Renderer::GetInstance().VramUpload((uint16_t *)clutOffset, clut->clutPackingX * 16, clut->clutPackingY, clut->length, 1); - curentPointer += sizeof(psxsplash::SPLASHPACKClut); + cursor += sizeof(psxsplash::SPLASHPACKClut); } + + // Read object name table (version 9+) + if (header->version >= 9 && header->nameTableOffset != 0) { + uint8_t* nameData = data + header->nameTableOffset; + setup.objectNames.reserve(header->gameObjectCount); + for (uint16_t i = 0; i < header->gameObjectCount; i++) { + uint8_t nameLen = *nameData++; + const char* nameStr = reinterpret_cast(nameData); + // Names are stored as length-prefixed, null-terminated strings + setup.objectNames.push_back(nameStr); + nameData += nameLen + 1; // +1 for null terminator + } + } + + // Read audio clip table (version 10+) + if (header->version >= 10 && header->audioClipCount > 0 && header->audioTableOffset != 0) { + // Audio table: per clip: uint32_t dataOffset, uint32_t sizeBytes, uint16_t sampleRate, uint8_t loop, uint8_t nameLen, uint32_t nameOffset + // Total 16 bytes per entry + uint8_t* audioTable = data + header->audioTableOffset; + setup.audioClips.reserve(header->audioClipCount); + setup.audioClipNames.reserve(header->audioClipCount); + for (uint16_t i = 0; i < header->audioClipCount; i++) { + uint32_t dataOff = *reinterpret_cast(audioTable); audioTable += 4; + uint32_t size = *reinterpret_cast(audioTable); audioTable += 4; + uint16_t rate = *reinterpret_cast(audioTable); audioTable += 2; + uint8_t loop = *audioTable++; + uint8_t nameLen = *audioTable++; + uint32_t nameOff = *reinterpret_cast(audioTable); audioTable += 4; + SplashpackSceneSetup::AudioClipSetup clip; + clip.adpcmData = data + dataOff; + clip.sizeBytes = size; + clip.sampleRate = rate; + clip.loop = (loop != 0); + clip.name = (nameLen > 0 && nameOff != 0) ? reinterpret_cast(data + nameOff) : nullptr; + setup.audioClips.push_back(clip); + setup.audioClipNames.push_back(clip.name); + } + } + + // Read fog configuration (version 11+) + if (header->version >= 11) { + setup.fogEnabled = header->fogEnabled != 0; + setup.fogR = header->fogR; + setup.fogG = header->fogG; + setup.fogB = header->fogB; + setup.fogDensity = header->fogDensity; + } + + // Read scene type (version 6+ stored it but it was never read until now) + setup.sceneType = header->sceneType; + } } // namespace psxsplash diff --git a/src/splashpack.cpp.bal b/src/splashpack.cpp.bal new file mode 100644 index 0000000..64f76ba --- /dev/null +++ b/src/splashpack.cpp.bal @@ -0,0 +1,295 @@ +#include "splashpack.hh" + +#include + +#include +#include +#include + +#include "bvh.hh" +#include "collision.hh" +#include "gameobject.hh" +#include "lua.h" +#include "mesh.hh" +#include "worldcollision.hh" +#include "navregion.hh" +#include "renderer.hh" + +namespace psxsplash { + +struct SPLASHPACKFileHeader { + char magic[2]; // "SP" + uint16_t version; // Format version (8 = movement params) + uint16_t luaFileCount; + uint16_t gameObjectCount; + uint16_t navmeshCount; + uint16_t textureAtlasCount; + uint16_t clutCount; + uint16_t colliderCount; + psyqo::GTE::PackedVec3 playerStartPos; + psyqo::GTE::PackedVec3 playerStartRot; + psyqo::FixedPoint<12, uint16_t> playerHeight; + uint16_t sceneLuaFileIndex; + // Version 3 additions: + uint16_t bvhNodeCount; + uint16_t bvhTriangleRefCount; + // Version 4 additions (component counts): + uint16_t interactableCount; + uint16_t healthCount; + uint16_t timerCount; + uint16_t spawnerCount; + // Version 5 additions (navgrid): + uint16_t hasNavGrid; // 1 if navgrid present, 0 otherwise + uint16_t reserved; // Alignment padding + // Version 6 additions (AABB + scene type): + uint16_t sceneType; // 0 = exterior, 1 = interior + uint16_t reserved2; // Alignment padding + // Version 7 additions (world collision + nav regions): + uint16_t worldCollisionMeshCount; + uint16_t worldCollisionTriCount; + uint16_t navRegionCount; + uint16_t navPortalCount; + // Version 8 additions (movement parameters): + uint16_t moveSpeed; // fp12 per-frame speed constant + uint16_t sprintSpeed; // fp12 per-frame speed constant + uint16_t jumpVelocity; // fp12 per-second initial jump velocity + uint16_t gravity; // fp12 per-second² downward acceleration + uint16_t playerRadius; // fp12 collision radius + uint16_t reserved3; // Alignment padding + // Version 9 additions (object names): + uint32_t nameTableOffset; // Offset to name string table (0 = no names) + // Version 10 additions (audio): + uint16_t audioClipCount; // Number of audio clips + uint16_t reserved4; // Alignment padding + uint32_t audioTableOffset; // Offset to audio clip table (0 = no audio) + // Version 11 additions (fog + room/portal): + uint8_t fogEnabled; // 0 = off, 1 = on + uint8_t fogR, fogG, fogB; // Fog color RGB + uint8_t fogDensity; // 1-10 density scale + uint8_t reserved5; // Alignment + uint16_t roomCount; // 0 = no room system (use BVH path) + uint16_t portalCount; + uint16_t roomTriRefCount; +}; +static_assert(sizeof(SPLASHPACKFileHeader) == 96, "SPLASHPACKFileHeader must be 96 bytes"); + +struct SPLASHPACKTextureAtlas { + uint32_t polygonsOffset; + uint16_t width, height; + uint16_t x, y; +}; + +struct SPLASHPACKClut { + uint32_t clutOffset; + uint16_t clutPackingX; + uint16_t clutPackingY; + uint16_t length; + uint16_t pad; +}; + +void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup) { + psyqo::Kernel::assert(data != nullptr, "Splashpack loading data pointer is null"); + psxsplash::SPLASHPACKFileHeader *header = reinterpret_cast(data); + psyqo::Kernel::assert(__builtin_memcmp(header->magic, "SP", 2) == 0, "Splashpack has incorrect magic"); + psyqo::Kernel::assert(header->version >= 8, "Splashpack version mismatch: re-export from SplashEdit"); + + setup.playerStartPosition = header->playerStartPos; + setup.playerStartRotation = header->playerStartRot; + setup.playerHeight = header->playerHeight; + + // Movement parameters (v8+) + setup.moveSpeed.value = header->moveSpeed; + setup.sprintSpeed.value = header->sprintSpeed; + setup.jumpVelocity.value = header->jumpVelocity; + setup.gravity.value = header->gravity; + setup.playerRadius.value = header->playerRadius; + + setup.luaFiles.reserve(header->luaFileCount); + setup.objects.reserve(header->gameObjectCount); + setup.colliders.reserve(header->colliderCount); + + // Reserve component arrays (version 4+) + if (header->version >= 4) { + setup.interactables.reserve(header->interactableCount); + } + + // V10 header = 84 bytes, V11+ = 96 bytes. sizeof() always returns 96, + // so we must compute the correct offset for older versions. + uint32_t headerSize = (header->version >= 11) ? 96 : 84; + uint8_t *cursor = data + headerSize; + + for (uint16_t i = 0; i < header->luaFileCount; i++) { + psxsplash::LuaFile *luaHeader = reinterpret_cast(cursor); + luaHeader->luaCode = reinterpret_cast(data + luaHeader->luaCodeOffset); + setup.luaFiles.push_back(luaHeader); + cursor += sizeof(psxsplash::LuaFile); + } + + // sceneLuaFileIndex is stored as uint16_t in header; 0xFFFF means "no scene script" (-1) + setup.sceneLuaFileIndex = (header->sceneLuaFileIndex == 0xFFFF) ? -1 : (int)header->sceneLuaFileIndex; + + for (uint16_t i = 0; i < header->gameObjectCount; i++) { + psxsplash::GameObject *go = reinterpret_cast(cursor); + go->polygons = reinterpret_cast(data + go->polygonsOffset); + setup.objects.push_back(go); + cursor += sizeof(psxsplash::GameObject); + } + + // Read collision data (after GameObjects) + for (uint16_t i = 0; i < header->colliderCount; i++) { + psxsplash::SPLASHPACKCollider *collider = reinterpret_cast(cursor); + setup.colliders.push_back(collider); + cursor += sizeof(psxsplash::SPLASHPACKCollider); + } + + // Read BVH data (version 3+) + if (header->version >= 3 && header->bvhNodeCount > 0) { + BVHNode* bvhNodes = reinterpret_cast(cursor); + cursor += header->bvhNodeCount * sizeof(BVHNode); + + TriangleRef* triangleRefs = reinterpret_cast(cursor); + cursor += header->bvhTriangleRefCount * sizeof(TriangleRef); + + setup.bvh.initialize(bvhNodes, header->bvhNodeCount, + triangleRefs, header->bvhTriangleRefCount); + } + + // Read component data (version 4+) + if (header->version >= 4) { + // Interactables + for (uint16_t i = 0; i < header->interactableCount; i++) { + psxsplash::Interactable *interactable = reinterpret_cast(cursor); + setup.interactables.push_back(interactable); + cursor += sizeof(psxsplash::Interactable); + } + + // Skip health components (legacy, 24 bytes each) + cursor += header->healthCount * 24; + + // Skip timers (legacy, 16 bytes each) + cursor += header->timerCount * 16; + + // Skip spawners (legacy, 44 bytes each) + cursor += header->spawnerCount * 44; + } + + // Read NavGrid (version 5+ — LEGACY, skip if present) + if (header->version >= 5 && header->hasNavGrid) { + // Skip NavGrid data: header (16 bytes) + cells + // NavGridHeader: 4 int32 = 16 bytes, then gridW*gridH*9 bytes + int32_t* navGridHeader = reinterpret_cast(cursor); + int32_t gridW = navGridHeader[2]; + int32_t gridH = navGridHeader[3]; + cursor += 16; // header + cursor += gridW * gridH * 9; // cells (9 bytes each) + // Align to 4 bytes + uintptr_t addr = reinterpret_cast(cursor); + cursor = reinterpret_cast((addr + 3) & ~3); + } + + // Read world collision soup (version 7+) + if (header->version >= 7 && header->worldCollisionMeshCount > 0) { + uintptr_t addr = reinterpret_cast(cursor); + cursor = reinterpret_cast((addr + 3) & ~3); + cursor = const_cast(setup.worldCollision.initializeFromData(cursor)); + } + + // Read nav regions (version 7+) + if (header->version >= 7 && header->navRegionCount > 0) { + uintptr_t addr = reinterpret_cast(cursor); + cursor = reinterpret_cast((addr + 3) & ~3); + cursor = const_cast(setup.navRegions.initializeFromData(cursor)); + } + + // Read room/portal data (version 11+, interior scenes) + // Must be read here (after nav regions, before navmesh skip / atlas metadata) + // to match the sequential cursor position where the writer places it. + if (header->version >= 11 && header->roomCount > 0) { + uintptr_t addr = reinterpret_cast(cursor); + cursor = reinterpret_cast((addr + 3) & ~3); + + setup.rooms = reinterpret_cast(cursor); + setup.roomCount = header->roomCount; + cursor += header->roomCount * sizeof(RoomData); + + setup.portals = reinterpret_cast(cursor); + setup.portalCount = header->portalCount; + cursor += header->portalCount * sizeof(PortalData); + + setup.roomTriRefs = reinterpret_cast(cursor); + setup.roomTriRefCount = header->roomTriRefCount; + cursor += header->roomTriRefCount * sizeof(TriangleRef); + } + + // Skip legacy navmesh metadata (still present in v7 files) + cursor += header->navmeshCount * 8; // Navmesh struct: 4+2+2 = 8 bytes + + for (uint16_t i = 0; i < header->textureAtlasCount; i++) { + psxsplash::SPLASHPACKTextureAtlas *atlas = reinterpret_cast(cursor); + uint8_t *offsetData = data + atlas->polygonsOffset; + uint16_t *castedData = reinterpret_cast(offsetData); + psxsplash::Renderer::GetInstance().VramUpload(castedData, atlas->x, atlas->y, atlas->width, atlas->height); + cursor += sizeof(psxsplash::SPLASHPACKTextureAtlas); + } + + for (uint16_t i = 0; i < header->clutCount; i++) { + psxsplash::SPLASHPACKClut *clut = reinterpret_cast(cursor); + uint8_t *clutOffset = data + clut->clutOffset; + psxsplash::Renderer::GetInstance().VramUpload((uint16_t *)clutOffset, clut->clutPackingX * 16, + clut->clutPackingY, clut->length, 1); + cursor += sizeof(psxsplash::SPLASHPACKClut); + } + + // Read object name table (version 9+) + if (header->version >= 9 && header->nameTableOffset != 0) { + uint8_t* nameData = data + header->nameTableOffset; + setup.objectNames.reserve(header->gameObjectCount); + for (uint16_t i = 0; i < header->gameObjectCount; i++) { + uint8_t nameLen = *nameData++; + const char* nameStr = reinterpret_cast(nameData); + // Names are stored as length-prefixed, null-terminated strings + setup.objectNames.push_back(nameStr); + nameData += nameLen + 1; // +1 for null terminator + } + } + + // Read audio clip table (version 10+) + if (header->version >= 10 && header->audioClipCount > 0 && header->audioTableOffset != 0) { + // Audio table: per clip: uint32_t dataOffset, uint32_t sizeBytes, uint16_t sampleRate, uint8_t loop, uint8_t nameLen, uint32_t nameOffset + // Total 16 bytes per entry + uint8_t* audioTable = data + header->audioTableOffset; + setup.audioClips.reserve(header->audioClipCount); + setup.audioClipNames.reserve(header->audioClipCount); + for (uint16_t i = 0; i < header->audioClipCount; i++) { + uint32_t dataOff = *reinterpret_cast(audioTable); audioTable += 4; + uint32_t size = *reinterpret_cast(audioTable); audioTable += 4; + uint16_t rate = *reinterpret_cast(audioTable); audioTable += 2; + uint8_t loop = *audioTable++; + uint8_t nameLen = *audioTable++; + uint32_t nameOff = *reinterpret_cast(audioTable); audioTable += 4; + SplashpackSceneSetup::AudioClipSetup clip; + clip.adpcmData = data + dataOff; + clip.sizeBytes = size; + clip.sampleRate = rate; + clip.loop = (loop != 0); + clip.name = (nameLen > 0 && nameOff != 0) ? reinterpret_cast(data + nameOff) : nullptr; + setup.audioClips.push_back(clip); + setup.audioClipNames.push_back(clip.name); + } + } + + // Read fog configuration (version 11+) + if (header->version >= 11) { + setup.fogEnabled = header->fogEnabled != 0; + setup.fogR = header->fogR; + setup.fogG = header->fogG; + setup.fogB = header->fogB; + setup.fogDensity = header->fogDensity; + } + + // Read scene type (version 6+ stored it but it was never read until now) + setup.sceneType = header->sceneType; + +} + +} // namespace psxsplash diff --git a/src/splashpack.hh b/src/splashpack.hh index d32936c..671cefb 100644 --- a/src/splashpack.hh +++ b/src/splashpack.hh @@ -4,20 +4,87 @@ #include +#include "bvh.hh" +#include "collision.hh" #include "gameobject.hh" #include "lua.h" -#include "navmesh.hh" +#include "worldcollision.hh" +#include "navregion.hh" +#include "audiomanager.hh" +#include "interactable.hh" namespace psxsplash { +/** + * Collision data as stored in the binary file (fixed layout for serialization) + * This is the binary-compatible version of CollisionData + */ +struct SPLASHPACKCollider { + // AABB bounds in fixed-point (24 bytes) + int32_t minX, minY, minZ; + int32_t maxX, maxY, maxZ; + // Collision metadata (8 bytes) + uint8_t collisionType; // CollisionType enum + uint8_t layerMask; // Which layers this collides with + uint16_t gameObjectIndex; // Which GameObject this belongs to + uint32_t padding; // Alignment padding +}; +static_assert(sizeof(SPLASHPACKCollider) == 32, "SPLASHPACKCollider must be 32 bytes"); + struct SplashpackSceneSetup { int sceneLuaFileIndex; eastl::vector luaFiles; eastl::vector objects; - eastl::vector navmeshes; + eastl::vector colliders; + + // New component arrays + eastl::vector interactables; + + // Object name table (v9+): parallel to objects, points into splashpack data + eastl::vector objectNames; + + // Audio clips (v10+): ADPCM data with metadata + struct AudioClipSetup { + const uint8_t* adpcmData; + uint32_t sizeBytes; + uint16_t sampleRate; + bool loop; + const char* name; // Points into splashpack data (null-terminated) + }; + eastl::vector audioClips; + + // Audio clip name table (v10+): parallel to audioClips, points into splashpack data + eastl::vector audioClipNames; + + BVHManager bvh; // Spatial acceleration structure for culling + WorldCollision worldCollision; // Triangle-level world collision (v7+) + NavRegionSystem navRegions; // Convex region navigation (v7+) psyqo::GTE::PackedVec3 playerStartPosition; psyqo::GTE::PackedVec3 playerStartRotation; psyqo::FixedPoint<12, uint16_t> playerHeight; + + // Scene type: 0=exterior (BVH culling), 1=interior (room/portal culling) + uint16_t sceneType = 0; + + // Fog configuration (v11+) + bool fogEnabled = false; + uint8_t fogR = 0, fogG = 0, fogB = 0; + uint8_t fogDensity = 5; + + // Room/portal data (v11+, interior scenes only) + const RoomData* rooms = nullptr; + uint16_t roomCount = 0; + const PortalData* portals = nullptr; + uint16_t portalCount = 0; + const TriangleRef* roomTriRefs = nullptr; + uint16_t roomTriRefCount = 0; + + // Movement parameters (v8+) + psyqo::FixedPoint<12, uint16_t> moveSpeed; // Per-frame speed constant (fp12) + psyqo::FixedPoint<12, uint16_t> sprintSpeed; // Per-frame sprint constant (fp12) + psyqo::FixedPoint<12, uint16_t> jumpVelocity; // Per-second initial velocity (fp12) + psyqo::FixedPoint<12, uint16_t> gravity; // Per-second² acceleration (fp12) + psyqo::FixedPoint<12, uint16_t> playerRadius; // Collision radius (fp12) }; class SplashPackLoader { @@ -25,4 +92,4 @@ class SplashPackLoader { void LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup); }; -}; // namespace psxsplash \ No newline at end of file +}; // namespace psxsplash diff --git a/src/triclip.cpp b/src/triclip.cpp new file mode 100644 index 0000000..f113241 --- /dev/null +++ b/src/triclip.cpp @@ -0,0 +1,197 @@ +#include "triclip.hh" + +namespace psxsplash { + +// ============================================================================ +// Screen-space Sutherland-Hodgman clipping +// ============================================================================ + +// Interpolate between two ClipVertex at parameter t (0..256 = 0.0..1.0, fp8). +// Uses fp8 to avoid overflow with int16 coordinates (int16 * 256 fits int32). +static ClipVertex lerpClip(const ClipVertex& a, const ClipVertex& b, int32_t t) { + ClipVertex r; + r.x = (int16_t)(a.x + (((int32_t)(b.x - a.x) * t) >> 8)); + r.y = (int16_t)(a.y + (((int32_t)(b.y - a.y) * t) >> 8)); + r.z = (int16_t)(a.z + (((int32_t)(b.z - a.z) * t) >> 8)); + r.u = (uint8_t)(a.u + (((int)(b.u) - (int)(a.u)) * t >> 8)); + r.v = (uint8_t)(a.v + (((int)(b.v) - (int)(a.v)) * t >> 8)); + r.r = (uint8_t)(a.r + (((int)(b.r) - (int)(a.r)) * t >> 8)); + r.g = (uint8_t)(a.g + (((int)(b.g) - (int)(a.g)) * t >> 8)); + r.b = (uint8_t)(a.b + (((int)(b.b) - (int)(a.b)) * t >> 8)); + return r; +} + +// Clip a polygon (in[] with inCount vertices) against a single edge. +// Edge is defined by axis (0=X, 1=Y), sign (+1 or -1), and threshold. +// Output written to out[], returns output vertex count. +static int clipEdge(const ClipVertex* in, int inCount, + ClipVertex* out, int axis, int sign, int16_t threshold) { + if (inCount == 0) return 0; + int outCount = 0; + + for (int i = 0; i < inCount; i++) { + const ClipVertex& cur = in[i]; + const ClipVertex& next = in[(i + 1) % inCount]; + + int16_t curVal = (axis == 0) ? cur.x : cur.y; + int16_t nextVal = (axis == 0) ? next.x : next.y; + + bool curInside = (sign > 0) ? (curVal <= threshold) : (curVal >= threshold); + bool nextInside = (sign > 0) ? (nextVal <= threshold) : (nextVal >= threshold); + + if (curInside) { + if (outCount < 8) out[outCount++] = cur; + if (!nextInside) { + // Exiting: compute intersection + int32_t den = (int32_t)nextVal - (int32_t)curVal; + if (den != 0) { + int32_t t = ((int32_t)(threshold - curVal) << 8) / den; + if (t < 0) t = 0; + if (t > 256) t = 256; + if (outCount < 8) out[outCount++] = lerpClip(cur, next, t); + } + } + } else if (nextInside) { + // Entering: compute intersection + int32_t den = (int32_t)nextVal - (int32_t)curVal; + if (den != 0) { + int32_t t = ((int32_t)(threshold - curVal) << 8) / den; + if (t < 0) t = 0; + if (t > 256) t = 256; + if (outCount < 8) out[outCount++] = lerpClip(cur, next, t); + } + } + } + return outCount; +} + +int clipTriangle(const ClipVertex& v0, const ClipVertex& v1, const ClipVertex& v2, + ClipResult& result) { + // Working buffers for Sutherland-Hodgman (max 8 vertices after 4 clips). + ClipVertex bufA[8], bufB[8]; + bufA[0] = v0; bufA[1] = v1; bufA[2] = v2; + int count = 3; + + // Clip against 4 edges: left, right, top, bottom. + count = clipEdge(bufA, count, bufB, 0, -1, CLIP_LEFT); // X >= CLIP_LEFT + count = clipEdge(bufB, count, bufA, 0, +1, CLIP_RIGHT); // X <= CLIP_RIGHT + count = clipEdge(bufA, count, bufB, 1, -1, CLIP_TOP); // Y >= CLIP_TOP + count = clipEdge(bufB, count, bufA, 1, +1, CLIP_BOTTOM); // Y <= CLIP_BOTTOM + + if (count < 3) return 0; + + // Triangulate the convex polygon into a fan from vertex 0. + int triCount = count - 2; + if (triCount > MAX_CLIP_TRIS) triCount = MAX_CLIP_TRIS; + + for (int i = 0; i < triCount; i++) { + result.verts[i * 3 + 0] = bufA[0]; + result.verts[i * 3 + 1] = bufA[i + 1]; + result.verts[i * 3 + 2] = bufA[i + 2]; + } + return triCount; +} + +// ============================================================================ +// Near-plane (3D view-space) clipping +// ============================================================================ + +ViewVertex lerpViewVertex(const ViewVertex& a, const ViewVertex& b, int32_t t) { + ViewVertex r; + r.x = a.x + (int32_t)(((int64_t)(b.x - a.x) * t) >> 12); + r.y = a.y + (int32_t)(((int64_t)(b.y - a.y) * t) >> 12); + r.z = a.z + (int32_t)(((int64_t)(b.z - a.z) * t) >> 12); + r.u = (uint8_t)(a.u + (((int)(b.u) - (int)(a.u)) * t >> 12)); + r.v = (uint8_t)(a.v + (((int)(b.v) - (int)(a.v)) * t >> 12)); + r.r = (uint8_t)(a.r + (((int)(b.r) - (int)(a.r)) * t >> 12)); + r.g = (uint8_t)(a.g + (((int)(b.g) - (int)(a.g)) * t >> 12)); + r.b = (uint8_t)(a.b + (((int)(b.b) - (int)(a.b)) * t >> 12)); + r.pad = 0; + return r; +} + +static inline int32_t nearPlaneT(int32_t zA, int32_t zB) { + constexpr int32_t nearZ = (int32_t)NEAR_PLANE_Z << 12; + int32_t num = nearZ - zA; + int32_t den = zB - zA; + if (den == 0) return 0; + int32_t absNum = num < 0 ? -num : num; + int shift = 0; + while (absNum > 0x7FFFF) { + absNum >>= 1; + shift++; + } + if (shift > 0) { + num >>= shift; + den >>= shift; + if (den == 0) return num > 0 ? 4096 : 0; + } + return (num << 12) / den; +} + +static inline bool isBehind(int32_t z) { + return z < ((int32_t)NEAR_PLANE_Z << 12); +} + +int nearPlaneClip(const ViewVertex& v0, const ViewVertex& v1, const ViewVertex& v2, + NearClipResult& result) { + bool b0 = isBehind(v0.z); + bool b1 = isBehind(v1.z); + bool b2 = isBehind(v2.z); + int behindCount = (int)b0 + (int)b1 + (int)b2; + + if (behindCount == 3) { + result.triCount = 0; + return 0; + } + if (behindCount == 0) { + result.triCount = 1; + result.verts[0] = v0; + result.verts[1] = v1; + result.verts[2] = v2; + return 1; + } + if (behindCount == 1) { + const ViewVertex* A; + const ViewVertex* B; + const ViewVertex* C; + if (b0) { A = &v0; B = &v1; C = &v2; } + else if (b1) { A = &v1; B = &v2; C = &v0; } + else { A = &v2; B = &v0; C = &v1; } + + int32_t tAB = nearPlaneT(A->z, B->z); + int32_t tAC = nearPlaneT(A->z, C->z); + ViewVertex AB = lerpViewVertex(*A, *B, tAB); + ViewVertex AC = lerpViewVertex(*A, *C, tAC); + + result.triCount = 2; + result.verts[0] = AB; + result.verts[1] = *B; + result.verts[2] = *C; + result.verts[3] = AB; + result.verts[4] = *C; + result.verts[5] = AC; + return 2; + } + { + const ViewVertex* A; + const ViewVertex* B; + const ViewVertex* C; + if (!b0) { A = &v0; B = &v1; C = &v2; } + else if (!b1) { A = &v1; B = &v2; C = &v0; } + else { A = &v2; B = &v0; C = &v1; } + + int32_t tBA = nearPlaneT(B->z, A->z); + int32_t tCA = nearPlaneT(C->z, A->z); + ViewVertex BA = lerpViewVertex(*B, *A, tBA); + ViewVertex CA = lerpViewVertex(*C, *A, tCA); + + result.triCount = 1; + result.verts[0] = *A; + result.verts[1] = BA; + result.verts[2] = CA; + return 1; + } +} + +} // namespace psxsplash diff --git a/src/triclip.hh b/src/triclip.hh new file mode 100644 index 0000000..91a5614 --- /dev/null +++ b/src/triclip.hh @@ -0,0 +1,116 @@ +#pragma once + +#include +#include +#include + +namespace psxsplash { + +// ============================================================================ +// Screen-space clipping types and functions +// ============================================================================ + +// Screen-space clip vertex with interpolatable attributes. +struct ClipVertex { + int16_t x, y, z; + uint8_t u, v; + uint8_t r, g, b; +}; + +// Maximum output triangles from clipping a single triangle against 4 edges. +// Sutherland-Hodgman can produce up to 7 vertices -> 5 triangles in a fan. +static constexpr int MAX_CLIP_TRIS = 5; + +// Result of screen-space triangle clipping. +struct ClipResult { + ClipVertex verts[MAX_CLIP_TRIS * 3]; +}; + +// GPU rasterizer limits: max vertex-to-vertex delta. +static constexpr int16_t MAX_DELTA_X = 1023; +static constexpr int16_t MAX_DELTA_Y = 511; + +// Screen-space clip region. Must be narrower than rasterizer limits (1023x511) +// so that any triangle fully inside has safe vertex deltas. +// Centered on screen (160,120), extended to half the rasterizer max in each direction. +static constexpr int16_t CLIP_LEFT = 160 - 510; // -350 +static constexpr int16_t CLIP_RIGHT = 160 + 510; // 670 +static constexpr int16_t CLIP_TOP = 120 - 254; // -134 +static constexpr int16_t CLIP_BOTTOM = 120 + 254; // 374 + +// Check if all 3 vertices are on the same side of any screen edge -> invisible. +inline bool isCompletelyOutside(const psyqo::Vertex& v0, + const psyqo::Vertex& v1, + const psyqo::Vertex& v2) { + int16_t x0 = v0.x, x1 = v1.x, x2 = v2.x; + int16_t y0 = v0.y, y1 = v1.y, y2 = v2.y; + + if (x0 < CLIP_LEFT && x1 < CLIP_LEFT && x2 < CLIP_LEFT) return true; + if (x0 > CLIP_RIGHT && x1 > CLIP_RIGHT && x2 > CLIP_RIGHT) return true; + if (y0 < CLIP_TOP && y1 < CLIP_TOP && y2 < CLIP_TOP) return true; + if (y0 > CLIP_BOTTOM && y1 > CLIP_BOTTOM && y2 > CLIP_BOTTOM) return true; + return false; +} + +// Check if any vertex is outside the clip region or vertex deltas exceed +// rasterizer limits. If true, the triangle needs screen-space clipping. +inline bool needsClipping(const psyqo::Vertex& v0, + const psyqo::Vertex& v1, + const psyqo::Vertex& v2) { + int16_t x0 = v0.x, x1 = v1.x, x2 = v2.x; + int16_t y0 = v0.y, y1 = v1.y, y2 = v2.y; + + // Check if any vertex is outside the clip region. + if (x0 < CLIP_LEFT || x0 > CLIP_RIGHT || + x1 < CLIP_LEFT || x1 > CLIP_RIGHT || + x2 < CLIP_LEFT || x2 > CLIP_RIGHT || + y0 < CLIP_TOP || y0 > CLIP_BOTTOM || + y1 < CLIP_TOP || y1 > CLIP_BOTTOM || + y2 < CLIP_TOP || y2 > CLIP_BOTTOM) { + return true; + } + + // Check vertex-to-vertex deltas against rasterizer limits. + int16_t minX = x0, maxX = x0; + int16_t minY = y0, maxY = y0; + if (x1 < minX) minX = x1; if (x1 > maxX) maxX = x1; + if (x2 < minX) minX = x2; if (x2 > maxX) maxX = x2; + if (y1 < minY) minY = y1; if (y1 > maxY) maxY = y1; + if (y2 < minY) minY = y2; if (y2 > maxY) maxY = y2; + + if ((int32_t)(maxX - minX) > MAX_DELTA_X) return true; + if ((int32_t)(maxY - minY) > MAX_DELTA_Y) return true; + + return false; +} + +// Sutherland-Hodgman screen-space triangle clipping. +// Clips against CLIP_LEFT/RIGHT/TOP/BOTTOM, then triangulates the result. +// Returns number of output triangles (0 to MAX_CLIP_TRIS), vertices in result. +int clipTriangle(const ClipVertex& v0, const ClipVertex& v1, const ClipVertex& v2, + ClipResult& result); + +// ============================================================================ +// Near-plane (3D view-space) clipping types and functions +// ============================================================================ + +#define NEAR_PLANE_Z 48 +#define MAX_NEARCLIP_TRIS 2 + +struct ViewVertex { + int32_t x, y, z; + uint8_t u, v; + uint8_t r, g, b; + uint8_t pad; +}; + +struct NearClipResult { + int triCount; + ViewVertex verts[MAX_NEARCLIP_TRIS * 3]; +}; + +int nearPlaneClip(const ViewVertex& v0, const ViewVertex& v1, const ViewVertex& v2, + NearClipResult& result); +ViewVertex lerpViewVertex(const ViewVertex& a, const ViewVertex& b, int32_t t); + +} // namespace psxsplash diff --git a/src/vram_config.h b/src/vram_config.h new file mode 100644 index 0000000..440dd88 --- /dev/null +++ b/src/vram_config.h @@ -0,0 +1,12 @@ +// Auto-generated by SplashEdit - do not edit manually. +#pragma once + +// GPU resolution +#define VRAM_RES_WIDTH 320 +#define VRAM_RES_HEIGHT 240 +#define VRAM_RES_ENUM psyqo::GPU::Resolution::W320 +#define VRAM_INTERLACE psyqo::GPU::Interlace::PROGRESSIVE + +// Framebuffer layout +#define VRAM_DUAL_BUFFER 1 +#define VRAM_VERTICAL 1 diff --git a/src/worldcollision.cpp b/src/worldcollision.cpp new file mode 100644 index 0000000..682bf72 --- /dev/null +++ b/src/worldcollision.cpp @@ -0,0 +1,621 @@ +#include "worldcollision.hh" + +#include +#include + +// One-shot collision diagnostics + +/** + * worldcollision.cpp - Player-vs-World Triangle Collision + * + * ALL math is 20.12 fixed-point. Intermediate products use int64_t + * to avoid overflow (20.12 * 20.12 = 40.24, shift >>12 back to 20.12). + * + * Performance budget: ~256 triangle tests per frame on 33MHz MIPS. + */ + +namespace psxsplash { + +// ============================================================================ +// Fixed-point helpers (20.12) +// ============================================================================ + +static constexpr int FRAC_BITS = 12; +static constexpr int32_t FP_ONE = 1 << FRAC_BITS; // 4096 + +// Multiply two 20.12 values → 20.12 +static inline int32_t fpmul(int32_t a, int32_t b) { + return (int32_t)(((int64_t)a * b) >> FRAC_BITS); +} + +// Fixed-point division: returns (a << 12) / b using only 32-bit DIV. +// Uses remainder theorem: (a * 4096) / b = (a/b)*4096 + ((a%b)*4096)/b +static inline int32_t fpdiv(int32_t a, int32_t b) { + if (b == 0) return 0; + int32_t q = a / b; + int32_t r = a - q * b; + // r * FP_ONE is safe when |r| < 524288 (which covers most game values) + return q * FP_ONE + (r << FRAC_BITS) / b; +} + +// Dot product of two 3-vectors in 20.12 +static inline int32_t dot3(int32_t ax, int32_t ay, int32_t az, + int32_t bx, int32_t by, int32_t bz) { + return (int32_t)((((int64_t)ax * bx) + ((int64_t)ay * by) + ((int64_t)az * bz)) >> FRAC_BITS); +} + +// Cross product components (each result is 20.12) +static inline void cross3(int32_t ax, int32_t ay, int32_t az, + int32_t bx, int32_t by, int32_t bz, + int32_t& rx, int32_t& ry, int32_t& rz) { + rx = (int32_t)(((int64_t)ay * bz - (int64_t)az * by) >> FRAC_BITS); + ry = (int32_t)(((int64_t)az * bx - (int64_t)ax * bz) >> FRAC_BITS); + rz = (int32_t)(((int64_t)ax * by - (int64_t)ay * bx) >> FRAC_BITS); +} + +// Square root approximation via Newton's method (for 20.12 input) +static int32_t fpsqrt(int32_t x) { + if (x <= 0) return 0; + // Initial guess: shift right by 6 (half of 12 fractional bits, then adjust) + int32_t guess = x; + // Rough initial guess + if (x > FP_ONE * 16) guess = x >> 4; + else if (x > FP_ONE) guess = x >> 2; + else guess = FP_ONE; + + // Newton iterations: guess = (guess + x/guess) / 2 in fixed-point + for (int i = 0; i < 8; i++) { + if (guess == 0) return 0; + int32_t div = fpdiv(x, guess); + guess = (guess + div) >> 1; + } + return guess; +} + +// Length squared of vector (result in 20.12, but represents a squared quantity) +static inline int32_t lengthSq(int32_t x, int32_t y, int32_t z) { + return dot3(x, y, z, x, y, z); +} + +// Clamp value to [lo, hi] +static inline int32_t fpclamp(int32_t val, int32_t lo, int32_t hi) { + if (val < lo) return lo; + if (val > hi) return hi; + return val; +} + +// ============================================================================ +// Initialization +// ============================================================================ + +const uint8_t* WorldCollision::initializeFromData(const uint8_t* data) { + // Read header + const auto* hdr = reinterpret_cast(data); + m_header = *hdr; + data += sizeof(CollisionDataHeader); + + // Mesh headers + m_meshes = reinterpret_cast(data); + data += m_header.meshCount * sizeof(CollisionMeshHeader); + + // Triangles + m_triangles = reinterpret_cast(data); + data += m_header.triangleCount * sizeof(CollisionTri); + + // Spatial chunks (exterior only) + if (m_header.chunkGridW > 0 && m_header.chunkGridH > 0) { + m_chunks = reinterpret_cast(data); + data += m_header.chunkGridW * m_header.chunkGridH * sizeof(CollisionChunk); + } else { + m_chunks = nullptr; + } + + + return data; +} + +// ============================================================================ +// Broad phase: gather candidate meshes +// ============================================================================ + +int WorldCollision::gatherCandidateMeshes(int32_t posX, int32_t posZ, + uint8_t currentRoom, + uint16_t* outIndices, + int maxIndices) const { + int count = 0; + + if (m_chunks && m_header.chunkGridW > 0) { + // Exterior: spatial grid lookup + // dividing two 20.12 values gives integer grid coords directly + int cx = 0, cz = 0; + if (m_header.chunkSize > 0) { + cx = (posX - m_header.chunkOriginX) / m_header.chunkSize; + cz = (posZ - m_header.chunkOriginZ) / m_header.chunkSize; + } + + // Check 3x3 neighborhood for robustness + for (int dz = -1; dz <= 1 && count < maxIndices; dz++) { + for (int dx = -1; dx <= 1 && count < maxIndices; dx++) { + int gx = cx + dx; + int gz = cz + dz; + if (gx < 0 || gx >= m_header.chunkGridW || gz < 0 || gz >= m_header.chunkGridH) + continue; + + const auto& chunk = m_chunks[gz * m_header.chunkGridW + gx]; + for (int i = 0; i < chunk.meshCount && count < maxIndices; i++) { + uint16_t mi = chunk.firstMeshIndex + i; + if (mi < m_header.meshCount) { + // Deduplicate: check if already added + bool dup = false; + for (int k = 0; k < count; k++) { + if (outIndices[k] == mi) { dup = true; break; } + } + if (!dup) { + outIndices[count++] = mi; + } + } + } + } + } + } else { + // Interior: filter by room index, or test all if no room system + for (uint16_t i = 0; i < m_header.meshCount && count < maxIndices; i++) { + if (currentRoom == 0xFF || m_meshes[i].roomIndex == currentRoom || + m_meshes[i].roomIndex == 0xFF) { + outIndices[count++] = i; + } + } + } + + return count; +} + +// ============================================================================ +// AABB helpers +// ============================================================================ + +bool WorldCollision::aabbOverlap(int32_t aMinX, int32_t aMinY, int32_t aMinZ, + int32_t aMaxX, int32_t aMaxY, int32_t aMaxZ, + int32_t bMinX, int32_t bMinY, int32_t bMinZ, + int32_t bMaxX, int32_t bMaxY, int32_t bMaxZ) { + return aMinX <= bMaxX && aMaxX >= bMinX && + aMinY <= bMaxY && aMaxY >= bMinY && + aMinZ <= bMaxZ && aMaxZ >= bMinZ; +} + +void WorldCollision::sphereToAABB(int32_t cx, int32_t cy, int32_t cz, int32_t r, + int32_t& minX, int32_t& minY, int32_t& minZ, + int32_t& maxX, int32_t& maxY, int32_t& maxZ) { + minX = cx - r; minY = cy - r; minZ = cz - r; + maxX = cx + r; maxY = cy + r; maxZ = cz + r; +} + +// ============================================================================ +// Sphere vs Triangle (closest point approach) +// ============================================================================ + +int32_t WorldCollision::sphereVsTriangle(int32_t cx, int32_t cy, int32_t cz, + int32_t radius, + const CollisionTri& tri, + int32_t& outNx, int32_t& outNy, int32_t& outNz) const { + // Compute vector from v0 to sphere center + int32_t px = cx - tri.v0x; + int32_t py = cy - tri.v0y; + int32_t pz = cz - tri.v0z; + + // Project onto triangle plane using precomputed normal + int32_t dist = dot3(px, py, pz, tri.nx, tri.ny, tri.nz); + + // Quick reject if too far from plane + int32_t absDist = dist >= 0 ? dist : -dist; + if (absDist > radius) return 0; + + // Find closest point on triangle to sphere center + // Use barycentric coordinates via edge projections + + // Precompute edge dot products for barycentric coords + int32_t d00 = dot3(tri.e1x, tri.e1y, tri.e1z, tri.e1x, tri.e1y, tri.e1z); + int32_t d01 = dot3(tri.e1x, tri.e1y, tri.e1z, tri.e2x, tri.e2y, tri.e2z); + int32_t d11 = dot3(tri.e2x, tri.e2y, tri.e2z, tri.e2x, tri.e2y, tri.e2z); + int32_t d20 = dot3(px, py, pz, tri.e1x, tri.e1y, tri.e1z); + int32_t d21 = dot3(px, py, pz, tri.e2x, tri.e2y, tri.e2z); + + // Barycentric denom using fpmul (stays in 32-bit) + int32_t denom = fpmul(d00, d11) - fpmul(d01, d01); + if (denom == 0) return 0; // Degenerate triangle + + // Barycentric numerators (32-bit via fpmul) + int32_t uNum = fpmul(d11, d20) - fpmul(d01, d21); + int32_t vNum = fpmul(d00, d21) - fpmul(d01, d20); + + // u, v in 20.12 using 32-bit division only + int32_t u = fpdiv(uNum, denom); + int32_t v = fpdiv(vNum, denom); + + // Clamp to triangle + int32_t w = FP_ONE - u - v; + + int32_t closestX, closestY, closestZ; + + if (u >= 0 && v >= 0 && w >= 0) { + // Point is inside triangle — closest point is the plane projection + closestX = cx - fpmul(dist, tri.nx); + closestY = cy - fpmul(dist, tri.ny); + closestZ = cz - fpmul(dist, tri.nz); + } else { + // Point is outside triangle — find closest point on edges/vertices + // Check all 3 edges and pick the closest point + + // v1 = v0 + e1, v2 = v0 + e2 + int32_t v1x = tri.v0x + tri.e1x; + int32_t v1y = tri.v0y + tri.e1y; + int32_t v1z = tri.v0z + tri.e1z; + int32_t v2x = tri.v0x + tri.e2x; + int32_t v2y = tri.v0y + tri.e2y; + int32_t v2z = tri.v0z + tri.e2z; + + int32_t bestDistSq = 0x7FFFFFFF; + closestX = tri.v0x; + closestY = tri.v0y; + closestZ = tri.v0z; + + // Helper lambda: closest point on segment [A, B] to point P + auto closestOnSeg = [&](int32_t ax, int32_t ay, int32_t az, + int32_t bx, int32_t by, int32_t bz, + int32_t& ox, int32_t& oy, int32_t& oz) { + int32_t abx = bx - ax, aby = by - ay, abz = bz - az; + int32_t apx = cx - ax, apy = cy - ay, apz = cz - az; + int32_t abLen = dot3(abx, aby, abz, abx, aby, abz); + if (abLen == 0) { ox = ax; oy = ay; oz = az; return; } + int32_t dotAP = dot3(apx, apy, apz, abx, aby, abz); + int32_t t = fpclamp(fpdiv(dotAP, abLen), 0, FP_ONE); + ox = ax + fpmul(t, abx); + oy = ay + fpmul(t, aby); + oz = az + fpmul(t, abz); + }; + + // Edge v0→v1 + int32_t ex, ey, ez; + closestOnSeg(tri.v0x, tri.v0y, tri.v0z, v1x, v1y, v1z, ex, ey, ez); + int32_t dx = cx - ex, dy = cy - ey, dz = cz - ez; + int32_t dsq = lengthSq(dx, dy, dz); + if (dsq < bestDistSq) { bestDistSq = dsq; closestX = ex; closestY = ey; closestZ = ez; } + + // Edge v0→v2 + closestOnSeg(tri.v0x, tri.v0y, tri.v0z, v2x, v2y, v2z, ex, ey, ez); + dx = cx - ex; dy = cy - ey; dz = cz - ez; + dsq = lengthSq(dx, dy, dz); + if (dsq < bestDistSq) { bestDistSq = dsq; closestX = ex; closestY = ey; closestZ = ez; } + + // Edge v1→v2 + closestOnSeg(v1x, v1y, v1z, v2x, v2y, v2z, ex, ey, ez); + dx = cx - ex; dy = cy - ey; dz = cz - ez; + dsq = lengthSq(dx, dy, dz); + if (dsq < bestDistSq) { bestDistSq = dsq; closestX = ex; closestY = ey; closestZ = ez; } + } + + // Compute vector from closest point to sphere center + int32_t nx = cx - closestX; + int32_t ny = cy - closestY; + int32_t nz = cz - closestZ; + + // Use 64-bit for distance-squared comparison to avoid 20.12 underflow. + // With small radii (e.g. radius=12 for 0.3m at GTE100), fpmul(12,12)=0 + // because 144>>12=0. This caused ALL collisions to silently fail. + // Both sides are in the same raw scale (no shift needed for comparison). + int64_t rawDistSq = (int64_t)nx * nx + (int64_t)ny * ny + (int64_t)nz * nz; + int64_t rawRadSq = (int64_t)radius * radius; + + if (rawDistSq >= rawRadSq || rawDistSq == 0) return 0; + + // For the actual distance value, use fpsqrt on the 20.12 representation. + // If the 20.12 value underflows to 0, estimate from 64-bit. + int32_t distSq32 = (int32_t)(rawDistSq >> FRAC_BITS); + int32_t distance; + if (distSq32 > 0) { + distance = fpsqrt(distSq32); + } else { + // Very close collision - distance is sub-unit in 20.12. + // Use triangle normal as push direction, penetration = radius. + outNx = tri.nx; + outNy = tri.ny; + outNz = tri.nz; + return radius; + } + + if (distance == 0) { + outNx = tri.nx; + outNy = tri.ny; + outNz = tri.nz; + return radius; + } + + // Normalize push direction using 32-bit division only + outNx = fpdiv(nx, distance); + outNy = fpdiv(ny, distance); + outNz = fpdiv(nz, distance); + + return radius - distance; // Penetration depth +} + +// ============================================================================ +// Ray vs Triangle (Möller-Trumbore, fixed-point) +// ============================================================================ + +int32_t WorldCollision::rayVsTriangle(int32_t ox, int32_t oy, int32_t oz, + int32_t dx, int32_t dy, int32_t dz, + const CollisionTri& tri) const { + // h = cross(D, e2) + int32_t hx, hy, hz; + cross3(dx, dy, dz, tri.e2x, tri.e2y, tri.e2z, hx, hy, hz); + + // a = dot(e1, h) + int32_t a = dot3(tri.e1x, tri.e1y, tri.e1z, hx, hy, hz); + if (a > -COLLISION_EPSILON && a < COLLISION_EPSILON) + return -1; // Ray parallel to triangle + + // f = 1/a — we'll defer the division by working with a as denominator + // s = O - v0 + int32_t sx = ox - tri.v0x; + int32_t sy = oy - tri.v0y; + int32_t sz = oz - tri.v0z; + + // u = f * dot(s, h) = dot(s, h) / a + int32_t sh = dot3(sx, sy, sz, hx, hy, hz); + // Check u in [0, 1]: sh/a must be in [0, a] if a > 0, or [a, 0] if a < 0 + if (a > 0) { + if (sh < 0 || sh > a) return -1; + } else { + if (sh > 0 || sh < a) return -1; + } + + // q = cross(s, e1) + int32_t qx, qy, qz; + cross3(sx, sy, sz, tri.e1x, tri.e1y, tri.e1z, qx, qy, qz); + + // v = f * dot(D, q) = dot(D, q) / a + int32_t dq = dot3(dx, dy, dz, qx, qy, qz); + if (a > 0) { + if (dq < 0 || sh + dq > a) return -1; + } else { + if (dq > 0 || sh + dq < a) return -1; + } + + // t = f * dot(e2, q) = dot(e2, q) / a + int32_t eq = dot3(tri.e2x, tri.e2y, tri.e2z, qx, qy, qz); + + // t in 20.12 using 32-bit division only + int32_t t = fpdiv(eq, a); + + if (t < COLLISION_EPSILON) return -1; // Behind ray origin + + return t; +} + +// ============================================================================ +// High-level: moveAndSlide +// ============================================================================ + +psyqo::Vec3 WorldCollision::moveAndSlide(const psyqo::Vec3& oldPos, + const psyqo::Vec3& newPos, + int32_t radius, + uint8_t currentRoom) const { + if (!isLoaded()) return newPos; + + int32_t posX = newPos.x.raw(); + int32_t posY = newPos.y.raw(); + int32_t posZ = newPos.z.raw(); + + // Gather candidate meshes + uint16_t meshIndices[32]; + int meshCount = gatherCandidateMeshes(posX, posZ, currentRoom, meshIndices, 32); + + // Sphere AABB for broad phase + int32_t sMinX, sMinY, sMinZ, sMaxX, sMaxY, sMaxZ; + sphereToAABB(posX, posY, posZ, radius + COLLISION_EPSILON, + sMinX, sMinY, sMinZ, sMaxX, sMaxY, sMaxZ); + + int triTests = 0; + int totalCollisions = 0; + + + + for (int iter = 0; iter < MAX_COLLISION_ITERATIONS; iter++) { + bool collided = false; + + for (int mi = 0; mi < meshCount && triTests < MAX_TRI_TESTS_PER_FRAME; mi++) { + const auto& mesh = m_meshes[meshIndices[mi]]; + + // Broad phase: sphere AABB vs mesh AABB + if (!aabbOverlap(sMinX, sMinY, sMinZ, sMaxX, sMaxY, sMaxZ, + mesh.aabbMinX, mesh.aabbMinY, mesh.aabbMinZ, + mesh.aabbMaxX, mesh.aabbMaxY, mesh.aabbMaxZ)) { + + continue; + } + + for (int ti = 0; ti < mesh.triangleCount && triTests < MAX_TRI_TESTS_PER_FRAME; ti++) { + const auto& tri = m_triangles[mesh.firstTriangle + ti]; + triTests++; + + // Skip trigger surfaces + if (tri.flags & SURFACE_TRIGGER) continue; + + // Skip floor and ceiling triangles — Y is resolved by nav regions. + // In PS1 space (Y-down): floor normals have ny < 0, ceiling ny > 0. + // If |ny| > walkable slope threshold, it's a floor/ceiling, not a wall. + int32_t absNy = tri.ny >= 0 ? tri.ny : -tri.ny; + if (absNy > WALKABLE_SLOPE_COS) continue; + + int32_t nx, ny, nz; + int32_t pen = sphereVsTriangle(posX, posY, posZ, radius, tri, nx, ny, nz); + + if (pen > 0) { + totalCollisions++; + // Push out along normal + posX += fpmul(pen + COLLISION_EPSILON, nx); + posY += fpmul(pen + COLLISION_EPSILON, ny); + posZ += fpmul(pen + COLLISION_EPSILON, nz); + + // Update sphere AABB + sphereToAABB(posX, posY, posZ, radius + COLLISION_EPSILON, + sMinX, sMinY, sMinZ, sMaxX, sMaxY, sMaxZ); + collided = true; + } + } + } + + if (!collided) break; + } + + psyqo::Vec3 result; + result.x.value = posX; + result.y.value = posY; + result.z.value = posZ; + return result; +} + +// ============================================================================ +// High-level: groundTrace +// ============================================================================ + +bool WorldCollision::groundTrace(const psyqo::Vec3& pos, + int32_t maxDist, + int32_t& groundY, + int32_t& groundNormalY, + uint8_t& surfaceFlags, + uint8_t currentRoom) const { + if (!isLoaded()) return false; + + int32_t ox = pos.x.raw(); + int32_t oy = pos.y.raw(); + int32_t oz = pos.z.raw(); + + // Ray direction: straight down (positive Y in PS1 space = down) + int32_t dx = 0, dy = FP_ONE, dz = 0; + + uint16_t meshIndices[32]; + int meshCount = gatherCandidateMeshes(ox, oz, currentRoom, meshIndices, 32); + + int32_t bestDist = maxDist; + bool hit = false; + + for (int mi = 0; mi < meshCount; mi++) { + const auto& mesh = m_meshes[meshIndices[mi]]; + + // Quick reject: check if mesh is below us + if (mesh.aabbMinY > oy + maxDist) continue; + if (mesh.aabbMaxY < oy) continue; + if (ox < mesh.aabbMinX || ox > mesh.aabbMaxX) continue; + if (oz < mesh.aabbMinZ || oz > mesh.aabbMaxZ) continue; + + for (int ti = 0; ti < mesh.triangleCount; ti++) { + const auto& tri = m_triangles[mesh.firstTriangle + ti]; + if (tri.flags & SURFACE_TRIGGER) continue; + + int32_t t = rayVsTriangle(ox, oy, oz, dx, dy, dz, tri); + if (t >= 0 && t < bestDist) { + bestDist = t; + groundY = oy + t; // Hit point Y + groundNormalY = tri.ny; + surfaceFlags = tri.flags; + hit = true; + } + } + } + + return hit; +} + +// ============================================================================ +// High-level: ceilingTrace +// ============================================================================ + +bool WorldCollision::ceilingTrace(const psyqo::Vec3& pos, + int32_t playerHeight, + int32_t& ceilingY, + uint8_t currentRoom) const { + if (!isLoaded()) return false; + + int32_t ox = pos.x.raw(); + int32_t oy = pos.y.raw(); + int32_t oz = pos.z.raw(); + + // Ray direction: straight up (negative Y in PS1 space) + int32_t dx = 0, dy = -FP_ONE, dz = 0; + + uint16_t meshIndices[32]; + int meshCount = gatherCandidateMeshes(ox, oz, currentRoom, meshIndices, 32); + + int32_t bestDist = playerHeight; + bool hit = false; + + for (int mi = 0; mi < meshCount; mi++) { + const auto& mesh = m_meshes[meshIndices[mi]]; + + if (mesh.aabbMaxY > oy) continue; + if (mesh.aabbMinY < oy - playerHeight) continue; + if (ox < mesh.aabbMinX || ox > mesh.aabbMaxX) continue; + if (oz < mesh.aabbMinZ || oz > mesh.aabbMaxZ) continue; + + for (int ti = 0; ti < mesh.triangleCount; ti++) { + const auto& tri = m_triangles[mesh.firstTriangle + ti]; + if (tri.flags & SURFACE_TRIGGER) continue; + + int32_t t = rayVsTriangle(ox, oy, oz, dx, dy, dz, tri); + if (t >= 0 && t < bestDist) { + bestDist = t; + ceilingY = oy - t; + hit = true; + } + } + } + + return hit; +} + +// ============================================================================ +// High-level: raycast (general purpose) +// ============================================================================ + +bool WorldCollision::raycast(int32_t ox, int32_t oy, int32_t oz, + int32_t dx, int32_t dy, int32_t dz, + int32_t maxDist, + CollisionHit& hit, + uint8_t currentRoom) const { + if (!isLoaded()) return false; + + uint16_t meshIndices[32]; + int meshCount = gatherCandidateMeshes(ox, oz, currentRoom, meshIndices, 32); + + int32_t bestDist = maxDist; + bool found = false; + + for (int mi = 0; mi < meshCount; mi++) { + const auto& mesh = m_meshes[meshIndices[mi]]; + + for (uint16_t ti = 0; ti < mesh.triangleCount; ti++) { + uint16_t triIdx = mesh.firstTriangle + ti; + const auto& tri = m_triangles[triIdx]; + + int32_t t = rayVsTriangle(ox, oy, oz, dx, dy, dz, tri); + if (t >= 0 && t < bestDist) { + bestDist = t; + hit.pointX = ox + fpmul(t, dx); + hit.pointY = oy + fpmul(t, dy); + hit.pointZ = oz + fpmul(t, dz); + hit.normalX = tri.nx; + hit.normalY = tri.ny; + hit.normalZ = tri.nz; + hit.distance = t; + hit.triangleIndex = triIdx; + hit.surfaceFlags = tri.flags; + found = true; + } + } + } + + return found; +} + +} // namespace psxsplash diff --git a/src/worldcollision.hh b/src/worldcollision.hh new file mode 100644 index 0000000..9efe224 --- /dev/null +++ b/src/worldcollision.hh @@ -0,0 +1,222 @@ +#pragma once + +/** + * worldcollision.hh - Player-vs-World Triangle Collision + * + * Architecture: + * 1. Broad phase: per-mesh AABB reject, then spatial grid (exterior) or + * room membership (interior) to narrow candidate meshes. + * 2. Narrow phase: per-triangle capsule-vs-triangle sweep. + * 3. Response: sliding projection along collision plane. + * + * All math is fixed-point 20.12. Zero floats. Deterministic at any framerate. + */ + +#include +#include +#include + +namespace psxsplash { + +// ============================================================================ +// Surface flags — packed per-triangle, exported from SplashEdit +// ============================================================================ +enum SurfaceFlag : uint8_t { + SURFACE_SOLID = 0x01, // Normal solid wall/floor + SURFACE_SLOPE = 0x02, // Steep slope (treated as wall for movement) + SURFACE_STAIRS = 0x04, // Staircase (smooth Y interpolation) + SURFACE_TRIGGER = 0x08, // Non-solid trigger volume + SURFACE_NO_WALK = 0x10, // Marks geometry as non-walkable floor +}; + +// ============================================================================ +// Collision triangle — world-space, pre-transformed, contiguous in memory +// 40 bytes each — v0(12) + v1(12) + v2(12) + normal(12) omitted to save +// Actually: 40 bytes = v0(12) + edge1(12) + edge2(12) + flags(1) + pad(3) +// We store edges for Moller-Trumbore intersection +// ============================================================================ +struct CollisionTri { + // Vertex 0 (world-space 20.12 fixed-point) + int32_t v0x, v0y, v0z; // 12 bytes + // Edge1 = v1 - v0 + int32_t e1x, e1y, e1z; // 12 bytes + // Edge2 = v2 - v0 + int32_t e2x, e2y, e2z; // 12 bytes + // Precomputed face normal (unit-ish, 20.12) + int32_t nx, ny, nz; // 12 bytes + // Surface properties + uint8_t flags; // SurfaceFlag bitmask + uint8_t roomIndex; // Room/chunk this tri belongs to (0xFF = none) + uint16_t pad; // Alignment +}; +static_assert(sizeof(CollisionTri) == 52, "CollisionTri must be 52 bytes"); + +// ============================================================================ +// Collision mesh header — one per collision mesh in the splashpack +// The triangles themselves follow contiguously after all headers. +// ============================================================================ +struct CollisionMeshHeader { + // World-space AABB for broad-phase rejection (20.12 fixed-point) + int32_t aabbMinX, aabbMinY, aabbMinZ; // 12 bytes + int32_t aabbMaxX, aabbMaxY, aabbMaxZ; // 12 bytes + // Offset into the collision triangle array + uint16_t firstTriangle; // Index of first CollisionTri + uint16_t triangleCount; // Number of triangles + // Room/chunk association + uint8_t roomIndex; // Interior room index (0xFF = exterior) + uint8_t pad[3]; +}; +static_assert(sizeof(CollisionMeshHeader) == 32, "CollisionMeshHeader must be 32 bytes"); + +// ============================================================================ +// Spatial chunk for exterior scenes — 2D grid over XZ +// ============================================================================ +struct CollisionChunk { + uint16_t firstMeshIndex; // Index into CollisionMeshHeader array + uint16_t meshCount; // Number of meshes in this chunk +}; +static_assert(sizeof(CollisionChunk) == 4, "CollisionChunk must be 4 bytes"); + +// ============================================================================ +// Collision data header — describes the entire collision dataset +// ============================================================================ +struct CollisionDataHeader { + uint16_t meshCount; // Number of CollisionMeshHeader entries + uint16_t triangleCount; // Total CollisionTri entries + uint16_t chunkGridW; // Spatial grid width (0 if interior) + uint16_t chunkGridH; // Spatial grid height (0 if interior) + int32_t chunkOriginX; // Grid origin X (20.12) + int32_t chunkOriginZ; // Grid origin Z (20.12) + int32_t chunkSize; // Cell size (20.12) + // Total: 20 bytes + // Followed by: meshCount * CollisionMeshHeader + // triangleCount * CollisionTri + // chunkGridW * chunkGridH * CollisionChunk (if exterior) +}; +static_assert(sizeof(CollisionDataHeader) == 20, "CollisionDataHeader must be 20 bytes"); + +// ============================================================================ +// Hit result from collision queries +// ============================================================================ +struct CollisionHit { + int32_t pointX, pointY, pointZ; // Hit point (20.12) + int32_t normalX, normalY, normalZ; // Hit normal (20.12) + int32_t distance; // Distance along ray (20.12) + uint16_t triangleIndex; // Which triangle was hit + uint8_t surfaceFlags; // SurfaceFlag of hit triangle + uint8_t pad; +}; + +// ============================================================================ +// Maximum slope angle for walkable surfaces +// cos(46°) ≈ 0.6947 → in 20.12 fixed-point = 2845 +// Surfaces with normal.y < this are treated as walls +// ============================================================================ +static constexpr int32_t WALKABLE_SLOPE_COS = 2845; // cos(46°) in 20.12 + +// Player collision capsule radius (20.12 fixed-point) +// ~0.5 world units at GTEScaling=100 → 0.005 GTE units → 20 in 20.12 +static constexpr int32_t PLAYER_RADIUS = 20; + +// Small epsilon for collision (20.12) +// ≈ 0.01 GTE units +static constexpr int32_t COLLISION_EPSILON = 41; + +// Maximum number of collision iterations per frame +static constexpr int MAX_COLLISION_ITERATIONS = 8; + +// Maximum triangles to test per frame (budget) +static constexpr int MAX_TRI_TESTS_PER_FRAME = 256; + +// ============================================================================ +// WorldCollision — main collision query interface +// Loaded from splashpack data, used by SceneManager every frame +// ============================================================================ +class WorldCollision { +public: + WorldCollision() = default; + + /// Initialize from splashpack data. Returns pointer past the data. + const uint8_t* initializeFromData(const uint8_t* data); + + /// Is collision data loaded? + bool isLoaded() const { return m_triangles != nullptr; } + + // ======================================================================== + // High-level queries used by the player movement system + // ======================================================================== + + /// Move a sphere from oldPos to newPos, sliding against world geometry. + /// Returns the final valid position after collision response. + /// radius is in 20.12 fixed-point. + psyqo::Vec3 moveAndSlide(const psyqo::Vec3& oldPos, + const psyqo::Vec3& newPos, + int32_t radius, + uint8_t currentRoom) const; + + /// Cast a ray downward from pos to find the ground. + /// Returns true if ground found within maxDist. + /// groundY and groundNormal are filled on hit. + bool groundTrace(const psyqo::Vec3& pos, + int32_t maxDist, + int32_t& groundY, + int32_t& groundNormalY, + uint8_t& surfaceFlags, + uint8_t currentRoom) const; + + /// Cast a ray upward to detect ceilings. + bool ceilingTrace(const psyqo::Vec3& pos, + int32_t playerHeight, + int32_t& ceilingY, + uint8_t currentRoom) const; + + /// Raycast against collision geometry. Returns true on hit. + bool raycast(int32_t ox, int32_t oy, int32_t oz, + int32_t dx, int32_t dy, int32_t dz, + int32_t maxDist, + CollisionHit& hit, + uint8_t currentRoom) const; + + /// Get mesh count for debugging + uint16_t getMeshCount() const { return m_header.meshCount; } + uint16_t getTriangleCount() const { return m_header.triangleCount; } + +private: + CollisionDataHeader m_header = {}; + const CollisionMeshHeader* m_meshes = nullptr; + const CollisionTri* m_triangles = nullptr; + const CollisionChunk* m_chunks = nullptr; // Only for exterior scenes + + /// Collect candidate mesh indices near a position. + /// For exterior: uses spatial grid. For interior: uses roomIndex. + int gatherCandidateMeshes(int32_t posX, int32_t posZ, + uint8_t currentRoom, + uint16_t* outIndices, + int maxIndices) const; + + /// Test a sphere against a single triangle. Returns penetration depth (>0 if colliding). + /// On collision, fills outNormal with the push-out direction. + int32_t sphereVsTriangle(int32_t cx, int32_t cy, int32_t cz, + int32_t radius, + const CollisionTri& tri, + int32_t& outNx, int32_t& outNy, int32_t& outNz) const; + + /// Ray vs triangle (Moller-Trumbore in fixed-point). + /// Returns distance along ray (20.12), or -1 if no hit. + int32_t rayVsTriangle(int32_t ox, int32_t oy, int32_t oz, + int32_t dx, int32_t dy, int32_t dz, + const CollisionTri& tri) const; + + /// AABB vs AABB test + static bool aabbOverlap(int32_t aMinX, int32_t aMinY, int32_t aMinZ, + int32_t aMaxX, int32_t aMaxY, int32_t aMaxZ, + int32_t bMinX, int32_t bMinY, int32_t bMinZ, + int32_t bMaxX, int32_t bMaxY, int32_t bMaxZ); + + /// Expand a point to an AABB with radius + static void sphereToAABB(int32_t cx, int32_t cy, int32_t cz, int32_t r, + int32_t& minX, int32_t& minY, int32_t& minZ, + int32_t& maxX, int32_t& maxY, int32_t& maxZ); +}; + +} // namespace psxsplash diff --git a/test_literal.cpp b/test_literal.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0a89308f88f7914763a5eba141d8f37d0bf38a32 GIT binary patch literal 192 zcmYk0-3o$06okL)pm$jC+N4{}US(NQOE7=h4f5sFX%UjcxtTNb%~`$)Ej>3n-c-~W zE{u3)CI&JZ-9OSRQt3l<2TnXRSB@N%^HIGk+v9l3?d5f%{&;^{PQK&W9nY%2CCL9$ Xu2a5d!2g1#ep literal 0 HcmV?d00001 diff --git a/third_party/nugget b/third_party/nugget index 7ed81a1..338ec49 160000 --- a/third_party/nugget +++ b/third_party/nugget @@ -1 +1 @@ -Subproject commit 7ed81a19ce8221cd42c0d45d1b737953088bd147 +Subproject commit 338ec49a574d0eb0a4218795bf526dcf2b43ecda