diff --git a/enhancements/60fps_alpha.patch b/enhancements/60fps_alpha.patch deleted file mode 100644 index 578ce1c0..00000000 --- a/enhancements/60fps_alpha.patch +++ /dev/null @@ -1,1512 +0,0 @@ -diff --git a/include/types.h b/include/types.h -index b3dc27e2..c46bdf01 100644 ---- a/include/types.h -+++ b/include/types.h -@@ -120,2 +120,6 @@ struct GraphNodeObject_sub - /*0x10 0x48*/ s32 animAccel; -+ s16 prevAnimFrame; -+ s16 prevAnimID; -+ u32 prevAnimFrameTimestamp; -+ struct Animation *prevAnimPtr; - }; -@@ -130,3 +134,10 @@ struct GraphNodeObject - /*0x20*/ Vec3f pos; -+ Vec3s prevAngle; -+ Vec3f prevPos; -+ u32 prevTimestamp; -+ Vec3f prevShadowPos; -+ u32 prevShadowPosTimestamp; - /*0x2C*/ Vec3f scale; -+ Vec3f prevScale; -+ u32 prevScaleTimestamp; - /*0x38*/ struct GraphNodeObject_sub curAnim; -@@ -134,3 +145,7 @@ struct GraphNodeObject - /*0x50*/ Mat4 *throwMatrix; // matrix ptr -+ Mat4 prevThrowMatrix; -+ u32 prevThrowMatrixTimestamp; -+ Mat4 *throwMatrixInterpolated; - /*0x54*/ Vec3f cameraToObject; -+ u32 skipInterpolationTimestamp; - }; -@@ -245,2 +260,6 @@ struct Surface - /*0x2C*/ struct Object *object; -+ Vec3s prevVertex1; -+ Vec3s prevVertex2; -+ Vec3s prevVertex3; -+ u32 modifiedTimestamp; - }; -diff --git a/src/engine/graph_node.h b/src/engine/graph_node.h -index 802d97a8..1b0d6772 100644 ---- a/src/engine/graph_node.h -+++ b/src/engine/graph_node.h -@@ -112,2 +112,4 @@ struct GraphNodePerspective - /*0x22*/ s16 far; // far clipping plane -+ f32 prevFov; -+ f32 prevTimestamp; - }; -@@ -120,3 +122,5 @@ struct DisplayListNode - Mtx *transform; -+ void *transformInterpolated; - void *displayList; -+ void *displayListInterpolated; - struct DisplayListNode *next; -@@ -187,3 +191,7 @@ struct GraphNodeCamera - /*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. -@@ -228,3 +236,4 @@ struct GraphNodeRotation - /*0x18*/ Vec3s rotation; -- u8 pad1E[2]; -+ Vec3s prevRotation; -+ u32 prevTimestamp; - }; -@@ -325,2 +334,5 @@ struct GraphNodeBackground - /*0x1C*/ s32 background; // background ID, or rgba5551 color if fnNode.func is null -+ Vec3f prevCameraPos; -+ Vec3f prevCameraFocus; -+ u32 prevCameraTimestamp; - }; -@@ -335,2 +347,4 @@ struct GraphNodeHeldObject - /*0x20*/ Vec3s translation; -+ Vec3f prevShadowPos; -+ u32 prevShadowPosTimestamp; - }; -diff --git a/src/engine/surface_collision.c b/src/engine/surface_collision.c -index 5b6775fe..2c11e254 100644 ---- a/src/engine/surface_collision.c -+++ b/src/engine/surface_collision.c -@@ -10,2 +10,3 @@ - #include "math_util.h" -+#include "game/game_init.h" - -@@ -396,2 +397,4 @@ f32 find_floor_height_and_data(f32 xPos, f32 yPos, f32 zPos, struct FloorGeometr - -+u8 gInterpolatingSurfaces; -+ - /** -@@ -401,3 +404,3 @@ static struct Surface *find_floor_from_list(struct SurfaceNode *surfaceNode, s32 - register struct Surface *surf; -- register s32 x1, z1, x2, z2, x3, z3; -+ register f32 x1, z1, x2, z2, x3, z3; - f32 nx, ny, nz; -@@ -406,2 +409,3 @@ static struct Surface *find_floor_from_list(struct SurfaceNode *surfaceNode, s32 - struct Surface *floor = NULL; -+ s32 interpolate; - -@@ -411,2 +415,3 @@ static struct Surface *find_floor_from_list(struct SurfaceNode *surfaceNode, s32 - surfaceNode = surfaceNode->next; -+ interpolate = gInterpolatingSurfaces && surf->modifiedTimestamp == gGlobalTimer; - -@@ -416,2 +421,16 @@ static struct Surface *find_floor_from_list(struct SurfaceNode *surfaceNode, s32 - 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; -+ } -+ } - -@@ -425,2 +444,6 @@ static struct Surface *find_floor_from_list(struct SurfaceNode *surfaceNode, s32 - z3 = surf->vertex3[2]; -+ if (interpolate) { -+ x3 = (surf->prevVertex3[0] + x3) / 2; -+ z3 = (surf->prevVertex3[2] + z3) / 2; -+ } - -@@ -444,6 +467,26 @@ static struct Surface *find_floor_from_list(struct SurfaceNode *surfaceNode, s32 - -- 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; -+ } - -@@ -462,2 +505,11 @@ 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; -diff --git a/src/engine/surface_load.c b/src/engine/surface_load.c -index ac2ee50c..323b7d05 100644 ---- a/src/engine/surface_load.c -+++ b/src/engine/surface_load.c -@@ -16,2 +16,3 @@ - #include "surface_load.h" -+#include "game/game_init.h" - -@@ -361,2 +362,7 @@ static struct Surface *read_surface_data(s16 *vertexData, s16 **vertexIndices) { - -+ 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; -diff --git a/src/game/camera.c b/src/game/camera.c -index bde06626..aa65c00b 100644 ---- a/src/game/camera.c -+++ b/src/game/camera.c -@@ -486,2 +486,6 @@ extern u8 sZoomOutAreaMasks[]; - -+static void skip_camera_interpolation(void) { -+ gLakituState.skipCameraInterpolationTimestamp = gGlobalTimer; -+} -+ - /** -@@ -5554,2 +5558,3 @@ s32 set_camera_mode_fixed(struct Camera *c, s16 x, s16 y, s16 z) { - sFixedModeBasePosition[2]); -+ skip_camera_interpolation(); - } -@@ -5716,2 +5721,3 @@ BAD_RETURN(s32) cam_rr_enter_building_side(struct Camera *c) { - c->mode = CAMERA_MODE_FIXED; -+ skip_camera_interpolation(); - } -@@ -5911,2 +5917,3 @@ BAD_RETURN(s32) cam_castle_enter_lobby(struct Camera *c) { - c->mode = CAMERA_MODE_FIXED; -+ skip_camera_interpolation(); - } -@@ -7281,2 +7288,3 @@ BAD_RETURN(s32) cutscene_ending_mario_fall_start(struct Camera *c) { - vec3f_set(c->pos, 165.f, 4725.f, 324.f); -+ skip_camera_interpolation(); - } -@@ -7313,2 +7321,3 @@ BAD_RETURN(s32) cutscene_ending_mario_land_closeup(struct Camera *c) { - vec3f_set(c->pos, -51.f, 988.f, -202.f); -+ skip_camera_interpolation(); - player2_rotate_cam(c, -0x2000, 0x2000, -0x2000, 0x2000); -@@ -7322,2 +7331,3 @@ BAD_RETURN(s32) cutscene_ending_reset_spline(UNUSED struct Camera *c) { - cutscene_reset_spline(); -+ skip_camera_interpolation(); - } -@@ -7357,2 +7367,3 @@ BAD_RETURN(s32) cutscene_ending_peach_appear_closeup(struct Camera *c) { - vec3f_set(c->focus, gCutsceneFocus->oPosX, gCutsceneFocus->oPosY + 125.f, gCutsceneFocus->oPosZ); -+ skip_camera_interpolation(); - } -@@ -7375,2 +7386,3 @@ BAD_RETURN(s32) cutscene_ending_peach_descends_start(UNUSED struct Camera *c) { - sCutsceneVars[2].point[1] = 150.f; -+ skip_camera_interpolation(); - } -@@ -7461,2 +7473,3 @@ BAD_RETURN(s32) cutscene_ending_dialog(struct Camera *c) { - vec3f_set(c->pos, -473.f, 970.f, -1152.f); -+ skip_camera_interpolation(); - player2_rotate_cam(c, -0x800, 0x2000, -0x2000, 0x2000); -@@ -7471,2 +7484,3 @@ BAD_RETURN(s32) cutscene_ending_kiss_closeup(struct Camera *c) { - vec3f_set(c->pos, -149.f, 1021.f, -1216.f); -+ skip_camera_interpolation(); - } -@@ -10375,2 +10389,3 @@ 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 173ab8a7..b1abdc4f 100644 ---- a/src/game/camera.h -+++ b/src/game/camera.h -@@ -659,2 +659,4 @@ struct LakituState - /*0xBC*/ s16 unused; -+ -+ u32 skipCameraInterpolationTimestamp; - }; -diff --git a/src/game/envfx_bubbles.c b/src/game/envfx_bubbles.c -index 16a92720..ee1b029d 100644 ---- a/src/game/envfx_bubbles.c -+++ b/src/game/envfx_bubbles.c -@@ -37,2 +37,16 @@ Vtx_t gBubbleTempVtx[3] = { - -+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; -+ } -+ } -+} -+ - /** -@@ -243,2 +257,3 @@ void envfx_update_whirlpool(void) { - (gEnvFxBuffer + i)->isAlive = 1; -+ (gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer; - -@@ -301,2 +316,3 @@ void envfx_update_jetstream(void) { - gEnvFxBubbleConfig[ENVFX_STATE_SRC_Y] + (random_float() * 400.0f - 200.0f); -+ (gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer; - } else { -@@ -508,2 +524,8 @@ Gfx *envfx_update_bubble_particles(s32 mode, UNUSED Vec3s marioPos, Vec3s camFro - Vec3s vertex3; -+ Vec3s interpolatedVertices[3]; -+ -+ static Vec3s prevVertex1; -+ static Vec3s prevVertex2; -+ static Vec3s prevVertex3; -+ static u32 prevTimestamp; - -@@ -523,2 +545,12 @@ Gfx *envfx_update_bubble_particles(s32 mode, UNUSED Vec3s marioPos, Vec3s camFro - -+ 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); -@@ -526,5 +558,24 @@ Gfx *envfx_update_bubble_particles(s32 mode, UNUSED Vec3s marioPos, Vec3s camFro - 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); -@@ -535,2 +586,7 @@ Gfx *envfx_update_bubble_particles(s32 mode, UNUSED Vec3s marioPos, Vec3s camFro - } -+ for (i = 0; i < sBubbleParticleMaxCount; i++) { -+ sPrevBubblePositions[i][0] = gEnvFxBuffer[i].xPos; -+ sPrevBubblePositions[i][1] = gEnvFxBuffer[i].yPos; -+ sPrevBubblePositions[i][2] = gEnvFxBuffer[i].zPos; -+ } - -diff --git a/src/game/envfx_snow.c b/src/game/envfx_snow.c -index c3c14a5c..d2212ef6 100644 ---- a/src/game/envfx_snow.c -+++ b/src/game/envfx_snow.c -@@ -56,2 +56,22 @@ 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); -+ } -+} -+ - /** -@@ -219,2 +239,3 @@ void envfx_update_snow_normal(s32 snowCylinderX, s32 snowCylinderY, s32 snowCyli - (gEnvFxBuffer + i)->isAlive = 1; -+ (gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer; - } else { -@@ -253,2 +274,3 @@ void envfx_update_snow_blizzard(s32 snowCylinderX, s32 snowCylinderY, s32 snowCy - (gEnvFxBuffer + i)->isAlive = 1; -+ (gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer; - } else { -@@ -296,2 +318,3 @@ void envfx_update_snow_water(s32 snowCylinderX, s32 snowCylinderY, s32 snowCylin - (gEnvFxBuffer + i)->isAlive = 1; -+ (gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer; - } -@@ -348,2 +371,4 @@ void append_snowflake_vertex_buffer(Gfx *gfx, s32 index, Vec3s vertex1, Vec3s ve - Vtx *vertBuf = (Vtx *) alloc_display_list(15 * sizeof(Vtx)); -+ Vtx *vertBufInterpolated = (Vtx *) alloc_display_list(15 * sizeof(Vtx)); -+ Vtx *v; - #ifdef VERSION_EU -@@ -397,3 +422,19 @@ void append_snowflake_vertex_buffer(Gfx *gfx, s32 index, Vec3s vertex1, Vec3s ve - -- gSPVertex(gfx, VIRTUAL_TO_PHYSICAL(vertBuf), 15, 0); -+ 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]; -+ } -+ sPrevSnowVertices[index / 5].pos = gfx; -+ gSPVertex(gfx, VIRTUAL_TO_PHYSICAL(vertBufInterpolated), 15, 0); - } -@@ -481,2 +522,4 @@ Gfx *envfx_update_snow(s32 snowMode, Vec3s marioPos, Vec3s camFrom, Vec3s camTo) - } -+ sPrevSnowParticleCount = gSnowParticleCount; -+ sPrevSnowTimestamp = gGlobalTimer; - -diff --git a/src/game/envfx_snow.h b/src/game/envfx_snow.h -index 7a83b536..f4acc2de 100644 ---- a/src/game/envfx_snow.h -+++ b/src/game/envfx_snow.h -@@ -27,3 +27,4 @@ struct EnvFxParticle { - s32 bubbleY; // for Bubbles, yPos is always set to this -- s8 filler20[56 - 0x20]; -+ //s8 filler20[56 - 0x20]; -+ u32 spawnTimestamp; - }; -diff --git a/src/game/hud.c b/src/game/hud.c -index 1540b675..0de6e0bb 100644 ---- a/src/game/hud.c -+++ b/src/game/hud.c -@@ -61,2 +61,16 @@ 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; -+ } -+} -+ - /** -@@ -113,2 +127,3 @@ void render_dl_power_meter(s16 numHealthWedges) { - Mtx *mtx; -+ f32 interpolatedY; - -@@ -120,3 +135,11 @@ void render_dl_power_meter(s16 numHealthWedges) { - -- 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; - -diff --git a/src/game/ingame_menu.c b/src/game/ingame_menu.c -index 3f02281a..d7d0aced 100644 ---- a/src/game/ingame_menu.c -+++ b/src/game/ingame_menu.c -@@ -118,2 +118,43 @@ u8 gMenuHoldKeyTimer = 0; - s32 gDialogResponse = 0; -+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; -+ } -+} - -@@ -940,2 +981,10 @@ void render_dialog_box_type(struct DialogEntry *dialog, s8 linesPerBox) { - 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); -@@ -948,2 +997,8 @@ void render_dialog_box_type(struct DialogEntry *dialog, s8 linesPerBox) { - 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), -@@ -1230,2 +1285,4 @@ void handle_dialog_text_and_pages(s8 colorMode, struct DialogEntry *dialog, s8 l - #else -+ sInterpolatedDialogOffset = gDialogScrollOffsetY + dialog->linesPerBox; -+ sInterpolatedDialogOffsetPos = gDisplayListHead; - create_dl_translation_matrix(MENU_MTX_NOPUSH, 0, (f32) gDialogScrollOffsetY, 0); -diff --git a/src/game/level_geo.c b/src/game/level_geo.c -index 4c98e705..abc51213 100644 ---- a/src/game/level_geo.c -+++ b/src/game/level_geo.c -@@ -36,2 +36,3 @@ Gfx *geo_envfx_main(s32 callContext, struct GraphNode *node, Mat4 mtxf) { - if (particleList != NULL) { -+#if 0 - Mtx *mtx = alloc_display_list(sizeof(*mtx)); -@@ -42,2 +43,5 @@ Gfx *geo_envfx_main(s32 callContext, struct GraphNode *node, Mat4 mtxf) { - gSPBranchList(&gfx[1], VIRTUAL_TO_PHYSICAL(particleList)); -+#else -+ gfx = particleList; -+#endif - execNode->fnNode.node.flags = (execNode->fnNode.node.flags & 0xFF) | 0x400; -diff --git a/src/game/object_helpers.c b/src/game/object_helpers.c -index 22b45b32..109d7f74 100644 ---- a/src/game/object_helpers.c -+++ b/src/game/object_helpers.c -@@ -1556,2 +1556,3 @@ void cur_obj_set_pos_to_home(void) { - o->oPosZ = o->oHomeZ; -+ o->header.gfx.skipInterpolationTimestamp = gGlobalTimer; - } -diff --git a/src/game/paintings.c b/src/game/paintings.c -index 6cae19c0..a304d4ae 100644 ---- a/src/game/paintings.c -+++ b/src/game/paintings.c -@@ -191,2 +191,28 @@ 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; -+ } -+} -+ - /** -@@ -892,2 +918,19 @@ Gfx *render_painting(u8 *img, s16 tWidth, s16 tHeight, s16 *textureMap, s16 mapV - -+ 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); -@@ -956,2 +999,3 @@ Gfx *painting_ripple_image(struct Painting *painting) { - } -+ sLastVerticesTimestamp = gGlobalTimer; - -@@ -993,2 +1037,3 @@ Gfx *painting_ripple_env_mapped(struct Painting *painting) { - gSPDisplayList(gfx++, render_painting(tArray[0], tWidth, tHeight, textureMap, meshVerts, meshTris, painting->alpha)); -+ sLastVerticesTimestamp = gGlobalTimer; - -diff --git a/src/game/rendering_graph_node.c b/src/game/rendering_graph_node.c -index d5bf5778..71656b49 100644 ---- a/src/game/rendering_graph_node.c -+++ b/src/game/rendering_graph_node.c -@@ -41,2 +41,4 @@ Mat4 gMatStack[32]; - Mtx *gMatStackFixed[32]; -+Mat4 gMatStackInterpolated[32]; -+Mtx *gMatStackInterpolatedFixed[32]; - -@@ -54,2 +56,3 @@ struct GeoAnimState { - /*0x0C*/ s16 *data; -+ s16 prevFrame; - }; -@@ -63,2 +66,3 @@ u8 gCurAnimEnabled; - s16 gCurrAnimFrame; -+s16 gPrevAnimFrame; - f32 gCurAnimTranslationMultiplier; -@@ -131,2 +135,42 @@ LookAt lookAt; - -+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; -+} -+ - /** -@@ -158,5 +202,10 @@ static void geo_process_master_list_sub(struct GraphNodeMasterList *node) { - 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; -@@ -176,3 +225,3 @@ static void geo_process_master_list_sub(struct GraphNodeMasterList *node) { - */ --static void geo_append_display_list(void *displayList, s16 layer) { -+static void geo_append_display_list2(void *displayList, void *displayListInterpolated, s16 layer) { - -@@ -186,3 +235,5 @@ static void geo_append_display_list(void *displayList, s16 layer) { - listNode->transform = gMatStackFixed[gMatStackIndex]; -+ listNode->transformInterpolated = gMatStackInterpolatedFixed[gMatStackIndex]; - listNode->displayList = displayList; -+ listNode->displayListInterpolated = displayListInterpolated; - listNode->next = 0; -@@ -197,2 +248,6 @@ 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); -+} -+ - /** -@@ -243,3 +298,5 @@ static void geo_process_perspective(struct GraphNodePerspective *node) { - u16 perspNorm; -+ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); - Mtx *mtx = alloc_display_list(sizeof(*mtx)); -+ f32 fovInterpolated; - -@@ -252,5 +309,19 @@ static void geo_process_perspective(struct GraphNodePerspective *node) { - 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; - -@@ -299,2 +370,35 @@ 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]); -+} -+ - /** -@@ -306,2 +410,5 @@ static void geo_process_camera(struct GraphNodeCamera *node) { - Mtx *mtx = alloc_display_list(sizeof(*mtx)); -+ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); -+ Vec3f posInterpolated; -+ Vec3f focusInterpolated; - -@@ -316,2 +423,27 @@ static void geo_process_camera(struct GraphNodeCamera *node) { - 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++; -@@ -319,2 +451,4 @@ static void geo_process_camera(struct GraphNodeCamera *node) { - gMatStackFixed[gMatStackIndex] = mtx; -+ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); -+ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; - if (node->fnNode.node.children != 0) { -@@ -322,2 +456,3 @@ static void geo_process_camera(struct GraphNodeCamera *node) { - node->matrixPtr = &gMatStack[gMatStackIndex]; -+ node->matrixPtrInterpolated = &gMatStackInterpolated[gMatStackIndex]; - geo_process_node_and_siblings(node->fnNode.node.children); -@@ -338,2 +473,3 @@ static void geo_process_translation_rotation(struct GraphNodeTranslationRotation - Mtx *mtx = alloc_display_list(sizeof(*mtx)); -+ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); - -@@ -342,2 +478,3 @@ static void geo_process_translation_rotation(struct GraphNodeTranslationRotation - mtxf_mul(gMatStack[gMatStackIndex + 1], mtxf, gMatStack[gMatStackIndex]); -+ mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], mtxf, gMatStackInterpolated[gMatStackIndex]); - gMatStackIndex++; -@@ -345,2 +482,4 @@ static void geo_process_translation_rotation(struct GraphNodeTranslationRotation - gMatStackFixed[gMatStackIndex] = mtx; -+ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); -+ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; - if (node->displayList != NULL) { -@@ -363,2 +502,3 @@ static void geo_process_translation(struct GraphNodeTranslation *node) { - Mtx *mtx = alloc_display_list(sizeof(*mtx)); -+ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); - -@@ -367,2 +507,3 @@ static void geo_process_translation(struct GraphNodeTranslation *node) { - mtxf_mul(gMatStack[gMatStackIndex + 1], mtxf, gMatStack[gMatStackIndex]); -+ mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], mtxf, gMatStackInterpolated[gMatStackIndex]); - gMatStackIndex++; -@@ -370,2 +511,4 @@ static void geo_process_translation(struct GraphNodeTranslation *node) { - gMatStackFixed[gMatStackIndex] = mtx; -+ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); -+ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; - if (node->displayList != NULL) { -@@ -387,2 +530,4 @@ static void geo_process_rotation(struct GraphNodeRotation *node) { - Mtx *mtx = alloc_display_list(sizeof(*mtx)); -+ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); -+ Vec3s rotationInterpolated; - -@@ -390,2 +535,9 @@ static void geo_process_rotation(struct GraphNodeRotation *node) { - 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++; -@@ -393,2 +545,4 @@ static void geo_process_rotation(struct GraphNodeRotation *node) { - gMatStackFixed[gMatStackIndex] = mtx; -+ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); -+ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; - if (node->displayList != NULL) { -@@ -411,2 +565,3 @@ static void geo_process_scale(struct GraphNodeScale *node) { - Mtx *mtx = alloc_display_list(sizeof(*mtx)); -+ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); - -@@ -414,2 +569,3 @@ static void geo_process_scale(struct GraphNodeScale *node) { - mtxf_scale_vec3f(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex], scaleVec); -+ mtxf_scale_vec3f(gMatStackInterpolated[gMatStackIndex + 1], gMatStackInterpolated[gMatStackIndex], scaleVec); - gMatStackIndex++; -@@ -417,2 +573,4 @@ static void geo_process_scale(struct GraphNodeScale *node) { - gMatStackFixed[gMatStackIndex] = mtx; -+ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); -+ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; - if (node->displayList != NULL) { -@@ -435,2 +593,3 @@ static void geo_process_billboard(struct GraphNodeBillboard *node) { - Mtx *mtx = alloc_display_list(sizeof(*mtx)); -+ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); - -@@ -440,2 +599,4 @@ static void geo_process_billboard(struct GraphNodeBillboard *node) { - gCurGraphNodeCamera->roll); -+ mtxf_billboard(gMatStackInterpolated[gMatStackIndex], gMatStackInterpolated[gMatStackIndex - 1], translation, -+ gCurGraphNodeCamera->roll); - if (gCurGraphNodeHeldObject != NULL) { -@@ -443,2 +604,4 @@ static void geo_process_billboard(struct GraphNodeBillboard *node) { - gCurGraphNodeHeldObject->objNode->header.gfx.scale); -+ mtxf_scale_vec3f(gMatStackInterpolated[gMatStackIndex], gMatStackInterpolated[gMatStackIndex], -+ gCurGraphNodeHeldObject->objNode->header.gfx.scale); - } else if (gCurGraphNodeObject != NULL) { -@@ -446,2 +609,4 @@ static void geo_process_billboard(struct GraphNodeBillboard *node) { - gCurGraphNodeObject->scale); -+ mtxf_scale_vec3f(gMatStackInterpolated[gMatStackIndex], gMatStackInterpolated[gMatStackIndex], -+ gCurGraphNodeObject->scale); - } -@@ -450,2 +615,4 @@ static void geo_process_billboard(struct GraphNodeBillboard *node) { - gMatStackFixed[gMatStackIndex] = mtx; -+ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); -+ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; - if (node->displayList != NULL) { -@@ -498,9 +665,35 @@ 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 != 0) { -- 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) { -@@ -529,43 +722,32 @@ static void geo_process_background(struct GraphNodeBackground *node) { - --/** -- * 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. -- */ --static void geo_process_animated_part(struct GraphNodeAnimatedPart *node) { -- Mat4 matrix; -- Vec3s rotation; -- Vec3f translation; -- Mtx *matrixPtr = alloc_display_list(sizeof(*matrixPtr)); -- -- vec3s_copy(rotation, gVec3sZero); -- vec3f_set(translation, node->translation[0], node->translation[1], node->translation[2]); -- if (gCurAnimType == ANIM_TYPE_TRANSLATION) { -- translation[0] += gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] -+static void anim_process(Vec3f translation, Vec3s rotation, u8 *animType, s16 animFrame, u16 **animAttribute) { -+ if (*animType == ANIM_TYPE_TRANSLATION) { -+ translation[0] += gCurAnimData[retrieve_animation_index(animFrame, animAttribute)] - * gCurAnimTranslationMultiplier; -- translation[1] += gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] -+ translation[1] += gCurAnimData[retrieve_animation_index(animFrame, animAttribute)] - * gCurAnimTranslationMultiplier; -- translation[2] += gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] -+ translation[2] += gCurAnimData[retrieve_animation_index(animFrame, animAttribute)] - * gCurAnimTranslationMultiplier; -- gCurAnimType = ANIM_TYPE_ROTATION; -+ *animType = ANIM_TYPE_ROTATION; - } else { -- if (gCurAnimType == ANIM_TYPE_LATERAL_TRANSLATION) { -+ if (*animType == ANIM_TYPE_LATERAL_TRANSLATION) { - translation[0] += -- gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] -+ gCurAnimData[retrieve_animation_index(animFrame, animAttribute)] - * gCurAnimTranslationMultiplier; -- gCurrAnimAttribute += 2; -+ *animAttribute += 2; - translation[2] += -- gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] -+ gCurAnimData[retrieve_animation_index(animFrame, animAttribute)] - * gCurAnimTranslationMultiplier; -- gCurAnimType = ANIM_TYPE_ROTATION; -+ *animType = ANIM_TYPE_ROTATION; - } else { -- if (gCurAnimType == ANIM_TYPE_VERTICAL_TRANSLATION) { -- gCurrAnimAttribute += 2; -+ if (*animType == ANIM_TYPE_VERTICAL_TRANSLATION) { -+ *animAttribute += 2; - translation[1] += -- gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] -+ gCurAnimData[retrieve_animation_index(animFrame, animAttribute)] - * gCurAnimTranslationMultiplier; -- gCurrAnimAttribute += 2; -- gCurAnimType = ANIM_TYPE_ROTATION; -- } else if (gCurAnimType == ANIM_TYPE_NO_TRANSLATION) { -- gCurrAnimAttribute += 6; -- gCurAnimType = ANIM_TYPE_ROTATION; -+ *animAttribute += 2; -+ *animType = ANIM_TYPE_ROTATION; -+ } else if (*animType == ANIM_TYPE_NO_TRANSLATION) { -+ *animAttribute += 6; -+ *animType = ANIM_TYPE_ROTATION; - } -@@ -574,9 +756,38 @@ static void geo_process_animated_part(struct GraphNodeAnimatedPart *node) { - -- if (gCurAnimType == ANIM_TYPE_ROTATION) { -- rotation[0] = gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)]; -- rotation[1] = gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)]; -- rotation[2] = gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)]; -+ if (*animType == ANIM_TYPE_ROTATION) { -+ rotation[0] = gCurAnimData[retrieve_animation_index(animFrame, animAttribute)]; -+ rotation[1] = gCurAnimData[retrieve_animation_index(animFrame, animAttribute)]; -+ rotation[2] = gCurAnimData[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. -+ */ -+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 = gCurAnimType; -+ -+ vec3s_copy(rotation, gVec3sZero); -+ vec3f_set(translation, node->translation[0], node->translation[1], node->translation[2]); -+ vec3s_copy(rotationInterpolated, rotation); -+ vec3f_copy(translationInterpolated, translation); -+ -+ anim_process(translationInterpolated, rotationInterpolated, &animType, gPrevAnimFrame, &animAttribute); -+ anim_process(translation, rotation, &gCurAnimType, gCurrAnimFrame, &gCurrAnimAttribute); -+ interpolate_vectors(translationInterpolated, translationInterpolated, translation); -+ interpolate_angles(rotationInterpolated, rotationInterpolated, rotation); -+ - 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++; -@@ -584,2 +795,4 @@ static void geo_process_animated_part(struct GraphNodeAnimatedPart *node) { - gMatStackFixed[gMatStackIndex] = matrixPtr; -+ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); -+ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; - if (node->displayList != NULL) { -@@ -615,2 +828,13 @@ void geo_set_animation_globals(struct GraphNodeObject_sub *node, s32 hasAnimatio - 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; -+ - gCurAnimEnabled = (anim->flags & ANIM_FLAG_5) == 0; -@@ -633,4 +857,6 @@ static void geo_process_shadow(struct GraphNodeShadow *node) { - Gfx *shadowList; -+ Gfx *shadowListInterpolated; - Mat4 mtxf; - Vec3f shadowPos; -+ Vec3f shadowPosInterpolated; - Vec3f animOffset; -@@ -642,2 +868,3 @@ static void geo_process_shadow(struct GraphNodeShadow *node) { - Mtx *mtx; -+ Mtx *mtxInterpolated; - -@@ -680,7 +907,34 @@ 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); -@@ -689,8 +943,17 @@ static void geo_process_shadow(struct GraphNodeShadow *node) { - 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 == 1) { -- 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); - } -@@ -791,2 +1054,11 @@ static int obj_is_in_view(struct GraphNodeObject *node, Mat4 matrix) { - -+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; -+ } -+ } -+} -+ - /** -@@ -797,2 +1069,3 @@ static void geo_process_object(struct Object *node) { - s32 hasAnimation = (node->header.gfx.node.flags & GRAPH_RENDER_HAS_ANIMATION) != 0; -+ Vec3f scaleInterpolated; - -@@ -802,12 +1075,69 @@ static void geo_process_object(struct Object *node) { - 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_CYLBOARD) { -+ 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_cylboard(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex], - node->header.gfx.pos, gCurGraphNodeCamera->roll); -+ mtxf_cylboard(gMatStackInterpolated[gMatStackIndex + 1], gMatStackInterpolated[gMatStackIndex], -+ posInterpolated, gCurGraphNodeCamera->roll); - } 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; - -@@ -815,3 +1145,6 @@ static void geo_process_object(struct Object *node) { - 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]; -@@ -826,2 +1159,3 @@ static void geo_process_object(struct Object *node) { - Mtx *mtx = alloc_display_list(sizeof(*mtx)); -+ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); - -@@ -829,2 +1163,4 @@ static void geo_process_object(struct Object *node) { - gMatStackFixed[gMatStackIndex] = mtx; -+ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); -+ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; - if (node->header.gfx.sharedChild != NULL) { -@@ -839,2 +1175,6 @@ static void geo_process_object(struct Object *node) { - } -+ } else { -+ node->header.gfx.prevThrowMatrixTimestamp = 0; -+ node->header.gfx.prevTimestamp = 0; -+ node->header.gfx.prevScaleTimestamp = 0; - } -@@ -844,2 +1184,3 @@ static void geo_process_object(struct Object *node) { - node->header.gfx.throwMatrix = NULL; -+ node->header.gfx.throwMatrixInterpolated = NULL; - } -@@ -870,2 +1211,4 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { - Mtx *mtx = alloc_display_list(sizeof(*mtx)); -+ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); -+ Vec3f scaleInterpolated; - -@@ -885,2 +1228,10 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { - -+ 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); -@@ -893,2 +1244,9 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { - 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) { -@@ -900,2 +1258,4 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { - gMatStackFixed[gMatStackIndex] = mtx; -+ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); -+ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; - gGeoTempState.type = gCurAnimType; -@@ -906,2 +1266,3 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { - gGeoTempState.data = gCurAnimData; -+ gGeoTempState.prevFrame = gPrevAnimFrame; - gCurAnimType = 0; -@@ -920,2 +1281,3 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { - gCurAnimData = gGeoTempState.data; -+ gPrevAnimFrame = gGeoTempState.prevFrame; - gMatStackIndex--; -@@ -1041,2 +1403,3 @@ void geo_process_root(struct GraphNodeRoot *node, Vp *b, Vp *c, s32 clearColor) - Vp *viewport = alloc_display_list(sizeof(*viewport)); -+ Vp *viewportInterpolated = viewport; - -@@ -1051,3 +1414,8 @@ void geo_process_root(struct GraphNodeRoot *node, Vp *b, Vp *c, s32 clearColor) - clear_frame_buffer(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; -@@ -1059,2 +1427,3 @@ void geo_process_root(struct GraphNodeRoot *node, Vp *b, Vp *c, s32 clearColor) - } -+ sPrevViewport = *viewport; - -@@ -1063,3 +1432,7 @@ void geo_process_root(struct GraphNodeRoot *node, Vp *b, Vp *c, s32 clearColor) - 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]), -diff --git a/src/game/screen_transition.c b/src/game/screen_transition.c -index b49ddaf5..d6656af2 100644 ---- a/src/game/screen_transition.c -+++ b/src/game/screen_transition.c -@@ -18,2 +18,15 @@ 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) { -@@ -87,2 +100,4 @@ s32 render_fade_transition_into_color(s8 fadeTimer, u8 transTime, struct WarpTra - -+#if 0 -+ - s16 calc_tex_transition_radius(s8 fadeTimer, s8 transTime, struct WarpTransitionData *transData) { -@@ -92,5 +107,18 @@ s16 calc_tex_transition_radius(s8 fadeTimer, s8 transTime, struct WarpTransition - -- return (s16)(result + 0.5);; -+ 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) { -@@ -168,2 +196,4 @@ void *sTextureTransitionID[] = { - -+#if 0 -+ - s32 render_textured_transition(s8 fadeTimer, s8 transTime, struct WarpTransitionData *transData, s8 texID, s8 transTexType) { -@@ -208,2 +238,52 @@ s32 render_textured_transition(s8 fadeTimer, s8 transTime, struct WarpTransition - -+#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 -+ - int render_screen_transition(s8 fadeTimer, s8 transType, u8 transTime, struct WarpTransitionData *transData) { -diff --git a/src/game/skybox.c b/src/game/skybox.c -index 258ef0a3..bf92d0d5 100644 ---- a/src/game/skybox.c -+++ b/src/game/skybox.c -@@ -253,3 +253,3 @@ void *create_skybox_ortho_matrix(s8 player) { - Gfx *init_skybox_display_list(s8 player, s8 background, s8 colorIndex) { -- s32 dlCommandCount = 7 + (3 * 3) * 7; // 5 for the start and end, plus 9 skybox tiles -+ s32 dlCommandCount = 124; // 5 for the start and end, plus 9 skybox tiles - void *skybox = alloc_display_list(dlCommandCount * sizeof(Gfx)); -diff --git a/src/menu/intro_geo.c b/src/menu/intro_geo.c -index 1d3be071..01c5ad61 100644 ---- a/src/menu/intro_geo.c -+++ b/src/menu/intro_geo.c -@@ -2,2 +2,3 @@ - -+#include "engine/math_util.h" - #include "game/memory.h" -@@ -72,2 +73,14 @@ s8 gameOverBackgroundFlipOrder[] = { 0x00, 0x01, 0x02, 0x03, 0x07, 0x0B, - -+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; -+ } -+} -+ - Gfx *geo_title_screen(s32 sp50, struct GraphNode *sp54, UNUSED void *context) { -@@ -82,2 +95,4 @@ Gfx *geo_title_screen(s32 sp50, struct GraphNode *sp54, UNUSED void *context) { - f32 scaleZ; // sp2c -+ Vec3f scale; -+ Vec3f scaleInterpolated; - graphNode = sp54; -@@ -113,2 +128,7 @@ Gfx *geo_title_screen(s32 sp50, struct GraphNode *sp54, UNUSED void *context) { - 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 = displayListIter; - gSPMatrix(displayListIter++, scaleMat, G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_PUSH); -diff --git a/src/pc/gfx/gfx_dxgi.cpp b/src/pc/gfx/gfx_dxgi.cpp -index 04674952..fa4eb33c 100644 ---- a/src/pc/gfx/gfx_dxgi.cpp -+++ b/src/pc/gfx/gfx_dxgi.cpp -@@ -38,6 +38,6 @@ - #define FRAME_INTERVAL_US_NUMERATOR 40000 --#define FRAME_INTERVAL_US_DENOMINATOR 1 -+#define FRAME_INTERVAL_US_DENOMINATOR 2 - #else - #define FRAME_INTERVAL_US_NUMERATOR 100000 --#define FRAME_INTERVAL_US_DENOMINATOR 3 -+#define FRAME_INTERVAL_US_DENOMINATOR 6 - #endif -diff --git a/src/pc/gfx/gfx_sdl2.c b/src/pc/gfx/gfx_sdl2.c -index 4d907893..0b21e230 100644 ---- a/src/pc/gfx/gfx_sdl2.c -+++ b/src/pc/gfx/gfx_sdl2.c -@@ -55,3 +55,3 @@ static bool use_timer = true; - // time between consequtive game frames --static const int frame_time = 1000 / FRAMERATE; -+static const int frame_time = 1000 / (2 * FRAMERATE); - -@@ -144,3 +144,7 @@ static inline void gfx_sdl_set_vsync(const bool enabled) { - SDL_GL_SetSwapInterval(1); -- const int vblanks = test_vsync(); -+ int vblanks = test_vsync(); -+ if (vblanks & 1) -+ vblanks = 0; // not divisible by 60, fuck that -+ else -+ vblanks /= 2; - if (vblanks) { -diff --git a/src/pc/pc_main.c b/src/pc/pc_main.c -index 3a725c03..7bff83d5 100644 ---- a/src/pc/pc_main.c -+++ b/src/pc/pc_main.c -@@ -87,2 +87,21 @@ void send_display_list(struct SPTask *spTask) { - -+static inline void patch_interpolations(void) { -+ 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); -+ 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(); -+} -+ - void produce_one_frame(void) { -@@ -114,2 +133,7 @@ void produce_one_frame(void) { - gfx_end_frame(); -+ -+ gfx_start_frame(); -+ patch_interpolations(); -+ send_display_list(gGfxSPTask); -+ gfx_end_frame(); - } \ No newline at end of file