diff --git a/include/d/actor/d_a_horse.h b/include/d/actor/d_a_horse.h index 37e4bac70f..7e8ef8c343 100644 --- a/include/d/actor/d_a_horse.h +++ b/include/d/actor/d_a_horse.h @@ -196,6 +196,9 @@ public: void copyReinPos(); void setReinPosHandSubstance(int); void setReinPosNormalSubstance(); +#if TARGET_PC + void lerpControlPoints(f32 alpha); +#endif void bgCheck(); bool checkSpecialWallHitSubstance(cXyz const&) const; void setServiceWaitTimer(); diff --git a/include/d/d_drawlist.h b/include/d/d_drawlist.h index 80dc9035df..9eecac2d61 100644 --- a/include/d/d_drawlist.h +++ b/include/d/d_drawlist.h @@ -435,6 +435,10 @@ public: m3DLineMatSortPacket[param_1->getMaterialID()].setMatDark(param_1); } +#if TARGET_PC + void refresh3DlineMats(const cXyz& eye); +#endif + void peekZdata() { mPeekZ.peekData(); } void entryZSortListZxlu(J3DPacket* i_packet, cXyz& param_1) { entryZSortXluDrawList(mDrawBuffers[DB_LIST_Z_XLU], i_packet, param_1); diff --git a/include/dusk/frame_interpolation.h b/include/dusk/frame_interpolation.h index 3dfa00d53b..98495d18db 100644 --- a/include/dusk/frame_interpolation.h +++ b/include/dusk/frame_interpolation.h @@ -6,6 +6,8 @@ #include #include +struct cXyz; + #ifdef __cplusplus namespace dusk { namespace frame_interp { @@ -15,6 +17,7 @@ void ensure_initialized(); void begin_record(); void end_record(); void interpolate(float step); +float get_interpolation_step(); void notify_sim_tick_complete(); uint32_t begin_presentation_ui_pass(); uint32_t get_presentation_ui_advance_ticks(); @@ -27,6 +30,8 @@ void record_final_mtx_raw(const Mtx* dest, const Mtx src); bool lookup_replacement(const void* source, Mtx out); bool lookup_concat_replacement(const void* lhs, const void* rhs, Mtx out); +void camera_eye_from_view_mtx(MtxP view_mtx, cXyz* o_eye); + } // namespace frame_interp } // namespace dusk #endif diff --git a/include/m_Do/m_Do_ext.h b/include/m_Do/m_Do_ext.h index e6973dc782..77d046f0de 100644 --- a/include/m_Do/m_Do_ext.h +++ b/include/m_Do/m_Do_ext.h @@ -541,6 +541,9 @@ public: virtual int getMaterialID() = 0; virtual void setMaterial() = 0; virtual void draw() = 0; +#if TARGET_PC + virtual void refreshGeometryForPresentationEye(const cXyz& eye) {} +#endif /* 0x4 */ mDoExt_3DlineMat_c* field_0x4; }; @@ -582,11 +585,19 @@ class dKy_tevstr_c; class mDoExt_3DlineMat1_c : public mDoExt_3DlineMat_c { public: int init(u16, u16, ResTIMG*, int); +#if TARGET_PC + void update(int, GXColor&, dKy_tevstr_c*, const cXyz* presentationEye = nullptr); + void update(int, f32, GXColor&, u16, dKy_tevstr_c*, const cXyz* presentationEye = nullptr); +#else void update(int, GXColor&, dKy_tevstr_c*); void update(int, f32, GXColor&, u16, dKy_tevstr_c*); +#endif int getMaterialID() { return 1; } void setMaterial(); void draw(); +#if TARGET_PC + void refreshGeometryForPresentationEye(const cXyz& eye) override; +#endif cXyz* getPos(int i_idx) { return mpLines[i_idx].field_0x0; } f32* getSize(int i_idx) { return mpLines[i_idx].field_0x4; } @@ -600,6 +611,11 @@ private: /* 0x34 */ u16 field_0x34; /* 0x36 */ u8 mIsDrawn; /* 0x38 */ mDoExt_3Dline_c* mpLines; +#if TARGET_PC + u8 mInterpLineKind; + f32 mInterpLineF; + u16 mInterpLineU16; +#endif }; class mDoExt_3DlineMat2_c : public mDoExt_3DlineMat1_c { @@ -616,6 +632,9 @@ public: void setMatDark(mDoExt_3DlineMat_c* i_mat) { setMat(i_mat); } void setMat(mDoExt_3DlineMat_c*); +#if TARGET_PC + mDoExt_3DlineMat_c* getFirstMat() const { return mp3DlineMat; } +#endif virtual void draw(); virtual ~mDoExt_3DlineMatSortPacket() {} diff --git a/src/d/actor/d_a_horse.cpp b/src/d/actor/d_a_horse.cpp index 976e398616..68112eba47 100644 --- a/src/d/actor/d_a_horse.cpp +++ b/src/d/actor/d_a_horse.cpp @@ -20,6 +20,21 @@ #include #include +#if TARGET_PC +#include "dusk/dusk.h" + +namespace { +// FRAME INTERP NOTE: Sim tick control point snapshots for interpolation +constexpr int kHorseReinSimMax = 75; +cXyz s_horseReinSimPrev[kHorseReinSimMax]; +cXyz s_horseReinSimCurr[kHorseReinSimMax]; +int s_horseReinSimNumPrev; +int s_horseReinSimNumCurr; +bool s_horseReinSimPrevValid; +bool s_horseReinSimCurrValid; +} // namespace +#endif + #define ANM_HS_BACK_WALK 6 #define ANM_HS_WALK_START 7 #define ANM_HS_EXCITEMENT 8 @@ -3016,6 +3031,20 @@ void daHorse_c::copyReinPos() { for (i = rein->field_0x8[0] - 1; i >= 0; i--, pos_p++) { *pos_p = rein->field_0x0[0][i]; } +#if TARGET_PC + if (field_0x1204 > 0) { + if (s_horseReinSimCurrValid && s_horseReinSimNumCurr > 0) { + memcpy(s_horseReinSimPrev, s_horseReinSimCurr, s_horseReinSimNumCurr * sizeof(cXyz)); + s_horseReinSimNumPrev = s_horseReinSimNumCurr; + s_horseReinSimPrevValid = true; + } + memcpy(s_horseReinSimCurr, m_reinLine.getPos(0), field_0x1204 * sizeof(cXyz)); + s_horseReinSimNumCurr = field_0x1204; + s_horseReinSimCurrValid = true; + } else { + s_horseReinSimCurrValid = false; + } +#endif } void daHorse_c::setReinPosHandSubstance(int param_0) { @@ -3127,6 +3156,30 @@ void daHorse_c::setReinPosNormalSubstance() { copyReinPos(); } +#if TARGET_PC +void daHorse_c::lerpControlPoints(f32 alpha) { + // FRAME INTERP NOTE: Currently only lerping points for Epona's reins. Need a more global solution. + if (!dusk::getSettings().game.enableFrameInterpolation || !s_horseReinSimPrevValid || !s_horseReinSimCurrValid) { + return; + } + const int nCurr = s_horseReinSimNumCurr; + const int nPrev = s_horseReinSimNumPrev; + if (nCurr <= 0) { + return; + } + int n = nPrev < nCurr ? nPrev : nCurr; + if (n <= 0 || n > kHorseReinSimMax) { + return; + } + cXyz* dst = m_reinLine.getPos(0); + for (int i = 0; i < n; i++) { + const cXyz& p0 = s_horseReinSimPrev[i]; + const cXyz& p1 = s_horseReinSimCurr[i]; + dst[i] = p0 + (p1 - p0) * alpha; + } +} +#endif + void daHorse_c::bgCheck() { if (m_procID != PROC_LARGE_DAMAGE_e) { static cXyz localCenterPos(0.0f, 100.0f, 0.0f); diff --git a/src/d/d_drawlist.cpp b/src/d/d_drawlist.cpp index f5dd0f23f5..9f2255b884 100644 --- a/src/d/d_drawlist.cpp +++ b/src/d/d_drawlist.cpp @@ -2017,3 +2017,13 @@ void dDlst_list_c::calcWipe() { dComIfGd_set2DXlu(&mWipeDlst); } } + +#if TARGET_PC +void dDlst_list_c::refresh3DlineMats(const cXyz& eye) { + for (int i = 0; i < 3; i++) { + for (mDoExt_3DlineMat_c* mat = m3DLineMatSortPacket[i].getFirstMat(); mat != NULL; mat = mat->field_0x4) { + mat->refreshGeometryForPresentationEye(eye); + } + } +} +#endif diff --git a/src/dusk/frame_interpolation.cpp b/src/dusk/frame_interpolation.cpp index a3777dea7e..0c21b2b5af 100644 --- a/src/dusk/frame_interpolation.cpp +++ b/src/dusk/frame_interpolation.cpp @@ -300,6 +300,10 @@ void interpolate(float step) { interpolate_branch(g_previous_recording.root, g_current_recording.root, g_step); } +float get_interpolation_step() { + return g_step; +} + void notify_sim_tick_complete() { ensure_initialized(); g_pending_presentation_ui_ticks++; @@ -394,5 +398,12 @@ bool lookup_concat_replacement(const void* lhs, const void* rhs, Mtx out) { return true; } +// TODO: Is there already a built-in function for this? +void camera_eye_from_view_mtx(MtxP view_mtx, cXyz* o_eye) { + o_eye->x = -(view_mtx[0][0] * view_mtx[0][3] + view_mtx[1][0] * view_mtx[1][3] + view_mtx[2][0] * view_mtx[2][3]); + o_eye->y = -(view_mtx[0][1] * view_mtx[0][3] + view_mtx[1][1] * view_mtx[1][3] + view_mtx[2][1] * view_mtx[2][3]); + o_eye->z = -(view_mtx[0][2] * view_mtx[0][3] + view_mtx[1][2] * view_mtx[1][3] + view_mtx[2][2] * view_mtx[2][3]); +} + } // namespace frame_interp } // namespace dusk diff --git a/src/m_Do/m_Do_ext.cpp b/src/m_Do/m_Do_ext.cpp index 0593815b1d..577ada0837 100644 --- a/src/m_Do/m_Do_ext.cpp +++ b/src/m_Do/m_Do_ext.cpp @@ -2398,7 +2398,12 @@ void mDoExt_3DlineMat0_c::draw() { var_r28++; } - field_0x16 ^= (u8)1; +#if TARGET_PC + if (!dusk::getSettings().game.enableFrameInterpolation) +#endif + { + field_0x16 ^= (u8)1; + } } void mDoExt_3DlineMat0_c::update(int param_0, f32 param_1, GXColor& param_2, u16 param_3, @@ -2648,6 +2653,9 @@ int mDoExt_3DlineMat1_c::init(u16 param_0, u16 param_1, ResTIMG* param_2, int pa field_0x4 = 0; mIsDrawn = 0; +#if TARGET_PC + mInterpLineKind = 0; +#endif GXInitTexObj(&mTextureObject, (void*)((intptr_t)param_2 + param_2->imageOffset), param_2->width, param_2->height, (GXTexFmt)param_2->format, (GXTexWrapMode)param_2->wrapS, @@ -2720,11 +2728,19 @@ void mDoExt_3DlineMat1_c::draw() { lines++; } GXSetTexCoordScaleManually(GX_TEXCOORD0, 0, 0, 0); - mIsDrawn ^= (u8)1; +#if TARGET_PC + if (!dusk::getSettings().game.enableFrameInterpolation) +#endif + { + mIsDrawn ^= (u8)1; + } } -void mDoExt_3DlineMat1_c::update(int param_0, f32 param_1, GXColor& param_2, u16 param_3, - dKy_tevstr_c* param_4) { +#if TARGET_PC +void mDoExt_3DlineMat1_c::update(int param_0, f32 param_1, GXColor& param_2, u16 param_3, dKy_tevstr_c* param_4, const cXyz* presentationEye) { +#else +void mDoExt_3DlineMat1_c::update(int param_0, f32 param_1, GXColor& param_2, u16 param_3, dKy_tevstr_c* param_4) { +#endif mColor = param_2; this->mpTevStr = param_4; if (param_0 < 0) { @@ -2734,6 +2750,13 @@ void mDoExt_3DlineMat1_c::update(int param_0, f32 param_1, GXColor& param_2, u16 } else { field_0x34 = param_0; } + +#if TARGET_PC + mInterpLineKind = 1; + mInterpLineF = param_1; + mInterpLineU16 = param_3; +#endif + view_class* sp_3c = dComIfGd_getView(); mDoExt_3Dline_c* sp_38 = mpLines; f32 local_f27 = param_3 != 0 ? param_1 / param_3 : 0.0f; @@ -2787,7 +2810,12 @@ void mDoExt_3DlineMat1_c::update(int param_0, f32 param_1, GXColor& param_2, u16 local_f31 += local_f30 * 0.02f * (8.0f / param_1); } +#if TARGET_PC + const cXyz& lineEye = (presentationEye != nullptr && dusk::getSettings().game.enableFrameInterpolation) ? *presentationEye : sp_3c->lookat.eye; + sp_13c = *local_r27 - lineEye; +#else sp_13c = *local_r27 - sp_3c->lookat.eye; +#endif sp_130 = sp_130.outprod(sp_13c); sp_130.normalizeZP(); @@ -2822,7 +2850,11 @@ void mDoExt_3DlineMat1_c::update(int param_0, f32 param_1, GXColor& param_2, u16 local_f31 += local_f30 * 0.02f * (8.0f / param_1); } +#if TARGET_PC + sp_13c = local_r27[0] - lineEye; +#else sp_13c = local_r27[0] - sp_3c->lookat.eye; +#endif sp_130 = sp_130.outprod(sp_13c); sp_130.normalizeZP(); @@ -2894,7 +2926,11 @@ void mDoExt_3DlineMat2_c::setMaterial() { GXLoadNrmMtxImm(cMtx_getIdentity(), 0); } -void mDoExt_3DlineMat1_c::update(int param_0, GXColor& param_2, dKy_tevstr_c* param_4) { +#if TARGET_PC +void mDoExt_3DlineMat1_c::update(int param_0, GXColor& param_2, dKy_tevstr_c* param_4, const cXyz* presentationEye) { +#else +void mDoExt_3DlineMat1_c::update(int param_0, GXColor& param_2, dKy_tevstr_c* param_4 +#endif mColor = param_2; this->mpTevStr = param_4; if (param_0 < 0) { @@ -2904,6 +2940,11 @@ void mDoExt_3DlineMat1_c::update(int param_0, GXColor& param_2, dKy_tevstr_c* pa } else { field_0x34 = param_0; } + +#if TARGET_PC + mInterpLineKind = 2; +#endif + view_class* stack_3c = dComIfGd_getView(); mDoExt_3Dline_c* sp_38 = mpLines; f32 local_f30; @@ -2929,6 +2970,12 @@ void mDoExt_3DlineMat1_c::update(int param_0, GXColor& param_2, dKy_tevstr_c* pa for (s32 sp_14 = 0; sp_14 < mNumLines; sp_14++) { local_r27 = sp_38[0].field_0x0; size_p = sp_38->field_0x4; +#if TARGET_PC + if (presentationEye != nullptr && dusk::getSettings().game.enableFrameInterpolation && size_p == NULL) { + sp_38 += 1; + continue; + } +#endif JUT_ASSERT(5875, size_p != NULL); sp_24 = sp_38->field_0x8[mIsDrawn]; sp_28 = sp_24; @@ -2942,7 +2989,12 @@ void mDoExt_3DlineMat1_c::update(int param_0, GXColor& param_2, dKy_tevstr_c* pa sp_130 = local_r27[1] - local_r27[0]; local_f30 = sp_130.abs(); local_f31 += local_f30 * 0.1f; +#if TARGET_PC + const cXyz& lineEye = (presentationEye != nullptr && dusk::getSettings().game.enableFrameInterpolation) ? *presentationEye : stack_3c->lookat.eye; + sp_13c = local_r27[0] - lineEye; +#else sp_13c = local_r27[0] - stack_3c->lookat.eye; +#endif sp_130 = sp_130.outprod(sp_13c); sp_130.normalizeZP(); local_r30->x = sp_130.x * 64.0f; @@ -2964,7 +3016,11 @@ void mDoExt_3DlineMat1_c::update(int param_0, GXColor& param_2, dKy_tevstr_c* pa sp_130 = local_r27[1] - local_r27[0]; local_f30 = sp_130.abs(); local_f31 += local_f30 * 0.1f; +#if TARGET_PC + sp_13c = local_r27[0] - lineEye; +#else sp_13c = local_r27[0] - stack_3c->lookat.eye; +#endif sp_130 = sp_130.outprod(sp_13c); sp_130.normalizeZP(); local_r30 += 2; @@ -3008,6 +3064,19 @@ void mDoExt_3DlineMat1_c::update(int param_0, GXColor& param_2, dKy_tevstr_c* pa } } +#if TARGET_PC +void mDoExt_3DlineMat1_c::refreshGeometryForPresentationEye(const cXyz& eye) { + if (!dusk::getSettings().game.enableFrameInterpolation) { + return; + } + if (mInterpLineKind == 1) { + update(field_0x34, mInterpLineF, mColor, mInterpLineU16, mpTevStr, &eye); + } else if (mInterpLineKind == 2) { + update(field_0x34, mColor, mpTevStr, &eye); + } +} +#endif + void mDoExt_3DlineMatSortPacket::setMat(mDoExt_3DlineMat_c* i_3DlineMat) { if (mp3DlineMat == NULL) { dComIfGd_getListPacket()->entryImm(this, 0); diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index c27598989a..ff9e2042a0 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -51,6 +51,7 @@ #endif #if TARGET_PC +#include "d/actor/d_a_horse.h" #include "dusk/imgui/ImGuiConsole.hpp" #include "dusk/dusk.h" #endif @@ -1952,6 +1953,18 @@ int mDoGph_Painter() { GX_DEBUG_GROUP(dComIfGd_drawOpaListDark); } +#if TARGET_PC + if (dusk::getSettings().game.enableFrameInterpolation) { + cXyz pres_eye; + dusk::frame_interp::camera_eye_from_view_mtx(j3dSys.getViewMtx(), &pres_eye); + // FRAME INTERP NOTE: Currently only recalculating points for Epona's reins. Need a more global solution. + if (daHorse_c* horse = dComIfGp_getHorseActor()) { + horse->lerpControlPoints(dusk::frame_interp::get_interpolation_step()); + } + g_dComIfG_gameInfo.drawlist.refresh3DlineMats(pres_eye); + } +#endif + GX_DEBUG_GROUP(dComIfGd_drawOpaListPacket); #if DEBUG