From 2be0199497d204b38f989bb370cf9d66758519e5 Mon Sep 17 00:00:00 2001 From: KiritoDv Date: Sat, 7 Oct 2023 21:05:56 -0600 Subject: [PATCH 1/7] Added needed assets to avoid using soh.otr --- assets/fonts/Fipps-Regular.otf | Bin 0 -> 34220 bytes assets/fonts/PressStart2P-Regular.ttf | Bin 0 -> 116008 bytes assets/textures/buttons/ABtn.png | Bin 0 -> 10967 bytes assets/textures/buttons/BBtn.png | Bin 0 -> 1300 bytes assets/textures/buttons/CDown.png | Bin 0 -> 11241 bytes assets/textures/buttons/CLeft.png | Bin 0 -> 11232 bytes assets/textures/buttons/CRight.png | Bin 0 -> 11284 bytes assets/textures/buttons/CUp.png | Bin 0 -> 11219 bytes assets/textures/buttons/LBtn.png | Bin 0 -> 355 bytes assets/textures/buttons/RBtn.png | Bin 0 -> 379 bytes assets/textures/buttons/StartBtn.png | Bin 0 -> 11868 bytes assets/textures/buttons/ZBtn.png | Bin 0 -> 1241 bytes assets/textures/icons/gIcon.png | Bin 0 -> 7072 bytes .../textures/virtual/gEmptyTexture.rgba32.png | Bin 0 -> 194 bytes libultraship | 2 +- src/game/level_update.c | 4 ++-- src/port/Engine.cpp | 7 +++---- src/port/ui/ImguiUI.cpp | 4 +++- 18 files changed, 9 insertions(+), 8 deletions(-) create mode 100644 assets/fonts/Fipps-Regular.otf create mode 100644 assets/fonts/PressStart2P-Regular.ttf create mode 100644 assets/textures/buttons/ABtn.png create mode 100644 assets/textures/buttons/BBtn.png create mode 100644 assets/textures/buttons/CDown.png create mode 100644 assets/textures/buttons/CLeft.png create mode 100644 assets/textures/buttons/CRight.png create mode 100644 assets/textures/buttons/CUp.png create mode 100644 assets/textures/buttons/LBtn.png create mode 100644 assets/textures/buttons/RBtn.png create mode 100644 assets/textures/buttons/StartBtn.png create mode 100644 assets/textures/buttons/ZBtn.png create mode 100644 assets/textures/icons/gIcon.png create mode 100644 assets/textures/virtual/gEmptyTexture.rgba32.png diff --git a/assets/fonts/Fipps-Regular.otf b/assets/fonts/Fipps-Regular.otf new file mode 100644 index 0000000000000000000000000000000000000000..9334dad594277ba8339786217d75260e58436dc8 GIT binary patch literal 34220 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 SXU=y99^PLyB0t}XG5`Q>oR^&d literal 0 HcmV?d00001 diff --git a/assets/fonts/PressStart2P-Regular.ttf b/assets/fonts/PressStart2P-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..2442affbf6570d23c0b2b270cca8d49535df27bf GIT binary patch literal 116008 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 CWvzGs literal 0 HcmV?d00001 diff --git a/assets/textures/buttons/CDown.png b/assets/textures/buttons/CDown.png new file mode 100644 index 0000000000000000000000000000000000000000..741188eaf8cf052387192034e0dc72f27eba69ee GIT binary patch literal 11241 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 literal 0 HcmV?d00001 diff --git a/assets/textures/buttons/CLeft.png b/assets/textures/buttons/CLeft.png new file mode 100644 index 0000000000000000000000000000000000000000..5e26a2067e159b8bc2c9116664f9e72b5d7896fa GIT binary patch literal 11232 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANNv7pp=dN`ey06$*;-(=u~X z6-p`#QWa7wGSe6sDsHWvkvZwH6<^E$&mzSvc@K(Y)(O66EB^D-;iT5}OQ*YMl-*vW zIg@2VCrd=y-|zEt{~g|+u+}uN$kSBynAx?6+T?&YU)Rs`Pk;Q==Kr7C?)USboh?3W zywtn>b#~tO&%KpDe;mx_zixkicG3NhPvcJ9^p9r08+rWmU5{sfP2WG-8xVV4;p(LO zzHh%fJ+AvOqiFx*bp7E?fkwg>bKkif5*DsGuL0(FV;W!{p~#d_@tTdBn*FiY@4kA`QgoD`}1Ywy8k?v zK6?Ax*}1>>e%Gm9e|oo=u%NH`V!sV{vnA!;wAjpwXXd0{XJ*yIXgeesZ45(jjQaH?3z>eNu*&%?PE3d z1M9Ez?yr5!AHwQrnW$%RBWM4#7mw_Ai1M9leBSbUs?)hAmHG}HH=+%Wu?lQB$y#{w z=9}8GJ_|91tcZ8cUu0S4bj+7{napu=_M1e$>d&*^oZB2%^U12$jIm{cuVhYEMuX_$ z1M_9LWwL}+x;!T79QBww&8f>X$jfr&iLEk4XC?)o+xcWxG^eVV=E^5QUb<_4NnLPQ zx$;?5*4nj|mv5}t820$*F`w_YUv4a2KCk-a*6ob7ZZbxfPAu-Ry7k4_Cnagw%=DS1 z+n%0r(hQ5)bm`RUHM?%LWm~`5k-B_t`OVVp>|3R;=C7}BoV7dfGrMN3p8X2L?4ml| zharA0hRlZYvK{HJ3{KTY&xnaH4u2wI96fQPf}FDJM#IaRNe&XzKX1C*e3!kbzIFD* zPvJX$+AF4u?E24M#P(v}<8RDa!B6vKj;zzUGUJxRx5E0VX*HQqS8mN;w|ni8^t*pb z&h9Kfz3ZgiYX6qCQ}-0#4_n@K{GDd>v63mDPBkmPpILUcxGdA^&F20l)|C5eY;HZb zxIJ^*;RucwKhtXGSBAu{_E8VciaVLh$*unLzH#Af$-+6wCC?3%&VJ@x^7BGM|55E4?nQ+#!7S@^Xtc&glxzB>$Ywo@dGNprv-()Qo8{ZuzEG zxm_FelgtmSK05t$ZR^I#ZF}BoXs*w^EoASq>E3z+;g9`4igknS;oMxrs7A7yO?ylzj>G5|3w%Fp8F=Zj)b^VQ%YBDwr zy%QE*y^x!`S8R@d^rX3xj3<+qm_+Vezs9HB`|lgEQ*W=G;oCWR)@JiXqCb9yoY0%} zl3(x2uDe%0$_u?Pm|)tpF}daHgBwwkIrOg`c)z21>6+()W(N}|^*(j7U(M#hCA>94 zzGAlh^4I<0s%+jZ>}tX~3TIho-dd&luItv8cPAVc1ibltgQw++MCTgUxqeLkz7KZo zsDCi~S*yI+;u8NS+rt7|OT&x!b7j(>|2lod&^|rr<|M(wNhQ9j+(MN#GgiJ;*?XHQ z&dPDw`wK6a&iwrOqr-9gN^Lvg?(n1i7T*-bd~{Qnu(D<_yyf;U*x0<Ph@vA;Q-d`oDqm#(V1Gk2(kc%+`Pve0zRUD{Hn&vr6dl%4PM zaSjfdeL~|`os#0*^fz9OG5@BWT()bcca?YQn@bvfa#25Jb$_2&yFKT-VBVLGB}F>I z^9rtfk?RxI|H1yOglYQuMJ>wpS0g`$9-DD@LhzFsi%Cz9{Yg8*fAQh%o+q+TE2dgR zNhn(z$166q*&a^N+ZOCstQFPk@U+S2&j;UWK;gC>3n`1yWmTlK=QSMJ|}GxP$_r&_M6uzty) zu_@_=iC9s%bNub{owj}@=Oo?w>W<#I%ju&jlYei|LD7^}|8H(7Csn7PORqiiif79= z`J4lPj7}f7tYdnkTfSsfdupG>YmezS)}+c@Q`z}pM~luHhuJ3&N43lrODvFHdFg%r zE9vSo^GL4FSwDDRJuv1u@k{-}mW}td=I`1i?EBR9@up0TmFrDb8br+Wa*6P%bXl7p zy;NRR^29~gNnu?~)yIz%zuL31`0EGu8HIDgtz zp2>&yaO`FBt?{^Jcww(*DbKyjjwdE7ZLJYz=-8*ZM*5^(_`)|q*TT>IShQ>T8}n}4 zk_{8&q6}}{Vx9kd@2OjJY@Wx6M|-Ewy|CrwlL;HGGN)U(>Re@?ylu7R9+uNfzv2p%-SnoKBcUd zYQ6M+%~Ovr7sG-+y*#<+7k8!5q^r!fXWEW%q#1p2dnk0|=#wk6mZT{MPfTNveC~6J zDPaB$ledpEU7{ByPp@uTFg>T~vhqdGFBk7j-@X3E(I2yq@tZctEt@3%x5`K8{PS6U zJr~{|E=ySz|BLtb;gD^`bFAi>uX!iaYni05b;YsOy8i#78E;#zj`*eRG~v$0hzw$p2+-shzp|G^c!asR$|E|x{1mz5{%sq))9D`QiOai`*os#`mDuD!dI z*SB@+yeXIL&tK(=3;n-M)mb}3z{9&#$o=O2WYaUxnG8#WYc4wNT``sWUF_Dz6%C#X z4&J=g7j2vKcKQ-MliimX`mXXBUi$4hb;iU0uTKgcdcW6vKi}!6A4I1eoLKXvwr}}s znNt?4%N|&`tDf68`SQeR%i3mnU#i*md_9}!%-7j%yKhf;bnE%Q$ff^+cPCne?^!a> zXzG>>8UL3>-EUXDRXMdOBx4JsYwsTtek&$?e9Inz`&4> zwQ-kOP?8L4+}Zj#@--On?D{Thm)M$i?zzrf_I$ZN^(ozgK^a^tW4}+{rt4z5%|XV{ ztHYk*Jkz@<>w}9JkCkYin7ZuJkS7P7#LeKot*TKg*_{4;XZ~XMlQVffbRH@8G1qy- z`)H=G_wzmeGD)tUigxO9<}aF#^v6t(t$Q3(5bU@ITt!C41&k*IES~1IF z%?hcf+>+Bp&qdFn_W<@pyMAGYT55(pbpDEf{4Ql|LUQ9 z(I3vDlTWl7CnhtBu9zTW|3m)IyK86seRp{t)VT07>9)MGLxOn5m7L$dZ)}rRZ}=~{ z<5JW=FWDJ?RrX%HbjaP|y;(uw-Ahkw9~7}m)Lc?^E>f}gRh-X$@BE9|n;h=zUfBEW z+qFB#UouZvwr*N}L$hZxN)4{^3rViZPPR-@vbW>1sj#ZZEyztRNmQuF&B-gas<2f8n`@Ot;j4hQnKSxuqjGOvkG!?gBnqkl4h%vQBqQ1rLSLJ zUanVete0Puu5V~*X{m2uq;F)TTa=QfTU?n}l31aeSF8*&0%C?sYH@N=Wgg98y`3svneEoL^d$ zoa$PZnpdKXY-vGGX)&^$rNyZrA+Wo1QquG*av?S&>v8w=4S?$b1!H<0NfnH{2idCXTnu(=JN}6txS)!?~iLsHPZjxz=xvr&Uim_R8nqiVrYATWu z{zaMTnR$sh$gTnzm6DlaWol}elx&%lqHAc7YNTsokesNSWRzy9n`Drhl9Xm)W|U|K zaSbS7tsDb9ZIz7lkVFD<5=+wZi*jw1!ZY(y^2>|C_JS3MWag#@mn4FM&Ctxm)Xdb- z(7?pd(#*uz9HA&IwWv5VKM!Q8p@E*E1z08}*~%@yC^xahRw*+#F+EkkATJ#(QIT6< zk(=&@p ziYh$`cnVFO4J2WDWEPj?7gZwJADmhU;X%BWlL-zg1qE>awMt9|g`%xeabj6&3OEWB zU{c8$iFxU%DYi<`Tmlo%#FAhwlTr-KjLpn-(~Q&1bWKtX(sV77k_~hXlZ_2ck}S*& zlb|sVH@!GNt)x7$DAh4NHLt{0$vrc-036Q>8qjp6iRzm2j8sq}GB7d%mtMvphGtfV z7FI?kN|4xAu+aw*^Un}JBuqntXEw=ImCAi?yf`XjP)FMd8fYVKIY9YiV z8+{z=ko1F-T|PnW$jTtPo%0JSi!#$QO8m?7@LG>WA;biq%;eO(V!U=ED}e+HQVn9q z1r4eo7dJaD8+~v!2CC8^VF9XAXo;bzkCs*_D2!S{QuvMr*JyB&6apkE9!*`N!9`LC zkfeAtbx|$2xDb8i)Vvg1rE(>EyCo`j{TLV+*pj^6T^Rm@;DWu&Co?cGa29w(7Bet# z3xhBt!>lEaj?!O5r~aQ{6cs6$RFe!au; zKf0cOKVCA32izmsJfbyVvlQ78AP2zspTB%zP>Fd(l-UFoVY3+7GMJ?>8e}<)4WbD- z1g`^-Eyrgm$RczMa>%>S|G>g#nQw5~gH;0AVvq@>S`N|!bHM8l5J!N-yer>f*oPs2 zY&kg=gDe7JkOM$82;Y4C2SkA}x+9ptqDTbHQW%XSMK)7imIbF?6bHQApajld)U+IC zGt3cN+l9b(z{KGWxU)p;KXS0a6c9_#oBWgkgo#m)>}#ULJ@6AT}`=WHAVX)WR$WvGHM$BVK*@$IzL^2aXD`1Mq3U zB?q$@Bu}j6Ahqb2EC=ASTttDi5Cl1f@zivY|3bV>48#OKvc(|H$UcWzuB-VSB!z)N zX#iPp|M_2FE;xgNBzXxqo*2u)CSoB#=A#S0geF9yqJR?1(Je;j6XgJOO)x$z*kOEZ zG&bKOt78IH79fl)h|W&Rl|~n$h>vU^(IJ4%5pN!R!Rru0z6WUr*M_j7(L_@KBt{T} z(gd~~0Mdw@3vfAL(z>qv9ugpnK^U$8i2)1# zS0Da@MZwt+!~&JiAj3gyVz8qpGsFJv5JN!fL5=_=N?aC$)L_HlC;&;!FB1Tl&9-`= zwQC>|;xV#A^ljiqVi*AO{lv9j!Q#8l`~)k+h=RkHASD!76%ho;$M`IT8G&p$ObkTB z9gx`d0qi1>ATbzZF$lxdf*b+DFg}b1Sq#E3K8S|-9>m756c!cQitywCQb#gIw-i0- zVFtoO044yVuii&ABw=F2&>)LJ7+EdMa%3?W8zTfj60lGJu|bUoM??Gx6Wv0PJ`i5M z?>hr1cY)X-j2!eJJ_uuV07wFaVXYPrTZhCb0a*^x2f|Bsf5&MLP6?1k5Jq+ch!4WJ z9JBY_F9uiv15yXV_$&v>6LbJb5eOqY1Xh=W#Ia$JBM4ax(?_%eU>ZPlQ78|%&;qfM eF{o@u<`cud{@ry(qg-V>NUf)^X_+~x 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 literal 0 HcmV?d00001 diff --git a/assets/textures/buttons/LBtn.png b/assets/textures/buttons/LBtn.png new file mode 100644 index 0000000000000000000000000000000000000000..2e0a8f00c3b02c2b5d254311bfda6a703f13efa7 GIT binary patch literal 355 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4rT@h2A3sW#~2tG7>k44ofy`glX=O&z`&C3 z=+6>8-mT>29Knt_2qpd`pI_&+LOc%U3B$-uzCS>O>_%)r2R7=#&* z=dVa%U|?9_>Eak-;lK6rZJ}lb9@juFk&8}0|L@Nga+>9L_`#P=@h;~tna4(dv3{^j zH;t)e?+n2i=Ve#itUkY7aNn9ZezOP0DNPdVYd-rcJYp7jETzKGd_y5q;FqJ)hE9%y zY)U>8K3`)MV7PHk+vS14#_xH1oHTa@@d;Wa?Qzl+bWogMnaLoQ6T*k44ofy`glX=O&z`&C3 z=FJE3?UvF-1US3|VudhFU{`~y> z{OQxDXJ=<8CnrZoM>8-m^eO>_%)r2R7=#&* z=dVa%U|`tc>Eak-;lFnBZN6p&0oFhl0muLU^LMw$Mm<^NdD~MYu}wfOc4{hM;G9QBId zwk_Tan|$UqZwWbI_S0y>RURF|#hdQ^c5o>$adG{pXEE=;j%52EsX5QN=UtPyc5v&$ RO$-bS44$rjF6*2UngH=_mIVL+ literal 0 HcmV?d00001 diff --git a/assets/textures/buttons/StartBtn.png b/assets/textures/buttons/StartBtn.png new file mode 100644 index 0000000000000000000000000000000000000000..c3e08dc3600853304c65a79b2ba937d297c03c78 GIT binary patch literal 11868 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 R%fP_E;OXk;vd$@?2>{)LqU8Vp literal 0 HcmV?d00001 diff --git a/assets/textures/virtual/gEmptyTexture.rgba32.png b/assets/textures/virtual/gEmptyTexture.rgba32.png new file mode 100644 index 0000000000000000000000000000000000000000..eaf806b71c2ffd1f7020e54bacc43fb9d8149fd8 GIT binary patch literal 194 zcmeAS@N?(olHy`uVBq!ia0y~yU}ykg4mJh`hQoG=rx_R+7>k44ofy`glX=O&z`&C3 z=action != ACT_UNINITIALIZED) { if (save_file_exists(gCurrSaveFileNum - 1)) { set_mario_action(gMarioState, ACT_IDLE, 0); - } else { + } else if (CVarGetInteger("gDisablePeachCutscene", 0) == 0){ set_mario_action(gMarioState, ACT_INTRO_CUTSCENE, 0); val4 = TRUE; } @@ -1263,7 +1263,7 @@ s32 lvl_init_from_save_file(UNUSED s16 arg0, s32 levelNum) { #endif sWarpDest.type = WARP_TYPE_NOT_WARPING; sDelayedWarpOp = WARP_OP_NONE; - gNeverEnteredCastle = !save_file_exists(gCurrSaveFileNum - 1); + gNeverEnteredCastle = !save_file_exists(gCurrSaveFileNum - 1) || CVarGetInteger("gDisablePeachCutscene", 0) == 0; gCurrLevelNum = levelNum; gCurrCourseNum = COURSE_NONE; diff --git a/src/port/Engine.cpp b/src/port/Engine.cpp index 45e364a5..61ca11f5 100644 --- a/src/port/Engine.cpp +++ b/src/port/Engine.cpp @@ -30,9 +30,9 @@ GameEngine::GameEngine(){ if (std::filesystem::exists(cubePath)) { OTRFiles.push_back(cubePath); } - std::string sohOtrPath = LUS::Context::GetPathRelativeToAppBundle("soh.otr"); - if (std::filesystem::exists(sohOtrPath)) { - OTRFiles.push_back(sohOtrPath); + std::string sm64OtrPath = LUS::Context::GetPathRelativeToAppBundle("sm64.otr"); + if (std::filesystem::exists(sm64OtrPath)) { + OTRFiles.push_back(sm64OtrPath); } std::string patchesPath = LUS::Context::GetPathRelativeToAppDirectory("mods"); if (patchesPath.length() > 0 && std::filesystem::exists(patchesPath)) { @@ -63,7 +63,6 @@ void GameEngine::Create(){ void GameEngine::Destroy(){ GameEngine::AudioExit(); - this->context = nullptr; } bool ShouldClearTextureCacheAtEndOfFrame = false; diff --git a/src/port/ui/ImguiUI.cpp b/src/port/ui/ImguiUI.cpp index d2c58248..bbc13e4d 100644 --- a/src/port/ui/ImguiUI.cpp +++ b/src/port/ui/ImguiUI.cpp @@ -269,7 +269,7 @@ void DrawGameMenu() { if (ImGui::BeginMenu("Ghostship")) { if (ImGui::MenuItem("Reset", #ifdef __APPLE__ - "Command-R" + "Command-R" #else "Ctrl+R" #endif @@ -296,6 +296,8 @@ void DrawEnhancementsMenu() { if (ImGui::BeginMenu("Gameplay")) { UIWidgets::PaddedEnhancementCheckbox("No Level of Detail (LOD)", "gDisableLOD", true, false); UIWidgets::Tooltip("Disable Level of Detail (LOD) to avoid models using lower poly versions at a distance"); + UIWidgets::PaddedEnhancementCheckbox("Avoid playing peach cutscene", "gDisablePeachCutscene", true, false); + UIWidgets::Tooltip("Avoid playing the peach cutscene when starting a new game"); ImGui::EndMenu(); } From d1928be97d10b34c9c17a92892eba09fa0808b1d Mon Sep 17 00:00:00 2001 From: KiritoDv Date: Sat, 7 Oct 2023 21:38:26 -0600 Subject: [PATCH 2/7] Changed dictionary load order because it crashes on windows --- src/port/Engine.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/port/Engine.cpp b/src/port/Engine.cpp index 61ca11f5..79a23adc 100644 --- a/src/port/Engine.cpp +++ b/src/port/Engine.cpp @@ -54,6 +54,7 @@ GameEngine::GameEngine(){ this->context->GetResourceManager()->GetResourceLoader()->RegisterResourceFactory(LUS::ResourceType::SDialog, "Dialog", std::make_shared()); this->context->GetResourceManager()->GetResourceLoader()->RegisterResourceFactory(LUS::ResourceType::Dictionary, "Dictionary", std::make_shared()); GameEngine::AudioInit(); + this->LoadDictionary(); } void GameEngine::Create(){ @@ -168,7 +169,6 @@ void GameEngine::AudioExit() { void GameEngine::LoadDictionary() { this->dictionary = static_cast> *>(ResourceGetDataByName("__OTR__texts/strings/global")); - int bp = 0; } extern "C" uint32_t GameEngine_GetSampleRate() { @@ -267,11 +267,8 @@ extern "C" uint8_t* GameEngine_LoadTranslation(const char* key) { auto engine = GameEngine::Instance; auto dictionary = engine->dictionary; - if (dictionary == nullptr) { - engine->LoadDictionary(); - } - assert(engine->dictionary != nullptr); assert(engine->dictionary->contains(key)); + return engine->dictionary->at(key).data(); } \ No newline at end of file From ebc18605c09f23fd931193ba8cf78ab52465f33a Mon Sep 17 00:00:00 2001 From: KiritoDv Date: Sun, 8 Oct 2023 00:16:03 -0600 Subject: [PATCH 3/7] Moved debug level selector to a cvar --- src/game/main.h | 3 ++- src/menu/title_screen.c | 2 +- src/port/Variables.cpp | 1 - src/port/ui/ImguiUI.cpp | 41 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/game/main.h b/src/game/main.h index 6028cbdb..c533b6ab 100644 --- a/src/game/main.h +++ b/src/game/main.h @@ -63,11 +63,12 @@ extern struct SPTask *gActiveSPTask; extern u32 gNumVblanks; extern s8 gResetTimer; extern s8 gNmiResetBarsTimer; -extern s8 gDebugLevelSelect; extern s8 D_8032C650; extern s8 gShowProfiler; extern s8 gShowDebugText; +#define gDebugLevelSelect (CVarGetInteger("gEnableDebugMode", 0) == 1) + void exec_display_list(struct SPTask *spTask); #endif // MAIN_H diff --git a/src/menu/title_screen.c b/src/menu/title_screen.c index e640ea0e..0c7d6694 100644 --- a/src/menu/title_screen.c +++ b/src/menu/title_screen.c @@ -143,7 +143,7 @@ s16 intro_level_select(void) { // ... the level select quit combo is being pressed, which uses START. If this // is the case, quit the menu instead. if (gPlayer1Controller->buttonDown == QUIT_LEVEL_SELECT_COMBO) { - gDebugLevelSelect = FALSE; + // CVarSetInteger("gEnableDebugMode", 0); return -1; } play_sound(SOUND_MENU_STAR_SOUND, gGlobalSoundSource); diff --git a/src/port/Variables.cpp b/src/port/Variables.cpp index ffce12a3..1fccfaa4 100644 --- a/src/port/Variables.cpp +++ b/src/port/Variables.cpp @@ -15,7 +15,6 @@ u64 osClockRate = 62500000; s8 gShowProfiler = FALSE; s8 gShowDebugText = FALSE; -s8 gDebugLevelSelect = FALSE; OSIoMesg gDmaIoMesg; OSMesg gMainReceivedMesg; diff --git a/src/port/ui/ImguiUI.cpp b/src/port/ui/ImguiUI.cpp index bbc13e4d..d7ffb150 100644 --- a/src/port/ui/ImguiUI.cpp +++ b/src/port/ui/ImguiUI.cpp @@ -305,6 +305,44 @@ void DrawEnhancementsMenu() { } } +void DrawDebugMenu() { + if (ImGui::BeginMenu("Developer")) { + + UIWidgets::PaddedEnhancementCheckbox("Enable level selector", "gEnableDebugMode", true, false); + UIWidgets::Tooltip("Enable the level selector in the main menu"); + UIWidgets::Spacer(0); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(12.0f, 6.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.22f, 0.38f, 0.56f, 1.0f)); + if (GameUI::mStatsWindow) { + if (ImGui::Button( + GameUI::GetWindowButtonText("Stats", CVarGetInteger("gStatsEnabled", 0)).c_str(), + ImVec2(-1.0f, 0.0f))) { + GameUI::mStatsWindow->ToggleVisibility(); + } + UIWidgets::Tooltip("Shows the stats window, with your FPS and frametimes, and the OS " + "you're playing on"); + } + if (GameUI::mConsoleWindow) { + if (ImGui::Button( + GameUI::GetWindowButtonText("Console", CVarGetInteger("gConsoleEnabled", 0)) + .c_str(), + ImVec2(-1.0f, 0.0f))) { + GameUI::mConsoleWindow->ToggleVisibility(); + } + UIWidgets::Tooltip("Enables the console window, allowing you to input commands, type " + "help for some examples"); + } + + ImGui::PopStyleVar(3); + ImGui::PopStyleColor(1); + + ImGui::EndMenu(); + } +} + + void GameMenuBar::DrawElement() { if(ImGui::BeginMenuBar()){ DrawMenuBarIcon(); @@ -323,6 +361,9 @@ void GameMenuBar::DrawElement() { DrawEnhancementsMenu(); + ImGui::SetCursorPosY(0.0f); + DrawDebugMenu(); + ImGui::PopStyleVar(1); ImGui::EndMenuBar(); } From 953d72a045c4edf885459a9ee12b6f2cdddf031a Mon Sep 17 00:00:00 2001 From: KiritoDv Date: Sun, 8 Oct 2023 03:56:52 -0600 Subject: [PATCH 4/7] [WIP] Implemented frame interpolation --- include/sm64.h | 12 + include/types.h | 19 ++ src/engine/graph_node.h | 16 +- src/engine/surface_collision.c | 62 ++++- src/engine/surface_load.c | 6 + src/game/camera.c | 17 ++ src/game/camera.h | 2 + src/game/envfx_bubbles.c | 57 ++++ src/game/envfx_snow.c | 60 ++-- src/game/envfx_snow.h | 2 +- src/game/hud.c | 25 +- src/game/ingame_menu.c | 51 ++++ src/game/level_geo.c | 4 + src/game/object_helpers.c | 1 + src/game/paintings.c | 45 +++ src/game/rendering_graph_node.c | 469 ++++++++++++++++++++++++++++---- src/game/screen_transition.c | 80 ++++++ src/menu/intro_geo.c | 22 +- src/port/Engine.cpp | 18 +- src/port/Engine.h | 2 + src/port/Game.cpp | 27 ++ src/port/ui/ImguiUI.cpp | 106 ++++++++ 22 files changed, 1025 insertions(+), 78 deletions(-) diff --git a/include/sm64.h b/include/sm64.h index 8e030a61..82837911 100644 --- a/include/sm64.h +++ b/include/sm64.h @@ -500,6 +500,18 @@ extern void osCreateViManager(OSPri); extern uintptr_t osVirtualToPhysical(void *addr); extern void * osPhysicalToVirtual(u32); extern void osMapTLB(int32_t a, uint32_t b, void* c, uint32_t d, uint32_t e, uint32_t f); + +// Interpolation + +extern void mtx_patch_interpolated(void); +extern void patch_screen_transition_interpolated(void); +extern void patch_title_screen_scales(void); +extern void patch_interpolated_dialog(void); +extern void patch_interpolated_hud(void); +extern void patch_interpolated_paintings(void); +extern void patch_interpolated_bubble_particles(void); +extern void patch_interpolated_snow_particles(void); + #ifndef TRUE #define TRUE 1 #endif diff --git a/include/types.h b/include/types.h index bd088d5a..4592c7f9 100644 --- a/include/types.h +++ b/include/types.h @@ -109,6 +109,10 @@ struct AnimInfo { /*0x0A 0x42*/ u16 animTimer; /*0x0C 0x44*/ s32 animFrameAccelAssist; /*0x10 0x48*/ s32 animAccel; + s16 prevAnimFrame; + s16 prevAnimID; + u32 prevAnimFrameTimestamp; + struct Animation *prevAnimPtr; }; struct GraphNodeObject { @@ -118,11 +122,22 @@ struct GraphNodeObject { /*0x19*/ s8 activeAreaIndex; /*0x1A*/ Vec3s angle; /*0x20*/ Vec3f pos; + Vec3s prevAngle; + Vec3f prevPos; + u32 prevTimestamp; + Vec3f prevShadowPos; + u32 prevShadowPosTimestamp; /*0x2C*/ Vec3f scale; + Vec3f prevScale; + u32 prevScaleTimestamp; /*0x38*/ struct AnimInfo animInfo; /*0x4C*/ struct SpawnInfo *unk4C; /*0x50*/ Mat4 *throwMatrix; // matrix ptr + Mat4 prevThrowMatrix; + u32 prevThrowMatrixTimestamp; + Mat4 *throwMatrixInterpolated; /*0x54*/ Vec3f cameraToObject; + u32 skipInterpolationTimestamp; }; struct ObjectNode { @@ -227,6 +242,10 @@ struct Surface { } normal; /*0x28*/ f32 originOffset; /*0x2C*/ struct Object *object; + Vec3s prevVertex1; + Vec3s prevVertex2; + Vec3s prevVertex3; + u32 modifiedTimestamp; }; struct MarioBodyState { diff --git a/src/engine/graph_node.h b/src/engine/graph_node.h index b397932c..27b40957 100644 --- a/src/engine/graph_node.h +++ b/src/engine/graph_node.h @@ -105,6 +105,8 @@ struct GraphNodePerspective { /*0x1C*/ f32 fov; // horizontal field of view in degrees /*0x20*/ s16 near; // near clipping plane /*0x22*/ s16 far; // far clipping plane + f32 prevFov; + f32 prevTimestamp; }; /** An entry in the master list. It is a linked list of display lists @@ -112,7 +114,9 @@ struct GraphNodePerspective { */ struct DisplayListNode { Mtx *transform; + void *transformInterpolated; void *displayList; + void *displayListInterpolated; struct DisplayListNode *next; }; @@ -174,7 +178,11 @@ struct GraphNodeCamera { } config; /*0x1C*/ Vec3f pos; /*0x28*/ Vec3f focus; + Vec3f prevPos; + Vec3f prevFocus; + u32 prevTimestamp; /*0x34*/ Mat4 *matrixPtr; // pointer to look-at matrix of this camera as a Mat4 + Mat4 *matrixPtrInterpolated; /*0x38*/ s16 roll; // roll in look at matrix. Doesn't account for light direction unlike rollScreen. /*0x3A*/ s16 rollScreen; // rolls screen while keeping the light direction consistent }; @@ -212,7 +220,8 @@ struct GraphNodeRotation { /*0x00*/ struct GraphNode node; /*0x14*/ void *displayList; /*0x18*/ Vec3s rotation; - u8 filler[2]; + Vec3s prevRotation; + u32 prevTimestamp; }; /** GraphNode part that transforms itself and its children based on animation @@ -301,6 +310,9 @@ struct GraphNodeBackground { /*0x00*/ struct FnGraphNode fnNode; /*0x18*/ s32 unused; /*0x1C*/ s32 background; // background ID, or rgba5551 color if fnNode.func is null + Vec3f prevCameraPos; + Vec3f prevCameraFocus; + u32 prevCameraTimestamp; }; /** Renders the object that Mario is holding. @@ -310,6 +322,8 @@ struct GraphNodeHeldObject { /*0x18*/ s32 playerIndex; /*0x1C*/ struct Object *objNode; /*0x20*/ Vec3s translation; + Vec3f prevShadowPos; + u32 prevShadowPosTimestamp; }; /** A node that allows an object to specify a different culling radius than the diff --git a/src/engine/surface_collision.c b/src/engine/surface_collision.c index fad021b0..26599187 100644 --- a/src/engine/surface_collision.c +++ b/src/engine/surface_collision.c @@ -7,6 +7,7 @@ #include "game/object_list_processor.h" #include "surface_collision.h" #include "surface_load.h" +#include "game/game_init.h" /************************************************** * WALLS * @@ -395,26 +396,44 @@ f32 find_floor_height_and_data(f32 xPos, f32 yPos, f32 zPos, struct FloorGeometr return floorHeight; } +u8 gInterpolatingSurfaces; + /** * Iterate through the list of floors and find the first floor under a given point. */ static struct Surface *find_floor_from_list(struct SurfaceNode *surfaceNode, s32 x, s32 y, s32 z, f32 *pheight) { register struct Surface *surf; - register s32 x1, z1, x2, z2, x3, z3; + register f32 x1, z1, x2, z2, x3, z3; f32 nx, ny, nz; f32 oo; f32 height; struct Surface *floor = NULL; + s32 interpolate; // Iterate through the list of floors until there are no more floors. while (surfaceNode != NULL) { surf = surfaceNode->surface; surfaceNode = surfaceNode->next; + interpolate = gInterpolatingSurfaces && surf->modifiedTimestamp == gGlobalTimer; x1 = surf->vertex1[0]; z1 = surf->vertex1[2]; x2 = surf->vertex2[0]; z2 = surf->vertex2[2]; + if (interpolate) { + f32 diff = (surf->prevVertex1[0] - x1) * (surf->prevVertex1[0] - x1); + diff += (surf->prevVertex1[1] - surf->vertex1[1]) * (surf->prevVertex1[1] - surf->vertex1[1]); + diff += (surf->prevVertex1[2] - z1) * (surf->prevVertex1[2] - z1); + //printf("%f\n", sqrtf(diff)); + if (diff > 10000) { + interpolate = FALSE; + } else { + x1 = (surf->prevVertex1[0] + x1) / 2; + z1 = (surf->prevVertex1[2] + z1) / 2; + x2 = (surf->prevVertex2[0] + x2) / 2; + z2 = (surf->prevVertex2[2] + z2) / 2; + } + } // Check that the point is within the triangle bounds. if ((z1 - z) * (x2 - x1) - (x1 - x) * (z2 - z1) < 0) { @@ -424,6 +443,10 @@ static struct Surface *find_floor_from_list(struct SurfaceNode *surfaceNode, s32 // To slightly save on computation time, set this later. x3 = surf->vertex3[0]; z3 = surf->vertex3[2]; + if (interpolate) { + x3 = (surf->prevVertex3[0] + x3) / 2; + z3 = (surf->prevVertex3[2] + z3) / 2; + } if ((z2 - z) * (x3 - x2) - (x2 - x) * (z3 - z2) < 0) { continue; @@ -443,10 +466,30 @@ static struct Surface *find_floor_from_list(struct SurfaceNode *surfaceNode, s32 continue; } - nx = surf->normal.x; - ny = surf->normal.y; - nz = surf->normal.z; - oo = surf->originOffset; + if (interpolate) { + f32 y1, y2, y3; + f32 mag; + y1 = (surf->prevVertex1[1] + surf->vertex1[1]) / 2; + y2 = (surf->prevVertex2[1] + surf->vertex2[1]) / 2; + y3 = (surf->prevVertex3[1] + surf->vertex3[1]) / 2; + nx = (y2 - y1) * (z3 - z2) - (z2 - z1) * (y3 - y2); + ny = (z2 - z1) * (x3 - x2) - (x2 - x1) * (z3 - z2); + nz = (x2 - x1) * (y3 - y2) - (y2 - y1) * (x3 - x2); + mag = sqrtf(nx * nx + ny * ny + nz * nz); + if (mag < 0.0001) { + continue; + } + mag = (f32)(1.0 / mag); + nx *= mag; + ny *= mag; + nz *= mag; + oo = -(nx * x1 + ny * y1 + nz * z1); + } else { + nx = surf->normal.x; + ny = surf->normal.y; + nz = surf->normal.z; + oo = surf->originOffset; + } // If a wall, ignore it. Likely a remnant, should never occur. if (ny == 0.0f) { @@ -461,6 +504,15 @@ static struct Surface *find_floor_from_list(struct SurfaceNode *surfaceNode, s32 } *pheight = height; + if (interpolate) { + static struct Surface s; + s.type = surf->type; + s.normal.x = nx; + s.normal.y = ny; + s.normal.z = nz; + s.originOffset = oo; + return &s; + } floor = surf; break; } diff --git a/src/engine/surface_load.c b/src/engine/surface_load.c index 689f95fd..e002ea5a 100644 --- a/src/engine/surface_load.c +++ b/src/engine/surface_load.c @@ -12,6 +12,7 @@ #include "game/mario.h" #include "game/object_list_processor.h" #include "surface_load.h" +#include "game/game_init.h" s32 unused8038BE90; @@ -357,6 +358,11 @@ static struct Surface *read_surface_data(s16 *vertexData, s16 **vertexIndices) { surface = alloc_surface(); + vec3s_copy(surface->prevVertex1, surface->vertex1); + vec3s_copy(surface->prevVertex2, surface->vertex2); + vec3s_copy(surface->prevVertex3, surface->vertex3); + surface->modifiedTimestamp = gGlobalTimer; + surface->vertex1[0] = x1; surface->vertex2[0] = x2; surface->vertex3[0] = x3; diff --git a/src/game/camera.c b/src/game/camera.c index 79d7c7de..bb3d06cf 100644 --- a/src/game/camera.c +++ b/src/game/camera.c @@ -487,6 +487,10 @@ CameraTransition sModeTransitions[] = { extern u8 sDanceCutsceneIndexTable[][4]; extern u8 sZoomOutAreaMasks[]; +static void skip_camera_interpolation(void) { + gLakituState.skipCameraInterpolationTimestamp = gGlobalTimer; +} + /** * Starts a camera shake triggered by an interaction */ @@ -5504,6 +5508,7 @@ s32 set_camera_mode_fixed(struct Camera *c, s16 x, s16 y, s16 z) { c->mode = CAMERA_MODE_FIXED; vec3f_set(c->pos, sFixedModeBasePosition[0], sMarioCamState->pos[1], sFixedModeBasePosition[2]); + skip_camera_interpolation(); } return basePosSet; } @@ -5654,6 +5659,7 @@ BAD_RETURN(s32) cam_rr_enter_building_side(struct Camera *c) { if (c->mode != CAMERA_MODE_FIXED) { sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; c->mode = CAMERA_MODE_FIXED; + skip_camera_interpolation(); } } @@ -5849,6 +5855,7 @@ BAD_RETURN(s32) cam_castle_enter_lobby(struct Camera *c) { sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; set_fixed_cam_axis_sa_lobby(c->mode); c->mode = CAMERA_MODE_FIXED; + skip_camera_interpolation(); } } @@ -7212,6 +7219,7 @@ BAD_RETURN(s32) cutscene_unused_loop(UNUSED struct Camera *c) { BAD_RETURN(s32) cutscene_ending_mario_fall_start(struct Camera *c) { vec3f_set(c->focus, -26.f, 0.f, -137.f); vec3f_set(c->pos, 165.f, 4725.f, 324.f); + skip_camera_interpolation(); } /** @@ -7244,6 +7252,7 @@ BAD_RETURN(s32) cutscene_ending_mario_fall(struct Camera *c) { BAD_RETURN(s32) cutscene_ending_mario_land_closeup(struct Camera *c) { vec3f_set(c->focus, 85.f, 826.f, 250.f); vec3f_set(c->pos, -51.f, 988.f, -202.f); + skip_camera_interpolation(); player2_rotate_cam(c, -0x2000, 0x2000, -0x2000, 0x2000); } @@ -7253,6 +7262,7 @@ BAD_RETURN(s32) cutscene_ending_mario_land_closeup(struct Camera *c) { BAD_RETURN(s32) cutscene_ending_reset_spline(UNUSED struct Camera *c) { sCutsceneVars[9].point[0] = 0.f; cutscene_reset_spline(); + skip_camera_interpolation(); } /** @@ -7288,6 +7298,7 @@ BAD_RETURN(s32) cutscene_ending_peach_appear_closeup(struct Camera *c) { vec3f_set(c->pos, 179.f, 2463.f, -1216.f); c->pos[1] = gCutsceneFocus->oPosY + 35.f; vec3f_set(c->focus, gCutsceneFocus->oPosX, gCutsceneFocus->oPosY + 125.f, gCutsceneFocus->oPosZ); + skip_camera_interpolation(); } /** @@ -7306,6 +7317,7 @@ BAD_RETURN(s32) cutscene_ending_peach_appears(struct Camera *c) { BAD_RETURN(s32) cutscene_ending_peach_descends_start(UNUSED struct Camera *c) { cutscene_reset_spline(); sCutsceneVars[2].point[1] = 150.f; + skip_camera_interpolation(); } /** @@ -7393,6 +7405,7 @@ BAD_RETURN(s32) cutscene_ending_dialog(struct Camera *c) { vec3f_set(c->focus, 11.f, 983.f, -1273.f); vec3f_set(c->pos, -473.f, 970.f, -1152.f); player2_rotate_cam(c, -0x800, 0x2000, -0x2000, 0x2000); + skip_camera_interpolation(); } /** @@ -7402,6 +7415,7 @@ BAD_RETURN(s32) cutscene_ending_kiss_closeup(struct Camera *c) { set_fov_function(CAM_FOV_SET_29); vec3f_set(c->focus, 350.f, 1034.f, -1216.f); vec3f_set(c->pos, -149.f, 1021.f, -1216.f); + skip_camera_interpolation(); } /** @@ -7437,6 +7451,7 @@ BAD_RETURN(s32) cutscene_ending_kiss(struct Camera *c) { BAD_RETURN(s32) cutscene_ending_look_at_sky(struct Camera *c) { move_point_along_spline(c->focus, sEndingLookAtSkyFocus, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); vec3f_set(c->pos, 699.f, 1680.f, -703.f); + skip_camera_interpolation(); } /** @@ -10257,6 +10272,7 @@ BAD_RETURN(s32) cutscene_door_start(struct Camera *c) { BAD_RETURN(s32) cutscene_door_fix_cam(struct Camera *c) { vec3f_copy(c->pos, sCutsceneVars[0].point); vec3f_copy(c->focus, sCutsceneVars[1].point); + skip_camera_interpolation(); } /** @@ -10290,6 +10306,7 @@ BAD_RETURN(s32) cutscene_door_move_behind_mario(struct Camera *c) { } offset_rotated(c->pos, sMarioCamState->pos, camOffset, sCutsceneVars[0].angle); + skip_camera_interpolation(); } /** diff --git a/src/game/camera.h b/src/game/camera.h index 81a2034b..4aca8be6 100644 --- a/src/game/camera.h +++ b/src/game/camera.h @@ -639,6 +639,8 @@ struct LakituState { /// Mario's action from the previous frame. Only used to determine if Mario just finished a dive. /*0xB8*/ u32 lastFrameAction; /*0xBC*/ s16 unused; + + u32 skipCameraInterpolationTimestamp; }; // bss order hack to not affect BSS order. if possible, remove me, but it will be hard to match otherwise diff --git a/src/game/envfx_bubbles.c b/src/game/envfx_bubbles.c index feda81d2..6549c9cb 100644 --- a/src/game/envfx_bubbles.c +++ b/src/game/envfx_bubbles.c @@ -35,6 +35,20 @@ Vtx_t gBubbleTempVtx[3] = { { { 0, 0, 0 }, 0, { -498, 964 }, { 0xFF, 0xFF, 0xFF, 0xFF } }, }; +static Gfx sGfxSaved[60 / 5]; +static Gfx *sBubbleInterpolatedDisplayListPos[60 / 5]; +static Vec3s sPrevBubblePositions[60]; + +void patch_interpolated_bubble_particles(void) { + s32 i; + for (i = 0; i < 60 / 5; i++) { + if (sBubbleInterpolatedDisplayListPos[i] != NULL) { + *sBubbleInterpolatedDisplayListPos[i] = sGfxSaved[i]; + sBubbleInterpolatedDisplayListPos[i] = NULL; + } + } +} + /** * Check whether the particle with the given index is * laterally within distance of point (x, z). Used to @@ -239,6 +253,7 @@ void envfx_update_whirlpool(void) { (gEnvFxBuffer + i)->yPos = (i + gEnvFxBuffer)->bubbleY; (gEnvFxBuffer + i)->unusedBubbleVar = 0; (gEnvFxBuffer + i)->isAlive = TRUE; + (gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer; envfx_rotate_around_whirlpool(&(gEnvFxBuffer + i)->xPos, &(gEnvFxBuffer + i)->yPos, &(gEnvFxBuffer + i)->zPos); @@ -297,6 +312,7 @@ void envfx_update_jetstream(void) { + coss((gEnvFxBuffer + i)->angleAndDist[0]) * (gEnvFxBuffer + i)->angleAndDist[1]; (gEnvFxBuffer + i)->yPos = gEnvFxBubbleConfig[ENVFX_STATE_SRC_Y] + (random_float() * 400.0f - 200.0f); + (gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer; } else { (gEnvFxBuffer + i)->angleAndDist[1] += 10; (gEnvFxBuffer + i)->xPos += sins((gEnvFxBuffer + i)->angleAndDist[0]) * 10.0f; @@ -471,6 +487,12 @@ Gfx *envfx_update_bubble_particles(s32 mode, UNUSED Vec3s marioPos, Vec3s camFro Vec3s vertex1; Vec3s vertex2; Vec3s vertex3; + Vec3s interpolatedVertices[3]; + + static Vec3s prevVertex1; + static Vec3s prevVertex2; + static Vec3s prevVertex3; + static u32 prevTimestamp; Gfx *gfxStart = alloc_display_list(((sBubbleParticleMaxCount / 5) * 10 + sBubbleParticleMaxCount + 3) * sizeof(Gfx)); @@ -484,18 +506,53 @@ Gfx *envfx_update_bubble_particles(s32 mode, UNUSED Vec3s marioPos, Vec3s camFro envfx_bubbles_update_switch(mode, camTo, vertex1, vertex2, vertex3); rotate_triangle_vertices(vertex1, vertex2, vertex3, pitch, yaw); + if (gGlobalTimer == prevTimestamp + 1) { + interpolate_vectors_s16(interpolatedVertices[0], prevVertex1, vertex1); + interpolate_vectors_s16(interpolatedVertices[1], prevVertex2, vertex2); + interpolate_vectors_s16(interpolatedVertices[2], prevVertex3, vertex3); + } + vec3s_copy(prevVertex1, vertex1); + vec3s_copy(prevVertex2, vertex2); + vec3s_copy(prevVertex3, vertex3); + prevTimestamp = gGlobalTimer; + gSPDisplayList(sGfxCursor++, &tiny_bubble_dl_0B006D38); for (i = 0; i < sBubbleParticleMaxCount; i += 5) { + Vtx *interpolatedVertBuf = alloc_display_list(15 * sizeof(Vtx)); + s32 j, k; gDPPipeSync(sGfxCursor++); envfx_set_bubble_texture(mode, i); append_bubble_vertex_buffer(sGfxCursor++, i, vertex1, vertex2, vertex3, (Vtx *) gBubbleTempVtx); + sBubbleInterpolatedDisplayListPos[i / 5] = sGfxCursor; + for (j = 0; j < 5; j++) { + for (k = 0; k < 3; k++) { + Vtx *v = &interpolatedVertBuf[j * 3 + k]; + v->v = gBubbleTempVtx[k]; + if (gGlobalTimer != gEnvFxBuffer[i + j].spawnTimestamp && mode != ENVFX_LAVA_BUBBLES) { + v->v.ob[0] = (sPrevBubblePositions[i + j][0] + gEnvFxBuffer[i + j].xPos) / 2.0f + interpolatedVertices[k][0]; + v->v.ob[1] = (sPrevBubblePositions[i + j][1] + gEnvFxBuffer[i + j].yPos) / 2.0f + interpolatedVertices[k][1]; + v->v.ob[2] = (sPrevBubblePositions[i + j][2] + gEnvFxBuffer[i + j].zPos) / 2.0f + interpolatedVertices[k][2]; + } else { + v->v.ob[0] = gEnvFxBuffer[i + j].xPos + interpolatedVertices[k][0]; + v->v.ob[1] = gEnvFxBuffer[i + j].yPos + interpolatedVertices[k][1]; + v->v.ob[2] = gEnvFxBuffer[i + j].zPos + interpolatedVertices[k][2]; + } + } + } + gSPVertex(sGfxCursor++, VIRTUAL_TO_PHYSICAL(interpolatedVertBuf), 15, 0); + append_bubble_vertex_buffer(&sGfxSaved[i / 5], i, vertex1, vertex2, vertex3, (Vtx *) gBubbleTempVtx); gSP1Triangle(sGfxCursor++, 0, 1, 2, 0); gSP1Triangle(sGfxCursor++, 3, 4, 5, 0); gSP1Triangle(sGfxCursor++, 6, 7, 8, 0); gSP1Triangle(sGfxCursor++, 9, 10, 11, 0); gSP1Triangle(sGfxCursor++, 12, 13, 14, 0); } + for (i = 0; i < sBubbleParticleMaxCount; i++) { + sPrevBubblePositions[i][0] = gEnvFxBuffer[i].xPos; + sPrevBubblePositions[i][1] = gEnvFxBuffer[i].yPos; + sPrevBubblePositions[i][2] = gEnvFxBuffer[i].zPos; + } gSPDisplayList(sGfxCursor++, &tiny_bubble_dl_0B006AB0); gSPEndDisplayList(sGfxCursor++); diff --git a/src/game/envfx_snow.c b/src/game/envfx_snow.c index 5e1a032d..1b263271 100644 --- a/src/game/envfx_snow.c +++ b/src/game/envfx_snow.c @@ -55,6 +55,26 @@ extern void *tiny_bubble_dl_0B006AB0; extern void *tiny_bubble_dl_0B006A50; extern void *tiny_bubble_dl_0B006CD8; +static struct { + Gfx *pos; + Vtx vertices[15]; +} sPrevSnowVertices[140 / 5]; +static s16 sPrevSnowParticleCount; +static u32 sPrevSnowTimestamp; + +void patch_interpolated_snow_particles(void) { + int i; + + if (gGlobalTimer != sPrevSnowTimestamp + 1) { + return; + } + + for (i = 0; i < sPrevSnowParticleCount; i += 5) { + gSPVertex(sPrevSnowVertices[i / 5].pos, + VIRTUAL_TO_PHYSICAL(sPrevSnowVertices[i / 5].vertices), 15, 0); + } +} + /** * Initialize snow particles by allocating a buffer for storing their state * and setting a start amount. @@ -220,6 +240,7 @@ void envfx_update_snow_normal(s32 snowCylinderX, s32 snowCylinderY, s32 snowCyli 400.0f * random_float() - 200.0f + snowCylinderZ + (s16)(deltaZ * 2); (gEnvFxBuffer + i)->yPos = 200.0f * random_float() + snowCylinderY; (gEnvFxBuffer + i)->isAlive = TRUE; + (gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer; } else { (gEnvFxBuffer + i)->xPos += random_float() * 2 - 1.0f + (s16)(deltaX / 1.2); (gEnvFxBuffer + i)->yPos -= 2 -(s16)(deltaY * 0.8); @@ -254,6 +275,7 @@ void envfx_update_snow_blizzard(s32 snowCylinderX, s32 snowCylinderY, s32 snowCy 400.0f * random_float() - 200.0f + snowCylinderZ + (s16)(deltaZ * 2); (gEnvFxBuffer + i)->yPos = 400.0f * random_float() - 200.0f + snowCylinderY; (gEnvFxBuffer + i)->isAlive = TRUE; + (gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer; } else { (gEnvFxBuffer + i)->xPos += random_float() * 2 - 1.0f + (s16)(deltaX / 1.2) + 20.0f; (gEnvFxBuffer + i)->yPos -= 5 -(s16)(deltaY * 0.8); @@ -297,6 +319,7 @@ void envfx_update_snow_water(s32 snowCylinderX, s32 snowCylinderY, s32 snowCylin (gEnvFxBuffer + i)->zPos = 400.0f * random_float() - 200.0f + snowCylinderZ; (gEnvFxBuffer + i)->yPos = 400.0f * random_float() - 200.0f + snowCylinderY; (gEnvFxBuffer + i)->isAlive = TRUE; + (gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer; } } } @@ -347,29 +370,30 @@ void rotate_triangle_vertices(Vec3s vertex1, Vec3s vertex2, Vec3s vertex3, s16 p void append_snowflake_vertex_buffer(Gfx *gfx, s32 index, Vec3s vertex1, Vec3s vertex2, Vec3s vertex3) { s32 i = 0; Vtx *vertBuf = (Vtx *) alloc_display_list(15 * sizeof(Vtx)); + Vtx *vertBufInterpolated = (Vtx *) alloc_display_list(15 * sizeof(Vtx)); + Vtx *v; if (vertBuf == NULL) { return; } - for (i = 0; i < 15; i += 3) { - vertBuf[i] = gSnowTempVtx[0]; - (vertBuf + i)->v.ob[0] = (gEnvFxBuffer + (index + i / 3))->xPos + vertex1[0]; - (vertBuf + i)->v.ob[1] = (gEnvFxBuffer + (index + i / 3))->yPos + vertex1[1]; - (vertBuf + i)->v.ob[2] = (gEnvFxBuffer + (index + i / 3))->zPos + vertex1[2]; - - vertBuf[i + 1] = gSnowTempVtx[1]; - (vertBuf + i + 1)->v.ob[0] = (gEnvFxBuffer + (index + i / 3))->xPos + vertex2[0]; - (vertBuf + i + 1)->v.ob[1] = (gEnvFxBuffer + (index + i / 3))->yPos + vertex2[1]; - (vertBuf + i + 1)->v.ob[2] = (gEnvFxBuffer + (index + i / 3))->zPos + vertex2[2]; - - vertBuf[i + 2] = gSnowTempVtx[2]; - (vertBuf + i + 2)->v.ob[0] = (gEnvFxBuffer + (index + i / 3))->xPos + vertex3[0]; - (vertBuf + i + 2)->v.ob[1] = (gEnvFxBuffer + (index + i / 3))->yPos + vertex3[1]; - (vertBuf + i + 2)->v.ob[2] = (gEnvFxBuffer + (index + i / 3))->zPos + vertex3[2]; + for (i = 0; i < 15; i++) { + v = &sPrevSnowVertices[index / 5].vertices[i]; + vertBufInterpolated[i] = gSnowTempVtx[i % 3]; + if (index < sPrevSnowParticleCount && gGlobalTimer == sPrevSnowTimestamp + 1 && + gGlobalTimer != gEnvFxBuffer[index + i / 3].spawnTimestamp) { + vertBufInterpolated[i].v.ob[0] = (v->v.ob[0] + vertBuf[i].v.ob[0]) / 2; + vertBufInterpolated[i].v.ob[1] = (v->v.ob[1] + vertBuf[i].v.ob[1]) / 2; + vertBufInterpolated[i].v.ob[2] = (v->v.ob[2] + vertBuf[i].v.ob[2]) / 2; + } else { + vertBufInterpolated[i].v.ob[0] = vertBuf[i].v.ob[0]; + vertBufInterpolated[i].v.ob[1] = vertBuf[i].v.ob[1]; + vertBufInterpolated[i].v.ob[2] = vertBuf[i].v.ob[2]; + } + *v = vertBuf[i]; } - - gSPVertex(gfx, VIRTUAL_TO_PHYSICAL(vertBuf), 15, 0); + sPrevSnowVertices[index / 5].pos = gfx; + gSPVertex(gfx, VIRTUAL_TO_PHYSICAL(vertBufInterpolated), 15, 0); } /** @@ -453,6 +477,8 @@ Gfx *envfx_update_snow(s32 snowMode, Vec3s marioPos, Vec3s camFrom, Vec3s camTo) gSP1Triangle(gfx++, 9, 10, 11, 0); gSP1Triangle(gfx++, 12, 13, 14, 0); } + sPrevSnowParticleCount = gSnowParticleCount; + sPrevSnowTimestamp = gGlobalTimer; gSPDisplayList(gfx++, &tiny_bubble_dl_0B006AB0) gSPEndDisplayList(gfx++); diff --git a/src/game/envfx_snow.h b/src/game/envfx_snow.h index 92462d5d..23c6ff60 100644 --- a/src/game/envfx_snow.h +++ b/src/game/envfx_snow.h @@ -25,7 +25,7 @@ struct EnvFxParticle { s32 angleAndDist[2]; // for whirpools, [0] = angle from center, [1] = distance from center s32 unusedBubbleVar; // set to zero for bubbles when respawning, never used elsewhere s32 bubbleY; // for Bubbles, yPos is always set to this - u8 filler[24]; + u32 spawnTimestamp; }; extern s8 gEnvFxMode; diff --git a/src/game/hud.c b/src/game/hud.c index b6966622..c672798c 100644 --- a/src/game/hud.c +++ b/src/game/hud.c @@ -57,6 +57,20 @@ UNUSED static struct UnusedHUDStruct sUnusedHUDValues = { 0x00, 0x0A, 0x00 }; static struct CameraHUD sCameraHUD = { CAM_STATUS_NONE }; +static u32 sPowerMeterLastRenderTimestamp; +static s16 sPowerMeterLastY; +static Gfx *sPowerMeterDisplayListPos; + +void patch_interpolated_hud(void) { + if (sPowerMeterDisplayListPos != NULL) { + Mtx *mtx = alloc_display_list(sizeof(Mtx)); + guTranslate(mtx, (f32) sPowerMeterHUD.x, (f32) sPowerMeterHUD.y, 0); + gSPMatrix(sPowerMeterDisplayListPos, VIRTUAL_TO_PHYSICAL(mtx), + G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_PUSH); + sPowerMeterDisplayListPos = NULL; + } +} + /** * Renders a rgba16 16x16 glyph texture from a table list. */ @@ -107,12 +121,21 @@ void render_power_meter_health_segment(s16 numHealthWedges) { */ void render_dl_power_meter(s16 numHealthWedges) { Mtx *mtx = alloc_display_list(sizeof(Mtx)); + f32 interpolatedY; if (mtx == NULL) { return; } - guTranslate(mtx, (f32) sPowerMeterHUD.x, (f32) sPowerMeterHUD.y, 0); + if (gGlobalTimer == sPowerMeterLastRenderTimestamp + 1) { + interpolatedY = (sPowerMeterLastY + sPowerMeterHUD.y) / 2.0f; + } else { + interpolatedY = sPowerMeterHUD.y; + } + guTranslate(mtx, (f32) sPowerMeterHUD.x, interpolatedY, 0); + sPowerMeterLastY = sPowerMeterHUD.y; + sPowerMeterLastRenderTimestamp = gGlobalTimer; + sPowerMeterDisplayListPos = gDisplayListHead; gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(mtx++), G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_PUSH); diff --git a/src/game/ingame_menu.c b/src/game/ingame_menu.c index e87f20ec..5df05fbc 100644 --- a/src/game/ingame_menu.c +++ b/src/game/ingame_menu.c @@ -116,6 +116,41 @@ u8 gMenuHoldKeyIndex = 0; u8 gMenuHoldKeyTimer = 0; s32 gDialogResponse = DIALOG_RESPONSE_NONE; +static Gfx *sInterpolatedDialogOffsetPos; +static f32 sInterpolatedDialogOffset; +static Gfx *sInterpolatedDialogRotationPos; +static f32 sInterpolatedDialogScale; +static f32 sInterpolatedDialogRotation; +static Gfx *sInterpolatedDialogZoomPos; + +void patch_interpolated_dialog(void) { + Mtx *matrix; + + if (sInterpolatedDialogOffsetPos != NULL) { + matrix = (Mtx *) alloc_display_list(sizeof(Mtx)); + guTranslate(matrix, 0, sInterpolatedDialogOffset, 0); + gSPMatrix(sInterpolatedDialogOffsetPos, VIRTUAL_TO_PHYSICAL(matrix), G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH); + sInterpolatedDialogOffsetPos = NULL; + } + if (sInterpolatedDialogRotationPos != NULL) { + matrix = (Mtx *) alloc_display_list(sizeof(Mtx)); + guScale(matrix, 1.0 / sInterpolatedDialogScale, 1.0 / sInterpolatedDialogScale, 1.0f); + gSPMatrix(sInterpolatedDialogRotationPos++, VIRTUAL_TO_PHYSICAL(matrix), G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH); + matrix = (Mtx *) alloc_display_list(sizeof(Mtx)); + guRotate(matrix, sInterpolatedDialogRotation * 4.0f, 0, 0, 1.0f); + gSPMatrix(sInterpolatedDialogRotationPos, VIRTUAL_TO_PHYSICAL(matrix), G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH); + sInterpolatedDialogRotationPos = NULL; + } + if (sInterpolatedDialogZoomPos != NULL) { + matrix = (Mtx *) alloc_display_list(sizeof(Mtx)); + guTranslate(matrix, 65.0 - (65.0 / sInterpolatedDialogScale), (40.0 / sInterpolatedDialogScale) - 40, 0); + gSPMatrix(sInterpolatedDialogZoomPos++, VIRTUAL_TO_PHYSICAL(matrix), G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH); + matrix = (Mtx *) alloc_display_list(sizeof(Mtx)); + guScale(matrix, 1.0 / sInterpolatedDialogScale, 1.0 / sInterpolatedDialogScale, 1.0f); + gSPMatrix(sInterpolatedDialogZoomPos, VIRTUAL_TO_PHYSICAL(matrix), G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH); + sInterpolatedDialogZoomPos = NULL; + } +} void create_dl_identity_matrix(void) { Mtx *matrix = (Mtx *) alloc_display_list(sizeof(Mtx)); @@ -957,6 +992,14 @@ void render_dialog_box_type(struct DialogEntry *dialog, s8 linesPerBox) { switch (gDialogBoxType) { case DIALOG_TYPE_ROTATE: // Renders a dialog black box with zoom and rotation if (gDialogBoxState == DIALOG_STATE_OPENING || gDialogBoxState == DIALOG_STATE_CLOSING) { + sInterpolatedDialogRotationPos = gDisplayListHead; + if (gDialogBoxState == DIALOG_STATE_OPENING) { + sInterpolatedDialogScale = gDialogBoxScale - 2 / 2; + sInterpolatedDialogRotation = gDialogBoxOpenTimer - 7.5f / 2; + } else { + sInterpolatedDialogScale = gDialogBoxScale + 2 / 2; + sInterpolatedDialogRotation = gDialogBoxOpenTimer + 7.5f / 2; + } create_dl_scale_matrix(MENU_MTX_NOPUSH, 1.0 / gDialogBoxScale, 1.0 / gDialogBoxScale, 1.0f); // convert the speed into angle create_dl_rotation_matrix(MENU_MTX_NOPUSH, gDialogBoxOpenTimer * 4.0f, 0, 0, 1.0f); @@ -965,6 +1008,12 @@ void render_dialog_box_type(struct DialogEntry *dialog, s8 linesPerBox) { break; case DIALOG_TYPE_ZOOM: // Renders a dialog white box with zoom if (gDialogBoxState == DIALOG_STATE_OPENING || gDialogBoxState == DIALOG_STATE_CLOSING) { + sInterpolatedDialogZoomPos = gDisplayListHead; + if (gDialogBoxState == DIALOG_STATE_OPENING) { + sInterpolatedDialogScale = gDialogBoxScale - 2 / 2; + } else { + sInterpolatedDialogScale = gDialogBoxScale + 2 / 2; + } create_dl_translation_matrix(MENU_MTX_NOPUSH, 65.0 - (65.0 / gDialogBoxScale), (40.0 / gDialogBoxScale) - 40, 0); create_dl_scale_matrix(MENU_MTX_NOPUSH, 1.0 / gDialogBoxScale, 1.0 / gDialogBoxScale, 1.0f); @@ -1236,6 +1285,8 @@ void handle_dialog_text_and_pages(s8 colorMode, struct DialogEntry *dialog, s8 l #ifdef VERSION_EU gDialogY -= gDialogScrollOffsetY; #else + sInterpolatedDialogOffset = gDialogScrollOffsetY + dialog->linesPerBox; + sInterpolatedDialogOffsetPos = gDisplayListHead; create_dl_translation_matrix(MENU_MTX_NOPUSH, 0, (f32) gDialogScrollOffsetY, 0); #endif } diff --git a/src/game/level_geo.c b/src/game/level_geo.c index 0e274a89..e3946700 100644 --- a/src/game/level_geo.c +++ b/src/game/level_geo.c @@ -34,12 +34,16 @@ Gfx *geo_envfx_main(s32 callContext, struct GraphNode *node, Mat4 mtxf) { vec3f_to_vec3s(marioPos, gPlayerCameraState->pos); particleList = envfx_update_particles(snowMode, marioPos, camTo, camFrom); if (particleList != NULL) { +#if FALSE Mtx *mtx = alloc_display_list(sizeof(*mtx)); gfx = alloc_display_list(2 * sizeof(*gfx)); mtxf_to_mtx(mtx, mtxf); gSPMatrix(&gfx[0], VIRTUAL_TO_PHYSICAL(mtx), G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH); gSPBranchList(&gfx[1], VIRTUAL_TO_PHYSICAL(particleList)); +#else + gfx = particleList; +#endif execNode->fnNode.node.flags = (execNode->fnNode.node.flags & 0xFF) | 0x400; } SET_HIGH_U16_OF_32(*params, gAreaUpdateCounter); diff --git a/src/game/object_helpers.c b/src/game/object_helpers.c index 68885128..9d9ea5c1 100644 --- a/src/game/object_helpers.c +++ b/src/game/object_helpers.c @@ -1536,6 +1536,7 @@ void cur_obj_set_pos_to_home(void) { o->oPosX = o->oHomeX; o->oPosY = o->oHomeY; o->oPosZ = o->oHomeZ; + o->header.gfx.skipInterpolationTimestamp = gGlobalTimer; } void cur_obj_set_pos_to_home_and_stop(void) { diff --git a/src/game/paintings.c b/src/game/paintings.c index 9bfca393..f0ec4800 100644 --- a/src/game/paintings.c +++ b/src/game/paintings.c @@ -189,6 +189,32 @@ struct Painting **sPaintingGroups[] = { s16 gPaintingUpdateCounter = 1; s16 gLastPaintingUpdateCounter = 0; +static Vtx sLastVertices[2 * 264 * 3]; +static u32 sLastVerticesTimestamp; +static Vtx *sVerticesPtr[2]; +static s32 sVerticesCount; + +void patch_interpolated_paintings(void) { + if (sVerticesPtr[0] != NULL) { + s32 i; + if (sVerticesPtr[1] != NULL) { + for (i = 0; i < sVerticesCount / 2; i++) { + sVerticesPtr[0][i] = sLastVertices[i]; + } + for (; i < sVerticesCount; i++) { + sVerticesPtr[1][i - sVerticesCount / 2] = sLastVertices[i]; + } + } else { + for (i = 0; i < sVerticesCount; i++) { + sVerticesPtr[0][i] = sLastVertices[i]; + } + } + sVerticesPtr[0] = NULL; + sVerticesPtr[1] = NULL; + sVerticesCount = 0; + } +} + /** * Stop paintings in paintingGroup from rippling if their id is different from *idptr. */ @@ -893,6 +919,23 @@ Gfx *render_painting(u8 *img, s16 tWidth, s16 tHeight, s16 *textureMap, s16 mapV gSP1Triangle(gfx++, group * 3, group * 3 + 1, group * 3 + 2, 0); } + if (sVerticesCount >= numVtx * 2) { + sVerticesCount = 0; + } + for (map = 0; map < numVtx; map++) { + Vtx v = verts[map]; + if (gGlobalTimer == sLastVerticesTimestamp + 1) { + s32 i; + for (i = 0; i < 3; i++) { + verts[map].n.ob[i] = (v.n.ob[i] + sLastVertices[sVerticesCount + map].n.ob[i]) / 2; + verts[map].n.n[i] = (v.n.n[i] + sLastVertices[sVerticesCount + map].n.n[i]) / 2; + } + } + sLastVertices[sVerticesCount + map] = v; + } + sVerticesPtr[sVerticesCount / numVtx] = verts; + sVerticesCount += numVtx; + gSPEndDisplayList(gfx); return dlist; } @@ -957,6 +1000,7 @@ Gfx *painting_ripple_image(struct Painting *painting) { meshTris = textureMap[meshVerts * 3 + 1]; gSPDisplayList(gfx++, render_painting(textures[i], tWidth, tHeight, textureMap, meshVerts, meshTris, painting->alpha)); } + sLastVerticesTimestamp = gGlobalTimer; // Update the ripple, may automatically reset the painting's state. painting_update_ripple_state(painting); @@ -994,6 +1038,7 @@ Gfx *painting_ripple_env_mapped(struct Painting *painting) { meshVerts = textureMap[0]; meshTris = textureMap[meshVerts * 3 + 1]; gSPDisplayList(gfx++, render_painting(tArray[0], tWidth, tHeight, textureMap, meshVerts, meshTris, painting->alpha)); + sLastVerticesTimestamp = gGlobalTimer; // Update the ripple, may automatically reset the painting's state. painting_update_ripple_state(painting); diff --git a/src/game/rendering_graph_node.c b/src/game/rendering_graph_node.c index f077c184..5e6758c1 100644 --- a/src/game/rendering_graph_node.c +++ b/src/game/rendering_graph_node.c @@ -39,6 +39,8 @@ s16 gMatStackIndex; Mat4 gMatStack[32]; Mtx *gMatStackFixed[32]; +Mat4 gMatStackInterpolated[32]; +Mtx *gMatStackInterpolatedFixed[32]; /** * Animation nodes have state in global variables, so this struct captures @@ -52,6 +54,7 @@ struct GeoAnimState { /*0x04*/ f32 translationMultiplier; /*0x08*/ u16 *attribute; /*0x0C*/ s16 *data; + s16 prevFrame; }; // For some reason, this is a GeoAnimState struct, but the current state consists @@ -61,6 +64,7 @@ struct GeoAnimState gGeoTempState; u8 gCurrAnimType; u8 gCurrAnimEnabled; s16 gCurrAnimFrame; +s16 gPrevAnimFrame; f32 gCurrAnimTranslationMultiplier; u16 *gCurrAnimAttribute; s16 *gCurrAnimData; @@ -129,6 +133,46 @@ u16 gAreaUpdateCounter = 0; LookAt lookAt; #endif +static Gfx *sPerspectivePos; +static Mtx *sPerspectiveMtx; + +struct { + Gfx *pos; + void *mtx; + void *displayList; +} gMtxTbl[6400]; +s32 gMtxTblSize; + +static Gfx *sViewportPos; +static Vp sPrevViewport; + +void mtx_patch_interpolated(void) { + s32 i; + + if (sPerspectivePos != NULL) { + gSPMatrix(sPerspectivePos, VIRTUAL_TO_PHYSICAL(sPerspectiveMtx), G_MTX_PROJECTION | G_MTX_LOAD | G_MTX_NOPUSH); + } + + for (i = 0; i < gMtxTblSize; i++) { + Gfx *pos = gMtxTbl[i].pos; + gSPMatrix(pos++, VIRTUAL_TO_PHYSICAL(gMtxTbl[i].mtx), + G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH); + gSPDisplayList(pos++, gMtxTbl[i].displayList); + } + + if (sViewportPos != NULL) { + Gfx *saved = gDisplayListHead; + gDisplayListHead = sViewportPos; + make_viewport_clip_rect(&sPrevViewport); + gSPViewport(gDisplayListHead, VIRTUAL_TO_PHYSICAL(&sPrevViewport)); + gDisplayListHead = saved; + } + + gMtxTblSize = 0; + sPerspectivePos = NULL; + sViewportPos = NULL; +} + /** * Process a master list node. */ @@ -156,9 +200,14 @@ static void geo_process_master_list_sub(struct GraphNodeMasterList *node) { if ((currList = node->listHeads[i]) != NULL) { gDPSetRenderMode(gDisplayListHead++, modeList->modes[i], mode2List->modes[i]); while (currList != NULL) { - gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(currList->transform), + if ((u32) gMtxTblSize < sizeof(gMtxTbl) / sizeof(gMtxTbl[0])) { + gMtxTbl[gMtxTblSize].pos = gDisplayListHead; + gMtxTbl[gMtxTblSize].mtx = currList->transform; + gMtxTbl[gMtxTblSize++].displayList = currList->displayList; + } + gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(currList->transformInterpolated), G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH); - gSPDisplayList(gDisplayListHead++, currList->displayList); + gSPDisplayList(gDisplayListHead++, currList->displayListInterpolated); currList = currList->next; } } @@ -174,7 +223,7 @@ static void geo_process_master_list_sub(struct GraphNodeMasterList *node) { * parameter. Look at the RenderModeContainer struct to see the corresponding * render modes of layers. */ -static void geo_append_display_list(void *displayList, s16 layer) { +static void geo_append_display_list2(void *displayList, void *displayListInterpolated, s16 layer) { #ifdef F3DEX_GBI_2 gSPLookAt(gDisplayListHead++, &lookAt); @@ -184,7 +233,9 @@ static void geo_append_display_list(void *displayList, s16 layer) { alloc_only_pool_alloc(gDisplayListHeap, sizeof(struct DisplayListNode)); listNode->transform = gMatStackFixed[gMatStackIndex]; + listNode->transformInterpolated = gMatStackInterpolatedFixed[gMatStackIndex]; listNode->displayList = displayList; + listNode->displayListInterpolated = displayListInterpolated; listNode->next = 0; if (gCurGraphNodeMasterList->listHeads[layer] == 0) { gCurGraphNodeMasterList->listHeads[layer] = listNode; @@ -195,12 +246,16 @@ static void geo_append_display_list(void *displayList, s16 layer) { } } +static void geo_append_display_list(void *displayList, s16 layer) { + geo_append_display_list2(displayList, displayList, layer); +} + /** * Process the master list node. */ static void geo_process_master_list(struct GraphNodeMasterList *node) { s32 i; - UNUSED u8 filler[4]; + UNUSED s32 sp1C; if (gCurGraphNodeMasterList == NULL && node->node.children != NULL) { gCurGraphNodeMasterList = node; @@ -241,7 +296,9 @@ static void geo_process_perspective(struct GraphNodePerspective *node) { } if (node->fnNode.node.children != NULL) { u16 perspNorm; + Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); Mtx *mtx = alloc_display_list(sizeof(*mtx)); + f32 fovInterpolated; #ifdef VERSION_EU f32 aspect = ((f32) gCurGraphNodeRoot->width / (f32) gCurGraphNodeRoot->height) * 1.1f; @@ -250,9 +307,23 @@ static void geo_process_perspective(struct GraphNodePerspective *node) { #endif guPerspective(mtx, &perspNorm, node->fov, aspect, node->near, node->far, 1.0f); - gSPPerspNormalize(gDisplayListHead++, perspNorm); - gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(mtx), G_MTX_PROJECTION | G_MTX_LOAD | G_MTX_NOPUSH); + if (gGlobalTimer == node->prevTimestamp + 1 && gGlobalTimer != gLakituState.skipCameraInterpolationTimestamp) { + + fovInterpolated = (node->prevFov + node->fov) / 2.0f; + guPerspective(mtxInterpolated, &perspNorm, fovInterpolated, aspect, node->near, node->far, 1.0f); + gSPPerspNormalize(gDisplayListHead++, perspNorm); + + sPerspectivePos = gDisplayListHead; + sPerspectiveMtx = mtx; + gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(mtxInterpolated), + G_MTX_PROJECTION | G_MTX_LOAD | G_MTX_NOPUSH); + } else { + gSPPerspNormalize(gDisplayListHead++, perspNorm); + gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(mtx), G_MTX_PROJECTION | G_MTX_LOAD | G_MTX_NOPUSH); + } + node->prevFov = node->fov; + node->prevTimestamp = gGlobalTimer; gCurGraphNodeCamFrustum = node; geo_process_node_and_siblings(node->fnNode.node.children); @@ -308,6 +379,39 @@ static void geo_process_switch(struct GraphNodeSwitchCase *node) { } } +void interpolate_vectors(Vec3f res, Vec3f a, Vec3f b) { + res[0] = (a[0] + b[0]) / 2.0f; + res[1] = (a[1] + b[1]) / 2.0f; + res[2] = (a[2] + b[2]) / 2.0f; +} + +void interpolate_vectors_s16(Vec3s res, Vec3s a, Vec3s b) { + res[0] = (a[0] + b[0]) / 2; + res[1] = (a[1] + b[1]) / 2; + res[2] = (a[2] + b[2]) / 2; +} + +static s16 interpolate_angle(s16 a, s16 b) { + s32 absDiff = b - a; + if (absDiff < 0) { + absDiff = -absDiff; + } + if (absDiff >= 0x4000 && absDiff <= 0xC000) { + return b; + } + if (absDiff <= 0x8000) { + return (a + b) / 2; + } else { + return (a + b) / 2 + 0x8000; + } +} + +static void interpolate_angles(Vec3s res, Vec3s a, Vec3s b) { + res[0] = interpolate_angle(a[0], b[0]); + res[1] = interpolate_angle(a[1], b[1]); + res[2] = interpolate_angle(a[2], b[2]); +} + /** * Process a camera node. */ @@ -315,6 +419,9 @@ static void geo_process_camera(struct GraphNodeCamera *node) { Mat4 cameraTransform; Mtx *rollMtx = alloc_display_list(sizeof(*rollMtx)); Mtx *mtx = alloc_display_list(sizeof(*mtx)); + Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); + Vec3f posInterpolated; + Vec3f focusInterpolated; if (node->fnNode.func != NULL) { node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, gMatStack[gMatStackIndex]); @@ -325,12 +432,40 @@ static void geo_process_camera(struct GraphNodeCamera *node) { mtxf_lookat(cameraTransform, node->pos, node->focus, node->roll); mtxf_mul(gMatStack[gMatStackIndex + 1], cameraTransform, gMatStack[gMatStackIndex]); + + if (gGlobalTimer == node->prevTimestamp + 1 && gGlobalTimer != gLakituState.skipCameraInterpolationTimestamp) { + interpolate_vectors(posInterpolated, node->prevPos, node->pos); + interpolate_vectors(focusInterpolated, node->prevFocus, node->focus); + float magnitude = 0; + for (int i = 0; i < 3; i++) { + float diff = node->pos[i] - node->prevPos[i]; + magnitude += diff * diff; + } + if (magnitude > 500000) { + // Observed ~479000 in BBH when toggling R camera + // Can get over 3 million in VCUTM though... + vec3f_copy(posInterpolated, node->pos); + vec3f_copy(focusInterpolated, node->focus); + } + } else { + vec3f_copy(posInterpolated, node->pos); + vec3f_copy(focusInterpolated, node->focus); + } + vec3f_copy(node->prevPos, node->pos); + vec3f_copy(node->prevFocus, node->focus); + node->prevTimestamp = gGlobalTimer; + mtxf_lookat(cameraTransform, posInterpolated, focusInterpolated, node->roll); + mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], cameraTransform, gMatStackInterpolated[gMatStackIndex]); + gMatStackIndex++; mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); gMatStackFixed[gMatStackIndex] = mtx; + mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); + gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; if (node->fnNode.node.children != 0) { gCurGraphNodeCamera = node; node->matrixPtr = &gMatStack[gMatStackIndex]; + node->matrixPtrInterpolated = &gMatStackInterpolated[gMatStackIndex]; geo_process_node_and_siblings(node->fnNode.node.children); gCurGraphNodeCamera = NULL; } @@ -347,13 +482,17 @@ static void geo_process_translation_rotation(struct GraphNodeTranslationRotation Mat4 mtxf; Vec3f translation; Mtx *mtx = alloc_display_list(sizeof(*mtx)); + Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); vec3s_to_vec3f(translation, node->translation); mtxf_rotate_zxy_and_translate(mtxf, translation, node->rotation); mtxf_mul(gMatStack[gMatStackIndex + 1], mtxf, gMatStack[gMatStackIndex]); + mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], mtxf, gMatStackInterpolated[gMatStackIndex]); gMatStackIndex++; mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); gMatStackFixed[gMatStackIndex] = mtx; + mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); + gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; if (node->displayList != NULL) { geo_append_display_list(node->displayList, node->node.flags >> 8); } @@ -372,13 +511,17 @@ static void geo_process_translation(struct GraphNodeTranslation *node) { Mat4 mtxf; Vec3f translation; Mtx *mtx = alloc_display_list(sizeof(*mtx)); + Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); vec3s_to_vec3f(translation, node->translation); mtxf_rotate_zxy_and_translate(mtxf, translation, gVec3sZero); mtxf_mul(gMatStack[gMatStackIndex + 1], mtxf, gMatStack[gMatStackIndex]); + mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], mtxf, gMatStackInterpolated[gMatStackIndex]); gMatStackIndex++; mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); gMatStackFixed[gMatStackIndex] = mtx; + mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); + gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; if (node->displayList != NULL) { geo_append_display_list(node->displayList, node->node.flags >> 8); } @@ -396,12 +539,23 @@ static void geo_process_translation(struct GraphNodeTranslation *node) { static void geo_process_rotation(struct GraphNodeRotation *node) { Mat4 mtxf; Mtx *mtx = alloc_display_list(sizeof(*mtx)); + Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); + Vec3s rotationInterpolated; mtxf_rotate_zxy_and_translate(mtxf, gVec3fZero, node->rotation); mtxf_mul(gMatStack[gMatStackIndex + 1], mtxf, gMatStack[gMatStackIndex]); + if (gGlobalTimer == node->prevTimestamp + 1) { + interpolate_angles(rotationInterpolated, node->prevRotation, node->rotation); + mtxf_rotate_zxy_and_translate(mtxf, gVec3fZero, rotationInterpolated); + } + vec3s_copy(node->prevRotation, node->rotation); + node->prevTimestamp = gGlobalTimer; + mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], mtxf, gMatStackInterpolated[gMatStackIndex]); gMatStackIndex++; mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); gMatStackFixed[gMatStackIndex] = mtx; + mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); + gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; if (node->displayList != NULL) { geo_append_display_list(node->displayList, node->node.flags >> 8); } @@ -420,12 +574,16 @@ static void geo_process_scale(struct GraphNodeScale *node) { UNUSED Mat4 transform; Vec3f scaleVec; Mtx *mtx = alloc_display_list(sizeof(*mtx)); + Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); vec3f_set(scaleVec, node->scale, node->scale, node->scale); mtxf_scale_vec3f(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex], scaleVec); + mtxf_scale_vec3f(gMatStackInterpolated[gMatStackIndex + 1], gMatStackInterpolated[gMatStackIndex], scaleVec); gMatStackIndex++; mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); gMatStackFixed[gMatStackIndex] = mtx; + mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); + gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; if (node->displayList != NULL) { geo_append_display_list(node->displayList, node->node.flags >> 8); } @@ -444,21 +602,30 @@ static void geo_process_scale(struct GraphNodeScale *node) { static void geo_process_billboard(struct GraphNodeBillboard *node) { Vec3f translation; Mtx *mtx = alloc_display_list(sizeof(*mtx)); + Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); gMatStackIndex++; vec3s_to_vec3f(translation, node->translation); mtxf_billboard(gMatStack[gMatStackIndex], gMatStack[gMatStackIndex - 1], translation, gCurGraphNodeCamera->roll); + mtxf_billboard(gMatStackInterpolated[gMatStackIndex], gMatStackInterpolated[gMatStackIndex - 1], translation, + gCurGraphNodeCamera->roll); if (gCurGraphNodeHeldObject != NULL) { mtxf_scale_vec3f(gMatStack[gMatStackIndex], gMatStack[gMatStackIndex], gCurGraphNodeHeldObject->objNode->header.gfx.scale); + mtxf_scale_vec3f(gMatStackInterpolated[gMatStackIndex], gMatStackInterpolated[gMatStackIndex], + gCurGraphNodeHeldObject->objNode->header.gfx.scale); } else if (gCurGraphNodeObject != NULL) { mtxf_scale_vec3f(gMatStack[gMatStackIndex], gMatStack[gMatStackIndex], gCurGraphNodeObject->scale); + mtxf_scale_vec3f(gMatStackInterpolated[gMatStackIndex], gMatStackInterpolated[gMatStackIndex], + gCurGraphNodeObject->scale); } mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); gMatStackFixed[gMatStackIndex] = mtx; + mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); + gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; if (node->displayList != NULL) { geo_append_display_list(node->displayList, node->node.flags >> 8); } @@ -507,15 +674,46 @@ static void geo_process_generated_list(struct GraphNodeGenerated *node) { */ static void geo_process_background(struct GraphNodeBackground *node) { Gfx *list = NULL; + Gfx *listInterpolated = NULL; if (node->fnNode.func != NULL) { + Vec3f posCopy; + Vec3f focusCopy; + Vec3f posInterpolated; + Vec3f focusInterpolated; + + if (gGlobalTimer == node->prevCameraTimestamp + 1 && + gGlobalTimer != gLakituState.skipCameraInterpolationTimestamp) { + interpolate_vectors(posInterpolated, node->prevCameraPos, gLakituState.pos); + interpolate_vectors(focusInterpolated, node->prevCameraFocus, gLakituState.focus); + } else { + vec3f_copy(posInterpolated, gLakituState.pos); + vec3f_copy(focusInterpolated, gLakituState.focus); + } + vec3f_copy(node->prevCameraPos, gLakituState.pos); + vec3f_copy(node->prevCameraFocus, gLakituState.focus); + node->prevCameraTimestamp = gGlobalTimer; + list = node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, (struct AllocOnlyPool *) gMatStack[gMatStackIndex]); + vec3f_copy(posCopy, gLakituState.pos); + vec3f_copy(focusCopy, gLakituState.focus); + vec3f_copy(gLakituState.pos, posInterpolated); + vec3f_copy(gLakituState.focus, focusInterpolated); + listInterpolated = node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, NULL); + vec3f_copy(gLakituState.pos, posCopy); + vec3f_copy(gLakituState.focus, focusCopy); } + if (list != NULL) { - geo_append_display_list((void *) VIRTUAL_TO_PHYSICAL(list), node->fnNode.node.flags >> 8); + geo_append_display_list2((void *) VIRTUAL_TO_PHYSICAL(list), + (void *) VIRTUAL_TO_PHYSICAL(listInterpolated), node->fnNode.node.flags >> 8); } else if (gCurGraphNodeMasterList != NULL) { +#ifndef F3DEX_GBI_2E + Gfx *gfxStart = alloc_display_list(sizeof(Gfx) * 7); +#else Gfx *gfxStart = alloc_display_list(sizeof(Gfx) * 8); +#endif Gfx *gfx = gfxStart; gDPPipeSync(gfx++); @@ -534,6 +732,47 @@ static void geo_process_background(struct GraphNodeBackground *node) { } } +static void anim_process(Vec3f translation, Vec3s rotation, u8 *animType, s16 animFrame, u16 **animAttribute) { + if (*animType == ANIM_TYPE_TRANSLATION) { + translation[0] += gCurrAnimData[retrieve_animation_index(animFrame, animAttribute)] + * gCurrAnimTranslationMultiplier; + translation[1] += gCurrAnimData[retrieve_animation_index(animFrame, animAttribute)] + * gCurrAnimTranslationMultiplier; + translation[2] += gCurrAnimData[retrieve_animation_index(animFrame, animAttribute)] + * gCurrAnimTranslationMultiplier; + *animType = ANIM_TYPE_ROTATION; + } else { + if (*animType == ANIM_TYPE_LATERAL_TRANSLATION) { + translation[0] += + gCurrAnimData[retrieve_animation_index(animFrame, animAttribute)] + * gCurrAnimTranslationMultiplier; + *animAttribute += 2; + translation[2] += + gCurrAnimData[retrieve_animation_index(animFrame, animAttribute)] + * gCurrAnimTranslationMultiplier; + *animType = ANIM_TYPE_ROTATION; + } else { + if (*animType == ANIM_TYPE_VERTICAL_TRANSLATION) { + *animAttribute += 2; + translation[1] += + gCurrAnimData[retrieve_animation_index(animFrame, animAttribute)] + * gCurrAnimTranslationMultiplier; + *animAttribute += 2; + *animType = ANIM_TYPE_ROTATION; + } else if (*animType == ANIM_TYPE_NO_TRANSLATION) { + *animAttribute += 6; + *animType = ANIM_TYPE_ROTATION; + } + } + } + + if (*animType == ANIM_TYPE_ROTATION) { + rotation[0] = gCurrAnimData[retrieve_animation_index(animFrame, animAttribute)]; + rotation[1] = gCurrAnimData[retrieve_animation_index(animFrame, animAttribute)]; + rotation[2] = gCurrAnimData[retrieve_animation_index(animFrame, animAttribute)]; + } +} + /** * Render an animated part. The current animation state is not part of the node * but set in global variables. If an animated part is skipped, everything afterwards desyncs. @@ -542,53 +781,32 @@ static void geo_process_animated_part(struct GraphNodeAnimatedPart *node) { Mat4 matrix; Vec3s rotation; Vec3f translation; + Vec3s rotationInterpolated; + Vec3f translationInterpolated; Mtx *matrixPtr = alloc_display_list(sizeof(*matrixPtr)); + Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); + u16 *animAttribute = gCurrAnimAttribute; + u8 animType = gCurrAnimType; vec3s_copy(rotation, gVec3sZero); vec3f_set(translation, node->translation[0], node->translation[1], node->translation[2]); - if (gCurrAnimType == ANIM_TYPE_TRANSLATION) { - translation[0] += gCurrAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] - * gCurrAnimTranslationMultiplier; - translation[1] += gCurrAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] - * gCurrAnimTranslationMultiplier; - translation[2] += gCurrAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] - * gCurrAnimTranslationMultiplier; - gCurrAnimType = ANIM_TYPE_ROTATION; - } else { - if (gCurrAnimType == ANIM_TYPE_LATERAL_TRANSLATION) { - translation[0] += - gCurrAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] - * gCurrAnimTranslationMultiplier; - gCurrAnimAttribute += 2; - translation[2] += - gCurrAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] - * gCurrAnimTranslationMultiplier; - gCurrAnimType = ANIM_TYPE_ROTATION; - } else { - if (gCurrAnimType == ANIM_TYPE_VERTICAL_TRANSLATION) { - gCurrAnimAttribute += 2; - translation[1] += - gCurrAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] - * gCurrAnimTranslationMultiplier; - gCurrAnimAttribute += 2; - gCurrAnimType = ANIM_TYPE_ROTATION; - } else if (gCurrAnimType == ANIM_TYPE_NO_TRANSLATION) { - gCurrAnimAttribute += 6; - gCurrAnimType = ANIM_TYPE_ROTATION; - } - } - } + vec3s_copy(rotationInterpolated, rotation); + vec3f_copy(translationInterpolated, translation); + + anim_process(translationInterpolated, rotationInterpolated, &animType, gPrevAnimFrame, &animAttribute); + anim_process(translation, rotation, &gCurrAnimType, gCurrAnimFrame, &gCurrAnimAttribute); + interpolate_vectors(translationInterpolated, translationInterpolated, translation); + interpolate_angles(rotationInterpolated, rotationInterpolated, rotation); - if (gCurrAnimType == ANIM_TYPE_ROTATION) { - rotation[0] = gCurrAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)]; - rotation[1] = gCurrAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)]; - rotation[2] = gCurrAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)]; - } mtxf_rotate_xyz_and_translate(matrix, translation, rotation); mtxf_mul(gMatStack[gMatStackIndex + 1], matrix, gMatStack[gMatStackIndex]); + mtxf_rotate_xyz_and_translate(matrix, translationInterpolated, rotationInterpolated); + mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], matrix, gMatStackInterpolated[gMatStackIndex]); gMatStackIndex++; mtxf_to_mtx(matrixPtr, gMatStack[gMatStackIndex]); gMatStackFixed[gMatStackIndex] = matrixPtr; + mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); + gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; if (node->displayList != NULL) { geo_append_display_list(node->displayList, node->node.flags >> 8); } @@ -620,6 +838,17 @@ void geo_set_animation_globals(struct AnimInfo *node, s32 hasAnimation) { } gCurrAnimFrame = node->animFrame; + if (node->prevAnimPtr == anim && node->prevAnimID == node->animID && + gGlobalTimer == node->prevAnimFrameTimestamp + 1) { + gPrevAnimFrame = node->prevAnimFrame; + } else { + gPrevAnimFrame = node->animFrame; + } + node->prevAnimPtr = anim; + node->prevAnimID = node->animID; + node->prevAnimFrame = node->animFrame; + node->prevAnimFrameTimestamp = gGlobalTimer; + gCurrAnimEnabled = (anim->flags & ANIM_FLAG_5) == 0; gCurrAnimAttribute = segmented_to_virtual((void *) anim->index); gCurrAnimData = segmented_to_virtual((void *) anim->values); @@ -638,8 +867,10 @@ void geo_set_animation_globals(struct AnimInfo *node, s32 hasAnimation) { */ static void geo_process_shadow(struct GraphNodeShadow *node) { Gfx *shadowList; + Gfx *shadowListInterpolated; Mat4 mtxf; Vec3f shadowPos; + Vec3f shadowPosInterpolated; Vec3f animOffset; f32 objScale; f32 shadowScale; @@ -647,6 +878,7 @@ static void geo_process_shadow(struct GraphNodeShadow *node) { f32 cosAng; struct GraphNode *geo; Mtx *mtx; + Mtx *mtxInterpolated; if (gCurGraphNodeCamera != NULL && gCurGraphNodeObject != NULL) { if (gCurGraphNodeHeldObject != NULL) { @@ -685,21 +917,57 @@ static void geo_process_shadow(struct GraphNodeShadow *node) { } } + if (gCurGraphNodeHeldObject != NULL) { + if (gGlobalTimer == gCurGraphNodeHeldObject->prevShadowPosTimestamp + 1) { + interpolate_vectors(shadowPosInterpolated, gCurGraphNodeHeldObject->prevShadowPos, shadowPos); + } else { + vec3f_copy(shadowPosInterpolated, shadowPos); + } + vec3f_copy(gCurGraphNodeHeldObject->prevShadowPos, shadowPos); + gCurGraphNodeHeldObject->prevShadowPosTimestamp = gGlobalTimer; + } else { + if (gGlobalTimer == gCurGraphNodeObject->prevShadowPosTimestamp + 1 && + gGlobalTimer != gCurGraphNodeObject->skipInterpolationTimestamp) { + interpolate_vectors(shadowPosInterpolated, gCurGraphNodeObject->prevShadowPos, shadowPos); + } else { + vec3f_copy(shadowPosInterpolated, shadowPos); + } + vec3f_copy(gCurGraphNodeObject->prevShadowPos, shadowPos); + gCurGraphNodeObject->prevShadowPosTimestamp = gGlobalTimer; + } + + extern u8 gInterpolatingSurfaces; + gInterpolatingSurfaces = TRUE; + shadowListInterpolated = create_shadow_below_xyz(shadowPosInterpolated[0], shadowPosInterpolated[1], + shadowPosInterpolated[2], shadowScale, + node->shadowSolidity, node->shadowType); + gInterpolatingSurfaces = FALSE; shadowList = create_shadow_below_xyz(shadowPos[0], shadowPos[1], shadowPos[2], shadowScale, node->shadowSolidity, node->shadowType); - if (shadowList != NULL) { + if (shadowListInterpolated != NULL && shadowList != NULL) { mtx = alloc_display_list(sizeof(*mtx)); + mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); gMatStackIndex++; + mtxf_translate(mtxf, shadowPos); mtxf_mul(gMatStack[gMatStackIndex], mtxf, *gCurGraphNodeCamera->matrixPtr); mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); gMatStackFixed[gMatStackIndex] = mtx; + + mtxf_translate(mtxf, shadowPosInterpolated); + mtxf_mul(gMatStackInterpolated[gMatStackIndex], mtxf, *gCurGraphNodeCamera->matrixPtrInterpolated); + mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); + gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; + if (gShadowAboveWaterOrLava == TRUE) { - geo_append_display_list((void *) VIRTUAL_TO_PHYSICAL(shadowList), 4); + geo_append_display_list2((void *) VIRTUAL_TO_PHYSICAL(shadowList), + (void *) VIRTUAL_TO_PHYSICAL(shadowListInterpolated), 4); } else if (gMarioOnIceOrCarpet == 1) { - geo_append_display_list((void *) VIRTUAL_TO_PHYSICAL(shadowList), 5); + geo_append_display_list2((void *) VIRTUAL_TO_PHYSICAL(shadowList), + (void *) VIRTUAL_TO_PHYSICAL(shadowListInterpolated), 5); } else { - geo_append_display_list((void *) VIRTUAL_TO_PHYSICAL(shadowList), 6); + geo_append_display_list2((void *) VIRTUAL_TO_PHYSICAL(shadowList), + (void *) VIRTUAL_TO_PHYSICAL(shadowListInterpolated), 6); } gMatStackIndex--; } @@ -795,28 +1063,87 @@ static s32 obj_is_in_view(struct GraphNodeObject *node, Mat4 matrix) { return TRUE; } +static void interpolate_matrix(Mat4 result, Mat4 a, Mat4 b) { + s32 i, j; + for (i = 0; i < 4; i++) { + for (j = 0; j < 4; j++) { + result[i][j] = (a[i][j] + b[i][j]) / 2.0f; + } + } +} + /** * Process an object node. */ static void geo_process_object(struct Object *node) { Mat4 mtxf; s32 hasAnimation = (node->header.gfx.node.flags & GRAPH_RENDER_HAS_ANIMATION) != 0; + Vec3f scaleInterpolated; if (node->header.gfx.areaIndex == gCurGraphNodeRoot->areaIndex) { if (node->header.gfx.throwMatrix != NULL) { mtxf_mul(gMatStack[gMatStackIndex + 1], *node->header.gfx.throwMatrix, gMatStack[gMatStackIndex]); + if (gGlobalTimer == node->header.gfx.prevThrowMatrixTimestamp + 1 && + gGlobalTimer != node->header.gfx.skipInterpolationTimestamp) { + interpolate_matrix(mtxf, *node->header.gfx.throwMatrix, node->header.gfx.prevThrowMatrix); + mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], mtxf, + gMatStackInterpolated[gMatStackIndex]); + } else { + mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], (void *) node->header.gfx.throwMatrix, + gMatStackInterpolated[gMatStackIndex]); + } + mtxf_copy(node->header.gfx.prevThrowMatrix, *node->header.gfx.throwMatrix); + node->header.gfx.prevThrowMatrixTimestamp = gGlobalTimer; } else if (node->header.gfx.node.flags & GRAPH_RENDER_BILLBOARD) { + Vec3f posInterpolated; + if (gGlobalTimer == node->header.gfx.prevTimestamp + 1 && + gGlobalTimer != node->header.gfx.skipInterpolationTimestamp) { + interpolate_vectors(posInterpolated, node->header.gfx.prevPos, node->header.gfx.pos); + } else { + vec3f_copy(posInterpolated, node->header.gfx.pos); + } + vec3f_copy(node->header.gfx.prevPos, node->header.gfx.pos); + node->header.gfx.prevTimestamp = gGlobalTimer; mtxf_billboard(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex], node->header.gfx.pos, gCurGraphNodeCamera->roll); + mtxf_billboard(gMatStackInterpolated[gMatStackIndex + 1], gMatStackInterpolated[gMatStackIndex], + posInterpolated, gCurGraphNodeCamera->roll); } else { + Vec3f posInterpolated; + Vec3s angleInterpolated; + if (gGlobalTimer == node->header.gfx.prevTimestamp + 1 && + gGlobalTimer != node->header.gfx.skipInterpolationTimestamp) { + interpolate_vectors(posInterpolated, node->header.gfx.prevPos, node->header.gfx.pos); + interpolate_angles(angleInterpolated, node->header.gfx.prevAngle, node->header.gfx.angle); + } else { + vec3f_copy(posInterpolated, node->header.gfx.pos); + vec3s_copy(angleInterpolated, node->header.gfx.angle); + } + vec3f_copy(node->header.gfx.prevPos, node->header.gfx.pos); + vec3s_copy(node->header.gfx.prevAngle, node->header.gfx.angle); + node->header.gfx.prevTimestamp = gGlobalTimer; mtxf_rotate_zxy_and_translate(mtxf, node->header.gfx.pos, node->header.gfx.angle); mtxf_mul(gMatStack[gMatStackIndex + 1], mtxf, gMatStack[gMatStackIndex]); + mtxf_rotate_zxy_and_translate(mtxf, posInterpolated, angleInterpolated); + mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], mtxf, gMatStackInterpolated[gMatStackIndex]); } + if (gGlobalTimer == node->header.gfx.prevScaleTimestamp + 1 && + gGlobalTimer != node->header.gfx.skipInterpolationTimestamp) { + interpolate_vectors(scaleInterpolated, node->header.gfx.prevScale, node->header.gfx.scale); + } else { + vec3f_copy(scaleInterpolated, node->header.gfx.scale); + } + vec3f_copy(node->header.gfx.prevScale, node->header.gfx.scale); + node->header.gfx.prevScaleTimestamp = gGlobalTimer; + mtxf_scale_vec3f(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex + 1], node->header.gfx.scale); + mtxf_scale_vec3f(gMatStackInterpolated[gMatStackIndex + 1], gMatStackInterpolated[gMatStackIndex + 1], + scaleInterpolated); node->header.gfx.throwMatrix = &gMatStack[++gMatStackIndex]; + node->header.gfx.throwMatrixInterpolated = &gMatStackInterpolated[gMatStackIndex]; node->header.gfx.cameraToObject[0] = gMatStack[gMatStackIndex][3][0]; node->header.gfx.cameraToObject[1] = gMatStack[gMatStackIndex][3][1]; node->header.gfx.cameraToObject[2] = gMatStack[gMatStackIndex][3][2]; @@ -827,9 +1154,12 @@ static void geo_process_object(struct Object *node) { } if (obj_is_in_view(&node->header.gfx, gMatStack[gMatStackIndex])) { Mtx *mtx = alloc_display_list(sizeof(*mtx)); + Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); gMatStackFixed[gMatStackIndex] = mtx; + mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); + gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; if (node->header.gfx.sharedChild != NULL) { gCurGraphNodeObject = (struct GraphNodeObject *) node; node->header.gfx.sharedChild->parent = &node->header.gfx.node; @@ -840,11 +1170,16 @@ static void geo_process_object(struct Object *node) { if (node->header.gfx.node.children != NULL) { geo_process_node_and_siblings(node->header.gfx.node.children); } + } else { + node->header.gfx.prevThrowMatrixTimestamp = 0; + node->header.gfx.prevTimestamp = 0; + node->header.gfx.prevScaleTimestamp = 0; } gMatStackIndex--; gCurrAnimType = ANIM_TYPE_NONE; node->header.gfx.throwMatrix = NULL; + node->header.gfx.throwMatrixInterpolated = NULL; } } @@ -871,6 +1206,8 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { Mat4 mat; Vec3f translation; Mtx *mtx = alloc_display_list(sizeof(*mtx)); + Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); + Vec3f scaleInterpolated; #ifdef F3DEX_GBI_2 gSPLookAt(gDisplayListHead++, &lookAt); @@ -886,6 +1223,14 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { translation[1] = node->translation[1] / 4.0f; translation[2] = node->translation[2] / 4.0f; + if (gGlobalTimer == node->objNode->header.gfx.prevScaleTimestamp + 1) { + interpolate_vectors(scaleInterpolated, node->objNode->header.gfx.prevScale, node->objNode->header.gfx.scale); + } else { + vec3f_copy(scaleInterpolated, node->objNode->header.gfx.scale); + } + vec3f_copy(node->objNode->header.gfx.prevScale, node->objNode->header.gfx.scale); + node->objNode->header.gfx.prevScaleTimestamp = gGlobalTimer; + mtxf_translate(mat, translation); mtxf_copy(gMatStack[gMatStackIndex + 1], *gCurGraphNodeObject->throwMatrix); gMatStack[gMatStackIndex + 1][3][0] = gMatStack[gMatStackIndex][3][0]; @@ -894,6 +1239,13 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { mtxf_mul(gMatStack[gMatStackIndex + 1], mat, gMatStack[gMatStackIndex + 1]); mtxf_scale_vec3f(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex + 1], node->objNode->header.gfx.scale); + mtxf_copy(gMatStackInterpolated[gMatStackIndex + 1], (void *) gCurGraphNodeObject->throwMatrixInterpolated); + gMatStackInterpolated[gMatStackIndex + 1][3][0] = gMatStackInterpolated[gMatStackIndex][3][0]; + gMatStackInterpolated[gMatStackIndex + 1][3][1] = gMatStackInterpolated[gMatStackIndex][3][1]; + gMatStackInterpolated[gMatStackIndex + 1][3][2] = gMatStackInterpolated[gMatStackIndex][3][2]; + mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], mat, gMatStackInterpolated[gMatStackIndex + 1]); + mtxf_scale_vec3f(gMatStackInterpolated[gMatStackIndex + 1], gMatStackInterpolated[gMatStackIndex + 1], + scaleInterpolated); if (node->fnNode.func != NULL) { node->fnNode.func(GEO_CONTEXT_HELD_OBJ, &node->fnNode.node, (struct AllocOnlyPool *) gMatStack[gMatStackIndex + 1]); @@ -901,12 +1253,15 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { gMatStackIndex++; mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); gMatStackFixed[gMatStackIndex] = mtx; + mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); + gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; gGeoTempState.type = gCurrAnimType; gGeoTempState.enabled = gCurrAnimEnabled; gGeoTempState.frame = gCurrAnimFrame; gGeoTempState.translationMultiplier = gCurrAnimTranslationMultiplier; gGeoTempState.attribute = gCurrAnimAttribute; gGeoTempState.data = gCurrAnimData; + gGeoTempState.prevFrame = gPrevAnimFrame; gCurrAnimType = 0; gCurGraphNodeHeldObject = (void *) node; if (node->objNode->header.gfx.animInfo.curAnim != NULL) { @@ -921,6 +1276,7 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { gCurrAnimTranslationMultiplier = gGeoTempState.translationMultiplier; gCurrAnimAttribute = gGeoTempState.attribute; gCurrAnimData = gGeoTempState.data; + gPrevAnimFrame = gGeoTempState.prevFrame; gMatStackIndex--; } @@ -1037,11 +1393,12 @@ void geo_process_node_and_siblings(struct GraphNode *firstNode) { * to set up the projection and draw display lists. */ void geo_process_root(struct GraphNodeRoot *node, Vp *b, Vp *c, s32 clearColor) { - UNUSED u8 filler[4]; + UNUSED s32 unused; if (node->node.flags & GRAPH_RENDER_ACTIVE) { Mtx *initialMatrix; Vp *viewport = alloc_display_list(sizeof(*viewport)); + Vp *viewportInterpolated = viewport; gDisplayListHeap = alloc_only_pool_init(main_pool_available() - sizeof(struct AllocOnlyPool), MEMORY_POOL_LEFT); @@ -1052,7 +1409,12 @@ void geo_process_root(struct GraphNodeRoot *node, Vp *b, Vp *c, s32 clearColor) vec3s_set(viewport->vp.vscale, node->width * 4, node->height * 4, 511); if (b != NULL) { clear_framebuffer(clearColor); - make_viewport_clip_rect(b); + viewportInterpolated = alloc_display_list(sizeof(*viewportInterpolated)); + interpolate_vectors_s16(viewportInterpolated->vp.vtrans, sPrevViewport.vp.vtrans, b->vp.vtrans); + interpolate_vectors_s16(viewportInterpolated->vp.vscale, sPrevViewport.vp.vscale, b->vp.vscale); + + sViewportPos = gDisplayListHead; + make_viewport_clip_rect(viewportInterpolated); *viewport = *b; } @@ -1060,11 +1422,16 @@ void geo_process_root(struct GraphNodeRoot *node, Vp *b, Vp *c, s32 clearColor) clear_framebuffer(clearColor); make_viewport_clip_rect(c); } + sPrevViewport = *viewport; mtxf_identity(gMatStack[gMatStackIndex]); mtxf_to_mtx(initialMatrix, gMatStack[gMatStackIndex]); gMatStackFixed[gMatStackIndex] = initialMatrix; - gSPViewport(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(viewport)); + + mtxf_identity(gMatStackInterpolated[gMatStackIndex]); + gMatStackInterpolatedFixed[gMatStackIndex] = initialMatrix; + + gSPViewport(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(viewportInterpolated)); gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(gMatStackFixed[gMatStackIndex]), G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH); gCurGraphNodeRoot = node; diff --git a/src/game/screen_transition.c b/src/game/screen_transition.c index e0926f85..d1cd93e6 100644 --- a/src/game/screen_transition.c +++ b/src/game/screen_transition.c @@ -16,6 +16,19 @@ u8 sTransitionColorFadeCount[4] = { 0 }; u16 sTransitionTextureFadeCount[2] = { 0 }; +static Gfx *sScreenTransitionVerticesPos[2]; +static Vtx *sScreenTransitionVertices; + +void patch_screen_transition_interpolated(void) { + if (sScreenTransitionVerticesPos[0] != NULL) { + gSPVertex(sScreenTransitionVerticesPos[0], VIRTUAL_TO_PHYSICAL(sScreenTransitionVertices), 8, 0); + gSPVertex(sScreenTransitionVerticesPos[1], VIRTUAL_TO_PHYSICAL(sScreenTransitionVertices), 4, 0); + sScreenTransitionVerticesPos[0] = NULL; + sScreenTransitionVerticesPos[1] = NULL; + sScreenTransitionVertices = NULL; + } +} + s32 set_and_reset_transition_fade_timer(s8 fadeTimer, u8 transTime) { s32 reset = FALSE; @@ -85,6 +98,8 @@ s32 render_fade_transition_into_color(s8 fadeTimer, u8 transTime, struct WarpTra return dl_transition_color(fadeTimer, transTime, transData, alpha); } +#if FALSE + s16 calc_tex_transition_radius(s8 fadeTimer, s8 transTime, struct WarpTransitionData *transData) { f32 texRadius = transData->endTexRadius - transData->startTexRadius; f32 radiusTime = sTransitionColorFadeCount[fadeTimer] * texRadius / (f32)(transTime - 1); @@ -93,6 +108,19 @@ s16 calc_tex_transition_radius(s8 fadeTimer, s8 transTime, struct WarpTransition return (s16)(result + 0.5); } +#else + +s16 calc_tex_transition_radius(s8 fadeTimer, f32 interpolationFraction, s8 transTime, struct WarpTransitionData *transData) { + f32 texRadius = transData->endTexRadius - transData->startTexRadius; + f32 radiusTime = (sTransitionColorFadeCount[fadeTimer] == 0 ? 0 : + sTransitionColorFadeCount[fadeTimer] - 1 + interpolationFraction) * texRadius / (f32)(transTime - 1); + f32 result = transData->startTexRadius + radiusTime; + + return (s16)(result + 0.5); +} + +#endif + f32 calc_tex_transition_time(s8 fadeTimer, s8 transTime, struct WarpTransitionData *transData) { f32 startX = transData->startTexX; f32 startY = transData->startTexY; @@ -166,6 +194,8 @@ void *sTextureTransitionID[] = { texture_transition_bowser_half, }; +#if 0 + s32 render_textured_transition(s8 fadeTimer, s8 transTime, struct WarpTransitionData *transData, s8 texID, s8 transTexType) { f32 texTransTime = calc_tex_transition_time(fadeTimer, transTime, transData); u16 texTransPos = convert_tex_transition_angle_to_pos(transData); @@ -206,6 +236,56 @@ s32 render_textured_transition(s8 fadeTimer, s8 transTime, struct WarpTransition return set_and_reset_transition_fade_timer(fadeTimer, transTime); } +#else + +s32 render_textured_transition(s8 fadeTimer, s8 transTime, struct WarpTransitionData *transData, s8 texID, s8 transTexType) { + f32 texTransTime = calc_tex_transition_time(fadeTimer, transTime, transData); + u16 texTransPos = convert_tex_transition_angle_to_pos(transData); + s16 centerTransX = center_tex_transition_x(transData, texTransTime, texTransPos); + s16 centerTransY = center_tex_transition_y(transData, texTransTime, texTransPos); + s16 texTransRadius = calc_tex_transition_radius(fadeTimer, 1.0f, transTime, transData); + s16 texTransRadiusInterpolated = calc_tex_transition_radius(fadeTimer, 0.5f, transTime, transData); + Vtx *verts = alloc_display_list(8 * sizeof(*verts)); + Vtx *vertsInterpolated = alloc_display_list(8 * sizeof(*vertsInterpolated)); + + if (verts != NULL && vertsInterpolated != NULL) { + load_tex_transition_vertex(verts, fadeTimer, transData, centerTransX, centerTransY, texTransRadius, transTexType); + load_tex_transition_vertex(vertsInterpolated, fadeTimer, transData, centerTransX, centerTransY, texTransRadiusInterpolated, transTexType); + sScreenTransitionVertices = verts; + gSPDisplayList(gDisplayListHead++, dl_proj_mtx_fullscreen) + gDPSetCombineMode(gDisplayListHead++, G_CC_SHADE, G_CC_SHADE); + gDPSetRenderMode(gDisplayListHead++, G_RM_AA_OPA_SURF, G_RM_AA_OPA_SURF2); + sScreenTransitionVerticesPos[0] = gDisplayListHead; + gSPVertex(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(vertsInterpolated), 8, 0); + gSPDisplayList(gDisplayListHead++, dl_transition_draw_filled_region); + gDPPipeSync(gDisplayListHead++); + gDPSetCombineMode(gDisplayListHead++, G_CC_MODULATEIDECALA, G_CC_MODULATEIDECALA); + gDPSetRenderMode(gDisplayListHead++, G_RM_AA_XLU_SURF, G_RM_AA_XLU_SURF2); + gDPSetTextureFilter(gDisplayListHead++, G_TF_BILERP); + switch (transTexType) { + case TRANS_TYPE_MIRROR: + gDPLoadTextureBlock(gDisplayListHead++, sTextureTransitionID[texID], G_IM_FMT_IA, G_IM_SIZ_8b, 32, 64, 0, + G_TX_WRAP | G_TX_MIRROR, G_TX_WRAP | G_TX_MIRROR, 5, 6, G_TX_NOLOD, G_TX_NOLOD); + break; + case TRANS_TYPE_CLAMP: + gDPLoadTextureBlock(gDisplayListHead++, sTextureTransitionID[texID], G_IM_FMT_IA, G_IM_SIZ_8b, 64, 64, 0, + G_TX_CLAMP, G_TX_CLAMP, 6, 6, G_TX_NOLOD, G_TX_NOLOD); + break; + } + gSPTexture(gDisplayListHead++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_ON); + sScreenTransitionVerticesPos[1] = gDisplayListHead; + gSPVertex(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(vertsInterpolated), 4, 0); + gSPDisplayList(gDisplayListHead++, dl_draw_quad_verts_0123); + gSPTexture(gDisplayListHead++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_OFF); + gSPDisplayList(gDisplayListHead++, dl_screen_transition_end); + sTransitionTextureFadeCount[fadeTimer] += transData->texTimer; + } else { + } + return set_and_reset_transition_fade_timer(fadeTimer, transTime); +} + +#endif + s32 render_screen_transition(s8 fadeTimer, s8 transType, u8 transTime, struct WarpTransitionData *transData) { switch (transType) { case WARP_TRANSITION_FADE_FROM_COLOR: diff --git a/src/menu/intro_geo.c b/src/menu/intro_geo.c index a16c8237..4fb608d5 100644 --- a/src/menu/intro_geo.c +++ b/src/menu/intro_geo.c @@ -1,5 +1,6 @@ #include +#include "engine/math_util.h" #include "game/memory.h" #include "game/segment2.h" #include "game/segment7.h" @@ -36,6 +37,18 @@ static s32 sGameOverTableIndex; static s16 sIntroFrameCounter; static s32 sTmCopyrightAlpha; +static Gfx *sIntroScalePos; +static Vec3f sIntroScale; + +void patch_title_screen_scales(void) { + if (sIntroScalePos != NULL) { + Mtx *scaleMat = alloc_display_list(sizeof(*scaleMat)); + guScale(scaleMat, sIntroScale[0], sIntroScale[1], sIntroScale[2]); + gSPMatrix(sIntroScalePos, scaleMat, G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_PUSH); + sIntroScalePos = NULL; + } +} + /** * Geo callback to render the "Super Mario 64" logo on the title screen */ @@ -49,6 +62,8 @@ Gfx *geo_intro_super_mario_64_logo(s32 state, struct GraphNode *node, UNUSED voi f32 scaleX; f32 scaleY; f32 scaleZ; + Vec3f scale; + Vec3f scaleInterpolated; if (state != 1) { sIntroFrameCounter = 0; @@ -80,7 +95,12 @@ Gfx *geo_intro_super_mario_64_logo(s32 state, struct GraphNode *node, UNUSED voi scaleY = 0.0f; scaleZ = 0.0f; } - guScale(scaleMat, scaleX, scaleY, scaleZ); + + vec3f_set(scale, scaleX, scaleY, scaleZ); + interpolate_vectors(scaleInterpolated, sIntroScale, scale); + vec3f_set(sIntroScale, scaleX, scaleY, scaleZ); + guScale(scaleMat, scaleInterpolated[0], scaleInterpolated[1], scaleInterpolated[2]); + sIntroScalePos = dlIter; gSPMatrix(dlIter++, scaleMat, G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_PUSH); gSPDisplayList(dlIter++, &intro_seg7_dl_0700B3A0); // draw model diff --git a/src/port/Engine.cpp b/src/port/Engine.cpp index 79a23adc..61b3b634 100644 --- a/src/port/Engine.cpp +++ b/src/port/Engine.cpp @@ -45,7 +45,7 @@ GameEngine::GameEngine(){ } } this->context = LUS::Context::CreateInstance("Ghostship", "sm64", "ghostship.cfg.json", OTRFiles, { 0xFF2B5A63 }, 3); - this->context->GetWindow()->SetTargetFps(30); + this->context->GetWindow()->SetTargetFps(60); this->context->GetWindow()->SetMaximumFrameLatency(1); this->context->GetResourceManager()->GetResourceLoader()->RegisterResourceFactory(LUS::ResourceType::SAnim, "Animation", std::make_shared()); this->context->GetResourceManager()->GetResourceLoader()->RegisterResourceFactory(LUS::ResourceType::Bank, "Bank", std::make_shared()); @@ -98,6 +98,18 @@ void GameEngine::ProcessFrame(void (*run_one_game_iter)()) const { this->context->GetWindow()->MainLoop(run_one_game_iter); } +uint32_t GameEngine::GetInterpolationFPS() { + if (LUS::Context::GetInstance()->GetWindow()->GetWindowBackend() == LUS::WindowBackend::DX11) { + return CVarGetInteger("gInterpolationFPS", 30); + } + + if (CVarGetInteger("gMatchRefreshRate", 0)) { + return LUS::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate(); + } + + return std::min(LUS::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate(), CVarGetInteger("gInterpolationFPS", 30)); +} + // Audio void GameEngine::HandleAudioThread(){ @@ -171,6 +183,10 @@ void GameEngine::LoadDictionary() { this->dictionary = static_cast> *>(ResourceGetDataByName("__OTR__texts/strings/global")); } +extern "C" uint32_t GameEngine_GetInterpolatedFPS() { + return GameEngine::GetInterpolationFPS(); +} + extern "C" uint32_t GameEngine_GetSampleRate() { auto player = LUS::Context::GetInstance()->GetAudio()->GetAudioPlayer(); if (player == nullptr) { diff --git a/src/port/Engine.h b/src/port/Engine.h index f3cbc5b3..f9b75072 100644 --- a/src/port/Engine.h +++ b/src/port/Engine.h @@ -29,6 +29,7 @@ class GameEngine { void StartFrame() const; static void RunCommands(Gfx* Commands); void ProcessFrame(void (*run_one_game_iter)()) const; + static uint32_t GetInterpolationFPS(); static void HandleAudioThread(); static void StartAudioFrame(); static void EndAudioFrame(); @@ -40,6 +41,7 @@ class GameEngine { }; #else +uint32_t GameEngine_GetInterpolatedFPS(); uint32_t GameEngine_GetSampleRate(); uint32_t GameEngine_GetSamplesPerFrame(); float GameEngine_GetAspectRatio(); diff --git a/src/port/Game.cpp b/src/port/Game.cpp index 92ed6460..4c0efdd3 100644 --- a/src/port/Game.cpp +++ b/src/port/Game.cpp @@ -6,6 +6,7 @@ extern "C" { #include "audio/external.h" #include "game/game_init.h" +#include "sm64.h" } void alloc_pool() { @@ -14,9 +15,35 @@ void alloc_pool() { gEffectsMemoryPool = mem_pool_init(0x4000, MEMORY_POOL_LEFT); } +void patch_interpolations(void) { + mtx_patch_interpolated(); + patch_screen_transition_interpolated(); + patch_title_screen_scales(); + patch_interpolated_dialog(); + patch_interpolated_hud(); + patch_interpolated_paintings(); + patch_interpolated_bubble_particles(); + patch_interpolated_snow_particles(); +} + +#define ROUND_30(x) floor(x / 30) * 30 + extern "C" void exec_display_list(SPTask *spTask) { + uint32_t targetFPS = GameEngine::GetInterpolationFPS(); + GameEngine::Instance->context->GetWindow()->SetTargetFps(targetFPS); + + int threshold = CVarGetInteger("gExtraLatencyThreshold", 80); + GameEngine::Instance->context->GetWindow()->SetMaximumFrameLatency(threshold > 0 && targetFPS >= threshold ? 2 : 1); + GameEngine::RunCommands((Gfx*) spTask->task.t.data_ptr); + + if (targetFPS > 30) { + for (size_t i = 0; i < (ROUND_30(targetFPS) - 30) / 30; i++) { + patch_interpolations(); + GameEngine::RunCommands((Gfx *) spTask->task.t.data_ptr); + } + } } void push_frame() { diff --git a/src/port/ui/ImguiUI.cpp b/src/port/ui/ImguiUI.cpp index d7ffb150..cf0f3659 100644 --- a/src/port/ui/ImguiUI.cpp +++ b/src/port/ui/ImguiUI.cpp @@ -8,6 +8,7 @@ #include #include #include +#include "port/Engine.h" extern "C" { #include "audio/external.h" } @@ -169,6 +170,111 @@ void DrawSettingsMenu(){ #endif UIWidgets::Tooltip("Matches interpolation value to the current game's window refresh rate"); + { // FPS Slider + const int minFps = 30; + static int maxFps; + if (LUS::Context::GetInstance()->GetWindow()->GetWindowBackend() == LUS::WindowBackend::DX11) { + maxFps = 360; + } else { + maxFps = LUS::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate(); + } + int currentFps = fmax(fmin(GameEngine::GetInterpolationFPS(), maxFps), minFps); + #ifdef __WIIU__ + UIWidgets::Spacer(0); + // only support divisors of 60 on the Wii U + if (currentFps > 60) { + currentFps = 60; + } else { + currentFps = 60 / (60 / currentFps); + } + + int fpsSlider = 1; + if (currentFps == 30) { + ImGui::Text("FPS: Original (30)"); + } else { + ImGui::Text("FPS: %d", currentFps); + if (currentFps == 30) { + fpsSlider = 2; + } else { // currentFps == 60 + fpsSlider = 3; + } + } + if (CVarGetInteger("gMatchRefreshRate", 0)) { + UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); + } + + if (ImGui::Button(" - ##WiiUFPS")) { + fpsSlider--; + } + ImGui::SameLine(); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); + + UIWidgets::Spacer(0); + + ImGui::PushItemWidth(std::min((ImGui::GetContentRegionAvail().x - 60.0f), 260.0f)); + ImGui::SliderInt("##WiiUFPSSlider", &fpsSlider, 1, 3, "", ImGuiSliderFlags_AlwaysClamp); + ImGui::PopItemWidth(); + + ImGui::SameLine(); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); + if (ImGui::Button(" + ##WiiUFPS")) { + fpsSlider++; + } + + if (CVarGetInteger("gMatchRefreshRate", 0)) { + UIWidgets::ReEnableComponent(""); + } + if (fpsSlider > 3) { + fpsSlider = 3; + } else if (fpsSlider < 1) { + fpsSlider = 1; + } + + if (fpsSlider == 1) { + currentFps = 20; + } else if (fpsSlider == 2) { + currentFps = 30; + } else if (fpsSlider == 3) { + currentFps = 60; + } + CVarSetInteger("gInterpolationFPS", currentFps); + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + #else + bool matchingRefreshRate = + CVarGetInteger("gMatchRefreshRate", 0) && LUS::Context::GetInstance()->GetWindow()->GetWindowBackend() != LUS::WindowBackend::DX11; + UIWidgets::PaddedEnhancementSliderInt( + (currentFps == 20) ? "FPS: Original (20)" : "FPS: %d", + "##FPSInterpolation", "gInterpolationFPS", minFps, maxFps, "", 20, true, true, false, matchingRefreshRate); + #endif + if (LUS::Context::GetInstance()->GetWindow()->GetWindowBackend() == LUS::WindowBackend::DX11) { + UIWidgets::Tooltip( + "Uses Matrix Interpolation to create extra frames, resulting in smoother graphics. This is purely " + "visual and does not impact game logic, execution of glitches etc.\n\n" + "A higher target FPS than your monitor's refresh rate will waste resources, and might give a worse result." + ); + } else { + UIWidgets::Tooltip( + "Uses Matrix Interpolation to create extra frames, resulting in smoother graphics. This is purely " + "visual and does not impact game logic, execution of glitches etc." + ); + } + } // END FPS Slider + + if (LUS::Context::GetInstance()->GetWindow()->GetWindowBackend() == LUS::WindowBackend::DX11) { + UIWidgets::Spacer(0); + if (ImGui::Button("Match Refresh Rate")) { + int hz = LUS::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate(); + if (hz >= 30 && hz <= 360) { + CVarSetInteger("gInterpolationFPS", hz); + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + } + } + } else { + UIWidgets::PaddedEnhancementCheckbox("Match Refresh Rate", "gMatchRefreshRate", true, false); + } + + UIWidgets::Tooltip("Matches interpolation value to the current game's window refresh rate"); + if (LUS::Context::GetInstance()->GetWindow()->GetWindowBackend() == LUS::WindowBackend::DX11) { UIWidgets::PaddedEnhancementSliderInt(CVarGetInteger("gExtraLatencyThreshold", 80) == 0 ? "Jitter fix: Off" : "Jitter fix: >= %d FPS", "##ExtraLatencyThreshold", "gExtraLatencyThreshold", 0, 360, "", 80, true, true, false); From 3ab374d9b587aca95873fd56731beb0e702a2335 Mon Sep 17 00:00:00 2001 From: inspectredc Date: Sun, 8 Oct 2023 12:40:37 +0100 Subject: [PATCH 5/7] add includes --- include/sm64.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/sm64.h b/include/sm64.h index 82837911..b8f9be50 100644 --- a/include/sm64.h +++ b/include/sm64.h @@ -503,6 +503,9 @@ extern void osMapTLB(int32_t a, uint32_t b, void* c, uint32_t d, uint32_t e, uin // Interpolation +extern void *vec3s_copy(Vec3s dest, Vec3s src); +extern void interpolate_vectors(Vec3f res, Vec3f a, Vec3f b); +extern void interpolate_vectors_s16(Vec3s res, Vec3s a, Vec3s b); extern void mtx_patch_interpolated(void); extern void patch_screen_transition_interpolated(void); extern void patch_title_screen_scales(void); From 4e7ae7aaf46fae6baa98b472d4a68ed9d65b73d5 Mon Sep 17 00:00:00 2001 From: KiritoDv Date: Mon, 9 Oct 2023 07:00:04 -0600 Subject: [PATCH 6/7] First any fps implementation --- src/engine/surface_load.c | 1 + src/game/envfx_bubbles.c | 1 + src/game/rendering_graph_node.c | 76 +++++++++++++++++++++++++-------- src/game/rendering_graph_node.h | 2 + src/menu/intro_geo.c | 1 + src/port/Game.cpp | 46 ++++++++++++++------ 6 files changed, 95 insertions(+), 32 deletions(-) diff --git a/src/engine/surface_load.c b/src/engine/surface_load.c index e002ea5a..93147805 100644 --- a/src/engine/surface_load.c +++ b/src/engine/surface_load.c @@ -1,6 +1,7 @@ #include #include "sm64.h" +#include "engine/math_util.h" #include "game/ingame_menu.h" #include "graph_node.h" #include "behavior_script.h" diff --git a/src/game/envfx_bubbles.c b/src/game/envfx_bubbles.c index 6549c9cb..84acee31 100644 --- a/src/game/envfx_bubbles.c +++ b/src/game/envfx_bubbles.c @@ -10,6 +10,7 @@ #include "engine/behavior_script.h" #include "audio/external.h" #include "textures.h" +#include "rendering_graph_node.h" /** * This file implements environment effects that are not snow: diff --git a/src/game/rendering_graph_node.c b/src/game/rendering_graph_node.c index 5e6758c1..ed366fa2 100644 --- a/src/game/rendering_graph_node.c +++ b/src/game/rendering_graph_node.c @@ -42,6 +42,8 @@ Mtx *gMatStackFixed[32]; Mat4 gMatStackInterpolated[32]; Mtx *gMatStackInterpolatedFixed[32]; +extern float gInterpolationStep; + /** * Animation nodes have state in global variables, so this struct captures * the animation state so a 'context switch' can be made when rendering the @@ -379,31 +381,69 @@ static void geo_process_switch(struct GraphNodeSwitchCase *node) { } } +float lerp(f32 o, f32 n) { + return (1 - gInterpolationStep) * o + gInterpolationStep * n; +} + +s16 lerp_s(s16 o, s16 n) { + if (o == n) + return n; + int diff = o - n; + float w = 1 - gInterpolationStep; + if (-0x8000 <= diff && diff <= 0x8000) { + return (s16)(w * o + gInterpolationStep * n); + } else { + if (o < n) { + o += 0x10000; + } else { + n += 0x10000; + } + diff = o - n; + return (s16)(w * o + gInterpolationStep * n); + } +} + void interpolate_vectors(Vec3f res, Vec3f a, Vec3f b) { - res[0] = (a[0] + b[0]) / 2.0f; - res[1] = (a[1] + b[1]) / 2.0f; - res[2] = (a[2] + b[2]) / 2.0f; + res[0] = lerp(a[0], b[0]); + res[1] = lerp(a[1], b[1]); + res[2] = lerp(a[2], b[2]); } void interpolate_vectors_s16(Vec3s res, Vec3s a, Vec3s b) { - res[0] = (a[0] + b[0]) / 2; - res[1] = (a[1] + b[1]) / 2; - res[2] = (a[2] + b[2]) / 2; + res[0] = lerp_s(a[0], b[0]); + res[1] = lerp_s(a[1], b[1]); + res[2] = lerp_s(a[2], b[2]); } -static s16 interpolate_angle(s16 a, s16 b) { - s32 absDiff = b - a; - if (absDiff < 0) { - absDiff = -absDiff; - } - if (absDiff >= 0x4000 && absDiff <= 0xC000) { - return b; - } - if (absDiff <= 0x8000) { - return (a + b) / 2; +static s16 interpolate_angle(s16 os, s16 ns) { + if (os == ns) + return ns; + int o = (u16)os; + int n = (u16)ns; + u16 res; + int diff = o - n; + float w = 1 - gInterpolationStep; + if (-0x8000 <= diff && diff <= 0x8000) { + if (diff < -0x4000 || diff > 0x4000) { + return ns; + } + res = (u16)(w * o + gInterpolationStep * n); } else { - return (a + b) / 2 + 0x8000; + if (o < n) { + o += 0x10000; + } else { + n += 0x10000; + } + diff = o - n; + if (diff < -0x4000 || diff > 0x4000) { + return ns; + } + res = (u16)(w * o + gInterpolationStep * n); } + if (os / 327 == ns / 327 && (s16)res / 327 != os / 327) { + int bp = 0; + } + return res; } static void interpolate_angles(Vec3s res, Vec3s a, Vec3s b) { @@ -1067,7 +1107,7 @@ static void interpolate_matrix(Mat4 result, Mat4 a, Mat4 b) { s32 i, j; for (i = 0; i < 4; i++) { for (j = 0; j < 4; j++) { - result[i][j] = (a[i][j] + b[i][j]) / 2.0f; + result[i][j] = (1 - gInterpolationStep) * a[i][j] + gInterpolationStep * b[i][j]; } } } diff --git a/src/game/rendering_graph_node.h b/src/game/rendering_graph_node.h index 20a69f61..2e889be8 100644 --- a/src/game/rendering_graph_node.h +++ b/src/game/rendering_graph_node.h @@ -29,5 +29,7 @@ extern u16 gAreaUpdateCounter; void geo_process_node_and_siblings(struct GraphNode *firstNode); void geo_process_root(struct GraphNodeRoot *node, Vp *b, Vp *c, s32 clearColor); +void interpolate_vectors(Vec3f res, Vec3f a, Vec3f b); +void interpolate_vectors_s16(Vec3s res, Vec3s a, Vec3s b); #endif // RENDERING_GRAPH_NODE_H diff --git a/src/menu/intro_geo.c b/src/menu/intro_geo.c index 4fb608d5..55aacdb9 100644 --- a/src/menu/intro_geo.c +++ b/src/menu/intro_geo.c @@ -12,6 +12,7 @@ #include "game/game_init.h" #include "audio/external.h" #include "gfx_dimensions.h" +#include "game/rendering_graph_node.h" // frame counts for the zoom in, hold, and zoom out of title model #define INTRO_STEPS_ZOOM_IN 20 diff --git a/src/port/Game.cpp b/src/port/Game.cpp index 4c0efdd3..db4fd50a 100644 --- a/src/port/Game.cpp +++ b/src/port/Game.cpp @@ -9,13 +9,15 @@ extern "C" { #include "sm64.h" } +float gInterpolationStep = 0.0f; + void alloc_pool() { static u64 pool[1024 * 1024 * 4]; main_pool_init(pool, pool + sizeof(pool) / sizeof(pool[0])); gEffectsMemoryPool = mem_pool_init(0x4000, MEMORY_POOL_LEFT); } -void patch_interpolations(void) { +void patch_interpolations() { mtx_patch_interpolated(); patch_screen_transition_interpolated(); patch_title_screen_scales(); @@ -26,24 +28,40 @@ void patch_interpolations(void) { patch_interpolated_snow_particles(); } -#define ROUND_30(x) floor(x / 30) * 30 - extern "C" void exec_display_list(SPTask *spTask) { - uint32_t targetFPS = GameEngine::GetInterpolationFPS(); - GameEngine::Instance->context->GetWindow()->SetTargetFps(targetFPS); + int target_fps = GameEngine::GetInterpolationFPS(); + static int last_fps; + static int time; + int fps = target_fps; + int original_fps = 30; + + if (target_fps == 30 || original_fps > target_fps) { + fps = original_fps; + } + + if (last_fps != fps) { + time = 0; + } + + int next_original_frame = fps; + while (time + original_fps <= next_original_frame) { + time += original_fps; + if (time != next_original_frame) { + gInterpolationStep = (float)time / next_original_frame; + } + GameEngine::RunCommands((Gfx *) spTask->task.t.data_ptr); + patch_interpolations(); + } + + time -= fps; + + GameEngine::Instance->context->GetWindow()->SetTargetFps(fps); int threshold = CVarGetInteger("gExtraLatencyThreshold", 80); - GameEngine::Instance->context->GetWindow()->SetMaximumFrameLatency(threshold > 0 && targetFPS >= threshold ? 2 : 1); + GameEngine::Instance->context->GetWindow()->SetMaximumFrameLatency(threshold > 0 && target_fps >= threshold ? 2 : 1); - GameEngine::RunCommands((Gfx*) spTask->task.t.data_ptr); - - if (targetFPS > 30) { - for (size_t i = 0; i < (ROUND_30(targetFPS) - 30) / 30; i++) { - patch_interpolations(); - GameEngine::RunCommands((Gfx *) spTask->task.t.data_ptr); - } - } + last_fps = fps; } void push_frame() { From 8d924c58f28e73561d83d8404baee17d5f58b31c Mon Sep 17 00:00:00 2001 From: KiritoDv Date: Mon, 9 Oct 2023 22:47:18 -0600 Subject: [PATCH 7/7] Removed audio table files to allow multiversion support --- include/banks_table.h | 129 ---- include/samples_table.h | 717 --------------------- include/sequences_table.h | 119 ---- src/audio/internal.h | 3 +- src/audio/load.c | 5 +- src/audio/seqplayer.c | 3 +- src/port/Engine.cpp | 58 +- src/port/Engine.h | 7 +- src/port/importer/AudioBankFactory.cpp | 4 +- src/port/importer/AudioSequenceFactory.cpp | 7 +- src/port/importer/types/AudioBank.h | 2 +- src/port/importer/types/AudioSequence.h | 1 + 12 files changed, 66 insertions(+), 989 deletions(-) delete mode 100644 include/banks_table.h delete mode 100644 include/samples_table.h delete mode 100644 include/sequences_table.h diff --git a/include/banks_table.h b/include/banks_table.h deleted file mode 100644 index b1a3bad5..00000000 --- a/include/banks_table.h +++ /dev/null @@ -1,129 +0,0 @@ -#pragma once -#include "align_asset_macro.h" - -#define dgBank00 "__OTR__sound/banks/00_bank" -static const ALIGN_ASSET(2) char gBank00[] = dgBank00; - -#define dgBank01 "__OTR__sound/banks/01_bank" -static const ALIGN_ASSET(2) char gBank01[] = dgBank01; - -#define dgBank02 "__OTR__sound/banks/02_bank" -static const ALIGN_ASSET(2) char gBank02[] = dgBank02; - -#define dgBank03 "__OTR__sound/banks/03_bank" -static const ALIGN_ASSET(2) char gBank03[] = dgBank03; - -#define dgBank04 "__OTR__sound/banks/04_bank" -static const ALIGN_ASSET(2) char gBank04[] = dgBank04; - -#define dgBank05 "__OTR__sound/banks/05_bank" -static const ALIGN_ASSET(2) char gBank05[] = dgBank05; - -#define dgBank06 "__OTR__sound/banks/06_bank" -static const ALIGN_ASSET(2) char gBank06[] = dgBank06; - -#define dgBank07 "__OTR__sound/banks/07_bank" -static const ALIGN_ASSET(2) char gBank07[] = dgBank07; - -#define dgBank08 "__OTR__sound/banks/08_bank" -static const ALIGN_ASSET(2) char gBank08[] = dgBank08; - -#define dgBank09 "__OTR__sound/banks/09_bank" -static const ALIGN_ASSET(2) char gBank09[] = dgBank09; - -#define dgBank0A "__OTR__sound/banks/0A_bank" -static const ALIGN_ASSET(2) char gBank0A[] = dgBank0A; - -#define dgBank0B "__OTR__sound/banks/0B_bank" -static const ALIGN_ASSET(2) char gBank0B[] = dgBank0B; - -#define dgBank0C "__OTR__sound/banks/0C_bank" -static const ALIGN_ASSET(2) char gBank0C[] = dgBank0C; - -#define dgBank0D "__OTR__sound/banks/0D_bank" -static const ALIGN_ASSET(2) char gBank0D[] = dgBank0D; - -#define dgBank0E "__OTR__sound/banks/0E_bank" -static const ALIGN_ASSET(2) char gBank0E[] = dgBank0E; - -#define dgBank0F "__OTR__sound/banks/0F_bank" -static const ALIGN_ASSET(2) char gBank0F[] = dgBank0F; - -#define dgBank10 "__OTR__sound/banks/10_bank" -static const ALIGN_ASSET(2) char gBank10[] = dgBank10; - -#define dgBank11 "__OTR__sound/banks/11_bank" -static const ALIGN_ASSET(2) char gBank11[] = dgBank11; - -#define dgBank12 "__OTR__sound/banks/12_bank" -static const ALIGN_ASSET(2) char gBank12[] = dgBank12; - -#define dgBank13 "__OTR__sound/banks/13_bank" -static const ALIGN_ASSET(2) char gBank13[] = dgBank13; - -#define dgBank14 "__OTR__sound/banks/14_bank" -static const ALIGN_ASSET(2) char gBank14[] = dgBank14; - -#define dgBank15 "__OTR__sound/banks/15_bank" -static const ALIGN_ASSET(2) char gBank15[] = dgBank15; - -#define dgBank16 "__OTR__sound/banks/16_bank" -static const ALIGN_ASSET(2) char gBank16[] = dgBank16; - -#define dgBank17 "__OTR__sound/banks/17_bank" -static const ALIGN_ASSET(2) char gBank17[] = dgBank17; - -#define dgBank18 "__OTR__sound/banks/18_bank" -static const ALIGN_ASSET(2) char gBank18[] = dgBank18; - -#define dgBank19 "__OTR__sound/banks/19_bank" -static const ALIGN_ASSET(2) char gBank19[] = dgBank19; - -#define dgBank1A "__OTR__sound/banks/1A_bank" -static const ALIGN_ASSET(2) char gBank1A[] = dgBank1A; - -#define dgBank1B "__OTR__sound/banks/1B_bank" -static const ALIGN_ASSET(2) char gBank1B[] = dgBank1B; - -#define dgBank1C "__OTR__sound/banks/1C_bank" -static const ALIGN_ASSET(2) char gBank1C[] = dgBank1C; - -#define dgBank1D "__OTR__sound/banks/1D_bank" -static const ALIGN_ASSET(2) char gBank1D[] = dgBank1D; - -#define dgBank1E "__OTR__sound/banks/1E_bank" -static const ALIGN_ASSET(2) char gBank1E[] = dgBank1E; - -#define dgBank1F "__OTR__sound/banks/1F_bank" -static const ALIGN_ASSET(2) char gBank1F[] = dgBank1F; - -#define dgBank20 "__OTR__sound/banks/20_bank" -static const ALIGN_ASSET(2) char gBank20[] = dgBank20; - -#define dgBank21 "__OTR__sound/banks/21_bank" -static const ALIGN_ASSET(2) char gBank21[] = dgBank21; - -#define dgBank22 "__OTR__sound/banks/22_bank" -static const ALIGN_ASSET(2) char gBank22[] = dgBank22; - -#define dgBank23 "__OTR__sound/banks/23_bank" -static const ALIGN_ASSET(2) char gBank23[] = dgBank23; - -#define dgBank24 "__OTR__sound/banks/24_bank" -static const ALIGN_ASSET(2) char gBank24[] = dgBank24; - -#define dgBank25 "__OTR__sound/banks/25_bank" -static const ALIGN_ASSET(2) char gBank25[] = dgBank25; - -static const char* gBankTable[] = { - gBank00, gBank01, gBank02, gBank03, - gBank04, gBank05, gBank06, gBank07, - gBank08, gBank09, gBank0A, gBank0B, - gBank0C, gBank0D, gBank0E, gBank0F, - gBank10, gBank11, gBank12, gBank13, - gBank14, gBank15, gBank16, gBank17, - gBank18, gBank19, gBank1A, gBank1B, - gBank1C, gBank1D, gBank1E, gBank1F, - gBank20, gBank21, gBank22, gBank23, - gBank24, gBank25, -}; diff --git a/include/samples_table.h b/include/samples_table.h deleted file mode 100644 index 5f1a3aeb..00000000 --- a/include/samples_table.h +++ /dev/null @@ -1,717 +0,0 @@ -#pragma once -#include "align_asset_macro.h" - -#define dgSample00 "__OTR__sound/samples/sfx_1/00_twirl" -static const ALIGN_ASSET(2) char gSample00[] = dgSample00; - -#define dgSample01 "__OTR__sound/samples/sfx_1/01_brushing" -static const ALIGN_ASSET(2) char gSample01[] = dgSample01; - -#define dgSample02 "__OTR__sound/samples/sfx_1/02_hand_touch" -static const ALIGN_ASSET(2) char gSample02[] = dgSample02; - -#define dgSample03 "__OTR__sound/samples/sfx_1/03_yoshi" -static const ALIGN_ASSET(2) char gSample03[] = dgSample03; - -#define dgSample04 "__OTR__sound/samples/sfx_1/04_plop" -static const ALIGN_ASSET(2) char gSample04[] = dgSample04; - -#define dgSample05 "__OTR__sound/samples/sfx_1/05_heavy_landing" -static const ALIGN_ASSET(2) char gSample05[] = dgSample05; - -#define dgSample06 "__OTR__sound/samples/sfx_terrain/00_step_default" -static const ALIGN_ASSET(2) char gSample06[] = dgSample06; - -#define dgSample07 "__OTR__sound/samples/sfx_terrain/01_step_grass" -static const ALIGN_ASSET(2) char gSample07[] = dgSample07; - -#define dgSample08 "__OTR__sound/samples/sfx_terrain/02_step_stone" -static const ALIGN_ASSET(2) char gSample08[] = dgSample08; - -#define dgSample09 "__OTR__sound/samples/sfx_terrain/03_step_spooky" -static const ALIGN_ASSET(2) char gSample09[] = dgSample09; - -#define dgSample0A "__OTR__sound/samples/sfx_terrain/04_step_snow" -static const ALIGN_ASSET(2) char gSample0A[] = dgSample0A; - -#define dgSample0B "__OTR__sound/samples/sfx_terrain/05_step_ice" -static const ALIGN_ASSET(2) char gSample0B[] = dgSample0B; - -#define dgSample0C "__OTR__sound/samples/sfx_terrain/06_step_metal" -static const ALIGN_ASSET(2) char gSample0C[] = dgSample0C; - -#define dgSample0D "__OTR__sound/samples/sfx_terrain/07_step_sand" -static const ALIGN_ASSET(2) char gSample0D[] = dgSample0D; - -#define dgSample0E "__OTR__sound/samples/sfx_water/00_plunge" -static const ALIGN_ASSET(2) char gSample0E[] = dgSample0E; - -#define dgSample0F "__OTR__sound/samples/sfx_water/01_splash" -static const ALIGN_ASSET(2) char gSample0F[] = dgSample0F; - -#define dgSample10 "__OTR__sound/samples/sfx_water/02_swim" -static const ALIGN_ASSET(2) char gSample10[] = dgSample10; - -#define dgSample11 "__OTR__sound/samples/sfx_4/00" -static const ALIGN_ASSET(2) char gSample11[] = dgSample11; - -#define dgSample12 "__OTR__sound/samples/sfx_4/01" -static const ALIGN_ASSET(2) char gSample12[] = dgSample12; - -#define dgSample13 "__OTR__sound/samples/sfx_4/02" -static const ALIGN_ASSET(2) char gSample13[] = dgSample13; - -#define dgSample14 "__OTR__sound/samples/sfx_4/03" -static const ALIGN_ASSET(2) char gSample14[] = dgSample14; - -#define dgSample15 "__OTR__sound/samples/sfx_4/04" -static const ALIGN_ASSET(2) char gSample15[] = dgSample15; - -#define dgSample16 "__OTR__sound/samples/sfx_4/05" -static const ALIGN_ASSET(2) char gSample16[] = dgSample16; - -#define dgSample17 "__OTR__sound/samples/sfx_4/06" -static const ALIGN_ASSET(2) char gSample17[] = dgSample17; - -#define dgSample18 "__OTR__sound/samples/sfx_4/07" -static const ALIGN_ASSET(2) char gSample18[] = dgSample18; - -#define dgSample19 "__OTR__sound/samples/sfx_4/08" -static const ALIGN_ASSET(2) char gSample19[] = dgSample19; - -#define dgSample1A "__OTR__sound/samples/sfx_4/09" -static const ALIGN_ASSET(2) char gSample1A[] = dgSample1A; - -#define dgSample1B "__OTR__sound/samples/sfx_5/00" -static const ALIGN_ASSET(2) char gSample1B[] = dgSample1B; - -#define dgSample1C "__OTR__sound/samples/sfx_5/01" -static const ALIGN_ASSET(2) char gSample1C[] = dgSample1C; - -#define dgSample1D "__OTR__sound/samples/sfx_5/02" -static const ALIGN_ASSET(2) char gSample1D[] = dgSample1D; - -#define dgSample1E "__OTR__sound/samples/sfx_5/03" -static const ALIGN_ASSET(2) char gSample1E[] = dgSample1E; - -#define dgSample1F "__OTR__sound/samples/sfx_5/04" -static const ALIGN_ASSET(2) char gSample1F[] = dgSample1F; - -#define dgSample20 "__OTR__sound/samples/sfx_5/05" -static const ALIGN_ASSET(2) char gSample20[] = dgSample20; - -#define dgSample21 "__OTR__sound/samples/sfx_5/06" -static const ALIGN_ASSET(2) char gSample21[] = dgSample21; - -#define dgSample22 "__OTR__sound/samples/sfx_5/07" -static const ALIGN_ASSET(2) char gSample22[] = dgSample22; - -#define dgSample23 "__OTR__sound/samples/sfx_5/08" -static const ALIGN_ASSET(2) char gSample23[] = dgSample23; - -#define dgSample24 "__OTR__sound/samples/sfx_5/09" -static const ALIGN_ASSET(2) char gSample24[] = dgSample24; - -#define dgSample25 "__OTR__sound/samples/sfx_5/0A" -static const ALIGN_ASSET(2) char gSample25[] = dgSample25; - -#define dgSample26 "__OTR__sound/samples/sfx_5/0B" -static const ALIGN_ASSET(2) char gSample26[] = dgSample26; - -#define dgSample27 "__OTR__sound/samples/sfx_5/0C" -static const ALIGN_ASSET(2) char gSample27[] = dgSample27; - -#define dgSample28 "__OTR__sound/samples/sfx_5/0D" -static const ALIGN_ASSET(2) char gSample28[] = dgSample28; - -#define dgSample29 "__OTR__sound/samples/sfx_5/0E" -static const ALIGN_ASSET(2) char gSample29[] = dgSample29; - -#define dgSample2A "__OTR__sound/samples/sfx_5/0F" -static const ALIGN_ASSET(2) char gSample2A[] = dgSample2A; - -#define dgSample2B "__OTR__sound/samples/sfx_5/10" -static const ALIGN_ASSET(2) char gSample2B[] = dgSample2B; - -#define dgSample2C "__OTR__sound/samples/sfx_5/11" -static const ALIGN_ASSET(2) char gSample2C[] = dgSample2C; - -#define dgSample2D "__OTR__sound/samples/sfx_5/12" -static const ALIGN_ASSET(2) char gSample2D[] = dgSample2D; - -#define dgSample2E "__OTR__sound/samples/sfx_5/13" -static const ALIGN_ASSET(2) char gSample2E[] = dgSample2E; - -#define dgSample2F "__OTR__sound/samples/sfx_5/14" -static const ALIGN_ASSET(2) char gSample2F[] = dgSample2F; - -#define dgSample30 "__OTR__sound/samples/sfx_5/15" -static const ALIGN_ASSET(2) char gSample30[] = dgSample30; - -#define dgSample31 "__OTR__sound/samples/sfx_5/16" -static const ALIGN_ASSET(2) char gSample31[] = dgSample31; - -#define dgSample32 "__OTR__sound/samples/sfx_5/17" -static const ALIGN_ASSET(2) char gSample32[] = dgSample32; - -#define dgSample33 "__OTR__sound/samples/sfx_5/18" -static const ALIGN_ASSET(2) char gSample33[] = dgSample33; - -#define dgSample34 "__OTR__sound/samples/sfx_5/19" -static const ALIGN_ASSET(2) char gSample34[] = dgSample34; - -#define dgSample35 "__OTR__sound/samples/sfx_5/1A" -static const ALIGN_ASSET(2) char gSample35[] = dgSample35; - -#define dgSample36 "__OTR__sound/samples/sfx_5/1B" -static const ALIGN_ASSET(2) char gSample36[] = dgSample36; - -#define dgSample37 "__OTR__sound/samples/sfx_5/1C" -static const ALIGN_ASSET(2) char gSample37[] = dgSample37; - -#define dgSample38 "__OTR__sound/samples/sfx_6/00" -static const ALIGN_ASSET(2) char gSample38[] = dgSample38; - -#define dgSample39 "__OTR__sound/samples/sfx_6/01" -static const ALIGN_ASSET(2) char gSample39[] = dgSample39; - -#define dgSample3A "__OTR__sound/samples/sfx_6/02" -static const ALIGN_ASSET(2) char gSample3A[] = dgSample3A; - -#define dgSample3B "__OTR__sound/samples/sfx_6/03" -static const ALIGN_ASSET(2) char gSample3B[] = dgSample3B; - -#define dgSample3C "__OTR__sound/samples/sfx_6/04" -static const ALIGN_ASSET(2) char gSample3C[] = dgSample3C; - -#define dgSample3D "__OTR__sound/samples/sfx_6/05" -static const ALIGN_ASSET(2) char gSample3D[] = dgSample3D; - -#define dgSample3E "__OTR__sound/samples/sfx_6/06" -static const ALIGN_ASSET(2) char gSample3E[] = dgSample3E; - -#define dgSample3F "__OTR__sound/samples/sfx_6/07" -static const ALIGN_ASSET(2) char gSample3F[] = dgSample3F; - -#define dgSample40 "__OTR__sound/samples/sfx_6/08" -static const ALIGN_ASSET(2) char gSample40[] = dgSample40; - -#define dgSample41 "__OTR__sound/samples/sfx_6/09" -static const ALIGN_ASSET(2) char gSample41[] = dgSample41; - -#define dgSample42 "__OTR__sound/samples/sfx_6/0A" -static const ALIGN_ASSET(2) char gSample42[] = dgSample42; - -#define dgSample43 "__OTR__sound/samples/sfx_6/0B" -static const ALIGN_ASSET(2) char gSample43[] = dgSample43; - -#define dgSample44 "__OTR__sound/samples/sfx_6/0C" -static const ALIGN_ASSET(2) char gSample44[] = dgSample44; - -#define dgSample45 "__OTR__sound/samples/sfx_6/0D" -static const ALIGN_ASSET(2) char gSample45[] = dgSample45; - -#define dgSample46 "__OTR__sound/samples/sfx_7/00" -static const ALIGN_ASSET(2) char gSample46[] = dgSample46; - -#define dgSample47 "__OTR__sound/samples/sfx_7/01" -static const ALIGN_ASSET(2) char gSample47[] = dgSample47; - -#define dgSample48 "__OTR__sound/samples/sfx_7/02" -static const ALIGN_ASSET(2) char gSample48[] = dgSample48; - -#define dgSample49 "__OTR__sound/samples/sfx_7/03" -static const ALIGN_ASSET(2) char gSample49[] = dgSample49; - -#define dgSample4A "__OTR__sound/samples/sfx_7/04" -static const ALIGN_ASSET(2) char gSample4A[] = dgSample4A; - -#define dgSample4B "__OTR__sound/samples/sfx_7/05" -static const ALIGN_ASSET(2) char gSample4B[] = dgSample4B; - -#define dgSample4C "__OTR__sound/samples/sfx_7/06" -static const ALIGN_ASSET(2) char gSample4C[] = dgSample4C; - -#define dgSample4D "__OTR__sound/samples/sfx_7/07" -static const ALIGN_ASSET(2) char gSample4D[] = dgSample4D; - -#define dgSample4E "__OTR__sound/samples/sfx_7/08" -static const ALIGN_ASSET(2) char gSample4E[] = dgSample4E; - -#define dgSample4F "__OTR__sound/samples/sfx_7/09" -static const ALIGN_ASSET(2) char gSample4F[] = dgSample4F; - -#define dgSample50 "__OTR__sound/samples/sfx_7/0A" -static const ALIGN_ASSET(2) char gSample50[] = dgSample50; - -#define dgSample51 "__OTR__sound/samples/sfx_7/0B" -static const ALIGN_ASSET(2) char gSample51[] = dgSample51; - -#define dgSample52 "__OTR__sound/samples/sfx_7/0C" -static const ALIGN_ASSET(2) char gSample52[] = dgSample52; - -#define dgSample53 "__OTR__sound/samples/sfx_7/0D_chain_chomp_bark" -static const ALIGN_ASSET(2) char gSample53[] = dgSample53; - -#define dgSample54 "__OTR__sound/samples/sfx_mario/00_mario_jump_hoo" -static const ALIGN_ASSET(2) char gSample54[] = dgSample54; - -#define dgSample55 "__OTR__sound/samples/sfx_mario/01_mario_jump_wah" -static const ALIGN_ASSET(2) char gSample55[] = dgSample55; - -#define dgSample56 "__OTR__sound/samples/sfx_mario/02_mario_yah" -static const ALIGN_ASSET(2) char gSample56[] = dgSample56; - -#define dgSample57 "__OTR__sound/samples/sfx_mario/03_mario_haha" -static const ALIGN_ASSET(2) char gSample57[] = dgSample57; - -#define dgSample58 "__OTR__sound/samples/sfx_mario/04_mario_yahoo" -static const ALIGN_ASSET(2) char gSample58[] = dgSample58; - -#define dgSample59 "__OTR__sound/samples/sfx_mario/05_mario_uh" -static const ALIGN_ASSET(2) char gSample59[] = dgSample59; - -#define dgSample5A "__OTR__sound/samples/sfx_mario/06_mario_hrmm" -static const ALIGN_ASSET(2) char gSample5A[] = dgSample5A; - -#define dgSample5B "__OTR__sound/samples/sfx_mario/07_mario_wah2" -static const ALIGN_ASSET(2) char gSample5B[] = dgSample5B; - -#define dgSample5C "__OTR__sound/samples/sfx_mario/08_mario_whoa" -static const ALIGN_ASSET(2) char gSample5C[] = dgSample5C; - -#define dgSample5D "__OTR__sound/samples/sfx_mario/09_mario_eeuh" -static const ALIGN_ASSET(2) char gSample5D[] = dgSample5D; - -#define dgSample5E "__OTR__sound/samples/sfx_mario/0A_mario_attacked" -static const ALIGN_ASSET(2) char gSample5E[] = dgSample5E; - -#define dgSample5F "__OTR__sound/samples/sfx_mario/0B_mario_ooof" -static const ALIGN_ASSET(2) char gSample5F[] = dgSample5F; - -#define dgSample60 "__OTR__sound/samples/sfx_mario/0C_mario_here_we_go" -static const ALIGN_ASSET(2) char gSample60[] = dgSample60; - -#define dgSample61 "__OTR__sound/samples/sfx_mario/0D_mario_yawning" -static const ALIGN_ASSET(2) char gSample61[] = dgSample61; - -#define dgSample62 "__OTR__sound/samples/sfx_mario/0E_mario_snoring1" -static const ALIGN_ASSET(2) char gSample62[] = dgSample62; - -#define dgSample63 "__OTR__sound/samples/sfx_mario/0F_mario_snoring2" -static const ALIGN_ASSET(2) char gSample63[] = dgSample63; - -#define dgSample64 "__OTR__sound/samples/sfx_mario/10_mario_doh" -static const ALIGN_ASSET(2) char gSample64[] = dgSample64; - -#define dgSample65 "__OTR__sound/samples/sfx_mario/11_mario_game_over" -static const ALIGN_ASSET(2) char gSample65[] = dgSample65; - -#define dgSample66 "__OTR__sound/samples/sfx_mario/12_mario_hello" -static const ALIGN_ASSET(2) char gSample66[] = dgSample66; - -#define dgSample67 "__OTR__sound/samples/sfx_mario/13_mario_press_start_to_play" -static const ALIGN_ASSET(2) char gSample67[] = dgSample67; - -#define dgSample68 "__OTR__sound/samples/sfx_mario/14_mario_twirl_bounce" -static const ALIGN_ASSET(2) char gSample68[] = dgSample68; - -#define dgSample69 "__OTR__sound/samples/sfx_mario/15_mario_snoring3" -static const ALIGN_ASSET(2) char gSample69[] = dgSample69; - -#define dgSample6A "__OTR__sound/samples/sfx_mario/16_mario_so_longa_bowser" -static const ALIGN_ASSET(2) char gSample6A[] = dgSample6A; - -#define dgSample6B "__OTR__sound/samples/sfx_mario/17_mario_ima_tired" -static const ALIGN_ASSET(2) char gSample6B[] = dgSample6B; - -#define dgSample6C "__OTR__sound/samples/sfx_mario/18_mario_waha" -static const ALIGN_ASSET(2) char gSample6C[] = dgSample6C; - -#define dgSample6D "__OTR__sound/samples/sfx_mario/19_mario_yippee" -static const ALIGN_ASSET(2) char gSample6D[] = dgSample6D; - -#define dgSample6E "__OTR__sound/samples/sfx_mario/1A_mario_lets_a_go" -static const ALIGN_ASSET(2) char gSample6E[] = dgSample6E; - -#define dgSample6F "__OTR__sound/samples/sfx_9/00" -static const ALIGN_ASSET(2) char gSample6F[] = dgSample6F; - -#define dgSample70 "__OTR__sound/samples/sfx_9/01" -static const ALIGN_ASSET(2) char gSample70[] = dgSample70; - -#define dgSample71 "__OTR__sound/samples/sfx_9/02" -static const ALIGN_ASSET(2) char gSample71[] = dgSample71; - -#define dgSample72 "__OTR__sound/samples/sfx_9/03" -static const ALIGN_ASSET(2) char gSample72[] = dgSample72; - -#define dgSample73 "__OTR__sound/samples/sfx_9/04_camera_buzz" -static const ALIGN_ASSET(2) char gSample73[] = dgSample73; - -#define dgSample74 "__OTR__sound/samples/sfx_9/05_camera_shutter" -static const ALIGN_ASSET(2) char gSample74[] = dgSample74; - -#define dgSample75 "__OTR__sound/samples/sfx_9/06" -static const ALIGN_ASSET(2) char gSample75[] = dgSample75; - -#define dgSample76 "__OTR__sound/samples/sfx_mario_peach/00_mario_waaaooow" -static const ALIGN_ASSET(2) char gSample76[] = dgSample76; - -#define dgSample77 "__OTR__sound/samples/sfx_mario_peach/01_mario_hoohoo" -static const ALIGN_ASSET(2) char gSample77[] = dgSample77; - -#define dgSample78 "__OTR__sound/samples/sfx_mario_peach/02_mario_panting" -static const ALIGN_ASSET(2) char gSample78[] = dgSample78; - -#define dgSample79 "__OTR__sound/samples/sfx_mario_peach/03_mario_dying" -static const ALIGN_ASSET(2) char gSample79[] = dgSample79; - -#define dgSample7A "__OTR__sound/samples/sfx_mario_peach/04_mario_on_fire" -static const ALIGN_ASSET(2) char gSample7A[] = dgSample7A; - -#define dgSample7B "__OTR__sound/samples/sfx_mario_peach/05_mario_uh2" -static const ALIGN_ASSET(2) char gSample7B[] = dgSample7B; - -#define dgSample7C "__OTR__sound/samples/sfx_mario_peach/06_mario_coughing" -static const ALIGN_ASSET(2) char gSample7C[] = dgSample7C; - -#define dgSample7D "__OTR__sound/samples/sfx_mario_peach/07_mario_its_a_me_mario" -static const ALIGN_ASSET(2) char gSample7D[] = dgSample7D; - -#define dgSample7E "__OTR__sound/samples/sfx_mario_peach/08_mario_punch_yah" -static const ALIGN_ASSET(2) char gSample7E[] = dgSample7E; - -#define dgSample7F "__OTR__sound/samples/sfx_mario_peach/09_mario_punch_hoo" -static const ALIGN_ASSET(2) char gSample7F[] = dgSample7F; - -#define dgSample80 "__OTR__sound/samples/sfx_mario_peach/0A_mario_mama_mia" -static const ALIGN_ASSET(2) char gSample80[] = dgSample80; - -#define dgSample81 "__OTR__sound/samples/sfx_mario_peach/0B_mario_okey_dokey" -static const ALIGN_ASSET(2) char gSample81[] = dgSample81; - -#define dgSample82 "__OTR__sound/samples/sfx_mario_peach/0C_mario_drowning" -static const ALIGN_ASSET(2) char gSample82[] = dgSample82; - -#define dgSample83 "__OTR__sound/samples/sfx_mario_peach/0D_mario_thank_you_playing_my_game" -static const ALIGN_ASSET(2) char gSample83[] = dgSample83; - -#define dgSample84 "__OTR__sound/samples/sfx_mario_peach/0E_peach_dear_mario" -static const ALIGN_ASSET(2) char gSample84[] = dgSample84; - -#define dgSample85 "__OTR__sound/samples/sfx_mario_peach/0F_peach_mario" -static const ALIGN_ASSET(2) char gSample85[] = dgSample85; - -#define dgSample86 "__OTR__sound/samples/sfx_mario_peach/10_peach_power_of_the_stars" -static const ALIGN_ASSET(2) char gSample86[] = dgSample86; - -#define dgSample87 "__OTR__sound/samples/sfx_mario_peach/11_peach_thanks_to_you" -static const ALIGN_ASSET(2) char gSample87[] = dgSample87; - -#define dgSample88 "__OTR__sound/samples/sfx_mario_peach/12_peach_thank_you_mario" -static const ALIGN_ASSET(2) char gSample88[] = dgSample88; - -#define dgSample89 "__OTR__sound/samples/sfx_mario_peach/13_peach_something_special" -static const ALIGN_ASSET(2) char gSample89[] = dgSample89; - -#define dgSample8A "__OTR__sound/samples/sfx_mario_peach/14_peach_bake_a_cake" -static const ALIGN_ASSET(2) char gSample8A[] = dgSample8A; - -#define dgSample8B "__OTR__sound/samples/sfx_mario_peach/15_peach_for_mario" -static const ALIGN_ASSET(2) char gSample8B[] = dgSample8B; - -#define dgSample8C "__OTR__sound/samples/sfx_mario_peach/16_peach_mario2" -static const ALIGN_ASSET(2) char gSample8C[] = dgSample8C; - -#define dgSample8D "__OTR__sound/samples/instruments/00" -static const ALIGN_ASSET(2) char gSample8D[] = dgSample8D; - -#define dgSample8E "__OTR__sound/samples/instruments/01_banjo_1" -static const ALIGN_ASSET(2) char gSample8E[] = dgSample8E; - -#define dgSample8F "__OTR__sound/samples/instruments/02" -static const ALIGN_ASSET(2) char gSample8F[] = dgSample8F; - -#define dgSample90 "__OTR__sound/samples/instruments/03_human_whistle" -static const ALIGN_ASSET(2) char gSample90[] = dgSample90; - -#define dgSample91 "__OTR__sound/samples/instruments/04_bright_piano" -static const ALIGN_ASSET(2) char gSample91[] = dgSample91; - -#define dgSample92 "__OTR__sound/samples/instruments/05_acoustic_bass" -static const ALIGN_ASSET(2) char gSample92[] = dgSample92; - -#define dgSample93 "__OTR__sound/samples/instruments/06_kick_drum_1" -static const ALIGN_ASSET(2) char gSample93[] = dgSample93; - -#define dgSample94 "__OTR__sound/samples/instruments/07_rimshot" -static const ALIGN_ASSET(2) char gSample94[] = dgSample94; - -#define dgSample95 "__OTR__sound/samples/instruments/08" -static const ALIGN_ASSET(2) char gSample95[] = dgSample95; - -#define dgSample96 "__OTR__sound/samples/instruments/09" -static const ALIGN_ASSET(2) char gSample96[] = dgSample96; - -#define dgSample97 "__OTR__sound/samples/instruments/0A_tambourine" -static const ALIGN_ASSET(2) char gSample97[] = dgSample97; - -#define dgSample98 "__OTR__sound/samples/instruments/0B" -static const ALIGN_ASSET(2) char gSample98[] = dgSample98; - -#define dgSample99 "__OTR__sound/samples/instruments/0C_conga_stick" -static const ALIGN_ASSET(2) char gSample99[] = dgSample99; - -#define dgSample9A "__OTR__sound/samples/instruments/0D_clave" -static const ALIGN_ASSET(2) char gSample9A[] = dgSample9A; - -#define dgSample9B "__OTR__sound/samples/instruments/0E_hihat_closed" -static const ALIGN_ASSET(2) char gSample9B[] = dgSample9B; - -#define dgSample9C "__OTR__sound/samples/instruments/0F_hihat_open" -static const ALIGN_ASSET(2) char gSample9C[] = dgSample9C; - -#define dgSample9D "__OTR__sound/samples/instruments/10_cymbal_bell" -static const ALIGN_ASSET(2) char gSample9D[] = dgSample9D; - -#define dgSample9E "__OTR__sound/samples/instruments/11_splash_cymbal" -static const ALIGN_ASSET(2) char gSample9E[] = dgSample9E; - -#define dgSample9F "__OTR__sound/samples/instruments/12_snare_drum_1" -static const ALIGN_ASSET(2) char gSample9F[] = dgSample9F; - -#define dgSampleA0 "__OTR__sound/samples/instruments/13_snare_drum_2" -static const ALIGN_ASSET(2) char gSampleA0[] = dgSampleA0; - -#define dgSampleA1 "__OTR__sound/samples/instruments/14_strings_5" -static const ALIGN_ASSET(2) char gSampleA1[] = dgSampleA1; - -#define dgSampleA2 "__OTR__sound/samples/instruments/15_strings_4" -static const ALIGN_ASSET(2) char gSampleA2[] = dgSampleA2; - -#define dgSampleA3 "__OTR__sound/samples/instruments/16_french_horns" -static const ALIGN_ASSET(2) char gSampleA3[] = dgSampleA3; - -#define dgSampleA4 "__OTR__sound/samples/instruments/17_trumpet" -static const ALIGN_ASSET(2) char gSampleA4[] = dgSampleA4; - -#define dgSampleA5 "__OTR__sound/samples/instruments/18_timpani" -static const ALIGN_ASSET(2) char gSampleA5[] = dgSampleA5; - -#define dgSampleA6 "__OTR__sound/samples/instruments/19_brass" -static const ALIGN_ASSET(2) char gSampleA6[] = dgSampleA6; - -#define dgSampleA7 "__OTR__sound/samples/instruments/1A_slap_bass" -static const ALIGN_ASSET(2) char gSampleA7[] = dgSampleA7; - -#define dgSampleA8 "__OTR__sound/samples/instruments/1B_organ_2" -static const ALIGN_ASSET(2) char gSampleA8[] = dgSampleA8; - -#define dgSampleA9 "__OTR__sound/samples/instruments/1C" -static const ALIGN_ASSET(2) char gSampleA9[] = dgSampleA9; - -#define dgSampleAA "__OTR__sound/samples/instruments/1D" -static const ALIGN_ASSET(2) char gSampleAA[] = dgSampleAA; - -#define dgSampleAB "__OTR__sound/samples/instruments/1E_closed_triangle" -static const ALIGN_ASSET(2) char gSampleAB[] = dgSampleAB; - -#define dgSampleAC "__OTR__sound/samples/instruments/1F_open_triangle" -static const ALIGN_ASSET(2) char gSampleAC[] = dgSampleAC; - -#define dgSampleAD "__OTR__sound/samples/instruments/20_cabasa" -static const ALIGN_ASSET(2) char gSampleAD[] = dgSampleAD; - -#define dgSampleAE "__OTR__sound/samples/instruments/21_sine_bass" -static const ALIGN_ASSET(2) char gSampleAE[] = dgSampleAE; - -#define dgSampleAF "__OTR__sound/samples/instruments/22_boys_choir" -static const ALIGN_ASSET(2) char gSampleAF[] = dgSampleAF; - -#define dgSampleB0 "__OTR__sound/samples/instruments/23_strings_1" -static const ALIGN_ASSET(2) char gSampleB0[] = dgSampleB0; - -#define dgSampleB1 "__OTR__sound/samples/instruments/24_strings_2" -static const ALIGN_ASSET(2) char gSampleB1[] = dgSampleB1; - -#define dgSampleB2 "__OTR__sound/samples/instruments/25_strings_3" -static const ALIGN_ASSET(2) char gSampleB2[] = dgSampleB2; - -#define dgSampleB3 "__OTR__sound/samples/instruments/26_crystal_rhodes" -static const ALIGN_ASSET(2) char gSampleB3[] = dgSampleB3; - -#define dgSampleB4 "__OTR__sound/samples/instruments/27_harpsichord" -static const ALIGN_ASSET(2) char gSampleB4[] = dgSampleB4; - -#define dgSampleB5 "__OTR__sound/samples/instruments/28_sitar_1" -static const ALIGN_ASSET(2) char gSampleB5[] = dgSampleB5; - -#define dgSampleB6 "__OTR__sound/samples/instruments/29_orchestra_hit" -static const ALIGN_ASSET(2) char gSampleB6[] = dgSampleB6; - -#define dgSampleB7 "__OTR__sound/samples/instruments/2A" -static const ALIGN_ASSET(2) char gSampleB7[] = dgSampleB7; - -#define dgSampleB8 "__OTR__sound/samples/instruments/2B" -static const ALIGN_ASSET(2) char gSampleB8[] = dgSampleB8; - -#define dgSampleB9 "__OTR__sound/samples/instruments/2C" -static const ALIGN_ASSET(2) char gSampleB9[] = dgSampleB9; - -#define dgSampleBA "__OTR__sound/samples/instruments/2D_trombone" -static const ALIGN_ASSET(2) char gSampleBA[] = dgSampleBA; - -#define dgSampleBB "__OTR__sound/samples/instruments/2E_accordion" -static const ALIGN_ASSET(2) char gSampleBB[] = dgSampleBB; - -#define dgSampleBC "__OTR__sound/samples/instruments/2F_sleigh_bells" -static const ALIGN_ASSET(2) char gSampleBC[] = dgSampleBC; - -#define dgSampleBD "__OTR__sound/samples/instruments/30_rarefaction-lahna" -static const ALIGN_ASSET(2) char gSampleBD[] = dgSampleBD; - -#define dgSampleBE "__OTR__sound/samples/instruments/31_rarefaction-convolution" -static const ALIGN_ASSET(2) char gSampleBE[] = dgSampleBE; - -#define dgSampleBF "__OTR__sound/samples/instruments/32_metal_rimshot" -static const ALIGN_ASSET(2) char gSampleBF[] = dgSampleBF; - -#define dgSampleC0 "__OTR__sound/samples/instruments/33_kick_drum_2" -static const ALIGN_ASSET(2) char gSampleC0[] = dgSampleC0; - -#define dgSampleC1 "__OTR__sound/samples/instruments/34_alto_flute" -static const ALIGN_ASSET(2) char gSampleC1[] = dgSampleC1; - -#define dgSampleC2 "__OTR__sound/samples/instruments/35_gospel_organ" -static const ALIGN_ASSET(2) char gSampleC2[] = dgSampleC2; - -#define dgSampleC3 "__OTR__sound/samples/instruments/36_sawtooth_synth" -static const ALIGN_ASSET(2) char gSampleC3[] = dgSampleC3; - -#define dgSampleC4 "__OTR__sound/samples/instruments/37_square_synth" -static const ALIGN_ASSET(2) char gSampleC4[] = dgSampleC4; - -#define dgSampleC5 "__OTR__sound/samples/instruments/38_electric_kick_drum" -static const ALIGN_ASSET(2) char gSampleC5[] = dgSampleC5; - -#define dgSampleC6 "__OTR__sound/samples/instruments/39_sitar_2" -static const ALIGN_ASSET(2) char gSampleC6[] = dgSampleC6; - -#define dgSampleC7 "__OTR__sound/samples/instruments/3A_music_box" -static const ALIGN_ASSET(2) char gSampleC7[] = dgSampleC7; - -#define dgSampleC8 "__OTR__sound/samples/instruments/3B_banjo_2" -static const ALIGN_ASSET(2) char gSampleC8[] = dgSampleC8; - -#define dgSampleC9 "__OTR__sound/samples/instruments/3C_acoustic_guitar" -static const ALIGN_ASSET(2) char gSampleC9[] = dgSampleC9; - -#define dgSampleCA "__OTR__sound/samples/instruments/3D" -static const ALIGN_ASSET(2) char gSampleCA[] = dgSampleCA; - -#define dgSampleCB "__OTR__sound/samples/instruments/3E_monk_choir" -static const ALIGN_ASSET(2) char gSampleCB[] = dgSampleCB; - -#define dgSampleCC "__OTR__sound/samples/instruments/3F" -static const ALIGN_ASSET(2) char gSampleCC[] = dgSampleCC; - -#define dgSampleCD "__OTR__sound/samples/instruments/40_bell" -static const ALIGN_ASSET(2) char gSampleCD[] = dgSampleCD; - -#define dgSampleCE "__OTR__sound/samples/instruments/41_pan_flute" -static const ALIGN_ASSET(2) char gSampleCE[] = dgSampleCE; - -#define dgSampleCF "__OTR__sound/samples/instruments/42_vibraphone" -static const ALIGN_ASSET(2) char gSampleCF[] = dgSampleCF; - -#define dgSampleD0 "__OTR__sound/samples/instruments/43_harmonica" -static const ALIGN_ASSET(2) char gSampleD0[] = dgSampleD0; - -#define dgSampleD1 "__OTR__sound/samples/instruments/44_grand_piano" -static const ALIGN_ASSET(2) char gSampleD1[] = dgSampleD1; - -#define dgSampleD2 "__OTR__sound/samples/instruments/45_french_horns_lq" -static const ALIGN_ASSET(2) char gSampleD2[] = dgSampleD2; - -#define dgSampleD3 "__OTR__sound/samples/instruments/46_pizzicato_strings_1" -static const ALIGN_ASSET(2) char gSampleD3[] = dgSampleD3; - -#define dgSampleD4 "__OTR__sound/samples/instruments/47_pizzicato_strings_2" -static const ALIGN_ASSET(2) char gSampleD4[] = dgSampleD4; - -#define dgSampleD5 "__OTR__sound/samples/instruments/48_steel_drum" -static const ALIGN_ASSET(2) char gSampleD5[] = dgSampleD5; - -#define dgSampleD6 "__OTR__sound/samples/piranha_music_box/00_music_box" -static const ALIGN_ASSET(2) char gSampleD6[] = dgSampleD6; - -#define dgSampleD7 "__OTR__sound/samples/course_start/00_la" -static const ALIGN_ASSET(2) char gSampleD7[] = dgSampleD7; - -#define dgSampleD8 "__OTR__sound/samples/bowser_organ/00_organ_1" -static const ALIGN_ASSET(2) char gSampleD8[] = dgSampleD8; - -#define dgSampleD9 "__OTR__sound/samples/bowser_organ/01_organ_1_lq" -static const ALIGN_ASSET(2) char gSampleD9[] = dgSampleD9; - -#define dgSampleDA "__OTR__sound/samples/bowser_organ/02_boys_choir" -static const ALIGN_ASSET(2) char gSampleDA[] = dgSampleDA; - -static const char* gSampleTable[] = { - gSample00, gSample01, gSample02, gSample03, - gSample04, gSample05, gSample06, gSample07, - gSample08, gSample09, gSample0A, gSample0B, - gSample0C, gSample0D, gSample0E, gSample0F, - gSample10, gSample11, gSample12, gSample13, - gSample14, gSample15, gSample16, gSample17, - gSample18, gSample19, gSample1A, gSample1B, - gSample1C, gSample1D, gSample1E, gSample1F, - gSample20, gSample21, gSample22, gSample23, - gSample24, gSample25, gSample26, gSample27, - gSample28, gSample29, gSample2A, gSample2B, - gSample2C, gSample2D, gSample2E, gSample2F, - gSample30, gSample31, gSample32, gSample33, - gSample34, gSample35, gSample36, gSample37, - gSample38, gSample39, gSample3A, gSample3B, - gSample3C, gSample3D, gSample3E, gSample3F, - gSample40, gSample41, gSample42, gSample43, - gSample44, gSample45, gSample46, gSample47, - gSample48, gSample49, gSample4A, gSample4B, - gSample4C, gSample4D, gSample4E, gSample4F, - gSample50, gSample51, gSample52, gSample53, - gSample54, gSample55, gSample56, gSample57, - gSample58, gSample59, gSample5A, gSample5B, - gSample5C, gSample5D, gSample5E, gSample5F, - gSample60, gSample61, gSample62, gSample63, - gSample64, gSample65, gSample66, gSample67, - gSample68, gSample69, gSample6A, gSample6B, - gSample6C, gSample6D, gSample6E, gSample6F, - gSample70, gSample71, gSample72, gSample73, - gSample74, gSample75, gSample76, gSample77, - gSample78, gSample79, gSample7A, gSample7B, - gSample7C, gSample7D, gSample7E, gSample7F, - gSample80, gSample81, gSample82, gSample83, - gSample84, gSample85, gSample86, gSample87, - gSample88, gSample89, gSample8A, gSample8B, - gSample8C, gSample8D, gSample8E, gSample8F, - gSample90, gSample91, gSample92, gSample93, - gSample94, gSample95, gSample96, gSample97, - gSample98, gSample99, gSample9A, gSample9B, - gSample9C, gSample9D, gSample9E, gSample9F, - gSampleA0, gSampleA1, gSampleA2, gSampleA3, - gSampleA4, gSampleA5, gSampleA6, gSampleA7, - gSampleA8, gSampleA9, gSampleAA, gSampleAB, - gSampleAC, gSampleAD, gSampleAE, gSampleAF, - gSampleB0, gSampleB1, gSampleB2, gSampleB3, - gSampleB4, gSampleB5, gSampleB6, gSampleB7, - gSampleB8, gSampleB9, gSampleBA, gSampleBB, - gSampleBC, gSampleBD, gSampleBE, gSampleBF, - gSampleC0, gSampleC1, gSampleC2, gSampleC3, - gSampleC4, gSampleC5, gSampleC6, gSampleC7, - gSampleC8, gSampleC9, gSampleCA, gSampleCB, - gSampleCC, gSampleCD, gSampleCE, gSampleCF, - gSampleD0, gSampleD1, gSampleD2, gSampleD3, - gSampleD4, gSampleD5, gSampleD6, gSampleD7, - gSampleD8, gSampleD9, gSampleDA, -}; diff --git a/include/sequences_table.h b/include/sequences_table.h deleted file mode 100644 index 5572b8f1..00000000 --- a/include/sequences_table.h +++ /dev/null @@ -1,119 +0,0 @@ -#pragma once -#include "align_asset_macro.h" - -#define dgSequence00 "__OTR__sound/sequences/00_sound_player" -static const ALIGN_ASSET(2) char gSequence00[] = dgSequence00; - -#define dgSequence01 "__OTR__sound/sequences/01_cutscene_collect_star" -static const ALIGN_ASSET(2) char gSequence01[] = dgSequence01; - -#define dgSequence02 "__OTR__sound/sequences/02_menu_title_screen" -static const ALIGN_ASSET(2) char gSequence02[] = dgSequence02; - -#define dgSequence03 "__OTR__sound/sequences/03_level_grass" -static const ALIGN_ASSET(2) char gSequence03[] = dgSequence03; - -#define dgSequence04 "__OTR__sound/sequences/04_level_inside_castle" -static const ALIGN_ASSET(2) char gSequence04[] = dgSequence04; - -#define dgSequence05 "__OTR__sound/sequences/05_level_water" -static const ALIGN_ASSET(2) char gSequence05[] = dgSequence05; - -#define dgSequence06 "__OTR__sound/sequences/06_level_hot" -static const ALIGN_ASSET(2) char gSequence06[] = dgSequence06; - -#define dgSequence07 "__OTR__sound/sequences/07_level_boss_koopa" -static const ALIGN_ASSET(2) char gSequence07[] = dgSequence07; - -#define dgSequence08 "__OTR__sound/sequences/08_level_snow" -static const ALIGN_ASSET(2) char gSequence08[] = dgSequence08; - -#define dgSequence09 "__OTR__sound/sequences/09_level_slide" -static const ALIGN_ASSET(2) char gSequence09[] = dgSequence09; - -#define dgSequence0A "__OTR__sound/sequences/0A_level_spooky" -static const ALIGN_ASSET(2) char gSequence0A[] = dgSequence0A; - -#define dgSequence0B "__OTR__sound/sequences/0B_event_piranha_plant" -static const ALIGN_ASSET(2) char gSequence0B[] = dgSequence0B; - -#define dgSequence0C "__OTR__sound/sequences/0C_level_underground" -static const ALIGN_ASSET(2) char gSequence0C[] = dgSequence0C; - -#define dgSequence0D "__OTR__sound/sequences/0D_menu_star_select" -static const ALIGN_ASSET(2) char gSequence0D[] = dgSequence0D; - -#define dgSequence0E "__OTR__sound/sequences/0E_event_powerup" -static const ALIGN_ASSET(2) char gSequence0E[] = dgSequence0E; - -#define dgSequence0F "__OTR__sound/sequences/0F_event_metal_cap" -static const ALIGN_ASSET(2) char gSequence0F[] = dgSequence0F; - -#define dgSequence10 "__OTR__sound/sequences/10_event_koopa_message" -static const ALIGN_ASSET(2) char gSequence10[] = dgSequence10; - -#define dgSequence11 "__OTR__sound/sequences/11_level_koopa_road" -static const ALIGN_ASSET(2) char gSequence11[] = dgSequence11; - -#define dgSequence12 "__OTR__sound/sequences/12_event_high_score" -static const ALIGN_ASSET(2) char gSequence12[] = dgSequence12; - -#define dgSequence13 "__OTR__sound/sequences/13_event_merry_go_round" -static const ALIGN_ASSET(2) char gSequence13[] = dgSequence13; - -#define dgSequence14 "__OTR__sound/sequences/14_event_race" -static const ALIGN_ASSET(2) char gSequence14[] = dgSequence14; - -#define dgSequence15 "__OTR__sound/sequences/15_cutscene_star_spawn" -static const ALIGN_ASSET(2) char gSequence15[] = dgSequence15; - -#define dgSequence16 "__OTR__sound/sequences/16_event_boss" -static const ALIGN_ASSET(2) char gSequence16[] = dgSequence16; - -#define dgSequence17 "__OTR__sound/sequences/17_cutscene_collect_key" -static const ALIGN_ASSET(2) char gSequence17[] = dgSequence17; - -#define dgSequence18 "__OTR__sound/sequences/18_event_endless_stairs" -static const ALIGN_ASSET(2) char gSequence18[] = dgSequence18; - -#define dgSequence19 "__OTR__sound/sequences/19_level_boss_koopa_final" -static const ALIGN_ASSET(2) char gSequence19[] = dgSequence19; - -#define dgSequence1A "__OTR__sound/sequences/1A_cutscene_credits" -static const ALIGN_ASSET(2) char gSequence1A[] = dgSequence1A; - -#define dgSequence1B "__OTR__sound/sequences/1B_event_solve_puzzle" -static const ALIGN_ASSET(2) char gSequence1B[] = dgSequence1B; - -#define dgSequence1C "__OTR__sound/sequences/1C_event_toad_message" -static const ALIGN_ASSET(2) char gSequence1C[] = dgSequence1C; - -#define dgSequence1D "__OTR__sound/sequences/1D_event_peach_message" -static const ALIGN_ASSET(2) char gSequence1D[] = dgSequence1D; - -#define dgSequence1E "__OTR__sound/sequences/1E_cutscene_intro" -static const ALIGN_ASSET(2) char gSequence1E[] = dgSequence1E; - -#define dgSequence1F "__OTR__sound/sequences/1F_cutscene_victory" -static const ALIGN_ASSET(2) char gSequence1F[] = dgSequence1F; - -#define dgSequence20 "__OTR__sound/sequences/20_cutscene_ending" -static const ALIGN_ASSET(2) char gSequence20[] = dgSequence20; - -#define dgSequence21 "__OTR__sound/sequences/21_menu_file_select" -static const ALIGN_ASSET(2) char gSequence21[] = dgSequence21; - -#define dgSequence22 "__OTR__sound/sequences/22_cutscene_lakitu" -static const ALIGN_ASSET(2) char gSequence22[] = dgSequence22; - -static const char* gSequenceTable[] = { - gSequence00, gSequence01, gSequence02, gSequence03, - gSequence04, gSequence05, gSequence06, gSequence07, - gSequence08, gSequence09, gSequence0A, gSequence0B, - gSequence0C, gSequence0D, gSequence0E, gSequence0F, - gSequence10, gSequence11, gSequence12, gSequence13, - gSequence14, gSequence15, gSequence16, gSequence17, - gSequence18, gSequence19, gSequence1A, gSequence1B, - gSequence1C, gSequence1D, gSequence1E, gSequence1F, - gSequence20, gSequence21, gSequence22, -}; diff --git a/src/audio/internal.h b/src/audio/internal.h index fc742066..617e7587 100644 --- a/src/audio/internal.h +++ b/src/audio/internal.h @@ -222,7 +222,7 @@ struct AudioBank { struct CtlEntry { #ifndef VERSION_SH - u8 unused; + u8 bankId; #endif u8 numInstruments; u8 numDrums; @@ -238,6 +238,7 @@ struct AudioSequenceData { u32 bankCount; u8* banks; u8* data; + u8 id; }; struct M64ScriptState { diff --git a/src/audio/load.c b/src/audio/load.c index a67ab03f..131932e9 100644 --- a/src/audio/load.c +++ b/src/audio/load.c @@ -8,7 +8,6 @@ #include "load.h" #include "seqplayer.h" #include "sound/sound_data.h" -#include "sequences_table.h" #include #include @@ -393,7 +392,7 @@ uint8_t* load_sequence_immediate(s32 seqId, s32 arg1) { struct CtlEntry* load_banks_immediate(s32 seqId, u8 *outDefaultBank) { u32 bankId; - struct AudioSequenceData *seqData = ResourceGetDataByName(gSequenceTable[seqId]); + struct AudioSequenceData *seqData = GameEngine_LoadSequence(seqId); struct CtlEntry *output; for(size_t i = 0; i < seqData->bankCount; i++) { output = GameEngine_LoadBank(bankId = seqData->banks[i]); @@ -538,7 +537,7 @@ void audio_init() { #endif // Load headers for sounds and sequences - gSequenceCount = sizeof(gSequenceTable) / sizeof(gSequenceTable[0]); + gSequenceCount = GameEngine_GetSequenceCount(); init_sequence_players(); gAudioLoadLock = AUDIO_LOCK_NOT_LOADING; diff --git a/src/audio/seqplayer.c b/src/audio/seqplayer.c index b9d33c8b..90e29a85 100644 --- a/src/audio/seqplayer.c +++ b/src/audio/seqplayer.c @@ -954,7 +954,6 @@ u8 audioString107[] = "Error: Your assignchannel is stolen.\n"; #else // US/JP version with macros to simulate inlining by copt. Edit if you dare. #include "copt/seq_channel_layer_process_script_copt.inc.c" -#include "sequences_table.h" #endif @@ -1784,7 +1783,7 @@ void sequence_channel_process_script(struct SequenceChannel *seqChannel) { loBits = *(sp38 + gAlBankSets); cmd = gAlBankSets[(s32)sp38 + loBits - cmd]; #else - struct AudioSequenceData *sequence = ResourceGetDataByName(gSequenceTable[seqPlayer->seqId]); + struct AudioSequenceData *sequence = GameEngine_LoadSequence(seqPlayer->seqId); cmd = sequence->banks[cmd]; #endif #ifdef VERSION_SH diff --git a/src/port/Engine.cpp b/src/port/Engine.cpp index 61b3b634..625e027d 100644 --- a/src/port/Engine.cpp +++ b/src/port/Engine.cpp @@ -4,8 +4,6 @@ #include "port/importer/AudioBankFactory.h" #include "port/importer/AudioSampleFactory.h" #include "port/importer/AudioSequenceFactory.h" -#include "banks_table.h" -#include "sequences_table.h" #include "audio/GameAudio.h" #include "ZAPDUtils/Utils/StringHelper.h" #include "texts_table.h" @@ -53,13 +51,13 @@ GameEngine::GameEngine(){ this->context->GetResourceManager()->GetResourceLoader()->RegisterResourceFactory(LUS::ResourceType::Sequence, "Sequence", std::make_shared()); this->context->GetResourceManager()->GetResourceLoader()->RegisterResourceFactory(LUS::ResourceType::SDialog, "Dialog", std::make_shared()); this->context->GetResourceManager()->GetResourceLoader()->RegisterResourceFactory(LUS::ResourceType::Dictionary, "Dictionary", std::make_shared()); - GameEngine::AudioInit(); - this->LoadDictionary(); } void GameEngine::Create(){ - GameEngine::Instance = new GameEngine(); + auto instance = GameEngine::Instance = new GameEngine(); GameUI::SetupGuiElements(); + instance->AudioInit(); + instance->LoadDictionary(); } void GameEngine::Destroy(){ @@ -160,7 +158,23 @@ void GameEngine::EndAudioFrame(){ } void GameEngine::AudioInit() { - LUS::Context::GetInstance()->GetResourceManager()->LoadDirectory("sound"); + auto resourceMgr = LUS::Context::GetInstance()->GetResourceManager(); + resourceMgr->LoadDirectory("sound"); + auto banksFiles = resourceMgr->GetArchive()->ListFiles("sound/banks/*"); + + for(auto& bank : *banksFiles){ + auto path = "__OTR__" + bank; + auto ctl = static_cast(ResourceGetDataByName(path.c_str())); + this->bankMapTable[bank] = ctl->bankId; + } + + auto sequencesFiles = resourceMgr->GetArchive()->ListFiles("sound/sequences/*"); + + for(auto& sequence : *sequencesFiles){ + auto path = "__OTR__" + sequence; + auto seq = static_cast(ResourceGetDataByName(path.c_str())); + this->sequencesMapTable[seq->id] = path; + } if (!audio.running) { audio.running = true; @@ -183,6 +197,14 @@ void GameEngine::LoadDictionary() { this->dictionary = static_cast> *>(ResourceGetDataByName("__OTR__texts/strings/global")); } +uint8_t GameEngine::GetBankIdByName(const std::string& name) { + auto engine = GameEngine::Instance; + if(engine->bankMapTable.contains(name)){ + return engine->bankMapTable[name]; + } + return 0; +} + extern "C" uint32_t GameEngine_GetInterpolatedFPS() { return GameEngine::GetInterpolationFPS(); } @@ -212,15 +234,20 @@ extern "C" float GameEngine_GetAspectRatio() { extern "C" CtlEntry* GameEngine_LoadBank(uint8_t bankId) { auto engine = GameEngine::Instance; - if(bankId > (sizeof(gBankTable) / sizeof(gBankTable[0])) - 1){ + if(bankId >= engine->bankMapTable.size()){ return nullptr; } if(engine->banks.contains(bankId)){ return engine->banks[bankId]; } - auto ctl = static_cast(ResourceGetDataByName(gBankTable[bankId])); - engine->banks[bankId] = ctl; - return ctl; + for(auto& bank : engine->bankMapTable){ + if(bank.second == bankId){ + auto ctl = static_cast(ResourceGetDataByName(("__OTR__" + bank.first).c_str())); + engine->banks[bankId] = ctl; + return ctl; + } + } + return nullptr; } extern "C" uint8_t GameEngine_IsBankLoaded(uint8_t bankId) { @@ -238,17 +265,24 @@ extern "C" void GameEngine_UnloadBank(uint8_t bankId) { extern "C" AudioSequenceData* GameEngine_LoadSequence(uint8_t seqId) { auto engine = GameEngine::Instance; - if(seqId > (sizeof(gSequenceTable) / sizeof(gSequenceTable[0])) - 1){ + if(!engine->sequencesMapTable.contains(seqId)){ return nullptr; } + if(engine->sequences.contains(seqId)){ return engine->sequences[seqId]; } - auto sequences = static_cast(ResourceGetDataByName(gSequenceTable[seqId])); + + auto sequences = static_cast(ResourceGetDataByName(engine->sequencesMapTable[seqId].c_str())); engine->sequences[seqId] = sequences; return sequences; } +extern "C" uint32_t GameEngine_GetSequenceCount(){ + auto engine = GameEngine::Instance; + return engine->sequencesMapTable.size(); +} + extern "C" uint8_t GameEngine_IsSequenceLoaded(uint8_t seqId) { auto engine = GameEngine::Instance; GameEngine_LoadSequence(seqId); diff --git a/src/port/Engine.h b/src/port/Engine.h index f9b75072..735a3e08 100644 --- a/src/port/Engine.h +++ b/src/port/Engine.h @@ -22,10 +22,13 @@ class GameEngine { std::shared_ptr context; std::unordered_map banks; std::unordered_map sequences; + std::unordered_map bankMapTable; + std::unordered_map sequencesMapTable; std::unordered_map>* dictionary; GameEngine(); static void Create(); + void AudioInit(); void StartFrame() const; static void RunCommands(Gfx* Commands); void ProcessFrame(void (*run_one_game_iter)()) const; @@ -33,14 +36,13 @@ class GameEngine { static void HandleAudioThread(); static void StartAudioFrame(); static void EndAudioFrame(); - static void AudioInit(); static void AudioExit(); + static uint8_t GetBankIdByName(const std::string& name); void LoadDictionary(); void Destroy(); }; #else - uint32_t GameEngine_GetInterpolatedFPS(); uint32_t GameEngine_GetSampleRate(); uint32_t GameEngine_GetSamplesPerFrame(); @@ -49,6 +51,7 @@ struct CtlEntry* GameEngine_LoadBank(uint8_t bankId); uint8_t GameEngine_IsBankLoaded(uint8_t bankId); void GameEngine_UnloadBank(uint8_t bankId); struct AudioSequenceData* GameEngine_LoadSequence(uint8_t seqId); +uint32_t GameEngine_GetSequenceCount(); uint8_t GameEngine_IsSequenceLoaded(uint8_t seqId); void GameEngine_UnloadSequence(uint8_t seqId); uint32_t GameEngine_GetGameVersion(); diff --git a/src/port/importer/AudioBankFactory.cpp b/src/port/importer/AudioBankFactory.cpp index 10931d4a..cbd79cf1 100644 --- a/src/port/importer/AudioBankFactory.cpp +++ b/src/port/importer/AudioBankFactory.cpp @@ -30,11 +30,11 @@ void CubeOS::AudioBankFactoryV0::ParseFileBinary(std::shared_ptrReadUInt32(); uint32_t instrumentCount = reader->ReadUInt32(); for(size_t i = 0; i < instrumentCount; i++){ auto* instrument = new Instrument(); -// memset(instrument, 0, sizeof(Instrument)); bool valid = reader->ReadUByte(); if(!valid){ bank->instruments.push_back(nullptr); @@ -104,7 +104,7 @@ void CubeOS::AudioBankFactoryV0::ParseFileBinary(std::shared_ptrdrums.push_back(drum); } - bank->mData.unused = 1; + bank->mData.bankId = bankId; bank->mData.numInstruments = instrumentCount; bank->mData.numDrums = drumCount; bank->mData.instruments = bank->instruments.data(); diff --git a/src/port/importer/AudioSequenceFactory.cpp b/src/port/importer/AudioSequenceFactory.cpp index 88496bf8..f260617c 100644 --- a/src/port/importer/AudioSequenceFactory.cpp +++ b/src/port/importer/AudioSequenceFactory.cpp @@ -1,6 +1,8 @@ #include "AudioSequenceFactory.h" #include "port/importer/types/AudioSequence.h" #include "spdlog/spdlog.h" +#include "port/Engine.h" +#include "port/importer/types/AudioBank.h" std::shared_ptr CubeOS::AudioSequenceFactory::ReadResource(std::shared_ptr initData, std::shared_ptr reader) { auto resource = std::make_shared(initData); @@ -27,9 +29,11 @@ void CubeOS::AudioSequenceFactoryV0::ParseFileBinary(std::shared_ptrReadUInt32(); size_t bankCount = reader->ReadUInt32(); for(size_t i = 0; i < bankCount; i++){ - bank->banks.push_back(reader->ReadUByte()); + std::string bankName = reader->ReadString(); + bank->banks.push_back(GameEngine::GetBankIdByName(bankName)); } size_t sampleSize = reader->ReadUInt32(); @@ -40,4 +44,5 @@ void CubeOS::AudioSequenceFactoryV0::ParseFileBinary(std::shared_ptrmData.bankCount = bankCount; bank->mData.banks = bank->banks.data(); bank->mData.data = bank->sampleData.data(); + bank->mData.id = id; } \ No newline at end of file diff --git a/src/port/importer/types/AudioBank.h b/src/port/importer/types/AudioBank.h index fbb24d11..bb3c965c 100644 --- a/src/port/importer/types/AudioBank.h +++ b/src/port/importer/types/AudioBank.h @@ -34,7 +34,7 @@ struct Drum { }; struct CtlEntry { - uint8_t unused; + uint8_t bankId; uint8_t numInstruments; uint8_t numDrums; struct Instrument **instruments; diff --git a/src/port/importer/types/AudioSequence.h b/src/port/importer/types/AudioSequence.h index 8f29c92b..5001e60b 100644 --- a/src/port/importer/types/AudioSequence.h +++ b/src/port/importer/types/AudioSequence.h @@ -7,6 +7,7 @@ struct AudioSequenceData { uint32_t bankCount; uint8_t* banks; uint8_t* data; + uint8_t id; }; namespace CubeOS {