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() {