diff --git a/libultraship b/libultraship index 62f9df4f74..2bc8d1c7e2 160000 --- a/libultraship +++ b/libultraship @@ -1 +1 @@ -Subproject commit 62f9df4f74bf1fc2b3daf3cfb1aa7ecd2093e893 +Subproject commit 2bc8d1c7e231e1010fd304c3ef0375463a6fed48 diff --git a/mm/2s2h/BenPort.cpp b/mm/2s2h/BenPort.cpp index 5c8dc2e747..52b6983861 100644 --- a/mm/2s2h/BenPort.cpp +++ b/mm/2s2h/BenPort.cpp @@ -324,6 +324,11 @@ extern "C" uint32_t Ship_GetInterpolationFPS() { return OTRGlobals::Instance->GetInterpolationFPS(); } +// Numer of interpolated frames +extern "C" uint32_t Ship_GetInterpolationFrameCount() { + return ceil((float)Ship_GetInterpolationFPS() / 20.0f); +} + struct ExtensionEntry { std::string path; std::string ext; @@ -928,8 +933,11 @@ void RunCommands(Gfx* Commands, const std::vector // Process window events for resize, mouse, keyboard events wnd->HandleEvents(); + gInterpolationIndex = 0; + for (const auto& m : mtx_replacements) { wnd->DrawAndRunGraphicsCommands(Commands, m); + gInterpolationIndex++; } } diff --git a/mm/2s2h/BenPort.h b/mm/2s2h/BenPort.h index 8a7fef0bd4..5f8a82e1e3 100644 --- a/mm/2s2h/BenPort.h +++ b/mm/2s2h/BenPort.h @@ -137,6 +137,7 @@ void Controller_UnblockGameInput(); void Overlay_DisplayText(float duration, const char* text); void Overlay_DisplayText_Seconds(int seconds, const char* text); uint32_t Ship_GetInterpolationFPS(); +uint32_t Ship_GetInterpolationFrameCount(); void Gfx_RegisterBlendedTexture(const char* name, u8* mask, u8* replacement); void Gfx_UnregisterBlendedTexture(const char* name); diff --git a/mm/2s2h/Enhancements/FrameInterpolation/FrameInterpolation.cpp b/mm/2s2h/Enhancements/FrameInterpolation/FrameInterpolation.cpp index 6e982130e8..a0efa7082f 100644 --- a/mm/2s2h/Enhancements/FrameInterpolation/FrameInterpolation.cpp +++ b/mm/2s2h/Enhancements/FrameInterpolation/FrameInterpolation.cpp @@ -4,10 +4,17 @@ #include #include #include +#include #include "FrameInterpolation.h" #include "2s2h/BenPort.h" +#include +#include +#include +#include +#include + /* Frame interpolation. @@ -56,7 +63,7 @@ void Matrix_RotateZF(f32 z, u8 mode); void Matrix_RotateZYX(s16 x, s16 y, s16 z, u8 mode); void Matrix_TranslateRotateZYX(Vec3f* translation, Vec3s* rotation); void Matrix_SetTranslateRotateYXZ(f32 translateX, f32 translateY, f32 translateZ, Vec3s* rot); -Mtx* Matrix_MtxFToMtx(MtxF* src, Mtx* dest); +Mtx* Matrix_MtxFToMtx(MtxF* src, Mtx* dest, bool isViewMtx); Mtx* Matrix_ToMtx(Mtx* dest, char* file, s32 line); Mtx* Matrix_NewMtx(struct GraphicsContext* gfxCtx, char* file, s32 line); Mtx* Matrix_MtxFToNewMtx(MtxF* src, struct GraphicsContext* gfxCtx); @@ -74,6 +81,7 @@ void Matrix_SetTranslateScaleMtx2(Mtx* mtx, f32 scaleX, f32 scaleY, f32 scaleZ, f32 translateZ); MtxF* Matrix_GetCurrent(void); +void guMtxIdentF(f32 mf[4][4]); void SkinMatrix_MtxFMtxFMult(MtxF* mfA, MtxF* mfB, MtxF* dest); } @@ -152,6 +160,7 @@ union Data { struct { MtxF src; Mtx* dest; + bool isViewMtx; } matrix_mtxf_to_mtx; struct { @@ -221,12 +230,276 @@ struct InterpolateCtx { return &mtx_replacements[addr]; } - void interpolate_mtxf(MtxF* res, MtxF* o, MtxF* n) { - for (size_t i = 0; i < 4; i++) { - for (size_t j = 0; j < 4; j++) { - res->mf[i][j] = w * o->mf[i][j] + step * n->mf[i][j]; +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + + static void combine(float* a, float* b, float* res, float a1, float b1) { + res[0] = (a[0] * a1) + (b[0] * b1); + res[1] = (a[1] * a1) + (b[1] * b1); + res[2] = (a[2] * a1) + (b[2] * b1); + } + + static glm::mat4x4 ComposeRotationMatrix(glm::quat& quaternion) { + return glm::toMat4(quaternion); + } + + static MtxF ComposeScaleMatrix(glm::vec3* scales) { + MtxF scaleMtx; + + memset(&scaleMtx, 0.0f, 4 * 4 * sizeof(float)); + scaleMtx.mf[0][0] = scales->x; + scaleMtx.mf[1][1] = scales->y; + scaleMtx.mf[2][2] = scales->z; + scaleMtx.mf[3][3] = 1.0f; + + return scaleMtx; + } + + static void ComposePerspective(MtxF* m, glm::vec4* perspective) { + m->mf[0][3] = perspective->x; + m->mf[1][3] = perspective->y; + m->mf[2][3] = perspective->z; + m->mf[3][3] = perspective->w; + } + + static void ComposeTranslation(MtxF* m, glm::vec3* translate) { + m->mf[3][0] = translate->x; + m->mf[3][1] = translate->y; + m->mf[3][2] = translate->z; + } + + static MtxF ComposeMatrix(glm::vec3* translate, glm::vec3* scales, glm::vec4* skew, glm::vec4* perspective, + glm::quat* quaternion) { + MtxF m; + guMtxIdentF(m.mf); + + // Apply Perspective and Translation + ComposePerspective(&m, perspective); + ComposeTranslation(&m, translate); + + // Apply Rotation + glm::mat4x4 rotationMatrix = ComposeRotationMatrix(*quaternion); + multiply(m.mf, &rotationMatrix, m.mf); + + // Apply Skew + float temp[4][4]; + guMtxIdentF(temp); + + if (skew->z) { + temp[2][1] = skew->z; + multiply(m.mf, temp, m.mf); + guMtxIdentF(temp); + } + + if (skew->y) { + temp[2][1] = 0; + temp[2][0] = skew->y; + multiply(m.mf, temp, m.mf); + guMtxIdentF(temp); + } + + if (skew->x) { + temp[2][0] = 0; + temp[1][0] = skew->x; + multiply(m.mf, temp, m.mf); + guMtxIdentF(temp); + } + + // Apply Scale + multiply(m.mf, ComposeScaleMatrix(scales).mf, m.mf); + + return m; + } + + static void DecomposeScaleAndSkew(MtxF* mtx, glm::mat3x3& row, glm::vec3* scale, glm::vec4* skew) { + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; ++j) + row[i][j] = mtx->mf[i][j]; + + // Compute X scale factor and normalize first row. + scale->x = glm::length(row[0]); + row[0] = glm::normalize(row[0]); + + // Compute XY shear factor and make 2nd row orthogonal to 1st. + skew->x = glm::dot(row[0], row[1]); + combine((float*)&row[1], (float*)&row[0], (float*)&row[1], 1.0, -skew->x); + + // Now, compute Y scale and normalize 2nd row. + scale->y = glm::length(row[1]); + row[1] = glm::normalize(row[1]); + skew->x /= scale->y; + + // Compute XZ and YZ shears, orthogonalize 3rd row + skew->y = glm::dot(row[0], row[2]); + combine((float*)&row[2], (float*)&row[0], (float*)&row[2], 1.0, -skew->y); + skew->z = glm::dot(row[1], row[2]); + combine((float*)&row[2], (float*)&row[1], (float*)&row[2], 1.0, -skew->z); + + // Next, get Z scale and normalize 3rd row. + scale->z = glm::length(row[2]); + row[2] = glm::normalize(row[2]); + skew->y /= scale->z; + skew->z /= scale->z; + } + + static void DecomposeRotation(glm::mat3x3& row, glm::quat& quaternion) { + quaternion[0] = 0.5 * sqrt(MAX(1 + row[0][0] - row[1][1] - row[2][2], 0)); + quaternion[1] = 0.5 * sqrt(MAX(1 - row[0][0] + row[1][1] - row[2][2], 0)); + quaternion[2] = 0.5 * sqrt(MAX(1 - row[0][0] - row[1][1] + row[2][2], 0)); + quaternion[3] = 0.5 * sqrt(MAX(1 + row[0][0] + row[1][1] + row[2][2], 0)); + + if (row[2][1] > row[1][2]) + quaternion[0] = -quaternion[0]; + if (row[0][2] > row[2][0]) + quaternion[1] = -quaternion[1]; + if (row[1][0] > row[0][1]) + quaternion[2] = -quaternion[2]; + } + + static void DecomposeTranslate(MtxF* mtx, glm::vec3* translate) { + translate->x = mtx->mf[3][0]; + translate->y = mtx->mf[3][1]; + translate->z = mtx->mf[3][2]; + } + + static bool DecomposePerspectiveMatrix(MtxF* mtx, glm::mat4x4& perspectiveMatrix, glm::vec4* perspective) { + memcpy(&perspectiveMatrix, mtx->mf, 4 * 4 * sizeof(float)); + + for (int i = 0; i < 3; i++) + perspectiveMatrix[i][3] = 0.0f; + + perspectiveMatrix[3][3] = 1; + + if (glm::determinant(perspectiveMatrix) == 0) + return false; + + // First, isolate perspective. + if (mtx->mf[0][3] != 0 || mtx->mf[1][3] != 0 || mtx->mf[2][3] != 0) { + // Note: In practice, it seems we don't really work with perspective matrices... + glm::vec4 rightHandSide; + + // rightHandSide is the right hand side of the equation. + rightHandSide.x = mtx->mf[0][3]; + rightHandSide.y = mtx->mf[1][3]; + rightHandSide.z = mtx->mf[2][3]; + rightHandSide.w = mtx->mf[3][3]; + + // Solve the equation by inverting perspectiveMatrix and multiplying + // rightHandSide by the inverse. + glm::mat4x4 inversePerspectiveMatrix = glm::inverse(perspectiveMatrix); + glm::mat4x4 transposedInversePerspectiveMatrix = glm::transpose(inversePerspectiveMatrix); + + *perspective = transposedInversePerspectiveMatrix * rightHandSide; + } else { + // No perspective. + perspective->x = 0; + perspective->y = 0; + perspective->z = 0; + perspective->w = 1; + } + + return true; + } + + // Implementation based on Graphics Gems II + static bool DecomposeMatrix(MtxF* mtx, glm::vec3* translate, glm::vec3* scale, glm::vec4* skew, + glm::vec4* perspective, glm::quat& quaternion) { + // Normalize the matrix. + if (mtx->mf[3][3] == 0) + return false; + + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + mtx->mf[i][j] /= mtx->mf[3][3]; + + glm::mat4x4 perspectiveMatrix; + + if (!DecomposePerspectiveMatrix(mtx, perspectiveMatrix, perspective)) + return false; + + // Next take care of translation + DecomposeTranslate(mtx, translate); + + // Now get scale and shear. 'row' is a 3 element array of 3 component vectors + glm::mat3x3 row; + DecomposeScaleAndSkew(mtx, row, scale, skew); + + // At this point, the matrix (in rows) is orthonormal. + // Check for a coordinate system flip. If the determinant + // is -1, then negate the matrix and the scaling factors. + glm::vec3 pdum3 = glm::cross(row[1], row[2]); + + if (glm::dot(row[0], pdum3) < 0) { + for (int i = 0; i < 3; i++) { + scale[i] *= -1; + + row[i][0] *= -1; + row[i][1] *= -1; + row[i][2] *= -1; } } + + // Now, get the rotations out + DecomposeRotation(row, quaternion); + + return true; + } + + static void multiply(float a[4][4], float b[4][4], float result[4][4]) { + SkinMatrix_MtxFMtxFMult((MtxF*)a, (MtxF*)b, (MtxF*)result); + } + + static void multiply(float a[4][4], glm::mat4x4* b, float result[4][4]) { + SkinMatrix_MtxFMtxFMult((MtxF*)a, (MtxF*)b, (MtxF*)result); + } + + void InterpolateMatricesLegacy(MtxF* res, MtxF* o, MtxF* n) { + for (size_t i = 0; i < 4; i++) + for (size_t j = 0; j < 4; j++) + res->mf[i][j] = w * o->mf[i][j] + step * n->mf[i][j]; + } + + void interpolate_mtxf(MtxF* res, MtxF* o, MtxF* n, bool isViewMtx) { + glm::vec3 translateO, translateN; + glm::vec3 scalesO, scalesN; + glm::vec4 skewO, skewN; + glm::vec4 perspectiveO, perspectiveN; + glm::quat quaternionO, quaternionN; + + // View matrices won't work with the new interpolation technique, + // so we need to rely on how it was done before + if (isViewMtx) { + InterpolateMatricesLegacy(res, o, n); + return; + } + + bool success = DecomposeMatrix(o, &translateO, &scalesO, &skewO, &perspectiveO, quaternionO); + + if (!success) { + InterpolateMatricesLegacy(res, o, n); + return; + } + + bool success2 = DecomposeMatrix(n, &translateN, &scalesN, &skewN, &perspectiveN, quaternionN); + + if (!success2) { + InterpolateMatricesLegacy(res, o, n); + return; + } + + // Take the shortest route + if (glm::dot(quaternionO, quaternionN) < 0.0f) + quaternionN = -quaternionN; + + quaternionO = glm::slerp(quaternionO, quaternionN, step); + + translateO = glm::mix(translateO, translateN, step); + scalesO = glm::mix(scalesO, scalesN, step); + skewO = glm::mix(skewO, skewN, step); + perspectiveO = glm::mix(perspectiveO, perspectiveN, step); + + MtxF m = ComposeMatrix(&translateO, &scalesO, &skewO, &perspectiveO, &quaternionO); + memcpy(res->mf, &m.mf, 4 * 4 * sizeof(float)); } float lerp(f32 o, f32 n) { @@ -338,12 +611,12 @@ struct InterpolateCtx { break; case Op::MatrixPut: - interpolate_mtxf(&tmp_mtxf, &old_op.matrix_put.src, &new_op.matrix_put.src); + interpolate_mtxf(&tmp_mtxf, &old_op.matrix_put.src, &new_op.matrix_put.src, false); Matrix_Put(&tmp_mtxf); break; case Op::MatrixMult: - interpolate_mtxf(&tmp_mtxf, &old_op.matrix_mult.mf, &new_op.matrix_mult.mf); + interpolate_mtxf(&tmp_mtxf, &old_op.matrix_mult.mf, &new_op.matrix_mult.mf, false); Matrix_Mult(&tmp_mtxf, new_op.matrix_mult.mode); break; @@ -415,25 +688,27 @@ struct InterpolateCtx { case Op::MatrixMtxFToMtx: interpolate_mtxf(new_replacement(new_op.matrix_mtxf_to_mtx.dest), - &old_op.matrix_mtxf_to_mtx.src, &new_op.matrix_mtxf_to_mtx.src); + &old_op.matrix_mtxf_to_mtx.src, &new_op.matrix_mtxf_to_mtx.src, + old_op.matrix_mtxf_to_mtx.isViewMtx); break; case Op::MatrixToMtx: { //*new_replacement(new_op.matrix_to_mtx.dest) = *Matrix_GetCurrent(); if (old_op.matrix_to_mtx.has_adjusted && new_op.matrix_to_mtx.has_adjusted) { - interpolate_mtxf(&tmp_mtxf, &old_op.matrix_to_mtx.src, &new_op.matrix_to_mtx.src); + interpolate_mtxf(&tmp_mtxf, &old_op.matrix_to_mtx.src, &new_op.matrix_to_mtx.src, + false); SkinMatrix_MtxFMtxFMult(&actor_mtx, &tmp_mtxf, new_replacement(new_op.matrix_to_mtx.dest)); } else { interpolate_mtxf(new_replacement(new_op.matrix_to_mtx.dest), &old_op.matrix_to_mtx.src, - &new_op.matrix_to_mtx.src); + &new_op.matrix_to_mtx.src, false); } break; } case Op::MatrixReplaceRotation: interpolate_mtxf(&tmp_mtxf, &old_op.matrix_replace_rotation.mf, - &new_op.matrix_replace_rotation.mf); + &new_op.matrix_replace_rotation.mf, false); Matrix_ReplaceRotation(&tmp_mtxf); break; @@ -617,15 +892,16 @@ void FrameInterpolation_RecordMatrixSetTranslateRotateYXZ(f32 translateX, f32 tr } } -void FrameInterpolation_RecordMatrixMtxFToMtx(MtxF* src, Mtx* dest) { +void FrameInterpolation_RecordMatrixMtxFToMtx(MtxF* src, Mtx* dest, bool isViewMtx) { if (!is_recording) return; - append(Op::MatrixMtxFToMtx).matrix_mtxf_to_mtx = { *src, dest }; + append(Op::MatrixMtxFToMtx).matrix_mtxf_to_mtx = { *src, dest, isViewMtx }; } void FrameInterpolation_RecordMatrixToMtx(Mtx* dest, char* file, s32 line) { if (!is_recording) return; + auto& d = append(Op::MatrixToMtx).matrix_to_mtx = { dest }; if (has_inv_actor_mtx && !ignore_inv_actor_mtx) { d.has_adjusted = true; @@ -647,10 +923,10 @@ void FrameInterpolation_RecordMatrixRotateAxis(f32 angle, Vec3f* axis, u8 mode) append(Op::MatrixRotateAxis).matrix_rotate_axis = { angle, *axis, mode }; } -void FrameInterpolation_RecordSkinMatrixMtxFToMtx(MtxF* src, Mtx* dest) { +void FrameInterpolation_RecordSkinMatrixMtxFToMtx(MtxF* src, Mtx* dest, bool isViewMtx) { if (!is_recording) return; - FrameInterpolation_RecordMatrixMtxFToMtx(src, dest); + FrameInterpolation_RecordMatrixMtxFToMtx(src, dest, isViewMtx); } // https://stackoverflow.com/questions/1148309/inverting-a-4x4-matrix diff --git a/mm/2s2h/Enhancements/FrameInterpolation/FrameInterpolation.h b/mm/2s2h/Enhancements/FrameInterpolation/FrameInterpolation.h index a805b52909..d1e2e5e894 100644 --- a/mm/2s2h/Enhancements/FrameInterpolation/FrameInterpolation.h +++ b/mm/2s2h/Enhancements/FrameInterpolation/FrameInterpolation.h @@ -52,7 +52,7 @@ void FrameInterpolation_RecordMatrixTranslateRotateZYX(Vec3f* translation, Vec3s void FrameInterpolation_RecordMatrixSetTranslateRotateYXZ(f32 translateX, f32 translateY, f32 translateZ, Vec3s* rot); -void FrameInterpolation_RecordMatrixMtxFToMtx(MtxF* src, Mtx* dest); +void FrameInterpolation_RecordMatrixMtxFToMtx(MtxF* src, Mtx* dest, bool isViewMtx); void FrameInterpolation_RecordMatrixToMtx(Mtx* dest, char* file, s32 line); @@ -60,7 +60,7 @@ void FrameInterpolation_RecordMatrixReplaceRotation(MtxF* mf); void FrameInterpolation_RecordMatrixRotateAxis(f32 angle, Vec3f* axis, u8 mode); -void FrameInterpolation_RecordSkinMatrixMtxFToMtx(MtxF* src, Mtx* dest); +void FrameInterpolation_RecordSkinMatrixMtxFToMtx(MtxF* src, Mtx* dest, bool isViewMtx); #ifdef __cplusplus } diff --git a/mm/2s2h/resource/importer/AudioSampleFactory.cpp b/mm/2s2h/resource/importer/AudioSampleFactory.cpp index f3c5db017f..a9f9574d81 100644 --- a/mm/2s2h/resource/importer/AudioSampleFactory.cpp +++ b/mm/2s2h/resource/importer/AudioSampleFactory.cpp @@ -135,7 +135,8 @@ static void FlacDecoderWorker(std::shared_ptr audioSample, std drflac_close(flac); } -static void OggDecoderWorker(std::shared_ptr audioSample, std::shared_ptr sampleFile) { +static void OggDecoderWorker(std::shared_ptr audioSample, std::shared_ptr sampleFile, + std::shared_ptr initData) { OggVorbis_File vf; char dataBuff[4096]; long read = 0; @@ -178,7 +179,7 @@ static void OggDecoderWorker(std::shared_ptr audioSample, std: } case OggType::None: { char buff[2048]; - snprintf(buff, 2048, "Ogg file %s is not Vorbis or OPUS", sampleFile->InitData->Path.c_str()); + snprintf(buff, 2048, "Ogg file %s is not Vorbis or OPUS", initData->Path.c_str()); throw std::runtime_error(buff); break; } @@ -310,7 +311,7 @@ ResourceFactoryXMLAudioSampleV0::ReadResource(std::shared_ptr file, fileDecoderThread.detach(); return audioSample; } else if (strcmp(customFormatStr, "ogg") == 0) { - std::thread fileDecoderThread = std::thread(OggDecoderWorker, audioSample, sampleFile); + std::thread fileDecoderThread = std::thread(OggDecoderWorker, audioSample, sampleFile, initData); fileDecoderThread.detach(); return audioSample; } else if (strcmp(customFormatStr, "flac") == 0) { diff --git a/mm/CMakeLists.txt b/mm/CMakeLists.txt index 6d750b6b02..5e22cccf5b 100644 --- a/mm/CMakeLists.txt +++ b/mm/CMakeLists.txt @@ -37,6 +37,15 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(dr_libs) +include(FetchContent) +FetchContent_Declare( + glm + GIT_REPOSITORY https://github.com/g-truc/glm.git + GIT_TAG bf71a834948186f4097caa076cd2663c69a10e1e #refs/tags/1.0.1 +) + +FetchContent_MakeAvailable(glm) + ################################################################################ # Global configuration types ################################################################################ @@ -324,6 +333,7 @@ target_include_directories(${PROJECT_NAME} PRIVATE assets ${BOOST-INCLUDE} ${CMAKE_CURRENT_SOURCE_DIR}/assets/ ${dr_libs_SOURCE_DIR} + ${FETCHCONTENT_BASE_DIR}/glm-src/ . ) diff --git a/mm/include/gfx.h b/mm/include/gfx.h index ab031b0758..917e2518d6 100644 --- a/mm/include/gfx.h +++ b/mm/include/gfx.h @@ -233,8 +233,11 @@ Gfx* Gfx_BranchTexScroll(Gfx** gfxp, u32 x, u32 y, s32 width, s32 height); void func_8012CB04(Gfx** gfxp, u32 x, u32 y); Gfx* func_8012CB28(GraphicsContext* gfxCtx, u32 x, u32 y); Gfx* Gfx_TexScroll(GraphicsContext* gfxCtx, u32 x, u32 y, s32 width, s32 height); +Gfx* Gfx_TexScrollEx(GraphicsContext* gfxCtx, u32 x, u32 y, s32 width, s32 height, s32 xStep, s32 yStep); Gfx* Gfx_TwoTexScroll(GraphicsContext* gfxCtx, s32 tile1, u32 x1, u32 y1, s32 width1, s32 height1, s32 tile2, u32 x2, u32 y2, s32 width2, s32 height2); +Gfx* Gfx_TwoTexScrollEx(GraphicsContext* gfxCtx, s32 tile1, u32 x1, u32 y1, s32 width1, s32 height1, s32 tile2, u32 x2, + u32 y2, s32 width2, s32 height2, s32 xStep1, s32 yStep1, s32 xStep2, s32 yStep2); Gfx* Gfx_TwoTexScrollEnvColor(GraphicsContext* gfxCtx, s32 tile1, u32 x1, u32 y1, s32 width1, s32 height1, s32 tile2, u32 x2, u32 y2, s32 width2, s32 height2, s32 r, s32 g, s32 b, s32 a); Gfx* Gfx_EnvColor(GraphicsContext* gfxCtx, s32 r, s32 g, s32 b, s32 a); @@ -243,10 +246,12 @@ void func_8012CF0C(GraphicsContext* gfxCtx, s32 clearFb, s32 clearZb, u8 r, u8 g void func_8012D374(GraphicsContext* gfxCtx, u8 r, u8 g, u8 b); void func_8012D40C(f32* param_1, f32* param_2, s16* param_3); void gSPSegment(void* value, int segNum, uintptr_t target); +void gSPSegmentInterp(void* value, int segNum, uintptr_t target); void gSPSegmentLoadRes(void* value, int segNum, uintptr_t target); // void gDPSetTextureImage(Gfx* pkt, u32 format, u32 size, u32 width, uintptr_t i); // void gDPSetTextureImageFB(Gfx* pkt, u32 format, u32 size, u32 width, int fb); void gSPDisplayList(Gfx* pkt, Gfx* dl); +int gDPSetTileSizeInterp(Gfx* pkt, int t, float uls, float ult, float lrs, float lrt); void gSPDisplayListOffset(Gfx* pkt, Gfx* dl, int offset); void gSPVertex(Gfx* pkt, uintptr_t v, int n, int v0); void gSPInvalidateTexCache(Gfx* pkt, uintptr_t texAddr); diff --git a/mm/include/sys_matrix.h b/mm/include/sys_matrix.h index a471e4dde3..d22c25b3d9 100644 --- a/mm/include/sys_matrix.h +++ b/mm/include/sys_matrix.h @@ -49,7 +49,7 @@ void Matrix_SetTranslateRotateYXZ(f32 x, f32 y, f32 z, Vec3s* rot); /* Conversion and allocation operations */ -Mtx* Matrix_MtxFToMtx(MtxF* src, Mtx* dest); +Mtx* Matrix_MtxFToMtx(MtxF* src, Mtx* dest, bool isViewMtx); Mtx* Matrix_ToMtx(Mtx* dest); Mtx* Matrix_NewMtx(struct GraphicsContext* gfxCtx); Mtx* Matrix_MtxFToNewMtx(MtxF* src, struct GraphicsContext* gfxCtx); diff --git a/mm/include/z64animation.h b/mm/include/z64animation.h index 973d5f0b19..36aa411e99 100644 --- a/mm/include/z64animation.h +++ b/mm/include/z64animation.h @@ -176,7 +176,8 @@ typedef struct SkelAnime { /* 0x14 */ f32 animLength; // Total number of frames in the current animation's file. /* 0x18 */ f32 curFrame; // Current frame in the animation /* 0x1C */ f32 playSpeed; // Multiplied by R_UPDATE_RATE / 3 to get the animation's frame rate. - /* 0x20 */ Vec3s* jointTable; // Current translation of model and rotations of all limbs + /* 0x20 */ Vec3s* jointTable; // Current translation of model and rotations of all limbs + /* 0x20 */ Vec3s* extraJointTable; // For interpolation on skinned skeletons /* 0x24 */ Vec3s* morphTable; // Table of values used to morph between animations /* 0x28 */ f32 morphWeight; // Weight of the current animation morph as a fraction in [0,1] /* 0x2C */ f32 morphRate; // Reciprocal of the number of frames in the morph @@ -189,6 +190,9 @@ typedef struct SkelAnime { /* 0x36 */ s16 prevRot; // Previous rotation in worldspace. /* 0x38 */ Vec3s prevTransl; // Previous modelspace translation. /* 0x3E */ Vec3s baseTransl; // Base modelspace translation. + + bool isSkinned; + } SkelAnime; // size = 0x44 typedef s32 (*OverrideLimbDrawOpa)(struct PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* pos, Vec3s* rot, diff --git a/mm/include/z64math.h b/mm/include/z64math.h index 621c2d5b73..ada21e4c27 100644 --- a/mm/include/z64math.h +++ b/mm/include/z64math.h @@ -21,6 +21,13 @@ typedef struct { /* 0x8 */ f32 z; } Vec3f; // size = 0xC +typedef struct { + /* 0x0 */ f32 x; + /* 0x4 */ f32 y; + /* 0x8 */ f32 z; + /* 0xC */ f32 w; +} Vec4f; + typedef struct { /* 0x0 */ u16 x; /* 0x2 */ u16 y; diff --git a/mm/include/z64skin.h b/mm/include/z64skin.h index a9e2ebc011..fba9fcf233 100644 --- a/mm/include/z64skin.h +++ b/mm/include/z64skin.h @@ -7,6 +7,13 @@ struct GraphicsContext; struct GameState; struct PlayState; +// At 30fps, you have 2 interpolation frames +// At 60fps, you have 3. +// At 120fps, you have 6. +// Do monitors go beyond 144hz? +// We support up to 360fps, so let's go with that. +#define MAX_INTERP_FRAMES 18 + /** * Holds a compact version of a vertex used in the Skin system (doesn't has the x, y, z positions or the flag member) * It is used to initialise the Vtx used by an animated limb @@ -67,7 +74,7 @@ typedef struct { typedef struct { /* 0x0 */ u8 index; // alternates every draw cycle - /* 0x4 */ Vtx* buf[2]; // number of vertices in buffer determined by `totalVtxCount` + /* 0x4 */ Vtx* buf[2 * MAX_INTERP_FRAMES]; // number of vertices in buffer determined by `totalVtxCount` } SkinLimbVtx; // size = 0xC typedef struct { @@ -101,6 +108,6 @@ void Skin_InitAnimatedLimb(struct GameState* gameState, Skin* skin, s32 limbInde void Skin_Init(struct GameState* gameState, Skin* skin, SkeletonHeader* skeletonHeader, AnimationHeader* animationHeader); void Skin_Free(struct GameState* gameState, Skin* skin); s32 func_801387D4(Skin* skin, SkinLimb** skeleton, MtxF* limbMatrices, u8 parentIndex, u8 limbIndex); -s32 Skin_ApplyAnimTransformations(Skin* skin, MtxF* limbMatrices, Actor* actor, s32 setTranslation); +s32 Skin_ApplyAnimTransformations(Skin* skin, MtxF* limbMatrices, Actor* actor, s32 setTranslation, Vec3s* jointRot); #endif diff --git a/mm/src/code/stubs.c b/mm/src/code/stubs.c index da40b1a1a9..009ff8d205 100644 --- a/mm/src/code/stubs.c +++ b/mm/src/code/stubs.c @@ -167,6 +167,27 @@ void gSPSegment(void* value, int segNum, uintptr_t target) { __gSPSegment(value, segNum, target); } +void gSPSegmentInterp(void* value, int segNum, uintptr_t target) { + char* imgData = (char*)target; + + int res = ResourceMgr_OTRSigCheck(imgData); + + // OTRTODO: Disabled for now to fix an issue with HD Textures. + // With HD textures, we need to pass the path to F3D, not the raw texture data. + // Otherwise the needed metadata is not available for proper rendering... + // This should *not* cause any crashes, but some testing may be needed... + // UPDATE: To maintain compatability it will still do the old behavior if the resource is a display list. + // That should not affect HD textures. + if (res) { + uintptr_t desiredTarget = (uintptr_t)ResourceMgr_LoadIfDListByName(imgData); + + if (desiredTarget != NULL) + target = desiredTarget; + } + + __gSPSegmentInterp(value, segNum, target); +} + void gSPSegmentLoadRes(void* value, int segNum, uintptr_t target) { char* imgData = (char*)target; @@ -200,6 +221,14 @@ void gSPDisplayList(Gfx* pkt, Gfx* dl) { __gSPDisplayList(pkt, dl); } +int gDPSetTileSizeInterp(Gfx* pkt, int t, float uls, float ult, float lrs, float lrt) { + __gDPSetTileSizeInterp(pkt++, t, 0, 0, 0, 0); + memcpy(&pkt[0].words.w0, &uls, sizeof(float)); + memcpy(&pkt[0].words.w1, &ult, sizeof(float)); + memcpy(&pkt[1].words.w0, &lrs, sizeof(float)); + memcpy(&pkt[1].words.w1, &lrt, sizeof(float)); +} + void gSPDisplayListOffset(Gfx* pkt, Gfx* dl, int offset) { char* imgData = (char*)dl; @@ -633,7 +662,7 @@ void guOrtho(Mtx* m, float l, float r, float b, float t, float n, float f, float float mf[4][4]; guOrthoF(mf, l, r, b, t, n, f, scale); FrameInterpolation_RecordOpenChild("ortho", 0); - Matrix_MtxFToMtx((MtxF*)mf, m); + Matrix_MtxFToMtx((MtxF*)mf, m, false); FrameInterpolation_RecordCloseChild(); // guMtxF2L(mf, m); } @@ -678,7 +707,7 @@ void guPerspective(Mtx* m, u16* perspNorm, float fovy, float aspect, float near, float mf[4][4]; guPerspectiveF(mf, perspNorm, fovy, aspect, near, far, scale); - Matrix_MtxFToMtx((MtxF*)mf, m); + Matrix_MtxFToMtx((MtxF*)mf, m, false); // guPerspectiveF(mf, perspNorm, fovy, aspect, near, far, scale); // guMtxF2L(mf, m); } @@ -745,7 +774,7 @@ void guLookAt(Mtx* m, f32 xEye, f32 yEye, f32 zEye, f32 xAt, f32 yAt, f32 zAt, f guLookAtF(mf, xEye, yEye, zEye, xAt, yAt, zAt, xUp, yUp, zUp); - Matrix_MtxFToMtx((MtxF*)mf, m); + Matrix_MtxFToMtx((MtxF*)mf, m, true); // guMtxF2L(mf, m); } void guRotateF(float m[4][4], float a, float x, float y, float z) { diff --git a/mm/src/code/sys_matrix.c b/mm/src/code/sys_matrix.c index 2a13f41257..3fa99a7c9b 100644 --- a/mm/src/code/sys_matrix.c +++ b/mm/src/code/sys_matrix.c @@ -1181,8 +1181,8 @@ void Matrix_SetTranslateRotateYXZ(f32 x, f32 y, f32 z, Vec3s* rot) { * * @remark original name: "_MtxF_to_Mtx" */ -Mtx* Matrix_MtxFToMtx(MtxF* src, Mtx* dest) { - FrameInterpolation_RecordMatrixMtxFToMtx(src, dest); +Mtx* Matrix_MtxFToMtx(MtxF* src, Mtx* dest, bool isViewMtx) { + FrameInterpolation_RecordMatrixMtxFToMtx(src, dest, isViewMtx); // 2S2H [Port] For compatibility with modern systems this has been changed to use guMtxF2L guMtxF2L(src, dest); return dest; @@ -1231,7 +1231,7 @@ Mtx* Matrix_NewMtx(GraphicsContext* gfxCtx) { * @remark original name unknown, likely close to "_Matrix_MtxF_to_Mtx_new" */ Mtx* Matrix_MtxFToNewMtx(MtxF* src, GraphicsContext* gfxCtx) { - return Matrix_MtxFToMtx(src, GRAPH_ALLOC(gfxCtx, sizeof(Mtx))); + return Matrix_MtxFToMtx(src, GRAPH_ALLOC(gfxCtx, sizeof(Mtx)), false); } /** diff --git a/mm/src/code/z_play.c b/mm/src/code/z_play.c index 713f349585..e5ba06a780 100644 --- a/mm/src/code/z_play.c +++ b/mm/src/code/z_play.c @@ -1278,7 +1278,7 @@ void Play_DrawMain(PlayState* this) { this->billboardMtx = GRAPH_ALLOC(this->state.gfxCtx, 2 * sizeof(Mtx)); - Matrix_MtxFToMtx(&this->billboardMtxF, this->billboardMtx); + Matrix_MtxFToMtx(&this->billboardMtxF, this->billboardMtx, false); Matrix_RotateYF(BINANG_TO_RAD((s16)(Camera_GetCamDirYaw(GET_ACTIVE_CAM(this)) + 0x8000)), MTXMODE_NEW); Matrix_ToMtx(this->billboardMtx + 1); diff --git a/mm/src/code/z_rcp.c b/mm/src/code/z_rcp.c index 3adec7b982..75b2162b05 100644 --- a/mm/src/code/z_rcp.c +++ b/mm/src/code/z_rcp.c @@ -1378,32 +1378,89 @@ Gfx* func_8012CB28(GraphicsContext* gfxCtx, u32 x, u32 y) { } Gfx* Gfx_TexScroll(GraphicsContext* gfxCtx, u32 x, u32 y, s32 width, s32 height) { - Gfx* gfx = GRAPH_ALLOC(gfxCtx, 3 * sizeof(Gfx)); + Gfx_TexScrollEx(gfxCtx, x, y, width, height, 0, 0); +} + +Gfx* Gfx_TexScrollEx(GraphicsContext* gfxCtx, u32 x, u32 y, s32 width, s32 height, s32 xStep, s32 yStep) { + int interpFrames = Ship_GetInterpolationFrameCount(); + + Gfx* gfx = GRAPH_ALLOC(gfxCtx, (3 + (interpFrames * 4)) * sizeof(Gfx)); x %= 2048; y %= 2048; - gDPTileSync(&gfx[0]); - gDPSetTileSize(&gfx[1], 0, x, y, (x + ((width - 1) << 2)), (y + ((height - 1) << 2))); - gSPEndDisplayList(&gfx[2]); + float xFlt = (float)x; + float yFlt = (float)y; + + float xInc = (float)xStep / (float)interpFrames; + float yInc = (float)yStep / (float)interpFrames; + + int idx = 0; + + gDPTileSync(&gfx[idx++]); + + for (int i = 0; i < interpFrames; i++) { + gDPSetInterpolation(&gfx[idx++], i); + gDPSetTileSizeInterp(&gfx[idx], 0, xFlt, yFlt, (xFlt + ((width - 1) << 2)), (yFlt + ((height - 1) << 2))); + idx += 3; + + xFlt += xInc; + yFlt += yInc; + } + + gSPEndDisplayList(&gfx[idx++]); return gfx; } Gfx* Gfx_TwoTexScroll(GraphicsContext* gfxCtx, s32 tile1, u32 x1, u32 y1, s32 width1, s32 height1, s32 tile2, u32 x2, u32 y2, s32 width2, s32 height2) { - Gfx* gfx = GRAPH_ALLOC(gfxCtx, 5 * sizeof(Gfx)); + Gfx_TwoTexScrollEx(gfxCtx, tile1, x1, y1, width1, height1, tile2, x2, y2, width2, height2, 0, 0, 0, 0); +} + +Gfx* Gfx_TwoTexScrollEx(GraphicsContext* gfxCtx, s32 tile1, u32 x1, u32 y1, s32 width1, s32 height1, s32 tile2, u32 x2, + u32 y2, s32 width2, s32 height2, s32 xStep1, s32 yStep1, s32 xStep2, s32 yStep2) { + int interpFrames = Ship_GetInterpolationFrameCount(); + + Gfx* gfx = GRAPH_ALLOC(gfxCtx, (5 + (interpFrames * 7)) * sizeof(Gfx)); x1 %= 2048; y1 %= 2048; x2 %= 2048; y2 %= 2048; - gDPTileSync(&gfx[0]); - gDPSetTileSize(&gfx[1], tile1, x1, y1, (x1 + ((width1 - 1) << 2)), (y1 + ((height1 - 1) << 2))); - gDPTileSync(&gfx[2]); - gDPSetTileSize(&gfx[3], tile2, x2, y2, (x2 + ((width2 - 1) << 2)), (y2 + ((height2 - 1) << 2))); - gSPEndDisplayList(&gfx[4]); + float x1Flt = (float)x1; + float y1Flt = (float)y1; + float x2Flt = (float)x2; + float y2Flt = (float)y2; + + int index = 0; + + gDPTileSync(&gfx[index++]); + + float xInc1 = (float)xStep1 / (float)interpFrames; + float yInc1 = (float)yStep1 / (float)interpFrames; + + float xInc2 = (float)xStep2 / (float)interpFrames; + float yInc2 = (float)yStep2 / (float)interpFrames; + + for (int i = 0; i < interpFrames; i++) { + gDPSetInterpolation(&gfx[index++], i); + + gDPSetTileSizeInterp(&gfx[index], tile1, x1Flt, y1Flt, (x1Flt + ((width1 - 1) << 2)), + (y1Flt + ((height1 - 1) << 2))); + index += 3; + gDPSetTileSizeInterp(&gfx[index], tile2, x2Flt, y2Flt, (x2Flt + ((width2 - 1) << 2)), + (y2Flt + ((height2 - 1) << 2))); + index += 3; + + x1Flt += xInc1; + x2Flt += xInc2; + y1Flt += yInc1; + y2Flt += yInc2; + } + + gSPEndDisplayList(&gfx[index++]); return gfx; } diff --git a/mm/src/code/z_scene_proc.c b/mm/src/code/z_scene_proc.c index ab21c94ff8..1abaaa381b 100644 --- a/mm/src/code/z_scene_proc.c +++ b/mm/src/code/z_scene_proc.c @@ -86,9 +86,10 @@ void AnimatedMat_DrawTexScroll(PlayState* play, s32 segment, void* params) { * Returns a pointer to a two layer texture scroll displaylist. */ Gfx* AnimatedMat_TwoLayerTexScroll(PlayState* play, AnimatedMatTexScrollParams* params) { - return Gfx_TwoTexScroll(play->state.gfxCtx, 0, params[0].xStep * sMatAnimStep, -(params[0].yStep * sMatAnimStep), - params[0].width, params[0].height, 1, params[1].xStep * sMatAnimStep, - -(params[1].yStep * sMatAnimStep), params[1].width, params[1].height); + return Gfx_TwoTexScrollEx(play->state.gfxCtx, 0, params[0].xStep * sMatAnimStep, -(params[0].yStep * sMatAnimStep), + params[0].width, params[0].height, 1, params[1].xStep * sMatAnimStep, + -(params[1].yStep * sMatAnimStep), params[1].width, params[1].height, params[0].xStep, + -(params[0].yStep), params[1].xStep, -(params[1].yStep)); } /** diff --git a/mm/src/code/z_skelanime.c b/mm/src/code/z_skelanime.c index 924d368f9c..676718d7b6 100644 --- a/mm/src/code/z_skelanime.c +++ b/mm/src/code/z_skelanime.c @@ -620,30 +620,93 @@ void SkelAnime_DrawTransformFlexOpa(PlayState* play, void** skeleton, Vec3s* joi CLOSE_DISPS(play->state.gfxCtx); } +// TODO: Put this in a dedicated math file +// Actually there might already be something that accomplishes this... +static s16 LerpS16(float a, float b, float t) { + return (s16)(a + (b - a) * t); +} + /** * Copies frame data from the frame data table, indexed by the joint index table. * Indices below staticIndexMax are copied from that entry in the static frame data table. * Indices above staticIndexMax are offsets to a frame data array indexed by the frame. */ -void SkelAnime_GetFrameData(AnimationHeader* animation, s32 frame, s32 limbCount, Vec3s* frameTable) { +void SkelAnime_GetFrameData(AnimationHeader* animation, float frame, float animFrameCount, float animSpeed, + s32 limbCount, Vec3s* frameTable, Vec3s* interpFrameTable, bool isSkinnedSkeleton) { if (ResourceMgr_OTRSigCheck(animation)) animation = ResourceMgr_LoadAnimByName(animation); - AnimationHeader* animHeader = Lib_SegmentedToVirtual(animation); - JointIndex* jointIndices = Lib_SegmentedToVirtual(animHeader->jointIndices); - s16* frameData = Lib_SegmentedToVirtual(animHeader->frameData); - s16* dynamicData = &frameData[frame]; - s32 i; - u16 staticIndexMax = animHeader->staticIndexMax; + if (animSpeed != 0) { + // TODO: This feels wrong, but it prevents graphical issues + // Need to give this some proper thought later + if (animSpeed > 0) + animSpeed = 1; + else + animSpeed = -1; + } + + float fpsDiv = Ship_GetInterpolationFPS() / 20.0f; + + Vec3s* originalFrameTable = frameTable; + int interpolatedFrameCount = isSkinnedSkeleton ? Ship_GetInterpolationFrameCount() : 1; + + for (int j = 0; j < Ship_GetInterpolationFrameCount(); j++) { + if (j > 0) + frameTable = &interpFrameTable[limbCount * (j - 1)]; + + float framePerc = frame - (s32)frame; + s32 frameBase = (s32)frame; + s32 frameBaseNext = (s32)(frame + animSpeed); + + if (frameBaseNext < frameBase) { + int tmp = frameBase; + frameBase = frameBaseNext; + frameBaseNext = tmp; + } + + if (animFrameCount != 0) { + frameBase = fmodf(frameBase, animFrameCount); + frameBaseNext = fmodf(frameBaseNext, animFrameCount); + } + + if (frameBase < 0) + frameBase = animFrameCount - 1; + + if (frameBaseNext < 0) + frameBaseNext = animFrameCount - 1; - for (i = 0; i < limbCount; i++) { - // Debug prints here, this is needed to prevent loop unrolling - if ((frameTable == NULL) || (jointIndices == NULL) || (dynamicData == NULL)) {} - frameTable->x = jointIndices->x >= staticIndexMax ? dynamicData[jointIndices->x] : frameData[jointIndices->x]; - frameTable->y = jointIndices->y >= staticIndexMax ? dynamicData[jointIndices->y] : frameData[jointIndices->y]; - frameTable->z = jointIndices->z >= staticIndexMax ? dynamicData[jointIndices->z] : frameData[jointIndices->z]; - jointIndices++; - frameTable++; + AnimationHeader* animHeader = Lib_SegmentedToVirtual(animation); + JointIndex* jointIndices = Lib_SegmentedToVirtual(animHeader->jointIndices); + s16* frameData = Lib_SegmentedToVirtual(animHeader->frameData); + s16* dynamicData = &frameData[frameBase]; + s16* dynamicDataNext = &frameData[frameBaseNext]; + s32 i; + u16 staticIndexMax = animHeader->staticIndexMax; + + for (i = 0; i < limbCount; i++) { + s16 dynamicDataX = 0; + s16 dynamicDataY = 0; + s16 dynamicDataZ = 0; + + if (isSkinnedSkeleton) { + dynamicDataX = LerpS16(dynamicData[jointIndices->x], dynamicDataNext[jointIndices->x], framePerc); + dynamicDataY = LerpS16(dynamicData[jointIndices->y], dynamicDataNext[jointIndices->y], framePerc); + dynamicDataZ = LerpS16(dynamicData[jointIndices->z], dynamicDataNext[jointIndices->z], framePerc); + } else { + dynamicDataX = dynamicData[jointIndices->x]; + dynamicDataY = dynamicData[jointIndices->y]; + dynamicDataZ = dynamicData[jointIndices->z]; + } + + frameTable->x = jointIndices->x >= staticIndexMax ? dynamicDataX : frameData[jointIndices->x]; + frameTable->y = jointIndices->y >= staticIndexMax ? dynamicDataY : frameData[jointIndices->y]; + frameTable->z = jointIndices->z >= staticIndexMax ? dynamicDataZ : frameData[jointIndices->z]; + + jointIndices++; + frameTable++; + } + + frame += animSpeed / fpsDiv; } } @@ -1265,6 +1328,8 @@ void SkelAnime_InitPlayer(PlayState* play, SkelAnime* skelAnime, FlexSkeletonHea skelAnime->morphTable = (void*)ALIGN16((uintptr_t)morphTableBuffer); } + skelAnime->extraJointTable = ZeldaArena_Malloc(allocSize * MAX_INTERP_FRAMES); + PlayerAnimation_Change(play, skelAnime, animation, 1.0f, 0.0f, 0.0f, ANIMMODE_LOOP, 0.0f); } @@ -1587,6 +1652,9 @@ void SkelAnime_Init(PlayState* play, SkelAnime* skelAnime, SkeletonHeader* skele skelAnime->morphTable = morphTable; } + skelAnime->extraJointTable = + ZeldaArena_Malloc(sizeof(*skelAnime->jointTable) * skelAnime->limbCount * MAX_INTERP_FRAMES); + if (animation != NULL) { Animation_PlayLoop(skelAnime, animation); } @@ -1617,6 +1685,9 @@ void SkelAnime_InitFlex(PlayState* play, SkelAnime* skelAnime, FlexSkeletonHeade skelAnime->morphTable = morphTable; } + skelAnime->extraJointTable = + ZeldaArena_Malloc(sizeof(*skelAnime->jointTable) * skelAnime->limbCount * MAX_INTERP_FRAMES); + if (animation != NULL) { Animation_PlayLoop(skelAnime, animation); } @@ -1639,6 +1710,11 @@ void SkelAnime_InitSkin(GameState* gameState, SkelAnime* skelAnime, SkeletonHead skelAnime->jointTable = ZeldaArena_Malloc(sizeof(*skelAnime->jointTable) * skelAnime->limbCount); skelAnime->morphTable = ZeldaArena_Malloc(sizeof(*skelAnime->morphTable) * skelAnime->limbCount); + skelAnime->extraJointTable = + ZeldaArena_Malloc(sizeof(*skelAnime->jointTable) * skelAnime->limbCount * MAX_INTERP_FRAMES); + + skelAnime->isSkinned = true; + // Debug prints here, required to match. if (1) {} @@ -1723,9 +1799,12 @@ s32 SkelAnime_MorphTaper(SkelAnime* skelAnime) { * Gets frame data for the current frame as modified by morphTable and advances the morph */ void SkelAnime_AnimateFrame(SkelAnime* skelAnime) { - Vec3s nextjointTable[100]; + Vec3s nextjointTable[100 * MAX_INTERP_FRAMES]; + + SkelAnime_GetFrameData(skelAnime->animation, skelAnime->curFrame, skelAnime->animLength, skelAnime->playSpeed, + skelAnime->limbCount, skelAnime->jointTable, skelAnime->extraJointTable, + skelAnime->isSkinned); - SkelAnime_GetFrameData(skelAnime->animation, skelAnime->curFrame, skelAnime->limbCount, skelAnime->jointTable); if (skelAnime->mode & ANIM_INTERP) { s32 frame = skelAnime->curFrame; f32 partialFrame = skelAnime->curFrame - frame; @@ -1734,7 +1813,8 @@ void SkelAnime_AnimateFrame(SkelAnime* skelAnime) { if (frame >= (s32)skelAnime->animLength) { frame = 0; } - SkelAnime_GetFrameData(skelAnime->animation, frame, skelAnime->limbCount, nextjointTable); + SkelAnime_GetFrameData(skelAnime->animation, frame, skelAnime->animLength, skelAnime->playSpeed, + skelAnime->limbCount, nextjointTable, skelAnime->extraJointTable, skelAnime->isSkinned); SkelAnime_InterpFrameTable(skelAnime->limbCount, skelAnime->jointTable, skelAnime->jointTable, nextjointTable, partialFrame); } @@ -1791,7 +1871,9 @@ s32 SkelAnime_Once(SkelAnime* skelAnime) { f32 updateRate = gFramerateDivisorThird; if (skelAnime->curFrame == skelAnime->endFrame) { - SkelAnime_GetFrameData(skelAnime->animation, skelAnime->curFrame, skelAnime->limbCount, skelAnime->jointTable); + SkelAnime_GetFrameData(skelAnime->animation, skelAnime->curFrame, skelAnime->animLength, skelAnime->playSpeed, + skelAnime->limbCount, skelAnime->jointTable, skelAnime->extraJointTable, + skelAnime->isSkinned); SkelAnime_AnimateFrame(skelAnime); return true; } @@ -1842,13 +1924,16 @@ void Animation_ChangeImpl(SkelAnime* skelAnime, AnimationHeader* animation, f32 } else { skelAnime->update.normal = SkelAnime_Morph; } - SkelAnime_GetFrameData(animation, startFrame, skelAnime->limbCount, skelAnime->morphTable); + SkelAnime_GetFrameData(animation, startFrame, skelAnime->animLength, skelAnime->playSpeed, + skelAnime->limbCount, skelAnime->morphTable, skelAnime->extraJointTable, + skelAnime->isSkinned); } skelAnime->morphWeight = 1.0f; skelAnime->morphRate = 1.0f / morphFrames; } else { SkelAnime_SetUpdate(skelAnime); - SkelAnime_GetFrameData(animation, startFrame, skelAnime->limbCount, skelAnime->jointTable); + SkelAnime_GetFrameData(animation, startFrame, skelAnime->animLength, skelAnime->playSpeed, skelAnime->limbCount, + skelAnime->jointTable, skelAnime->extraJointTable, skelAnime->isSkinned); skelAnime->morphWeight = 0.0f; } diff --git a/mm/src/code/z_skin.c b/mm/src/code/z_skin.c index 5569c886b1..185edfa8e3 100644 --- a/mm/src/code/z_skin.c +++ b/mm/src/code/z_skin.c @@ -3,7 +3,7 @@ #include "z64skin.h" // 60 is an arbitrary number which specifies the max amount of limbs per skeleton this system supports -MtxF gSkinLimbMatrices[60]; +MtxF gSkinLimbMatrices[60 * MAX_INTERP_FRAMES]; static s32 sBssPad; @@ -75,56 +75,67 @@ void Skin_ApplyLimbModifications(GraphicsContext* gfxCtx, Skin* skin, s32 limbIn vtxEntry = &skin->vtxTable[limbIndex]; - vtxBuf = vtxEntry->buf[vtxEntry->index]; modifCount = data->limbModifCount; - for (modif = modifications; modif < &modifications[modifCount]; modif++) { - Vec3f spAC; - Vec3f spA0; - - skinVertices = (SkinVertex*)Lib_SegmentedToVirtual(modif->skinVertices); - limbTransformations = (SkinTransformation*)Lib_SegmentedToVirtual(modif->limbTransformations); - transformCount = modif->transformCount; - - if (transformCount == 1) { - spAC.x = limbTransformations[0].x; - spAC.y = limbTransformations[0].y; - spAC.z = limbTransformations[0].z; - - SkinMatrix_Vec3fMtxFMultXYZ(&gSkinLimbMatrices[limbTransformations[0].limbIndex], &spAC, &spDC); - } else if (arg3) { - transformationEntry = &limbTransformations[modif->unk_04]; - - spA0.x = transformationEntry->x; - spA0.y = transformationEntry->y; - spA0.z = transformationEntry->z; - SkinMatrix_Vec3fMtxFMultXYZ(&gSkinLimbMatrices[transformationEntry->limbIndex], &spA0, &spDC); - } else { - spDC.x = 0.0f; - spDC.y = 0.0f; - spDC.z = 0.0f; - - for (transformationEntry = limbTransformations; transformationEntry < &limbTransformations[transformCount]; - transformationEntry++) { - scale = transformationEntry->scale * 0.01f; - - sp88.x = transformationEntry->x; - sp88.y = transformationEntry->y; - sp88.z = transformationEntry->z; - - SkinMatrix_Vec3fMtxFMultXYZ(&gSkinLimbMatrices[transformationEntry->limbIndex], &sp88, &spD0); - - spDC.x += spD0.x * scale; - spDC.y += spD0.y * scale; - spDC.z += spD0.z * scale; + int limbCount = skin->limbCount + 1; + + for (int interpIdx = 0; interpIdx < Ship_GetInterpolationFrameCount(); interpIdx++) { + vtxBuf = vtxEntry->buf[vtxEntry->index + (interpIdx * 2)]; + + for (modif = modifications; modif < &modifications[modifCount]; modif++) { + Vec3f spAC; + Vec3f spA0; + + skinVertices = (SkinVertex*)Lib_SegmentedToVirtual(modif->skinVertices); + limbTransformations = (SkinTransformation*)Lib_SegmentedToVirtual(modif->limbTransformations); + transformCount = modif->transformCount; + + if (transformCount == 1) { + spAC.x = limbTransformations[0].x; + spAC.y = limbTransformations[0].y; + spAC.z = limbTransformations[0].z; + + SkinMatrix_Vec3fMtxFMultXYZ( + &gSkinLimbMatrices[(interpIdx * limbCount) + limbTransformations[0].limbIndex], &spAC, &spDC); + } else if (arg3) { + transformationEntry = &limbTransformations[modif->unk_04]; + + spA0.x = transformationEntry->x; + spA0.y = transformationEntry->y; + spA0.z = transformationEntry->z; + SkinMatrix_Vec3fMtxFMultXYZ( + &gSkinLimbMatrices[(interpIdx * limbCount) + transformationEntry->limbIndex], &spA0, &spDC); + } else { + spDC.x = 0.0f; + spDC.y = 0.0f; + spDC.z = 0.0f; + + for (transformationEntry = limbTransformations; + transformationEntry < &limbTransformations[transformCount]; transformationEntry++) { + scale = transformationEntry->scale * 0.01f; + + sp88.x = transformationEntry->x; + sp88.y = transformationEntry->y; + sp88.z = transformationEntry->z; + + SkinMatrix_Vec3fMtxFMultXYZ( + &gSkinLimbMatrices[(interpIdx * limbCount) + transformationEntry->limbIndex], &sp88, &spD0); + + spDC.x += spD0.x * scale; + spDC.y += spD0.y * scale; + spDC.z += spD0.z * scale; + } } + + Skin_UpdateVertices( + &gSkinLimbMatrices[(interpIdx * limbCount) + limbTransformations[modif->unk_04].limbIndex], + skinVertices, modif, vtxBuf, &spDC); } - Skin_UpdateVertices(&gSkinLimbMatrices[limbTransformations[modif->unk_04].limbIndex], skinVertices, modif, - vtxBuf, &spDC); + gSPSegmentInterp(POLY_OPA_DISP++, 0x08 + (interpIdx * 0x10), vtxBuf); } - gSPSegment(POLY_OPA_DISP++, 0x08, vtxEntry->buf[vtxEntry->index]); + // gSPSegment(POLY_OPA_DISP++, 0x08, vtxEntry->buf[vtxEntry->index]); vtxEntry->index = (vtxEntry->index == 0); @@ -192,7 +203,13 @@ void Skin_DrawImpl(Actor* actor, PlayState* play, Skin* skin, SkinPostDraw postD OPEN_DISPS(gfxCtx); if (!(drawFlags & SKIN_DRAW_FLAG_CUSTOM_TRANSFORMS)) { - Skin_ApplyAnimTransformations(skin, gSkinLimbMatrices, actor, setTranslation); + int limbCount = skin->limbCount + 1; + + Skin_ApplyAnimTransformations(skin, gSkinLimbMatrices, actor, setTranslation, skin->skelAnime.jointTable); + + for (int i = 0; i < Ship_GetInterpolationFrameCount(); i++) + Skin_ApplyAnimTransformations(skin, gSkinLimbMatrices + (limbCount * (i + 1)), actor, setTranslation, + &skin->skelAnime.extraJointTable[limbCount * i]); } skeleton = Lib_SegmentedToVirtual(skin->skeletonHeader->segment); diff --git a/mm/src/code/z_skin_awb.c b/mm/src/code/z_skin_awb.c index ab2f4dc3a3..3a7a5b0fd2 100644 --- a/mm/src/code/z_skin_awb.c +++ b/mm/src/code/z_skin_awb.c @@ -71,8 +71,8 @@ void Skin_Init(GameState* gameState, Skin* skin, SkeletonHeader* skeletonHeader, (((SkinLimb*)Lib_SegmentedToVirtual(skeleton[i]))->segment == NULL)) { vtxEntry->index = 0; - vtxEntry->buf[0] = NULL; - vtxEntry->buf[1] = NULL; + for (int j = 0; j < ARRAY_COUNT(vtxEntry->buf); j++) + vtxEntry->buf[j] = NULL; } else { SkinAnimatedLimbData* animatedLimbData = Lib_SegmentedToVirtual((((SkinLimb*)Lib_SegmentedToVirtual(skeleton[i]))->segment)); @@ -80,8 +80,9 @@ void Skin_Init(GameState* gameState, Skin* skin, SkeletonHeader* skeletonHeader, { s32 tmp; } vtxEntry->index = 0; - vtxEntry->buf[0] = ZeldaArena_Malloc(animatedLimbData->totalVtxCount * sizeof(Vtx)); - vtxEntry->buf[1] = ZeldaArena_Malloc(animatedLimbData->totalVtxCount * sizeof(Vtx)); + + for (int j = 0; j < ARRAY_COUNT(skin->vtxTable->buf); j++) + vtxEntry->buf[j] = ZeldaArena_Malloc(animatedLimbData->totalVtxCount * sizeof(Vtx)); Skin_InitAnimatedLimb(gameState, skin, i); } @@ -98,13 +99,11 @@ void Skin_Free(GameState* gameState, Skin* skin) { s32 i; for (i = 0; i < skin->limbCount; i++) { - if (skin->vtxTable[i].buf[0] != NULL) { - ZeldaArena_Free(skin->vtxTable[i].buf[0]); - skin->vtxTable[i].buf[0] = NULL; - } - if (skin->vtxTable[i].buf[1] != NULL) { - ZeldaArena_Free(skin->vtxTable[i].buf[1]); - skin->vtxTable[i].buf[1] = NULL; + for (int j = 0; j < ARRAY_COUNT(skin->vtxTable->buf); j++) { + if (skin->vtxTable[i].buf[j] != NULL) { + ZeldaArena_Free(skin->vtxTable[i].buf[j]); + skin->vtxTable[i].buf[j] = NULL; + } } } @@ -152,7 +151,7 @@ s32 func_801387D4(Skin* skin, SkinLimb** skeleton, MtxF* limbMatrices, u8 parent /** * Recursively applies matrix tranformations to each limb */ -s32 Skin_ApplyAnimTransformations(Skin* skin, MtxF* limbMatrices, Actor* actor, s32 setTranslation) { +s32 Skin_ApplyAnimTransformations(Skin* skin, MtxF* limbMatrices, Actor* actor, s32 setTranslation, Vec3s* jointRot) { s32 i; s32 pad; f32 yRot; @@ -163,7 +162,6 @@ s32 Skin_ApplyAnimTransformations(Skin* skin, MtxF* limbMatrices, Actor* actor, f32 xTransl; f32 zTransl; SkinLimb** skeleton = Lib_SegmentedToVirtual(skin->skeletonHeader->segment); - Vec3s* jointRot = skin->skelAnime.jointTable; jointRot++; xRot = jointRot->x; @@ -194,6 +192,7 @@ s32 Skin_ApplyAnimTransformations(Skin* skin, MtxF* limbMatrices, Actor* actor, yRot = jointRot->y; zRot = jointRot->z; jointRot++; + SkinMatrix_SetRotateRPYTranslate(&limbMatrices[i], xRot, yRot, zRot, xTransl, yTransl, zTransl); } diff --git a/mm/src/code/z_skin_matrix.c b/mm/src/code/z_skin_matrix.c index 1845286b2c..edf02c7ab4 100644 --- a/mm/src/code/z_skin_matrix.c +++ b/mm/src/code/z_skin_matrix.c @@ -507,7 +507,7 @@ void SkinMatrix_Vec3sToVec3f(Vec3s* src, Vec3f* dest) { } void SkinMatrix_MtxFToMtx(MtxF* src, Mtx* dest) { - FrameInterpolation_RecordSkinMatrixMtxFToMtx(src, dest); + FrameInterpolation_RecordSkinMatrixMtxFToMtx(src, dest, false); // 2S2H [Port] For compatibility with modern systems this has been changed to use guMtxF2L guMtxF2L(src, dest); } diff --git a/mm/src/code/z_view.c b/mm/src/code/z_view.c index c7c7e8883c..779b1f7e37 100644 --- a/mm/src/code/z_view.c +++ b/mm/src/code/z_view.c @@ -360,7 +360,7 @@ s32 View_ApplyPerspective(View* view) { MtxF projectionF; Matrix_MtxToMtxF(projection, &projectionF); SkinMatrix_MtxFMtxFMult(&projectionF, &flipF, &projectionF); - Matrix_MtxFToMtx(&projectionF, projectionFlipped); + Matrix_MtxFToMtx(&projectionF, projectionFlipped, false); } view->projection = *projection; @@ -552,7 +552,7 @@ s32 View_ApplyOrthoToOverlay(View* view) { MtxF projectionF; Matrix_MtxToMtxF(projection, &projectionF); SkinMatrix_MtxFMtxFMult(&projectionF, &flipF, &projectionF); - Matrix_MtxFToMtx(&projectionF, projectionFlipped); + Matrix_MtxFToMtx(&projectionF, projectionFlipped, false); } view->projection = *projection; diff --git a/mm/src/overlays/actors/ovl_Arrow_Fire/z_arrow_fire.c b/mm/src/overlays/actors/ovl_Arrow_Fire/z_arrow_fire.c index 74a60aaa04..1215e89724 100644 --- a/mm/src/overlays/actors/ovl_Arrow_Fire/z_arrow_fire.c +++ b/mm/src/overlays/actors/ovl_Arrow_Fire/z_arrow_fire.c @@ -290,8 +290,10 @@ void ArrowFire_Draw(Actor* thisx, PlayState* play) { FireArrow_SetQuadVerticies(this); gSPDisplayList(POLY_XLU_DISP++, gFireArrowMaterialDL); - gSPDisplayList(POLY_XLU_DISP++, Gfx_TwoTexScroll(play->state.gfxCtx, 0, 255 - ((frames * 2) % 256), 0, 64, 32, - 1, 255 - (frames % 256), 511 - ((frames * 10) % 512), 64, 64)); + + gSPDisplayList(POLY_XLU_DISP++, + Gfx_TwoTexScrollEx(play->state.gfxCtx, 0, 255 - ((frames * 2) % 256), 0, 64, 32, 1, + 255 - (frames % 256), 511 - ((frames * 10) % 512), 64, 64, -2, -10, -2, -10)); gSPDisplayList(POLY_XLU_DISP++, gFireArrowModelDL); CLOSE_DISPS(play->state.gfxCtx); diff --git a/mm/src/overlays/actors/ovl_Arrow_Ice/z_arrow_ice.c b/mm/src/overlays/actors/ovl_Arrow_Ice/z_arrow_ice.c index ccc931c16c..ca2eab2f94 100644 --- a/mm/src/overlays/actors/ovl_Arrow_Ice/z_arrow_ice.c +++ b/mm/src/overlays/actors/ovl_Arrow_Ice/z_arrow_ice.c @@ -228,9 +228,9 @@ void ArrowIce_Draw(Actor* thisx, PlayState* play) { Matrix_Translate(0.0f, -700.0f, 0.0f, MTXMODE_APPLY); gSPMatrix(POLY_XLU_DISP++, Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); gSPDisplayList(POLY_XLU_DISP++, gIceArrowMaterialDL); - gSPDisplayList(POLY_XLU_DISP++, - Gfx_TwoTexScroll(play->state.gfxCtx, 0, 511 - (stateFrames * 5) % 512, 0, 128, 32, 1, - 511 - (stateFrames * 10) % 512, 511 - (stateFrames * 10) % 512, 4, 16)); + gSPDisplayList(POLY_XLU_DISP++, Gfx_TwoTexScrollEx(play->state.gfxCtx, 0, 511 - (stateFrames * 5) % 512, 0, 128, + 32, 1, 511 - (stateFrames * 10) % 512, + 511 - (stateFrames * 10) % 512, 4, 16, -5, -10, -5, -10)); gSPDisplayList(POLY_XLU_DISP++, gIceArrowModelDL); CLOSE_DISPS(play->state.gfxCtx); diff --git a/mm/src/overlays/actors/ovl_Arrow_Light/z_arrow_light.c b/mm/src/overlays/actors/ovl_Arrow_Light/z_arrow_light.c index 5acdde3c04..70d7d989ea 100644 --- a/mm/src/overlays/actors/ovl_Arrow_Light/z_arrow_light.c +++ b/mm/src/overlays/actors/ovl_Arrow_Light/z_arrow_light.c @@ -222,9 +222,9 @@ void ArrowLight_Draw(Actor* thisx, PlayState* play) { gSPMatrix(POLY_XLU_DISP++, Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); gSPDisplayList(POLY_XLU_DISP++, gLightArrowMaterialDL); - gSPDisplayList(POLY_XLU_DISP++, - Gfx_TwoTexScroll(play->state.gfxCtx, 0, 511 - ((frames * 5) % 512), 0, 4, 32, 1, - 511 - ((frames * 10) % 512), 511 - ((frames * 30) % 512), 8, 16)); + gSPDisplayList(POLY_XLU_DISP++, Gfx_TwoTexScrollEx(play->state.gfxCtx, 0, 511 - ((frames * 5) % 512), 0, 4, 32, + 1, 511 - ((frames * 10) % 512), 511 - ((frames * 30) % 512), + 8, 16, -5, -30, -5, -30)); gSPDisplayList(POLY_XLU_DISP++, gLightArrowModelDL); CLOSE_DISPS(play->state.gfxCtx); } diff --git a/mm/src/overlays/actors/ovl_En_Horse/z_en_horse.c b/mm/src/overlays/actors/ovl_En_Horse/z_en_horse.c index 9d38823ac9..33540d5c43 100644 --- a/mm/src/overlays/actors/ovl_En_Horse/z_en_horse.c +++ b/mm/src/overlays/actors/ovl_En_Horse/z_en_horse.c @@ -1962,6 +1962,7 @@ void EnHorse_PlayIdleAnimation(EnHorse* this, s32 animIndex, f32 morphFrames, f3 this->stateFlags &= ~ENHORSE_LAND2_SOUND; } } + Animation_Change(&this->skin.skelAnime, sAnimationHeaders[this->type][this->animIndex], 1.0f, startFrames, Animation_GetLastFrame(sAnimationHeaders[this->type][this->animIndex]), ANIMMODE_ONCE, morphFrames); @@ -4202,6 +4203,10 @@ void EnHorse_Update(Actor* thisx, PlayState* play2) { Vec3f dustVel = { 0.0f, 1.0f, 0.0f }; Player* player = GET_PLAYER(play); + for (int i = 0; i < Ship_GetInterpolationFrameCount(); i++) + memcpy(&this->skin.skelAnime.extraJointTable[i * this->skin.skelAnime.limbCount], + this->skin.skelAnime.jointTable, this->skin.skelAnime.limbCount * sizeof(Vec3s)); + if (this->type == HORSE_TYPE_2) { Actor_SetScale(&this->actor, 0.00648f); } else if (this->type == HORSE_TYPE_DONKEY) { @@ -4235,6 +4240,9 @@ void EnHorse_Update(Actor* thisx, PlayState* play2) { this->unk_3EC = thisx->world.rot.y; if ((this->animIndex == ENHORSE_ANIM_STOPPING) || (this->animIndex == ENHORSE_ANIM_REARING)) { this->skin.skelAnime.jointTable[0].y += 0x154; + + for (int i = 0; i < Ship_GetInterpolationFrameCount(); i++) + this->skin.skelAnime.extraJointTable[this->skin.skelAnime.limbCount * i].y += 0x154; } this->curFrame = this->skin.skelAnime.curFrame; @@ -4718,6 +4726,12 @@ void EnHorse_Draw(Actor* thisx, PlayState* play) { this->skin.skelAnime.jointTable->x = 0; this->skin.skelAnime.jointTable->y = 0; this->skin.skelAnime.jointTable->z = 0; + + for (int i = 0; i < Ship_GetInterpolationFrameCount(); i++) { + this->skin.skelAnime.extraJointTable[this->skin.skelAnime.limbCount * i].x = 0; + this->skin.skelAnime.extraJointTable[this->skin.skelAnime.limbCount * i].y = 0; + this->skin.skelAnime.extraJointTable[this->skin.skelAnime.limbCount * i].z = 0; + } } SkelAnime_DrawFlexOpa(play, this->skin.skelAnime.skeleton, this->skin.skelAnime.jointTable, this->skin.skelAnime.dListCount, func_80888D18, NULL, &this->actor); diff --git a/mm/src/overlays/actors/ovl_Obj_Syokudai/z_obj_syokudai.c b/mm/src/overlays/actors/ovl_Obj_Syokudai/z_obj_syokudai.c index fd11eb3255..d24dc0387e 100644 --- a/mm/src/overlays/actors/ovl_Obj_Syokudai/z_obj_syokudai.c +++ b/mm/src/overlays/actors/ovl_Obj_Syokudai/z_obj_syokudai.c @@ -313,8 +313,9 @@ void ObjSyokudai_Draw(Actor* thisx, PlayState* play) { flameScale *= 0.0027f; Gfx_SetupDL25_Xlu(play->state.gfxCtx); gSPSegment(POLY_XLU_DISP++, 0x08, - Gfx_TwoTexScroll(play->state.gfxCtx, 0, 0, 0, 0x20, 0x40, 1, 0, - (this->flameTexScroll * -OBJ_SYOKUDAI_SNUFF_DEFAULT) & 0x1FF, 0x20, 0x80)); + Gfx_TwoTexScrollEx(play->state.gfxCtx, 0, 0, 0, 0x20, 0x40, 1, 0, + (this->flameTexScroll * -OBJ_SYOKUDAI_SNUFF_DEFAULT) & 0x1FF, 0x20, 0x80, 0, + -OBJ_SYOKUDAI_SNUFF_DEFAULT, 0, -OBJ_SYOKUDAI_SNUFF_DEFAULT)); gDPSetPrimColor(POLY_XLU_DISP++, 0x80, 0x80, 255, 255, 0, 255); gDPSetEnvColor(POLY_XLU_DISP++, 255, 0, 0, 0); Matrix_Translate(0.0f, OBJ_SYOKUDAI_FLAME_HEIGHT, 0.0f, MTXMODE_APPLY);