diff --git a/include/types.h b/include/types.h index b3dc27e..c46bdf0 100644 --- a/include/types.h +++ b/include/types.h @@ -118,6 +118,10 @@ struct GraphNodeObject_sub /*0x0A 0x42*/ u16 animTimer; /*0x0C 0x44*/ s32 animFrameAccelAssist; /*0x10 0x48*/ s32 animAccel; + s16 prevAnimFrame; + s16 prevAnimID; + u32 prevAnimFrameTimestamp; + struct Animation *prevAnimPtr; }; struct GraphNodeObject @@ -128,11 +132,22 @@ struct GraphNodeObject /*0x19*/ s8 unk19; /*0x1A*/ Vec3s angle; /*0x20*/ Vec3f pos; + Vec3s prevAngle; + Vec3f prevPos; + u32 prevTimestamp; + Vec3f prevShadowPos; + u32 prevShadowPosTimestamp; /*0x2C*/ Vec3f scale; + Vec3f prevScale; + u32 prevScaleTimestamp; /*0x38*/ struct GraphNodeObject_sub unk38; /*0x4C*/ struct SpawnInfo *unk4C; /*0x50*/ Mat4 *throwMatrix; // matrix ptr + Mat4 prevThrowMatrix; + u32 prevThrowMatrixTimestamp; + Mat4 *throwMatrixInterpolated; /*0x54*/ Vec3f cameraToObject; + u32 skipInterpolationTimestamp; }; struct ObjectNode @@ -243,6 +258,10 @@ struct Surface } normal; /*0x28*/ f32 originOffset; /*0x2C*/ struct Object *object; + Vec3s prevVertex1; + Vec3s prevVertex2; + Vec3s prevVertex3; + u32 modifiedTimestamp; }; struct MarioBodyState diff --git a/src/engine/graph_node.h b/src/engine/graph_node.h index 802d97a..1b0d677 100644 --- a/src/engine/graph_node.h +++ b/src/engine/graph_node.h @@ -110,6 +110,8 @@ struct GraphNodePerspective /*0x1C*/ f32 fov; // horizontal field of view in degrees /*0x20*/ s16 near; // near clipping plane /*0x22*/ s16 far; // far clipping plane + f32 prevFov; + f32 prevTimestamp; }; /** An entry in the master list. It is a linked list of display lists @@ -118,7 +120,9 @@ struct GraphNodePerspective struct DisplayListNode { Mtx *transform; + void *transformInterpolated; void *displayList; + void *displayListInterpolated; struct DisplayListNode *next; }; @@ -185,7 +189,11 @@ struct GraphNodeCamera } config; /*0x1C*/ Vec3f pos; /*0x28*/ Vec3f focus; + Vec3f prevPos; + Vec3f prevFocus; + u32 prevTimestamp; /*0x34*/ Mat4 *matrixPtr; // pointer to look-at matrix of this camera as a Mat4 + Mat4 *matrixPtrInterpolated; /*0x38*/ s16 roll; // roll in look at matrix. Doesn't account for light direction unlike rollScreen. /*0x3A*/ s16 rollScreen; // rolls screen while keeping the light direction consistent }; @@ -226,7 +234,8 @@ struct GraphNodeRotation /*0x00*/ struct GraphNode node; /*0x14*/ void *displayList; /*0x18*/ Vec3s rotation; - u8 pad1E[2]; + Vec3s prevRotation; + u32 prevTimestamp; }; /** GraphNode part that transforms itself and its children based on animation @@ -323,6 +332,9 @@ struct GraphNodeBackground /*0x00*/ struct FnGraphNode fnNode; /*0x18*/ s32 unused; /*0x1C*/ s32 background; // background ID, or rgba5551 color if fnNode.func is null + Vec3f prevCameraPos; + Vec3f prevCameraFocus; + u32 prevCameraTimestamp; }; /** Renders the object that Mario is holding. @@ -333,6 +345,8 @@ struct GraphNodeHeldObject /*0x18*/ s32 playerIndex; /*0x1C*/ struct Object *objNode; /*0x20*/ Vec3s translation; + Vec3f prevShadowPos; + u32 prevShadowPosTimestamp; }; /** A node that allows an object to specify a different culling radius than the diff --git a/src/engine/surface_collision.c b/src/engine/surface_collision.c index 5b6775f..2c11e25 100644 --- a/src/engine/surface_collision.c +++ b/src/engine/surface_collision.c @@ -8,6 +8,7 @@ #include "surface_collision.h" #include "surface_load.h" #include "math_util.h" +#include "game/game_init.h" /************************************************** * WALLS * @@ -394,26 +395,44 @@ f32 find_floor_height_and_data(f32 xPos, f32 yPos, f32 zPos, struct FloorGeometr return floorHeight; } +u8 gInterpolatingSurfaces; + /** * Iterate through the list of floors and find the first floor under a given point. */ static struct Surface *find_floor_from_list(struct SurfaceNode *surfaceNode, s32 x, s32 y, s32 z, f32 *pheight) { register struct Surface *surf; - register s32 x1, z1, x2, z2, x3, z3; + register f32 x1, z1, x2, z2, x3, z3; f32 nx, ny, nz; f32 oo; f32 height; struct Surface *floor = NULL; + s32 interpolate; // Iterate through the list of floors until there are no more floors. while (surfaceNode != NULL) { surf = surfaceNode->surface; surfaceNode = surfaceNode->next; + interpolate = gInterpolatingSurfaces && surf->modifiedTimestamp == gGlobalTimer; x1 = surf->vertex1[0]; z1 = surf->vertex1[2]; x2 = surf->vertex2[0]; z2 = surf->vertex2[2]; + if (interpolate) { + f32 diff = (surf->prevVertex1[0] - x1) * (surf->prevVertex1[0] - x1); + diff += (surf->prevVertex1[1] - surf->vertex1[1]) * (surf->prevVertex1[1] - surf->vertex1[1]); + diff += (surf->prevVertex1[2] - z1) * (surf->prevVertex1[2] - z1); + //printf("%f\n", sqrtf(diff)); + if (diff > 10000) { + interpolate = FALSE; + } else { + x1 = (surf->prevVertex1[0] + x1) / 2; + z1 = (surf->prevVertex1[2] + z1) / 2; + x2 = (surf->prevVertex2[0] + x2) / 2; + z2 = (surf->prevVertex2[2] + z2) / 2; + } + } // Check that the point is within the triangle bounds. if ((z1 - z) * (x2 - x1) - (x1 - x) * (z2 - z1) < 0) { @@ -423,6 +442,10 @@ static struct Surface *find_floor_from_list(struct SurfaceNode *surfaceNode, s32 // To slightly save on computation time, set this later. x3 = surf->vertex3[0]; z3 = surf->vertex3[2]; + if (interpolate) { + x3 = (surf->prevVertex3[0] + x3) / 2; + z3 = (surf->prevVertex3[2] + z3) / 2; + } if ((z2 - z) * (x3 - x2) - (x2 - x) * (z3 - z2) < 0) { continue; @@ -442,10 +465,30 @@ static struct Surface *find_floor_from_list(struct SurfaceNode *surfaceNode, s32 continue; } - nx = surf->normal.x; - ny = surf->normal.y; - nz = surf->normal.z; - oo = surf->originOffset; + if (interpolate) { + f32 y1, y2, y3; + f32 mag; + y1 = (surf->prevVertex1[1] + surf->vertex1[1]) / 2; + y2 = (surf->prevVertex2[1] + surf->vertex2[1]) / 2; + y3 = (surf->prevVertex3[1] + surf->vertex3[1]) / 2; + nx = (y2 - y1) * (z3 - z2) - (z2 - z1) * (y3 - y2); + ny = (z2 - z1) * (x3 - x2) - (x2 - x1) * (z3 - z2); + nz = (x2 - x1) * (y3 - y2) - (y2 - y1) * (x3 - x2); + mag = sqrtf(nx * nx + ny * ny + nz * nz); + if (mag < 0.0001) { + continue; + } + mag = (f32)(1.0 / mag); + nx *= mag; + ny *= mag; + nz *= mag; + oo = -(nx * x1 + ny * y1 + nz * z1); + } else { + nx = surf->normal.x; + ny = surf->normal.y; + nz = surf->normal.z; + oo = surf->originOffset; + } // If a wall, ignore it. Likely a remnant, should never occur. if (ny == 0.0f) { @@ -460,6 +503,15 @@ static struct Surface *find_floor_from_list(struct SurfaceNode *surfaceNode, s32 } *pheight = height; + if (interpolate) { + static struct Surface s; + s.type = surf->type; + s.normal.x = nx; + s.normal.y = ny; + s.normal.z = nz; + s.originOffset = oo; + return &s; + } floor = surf; break; } diff --git a/src/engine/surface_load.c b/src/engine/surface_load.c index ac2ee50..323b7d0 100644 --- a/src/engine/surface_load.c +++ b/src/engine/surface_load.c @@ -14,6 +14,7 @@ #include "game/mario.h" #include "game/object_list_processor.h" #include "surface_load.h" +#include "game/game_init.h" s32 unused8038BE90; @@ -359,6 +360,11 @@ static struct Surface *read_surface_data(s16 *vertexData, s16 **vertexIndices) { surface = alloc_surface(); + vec3s_copy(surface->prevVertex1, surface->vertex1); + vec3s_copy(surface->prevVertex2, surface->vertex2); + vec3s_copy(surface->prevVertex3, surface->vertex3); + surface->modifiedTimestamp = gGlobalTimer; + surface->vertex1[0] = x1; surface->vertex2[0] = x2; surface->vertex3[0] = x3; diff --git a/src/game/camera.c b/src/game/camera.c index bde0662..9351dea 100644 --- a/src/game/camera.c +++ b/src/game/camera.c @@ -484,6 +484,10 @@ CameraTransition sModeTransitions[] = { extern u8 sDanceCutsceneIndexTable[][4]; extern u8 sZoomOutAreaMasks[]; +static void skip_camera_interpolation(void) { + gLakituState.skipCameraInterpolationTimestamp = gGlobalTimer; +} + /** * Starts a camera shake triggered by an interaction */ @@ -5552,6 +5556,7 @@ s32 set_camera_mode_fixed(struct Camera *c, s16 x, s16 y, s16 z) { c->mode = CAMERA_MODE_FIXED; vec3f_set(c->pos, sFixedModeBasePosition[0], sMarioCamState->pos[1], sFixedModeBasePosition[2]); + skip_camera_interpolation(); } return basePosSet; } @@ -5714,6 +5719,7 @@ BAD_RETURN(s32) cam_rr_enter_building_side(struct Camera *c) { if (c->mode != CAMERA_MODE_FIXED) { sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; c->mode = CAMERA_MODE_FIXED; + skip_camera_interpolation(); } } @@ -5909,6 +5915,7 @@ BAD_RETURN(s32) cam_castle_enter_lobby(struct Camera *c) { sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; set_fixed_cam_axis_sa_lobby(c->mode); c->mode = CAMERA_MODE_FIXED; + skip_camera_interpolation(); } } @@ -7279,6 +7286,7 @@ BAD_RETURN(s32) cutscene_unused_loop(UNUSED struct Camera *c) { BAD_RETURN(s32) cutscene_ending_mario_fall_start(struct Camera *c) { vec3f_set(c->focus, -26.f, 0.f, -137.f); vec3f_set(c->pos, 165.f, 4725.f, 324.f); + skip_camera_interpolation(); } /** @@ -7311,6 +7319,7 @@ BAD_RETURN(s32) cutscene_ending_mario_fall(struct Camera *c) { BAD_RETURN(s32) cutscene_ending_mario_land_closeup(struct Camera *c) { vec3f_set(c->focus, 85.f, 826.f, 250.f); vec3f_set(c->pos, -51.f, 988.f, -202.f); + skip_camera_interpolation(); player2_rotate_cam(c, -0x2000, 0x2000, -0x2000, 0x2000); } @@ -7320,6 +7329,7 @@ BAD_RETURN(s32) cutscene_ending_mario_land_closeup(struct Camera *c) { BAD_RETURN(s32) cutscene_ending_reset_spline(UNUSED struct Camera *c) { sCutsceneVars[9].point[0] = 0.f; cutscene_reset_spline(); + skip_camera_interpolation(); } /** @@ -7355,6 +7365,7 @@ BAD_RETURN(s32) cutscene_ending_peach_appear_closeup(struct Camera *c) { vec3f_set(c->pos, 179.f, 2463.f, -1216.f); c->pos[1] = gCutsceneFocus->oPosY + 35.f; vec3f_set(c->focus, gCutsceneFocus->oPosX, gCutsceneFocus->oPosY + 125.f, gCutsceneFocus->oPosZ); + skip_camera_interpolation(); } /** @@ -7373,6 +7384,7 @@ BAD_RETURN(s32) cutscene_ending_peach_appears(struct Camera *c) { BAD_RETURN(s32) cutscene_ending_peach_descends_start(UNUSED struct Camera *c) { cutscene_reset_spline(); sCutsceneVars[2].point[1] = 150.f; + skip_camera_interpolation(); } /** @@ -7459,6 +7471,7 @@ BAD_RETURN(s32) cutscene_ending_peach_wakeup(struct Camera *c) { BAD_RETURN(s32) cutscene_ending_dialog(struct Camera *c) { vec3f_set(c->focus, 11.f, 983.f, -1273.f); vec3f_set(c->pos, -473.f, 970.f, -1152.f); + skip_camera_interpolation(); player2_rotate_cam(c, -0x800, 0x2000, -0x2000, 0x2000); } @@ -7469,6 +7482,7 @@ BAD_RETURN(s32) cutscene_ending_kiss_closeup(struct Camera *c) { set_fov_function(CAM_FOV_SET_29); vec3f_set(c->focus, 350.f, 1034.f, -1216.f); vec3f_set(c->pos, -149.f, 1021.f, -1216.f); + skip_camera_interpolation(); } /** @@ -7504,6 +7518,7 @@ BAD_RETURN(s32) cutscene_ending_kiss(struct Camera *c) { BAD_RETURN(s32) cutscene_ending_look_at_sky(struct Camera *c) { move_point_along_spline(c->focus, sEndingLookAtSkyFocus, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); vec3f_set(c->pos, 699.f, 1680.f, -703.f); + skip_camera_interpolation(); } /** @@ -10340,6 +10355,7 @@ BAD_RETURN(s32) cutscene_door_start(struct Camera *c) { BAD_RETURN(s32) cutscene_door_fix_cam(struct Camera *c) { vec3f_copy(c->pos, sCutsceneVars[0].point); vec3f_copy(c->focus, sCutsceneVars[1].point); + skip_camera_interpolation(); } /** @@ -10373,6 +10389,7 @@ BAD_RETURN(s32) cutscene_door_move_behind_mario(struct Camera *c) { } offset_rotated(c->pos, sMarioCamState->pos, camOffset, sCutsceneVars[0].angle); + skip_camera_interpolation(); } /** diff --git a/src/game/camera.h b/src/game/camera.h index 173ab8a..b1abdc4 100644 --- a/src/game/camera.h +++ b/src/game/camera.h @@ -657,6 +657,8 @@ struct LakituState /// Mario's action from the previous frame. Only used to determine if Mario just finished a dive. /*0xB8*/ u32 lastFrameAction; /*0xBC*/ s16 unused; + + u32 skipCameraInterpolationTimestamp; }; // bss order hack to not affect BSS order. if possible, remove me, but it will be hard to match otherwise diff --git a/src/game/envfx_bubbles.c b/src/game/envfx_bubbles.c index 16a9272..ee1b029 100644 --- a/src/game/envfx_bubbles.c +++ b/src/game/envfx_bubbles.c @@ -35,6 +35,20 @@ Vtx_t gBubbleTempVtx[3] = { { { 0, 0, 0 }, 0, { -498, 964 }, { 0xFF, 0xFF, 0xFF, 0xFF } }, }; +static Gfx sGfxSaved[60 / 5]; +static Gfx *sBubbleInterpolatedDisplayListPos[60 / 5]; +static Vec3s sPrevBubblePositions[60]; + +void patch_interpolated_bubble_particles(void) { + s32 i; + for (i = 0; i < 60 / 5; i++) { + if (sBubbleInterpolatedDisplayListPos[i] != NULL) { + *sBubbleInterpolatedDisplayListPos[i] = sGfxSaved[i]; + sBubbleInterpolatedDisplayListPos[i] = NULL; + } + } +} + /** * Check whether the particle with the given index is * laterally within distance of point (x, z). Used to @@ -241,6 +255,7 @@ void envfx_update_whirlpool(void) { (gEnvFxBuffer + i)->yPos = (i + gEnvFxBuffer)->bubbleY; (gEnvFxBuffer + i)->unusedBubbleVar = 0; (gEnvFxBuffer + i)->isAlive = 1; + (gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer; envfx_rotate_around_whirlpool(&(gEnvFxBuffer + i)->xPos, &(gEnvFxBuffer + i)->yPos, &(gEnvFxBuffer + i)->zPos); @@ -299,6 +314,7 @@ void envfx_update_jetstream(void) { + coss((gEnvFxBuffer + i)->angleAndDist[0]) * (gEnvFxBuffer + i)->angleAndDist[1]; (gEnvFxBuffer + i)->yPos = gEnvFxBubbleConfig[ENVFX_STATE_SRC_Y] + (random_float() * 400.0f - 200.0f); + (gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer; } else { (gEnvFxBuffer + i)->angleAndDist[1] += 10; (gEnvFxBuffer + i)->xPos += sins((gEnvFxBuffer + i)->angleAndDist[0]) * 10.0f; @@ -506,6 +522,12 @@ Gfx *envfx_update_bubble_particles(s32 mode, UNUSED Vec3s marioPos, Vec3s camFro Vec3s vertex1; Vec3s vertex2; Vec3s vertex3; + Vec3s interpolatedVertices[3]; + + static Vec3s prevVertex1; + static Vec3s prevVertex2; + static Vec3s prevVertex3; + static u32 prevTimestamp; Gfx *gfxStart; @@ -521,18 +543,52 @@ Gfx *envfx_update_bubble_particles(s32 mode, UNUSED Vec3s marioPos, Vec3s camFro envfx_bubbles_update_switch(mode, camTo, vertex1, vertex2, vertex3); rotate_triangle_vertices(vertex1, vertex2, vertex3, pitch, yaw); + if (gGlobalTimer == prevTimestamp + 1) { + interpolate_vectors_s16(interpolatedVertices[0], prevVertex1, vertex1); + interpolate_vectors_s16(interpolatedVertices[1], prevVertex2, vertex2); + interpolate_vectors_s16(interpolatedVertices[2], prevVertex3, vertex3); + } + vec3s_copy(prevVertex1, vertex1); + vec3s_copy(prevVertex2, vertex2); + vec3s_copy(prevVertex3, vertex3); + prevTimestamp = gGlobalTimer; + gSPDisplayList(sGfxCursor++, &tiny_bubble_dl_0B006D38); for (i = 0; i < sBubbleParticleMaxCount; i += 5) { + Vtx *interpolatedVertBuf = alloc_display_list(15 * sizeof(Vtx)); + s32 j, k; gDPPipeSync(sGfxCursor++); envfx_set_bubble_texture(mode, i); - append_bubble_vertex_buffer(sGfxCursor++, i, vertex1, vertex2, vertex3, (Vtx *) gBubbleTempVtx); + sBubbleInterpolatedDisplayListPos[i / 5] = sGfxCursor; + for (j = 0; j < 5; j++) { + for (k = 0; k < 3; k++) { + Vtx *v = &interpolatedVertBuf[j * 3 + k]; + v->v = gBubbleTempVtx[k]; + if (gGlobalTimer != gEnvFxBuffer[i + j].spawnTimestamp && mode != ENVFX_LAVA_BUBBLES) { + v->v.ob[0] = (sPrevBubblePositions[i + j][0] + gEnvFxBuffer[i + j].xPos) / 2.0f + interpolatedVertices[k][0]; + v->v.ob[1] = (sPrevBubblePositions[i + j][1] + gEnvFxBuffer[i + j].yPos) / 2.0f + interpolatedVertices[k][1]; + v->v.ob[2] = (sPrevBubblePositions[i + j][2] + gEnvFxBuffer[i + j].zPos) / 2.0f + interpolatedVertices[k][2]; + } else { + v->v.ob[0] = gEnvFxBuffer[i + j].xPos + interpolatedVertices[k][0]; + v->v.ob[1] = gEnvFxBuffer[i + j].yPos + interpolatedVertices[k][1]; + v->v.ob[2] = gEnvFxBuffer[i + j].zPos + interpolatedVertices[k][2]; + } + } + } + gSPVertex(sGfxCursor++, VIRTUAL_TO_PHYSICAL(interpolatedVertBuf), 15, 0); + append_bubble_vertex_buffer(&sGfxSaved[i / 5], i, vertex1, vertex2, vertex3, (Vtx *) gBubbleTempVtx); gSP1Triangle(sGfxCursor++, 0, 1, 2, 0); gSP1Triangle(sGfxCursor++, 3, 4, 5, 0); gSP1Triangle(sGfxCursor++, 6, 7, 8, 0); gSP1Triangle(sGfxCursor++, 9, 10, 11, 0); gSP1Triangle(sGfxCursor++, 12, 13, 14, 0); } + for (i = 0; i < sBubbleParticleMaxCount; i++) { + sPrevBubblePositions[i][0] = gEnvFxBuffer[i].xPos; + sPrevBubblePositions[i][1] = gEnvFxBuffer[i].yPos; + sPrevBubblePositions[i][2] = gEnvFxBuffer[i].zPos; + } gSPDisplayList(sGfxCursor++, &tiny_bubble_dl_0B006AB0); gSPEndDisplayList(sGfxCursor++); diff --git a/src/game/envfx_snow.c b/src/game/envfx_snow.c index c3c14a5..d2212ef 100644 --- a/src/game/envfx_snow.c +++ b/src/game/envfx_snow.c @@ -54,6 +54,26 @@ extern void *tiny_bubble_dl_0B006AB0; extern void *tiny_bubble_dl_0B006A50; extern void *tiny_bubble_dl_0B006CD8; +static struct { + Gfx *pos; + Vtx vertices[15]; +} sPrevSnowVertices[140 / 5]; +static s16 sPrevSnowParticleCount; +static u32 sPrevSnowTimestamp; + +void patch_interpolated_snow_particles(void) { + int i; + + if (gGlobalTimer != sPrevSnowTimestamp + 1) { + return; + } + + for (i = 0; i < sPrevSnowParticleCount; i += 5) { + gSPVertex(sPrevSnowVertices[i / 5].pos, + VIRTUAL_TO_PHYSICAL(sPrevSnowVertices[i / 5].vertices), 15, 0); + } +} + /** * Initialize snow particles by allocating a buffer for storing their state * and setting a start amount. @@ -217,6 +237,7 @@ void envfx_update_snow_normal(s32 snowCylinderX, s32 snowCylinderY, s32 snowCyli 400.0f * random_float() - 200.0f + snowCylinderZ + (s16)(deltaZ * 2); (gEnvFxBuffer + i)->yPos = 200.0f * random_float() + snowCylinderY; (gEnvFxBuffer + i)->isAlive = 1; + (gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer; } else { (gEnvFxBuffer + i)->xPos += random_float() * 2 - 1.0f + (s16)(deltaX / 1.2); (gEnvFxBuffer + i)->yPos -= 2 -(s16)(deltaY * 0.8); @@ -251,6 +272,7 @@ void envfx_update_snow_blizzard(s32 snowCylinderX, s32 snowCylinderY, s32 snowCy 400.0f * random_float() - 200.0f + snowCylinderZ + (s16)(deltaZ * 2); (gEnvFxBuffer + i)->yPos = 400.0f * random_float() - 200.0f + snowCylinderY; (gEnvFxBuffer + i)->isAlive = 1; + (gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer; } else { (gEnvFxBuffer + i)->xPos += random_float() * 2 - 1.0f + (s16)(deltaX / 1.2) + 20.0f; (gEnvFxBuffer + i)->yPos -= 5 -(s16)(deltaY * 0.8); @@ -294,6 +316,7 @@ void envfx_update_snow_water(s32 snowCylinderX, s32 snowCylinderY, s32 snowCylin (gEnvFxBuffer + i)->zPos = 400.0f * random_float() - 200.0f + snowCylinderZ; (gEnvFxBuffer + i)->yPos = 400.0f * random_float() - 200.0f + snowCylinderY; (gEnvFxBuffer + i)->isAlive = 1; + (gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer; } } } @@ -346,6 +369,8 @@ void rotate_triangle_vertices(Vec3s vertex1, Vec3s vertex2, Vec3s vertex3, s16 p void append_snowflake_vertex_buffer(Gfx *gfx, s32 index, Vec3s vertex1, Vec3s vertex2, Vec3s vertex3) { s32 i = 0; Vtx *vertBuf = (Vtx *) alloc_display_list(15 * sizeof(Vtx)); + Vtx *vertBufInterpolated = (Vtx *) alloc_display_list(15 * sizeof(Vtx)); + Vtx *v; #ifdef VERSION_EU Vtx *p; #endif @@ -395,7 +420,23 @@ void append_snowflake_vertex_buffer(Gfx *gfx, s32 index, Vec3s vertex1, Vec3s ve #endif } - 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); } /** @@ -479,6 +520,8 @@ Gfx *envfx_update_snow(s32 snowMode, Vec3s marioPos, Vec3s camFrom, Vec3s camTo) gSP1Triangle(gfx++, 9, 10, 11, 0); gSP1Triangle(gfx++, 12, 13, 14, 0); } + sPrevSnowParticleCount = gSnowParticleCount; + sPrevSnowTimestamp = gGlobalTimer; gSPDisplayList(gfx++, &tiny_bubble_dl_0B006AB0) gSPEndDisplayList(gfx++); diff --git a/src/game/envfx_snow.h b/src/game/envfx_snow.h index 7a83b53..f4acc2d 100644 --- a/src/game/envfx_snow.h +++ b/src/game/envfx_snow.h @@ -25,7 +25,8 @@ struct EnvFxParticle { s32 angleAndDist[2]; // for whirpools, [0] = angle from center, [1] = distance from center s32 unusedBubbleVar; // set to zero for bubbles when respawning, never used elsewhere s32 bubbleY; // for Bubbles, yPos is always set to this - s8 filler20[56 - 0x20]; + //s8 filler20[56 - 0x20]; + u32 spawnTimestamp; }; extern s8 gEnvFxMode; diff --git a/src/game/hud.c b/src/game/hud.c index 1540b67..0de6e0b 100644 --- a/src/game/hud.c +++ b/src/game/hud.c @@ -59,6 +59,20 @@ static struct UnusedHUDStruct sUnusedHUDValues = { 0x00, 0x0A, 0x00 }; static struct CameraHUD sCameraHUD = { CAM_STATUS_NONE }; +static u32 sPowerMeterLastRenderTimestamp; +static s16 sPowerMeterLastY; +static Gfx *sPowerMeterDisplayListPos; + +void patch_interpolated_hud(void) { + if (sPowerMeterDisplayListPos != NULL) { + Mtx *mtx = alloc_display_list(sizeof(Mtx)); + guTranslate(mtx, (f32) sPowerMeterHUD.x, (f32) sPowerMeterHUD.y, 0); + gSPMatrix(sPowerMeterDisplayListPos, VIRTUAL_TO_PHYSICAL(mtx), + G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_PUSH); + sPowerMeterDisplayListPos = NULL; + } +} + /** * Renders a rgba16 16x16 glyph texture from a table list. */ @@ -111,6 +125,7 @@ void render_power_meter_health_segment(s16 numHealthWedges) { */ void render_dl_power_meter(s16 numHealthWedges) { Mtx *mtx; + f32 interpolatedY; mtx = alloc_display_list(sizeof(Mtx)); @@ -118,7 +133,15 @@ void render_dl_power_meter(s16 numHealthWedges) { return; } - guTranslate(mtx, (f32) sPowerMeterHUD.x, (f32) sPowerMeterHUD.y, 0); + if (gGlobalTimer == sPowerMeterLastRenderTimestamp + 1) { + interpolatedY = (sPowerMeterLastY + sPowerMeterHUD.y) / 2.0f; + } else { + interpolatedY = sPowerMeterHUD.y; + } + guTranslate(mtx, (f32) sPowerMeterHUD.x, interpolatedY, 0); + sPowerMeterLastY = sPowerMeterHUD.y; + sPowerMeterLastRenderTimestamp = gGlobalTimer; + sPowerMeterDisplayListPos = gDisplayListHead; gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(mtx++), G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_PUSH); diff --git a/src/game/ingame_menu.c b/src/game/ingame_menu.c index 7ae9f1e..1c23c96 100644 --- a/src/game/ingame_menu.c +++ b/src/game/ingame_menu.c @@ -130,6 +130,42 @@ s32 gDialogResponse = 0; static struct CachedChar { u8 used; u8 data[CHCACHE_BUFLEN]; } charCache[256]; #endif // VERSION +static Gfx *sInterpolatedDialogOffsetPos; +static f32 sInterpolatedDialogOffset; +static Gfx *sInterpolatedDialogRotationPos; +static f32 sInterpolatedDialogScale; +static f32 sInterpolatedDialogRotation; +static Gfx *sInterpolatedDialogZoomPos; + +void patch_interpolated_dialog(void) { + Mtx *matrix; + + if (sInterpolatedDialogOffsetPos != NULL) { + matrix = (Mtx *) alloc_display_list(sizeof(Mtx)); + guTranslate(matrix, 0, sInterpolatedDialogOffset, 0); + gSPMatrix(sInterpolatedDialogOffsetPos, VIRTUAL_TO_PHYSICAL(matrix), G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH); + sInterpolatedDialogOffsetPos = NULL; + } + if (sInterpolatedDialogRotationPos != NULL) { + matrix = (Mtx *) alloc_display_list(sizeof(Mtx)); + guScale(matrix, 1.0 / sInterpolatedDialogScale, 1.0 / sInterpolatedDialogScale, 1.0f); + gSPMatrix(sInterpolatedDialogRotationPos++, VIRTUAL_TO_PHYSICAL(matrix), G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH); + matrix = (Mtx *) alloc_display_list(sizeof(Mtx)); + guRotate(matrix, sInterpolatedDialogRotation * 4.0f, 0, 0, 1.0f); + gSPMatrix(sInterpolatedDialogRotationPos, VIRTUAL_TO_PHYSICAL(matrix), G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH); + sInterpolatedDialogRotationPos = NULL; + } + if (sInterpolatedDialogZoomPos != NULL) { + matrix = (Mtx *) alloc_display_list(sizeof(Mtx)); + guTranslate(matrix, 65.0 - (65.0 / sInterpolatedDialogScale), (40.0 / sInterpolatedDialogScale) - 40, 0); + gSPMatrix(sInterpolatedDialogZoomPos++, VIRTUAL_TO_PHYSICAL(matrix), G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH); + matrix = (Mtx *) alloc_display_list(sizeof(Mtx)); + guScale(matrix, 1.0 / sInterpolatedDialogScale, 1.0 / sInterpolatedDialogScale, 1.0f); + gSPMatrix(sInterpolatedDialogZoomPos, VIRTUAL_TO_PHYSICAL(matrix), G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH); + sInterpolatedDialogZoomPos = NULL; + } +} + void create_dl_identity_matrix(void) { Mtx *matrix = (Mtx *) alloc_display_list(sizeof(Mtx)); @@ -969,6 +1005,14 @@ void render_dialog_box_type(struct DialogEntry *dialog, s8 linesPerBox) { switch (gDialogBoxType) { case DIALOG_TYPE_ROTATE: // Renders a dialog black box with zoom and rotation if (gDialogBoxState == DIALOG_STATE_OPENING || gDialogBoxState == DIALOG_STATE_CLOSING) { + sInterpolatedDialogRotationPos = gDisplayListHead; + if (gDialogBoxState == DIALOG_STATE_OPENING) { + sInterpolatedDialogScale = gDialogBoxScale - 2 / 2; + sInterpolatedDialogRotation = gDialogBoxOpenTimer - 7.5f / 2; + } else { + sInterpolatedDialogScale = gDialogBoxScale + 2 / 2; + sInterpolatedDialogRotation = gDialogBoxOpenTimer + 7.5f / 2; + } create_dl_scale_matrix(MENU_MTX_NOPUSH, 1.0 / gDialogBoxScale, 1.0 / gDialogBoxScale, 1.0f); // convert the speed into angle create_dl_rotation_matrix(MENU_MTX_NOPUSH, gDialogBoxOpenTimer * 4.0f, 0, 0, 1.0f); @@ -977,6 +1021,12 @@ void render_dialog_box_type(struct DialogEntry *dialog, s8 linesPerBox) { break; case DIALOG_TYPE_ZOOM: // Renders a dialog white box with zoom if (gDialogBoxState == DIALOG_STATE_OPENING || gDialogBoxState == DIALOG_STATE_CLOSING) { + sInterpolatedDialogZoomPos = gDisplayListHead; + if (gDialogBoxState == DIALOG_STATE_OPENING) { + sInterpolatedDialogScale = gDialogBoxScale - 2 / 2; + } else { + sInterpolatedDialogScale = gDialogBoxScale + 2 / 2; + } create_dl_translation_matrix(MENU_MTX_NOPUSH, 65.0 - (65.0 / gDialogBoxScale), (40.0 / gDialogBoxScale) - 40, 0); create_dl_scale_matrix(MENU_MTX_NOPUSH, 1.0 / gDialogBoxScale, 1.0 / gDialogBoxScale, 1.0f); @@ -1259,6 +1309,8 @@ void handle_dialog_text_and_pages(s8 colorMode, struct DialogEntry *dialog, s8 l #ifdef VERSION_EU gDialogY -= gDialogScrollOffsetY; #else + sInterpolatedDialogOffset = gDialogScrollOffsetY + dialog->linesPerBox; + sInterpolatedDialogOffsetPos = gDisplayListHead; create_dl_translation_matrix(MENU_MTX_NOPUSH, 0, (f32) gDialogScrollOffsetY, 0); #endif } diff --git a/src/game/level_geo.c b/src/game/level_geo.c index 4c98e70..abc5121 100644 --- a/src/game/level_geo.c +++ b/src/game/level_geo.c @@ -34,12 +34,16 @@ Gfx *geo_envfx_main(s32 callContext, struct GraphNode *node, Mat4 mtxf) { vec3f_to_vec3s(marioPos, gPlayerCameraState->pos); particleList = envfx_update_particles(snowMode, marioPos, camTo, camFrom); if (particleList != NULL) { +#if 0 Mtx *mtx = alloc_display_list(sizeof(*mtx)); gfx = alloc_display_list(2 * sizeof(*gfx)); mtxf_to_mtx(mtx, mtxf); gSPMatrix(&gfx[0], VIRTUAL_TO_PHYSICAL(mtx), G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH); gSPBranchList(&gfx[1], VIRTUAL_TO_PHYSICAL(particleList)); +#else + gfx = particleList; +#endif execNode->fnNode.node.flags = (execNode->fnNode.node.flags & 0xFF) | 0x400; } SET_HIGH_U16_OF_32(*params, gAreaUpdateCounter); diff --git a/src/game/object_helpers.c b/src/game/object_helpers.c index 22b45b3..109d7f7 100644 --- a/src/game/object_helpers.c +++ b/src/game/object_helpers.c @@ -1554,6 +1554,7 @@ void cur_obj_set_pos_to_home(void) { o->oPosX = o->oHomeX; o->oPosY = o->oHomeY; o->oPosZ = o->oHomeZ; + o->header.gfx.skipInterpolationTimestamp = gGlobalTimer; } void cur_obj_set_pos_to_home_and_stop(void) { diff --git a/src/game/paintings.c b/src/game/paintings.c index 6cae19c..a304d4a 100644 --- a/src/game/paintings.c +++ b/src/game/paintings.c @@ -189,6 +189,32 @@ struct Painting **sPaintingGroups[] = { s16 gPaintingUpdateCounter = 1; s16 gLastPaintingUpdateCounter = 0; +static Vtx sLastVertices[2 * 264 * 3]; +static u32 sLastVerticesTimestamp; +static Vtx *sVerticesPtr[2]; +static s32 sVerticesCount; + +void patch_interpolated_paintings(void) { + if (sVerticesPtr[0] != NULL) { + s32 i; + if (sVerticesPtr[1] != NULL) { + for (i = 0; i < sVerticesCount / 2; i++) { + sVerticesPtr[0][i] = sLastVertices[i]; + } + for (; i < sVerticesCount; i++) { + sVerticesPtr[1][i - sVerticesCount / 2] = sLastVertices[i]; + } + } else { + for (i = 0; i < sVerticesCount; i++) { + sVerticesPtr[0][i] = sLastVertices[i]; + } + } + sVerticesPtr[0] = NULL; + sVerticesPtr[1] = NULL; + sVerticesCount = 0; + } +} + /** * Stop paintings in paintingGroup from rippling if their id is different from *idptr. */ @@ -890,6 +916,23 @@ Gfx *render_painting(u8 *img, s16 tWidth, s16 tHeight, s16 *textureMap, s16 mapV gSP1Triangle(gfx++, group * 3, group * 3 + 1, group * 3 + 2, 0); } + if (sVerticesCount >= numVtx * 2) { + sVerticesCount = 0; + } + for (map = 0; map < numVtx; map++) { + Vtx v = verts[map]; + if (gGlobalTimer == sLastVerticesTimestamp + 1) { + s32 i; + for (i = 0; i < 3; i++) { + verts[map].n.ob[i] = (v.n.ob[i] + sLastVertices[sVerticesCount + map].n.ob[i]) / 2; + verts[map].n.n[i] = (v.n.n[i] + sLastVertices[sVerticesCount + map].n.n[i]) / 2; + } + } + sLastVertices[sVerticesCount + map] = v; + } + sVerticesPtr[sVerticesCount / numVtx] = verts; + sVerticesCount += numVtx; + gSPEndDisplayList(gfx); return dlist; } @@ -954,6 +997,7 @@ Gfx *painting_ripple_image(struct Painting *painting) { meshTris = textureMap[meshVerts * 3 + 1]; gSPDisplayList(gfx++, render_painting(textures[i], tWidth, tHeight, textureMap, meshVerts, meshTris, painting->alpha)); } + sLastVerticesTimestamp = gGlobalTimer; // Update the ripple, may automatically reset the painting's state. painting_update_ripple_state(painting); @@ -991,6 +1035,7 @@ Gfx *painting_ripple_env_mapped(struct Painting *painting) { meshVerts = textureMap[0]; meshTris = textureMap[meshVerts * 3 + 1]; gSPDisplayList(gfx++, render_painting(tArray[0], tWidth, tHeight, textureMap, meshVerts, meshTris, painting->alpha)); + sLastVerticesTimestamp = gGlobalTimer; // Update the ripple, may automatically reset the painting's state. painting_update_ripple_state(painting); diff --git a/src/game/rendering_graph_node.c b/src/game/rendering_graph_node.c index d5bf577..71656b4 100644 --- a/src/game/rendering_graph_node.c +++ b/src/game/rendering_graph_node.c @@ -39,6 +39,8 @@ s16 gMatStackIndex; Mat4 gMatStack[32]; Mtx *gMatStackFixed[32]; +Mat4 gMatStackInterpolated[32]; +Mtx *gMatStackInterpolatedFixed[32]; /** * Animation nodes have state in global variables, so this struct captures @@ -52,6 +54,7 @@ struct GeoAnimState { /*0x04*/ f32 translationMultiplier; /*0x08*/ u16 *attribute; /*0x0C*/ s16 *data; + s16 prevFrame; }; // For some reason, this is a GeoAnimState struct, but the current state consists @@ -61,6 +64,7 @@ struct GeoAnimState gGeoTempState; u8 gCurAnimType; u8 gCurAnimEnabled; s16 gCurrAnimFrame; +s16 gPrevAnimFrame; f32 gCurAnimTranslationMultiplier; u16 *gCurrAnimAttribute; s16 *gCurAnimData; @@ -129,6 +133,46 @@ u16 gAreaUpdateCounter = 0; LookAt lookAt; #endif +static Gfx *sPerspectivePos; +static Mtx *sPerspectiveMtx; + +struct { + Gfx *pos; + void *mtx; + void *displayList; +} gMtxTbl[6400]; +s32 gMtxTblSize; + +static Gfx *sViewportPos; +static Vp sPrevViewport; + +void mtx_patch_interpolated(void) { + s32 i; + + if (sPerspectivePos != NULL) { + gSPMatrix(sPerspectivePos, VIRTUAL_TO_PHYSICAL(sPerspectiveMtx), G_MTX_PROJECTION | G_MTX_LOAD | G_MTX_NOPUSH); + } + + for (i = 0; i < gMtxTblSize; i++) { + Gfx *pos = gMtxTbl[i].pos; + gSPMatrix(pos++, VIRTUAL_TO_PHYSICAL(gMtxTbl[i].mtx), + G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH); + gSPDisplayList(pos++, gMtxTbl[i].displayList); + } + + if (sViewportPos != NULL) { + Gfx *saved = gDisplayListHead; + gDisplayListHead = sViewportPos; + make_viewport_clip_rect(&sPrevViewport); + gSPViewport(gDisplayListHead, VIRTUAL_TO_PHYSICAL(&sPrevViewport)); + gDisplayListHead = saved; + } + + gMtxTblSize = 0; + sPerspectivePos = NULL; + sViewportPos = NULL; +} + /** * Process a master list node. */ @@ -156,9 +200,14 @@ static void geo_process_master_list_sub(struct GraphNodeMasterList *node) { if ((currList = node->listHeads[i]) != NULL) { gDPSetRenderMode(gDisplayListHead++, modeList->modes[i], mode2List->modes[i]); while (currList != NULL) { - gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(currList->transform), + if ((u32) gMtxTblSize < sizeof(gMtxTbl) / sizeof(gMtxTbl[0])) { + gMtxTbl[gMtxTblSize].pos = gDisplayListHead; + gMtxTbl[gMtxTblSize].mtx = currList->transform; + gMtxTbl[gMtxTblSize++].displayList = currList->displayList; + } + gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(currList->transformInterpolated), G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH); - gSPDisplayList(gDisplayListHead++, currList->displayList); + gSPDisplayList(gDisplayListHead++, currList->displayListInterpolated); currList = currList->next; } } @@ -174,7 +223,7 @@ static void geo_process_master_list_sub(struct GraphNodeMasterList *node) { * parameter. Look at the RenderModeContainer struct to see the corresponding * render modes of layers. */ -static void geo_append_display_list(void *displayList, s16 layer) { +static void geo_append_display_list2(void *displayList, void *displayListInterpolated, s16 layer) { #ifdef F3DEX_GBI_2 gSPLookAt(gDisplayListHead++, &lookAt); @@ -184,7 +233,9 @@ static void geo_append_display_list(void *displayList, s16 layer) { alloc_only_pool_alloc(gDisplayListHeap, sizeof(struct DisplayListNode)); listNode->transform = gMatStackFixed[gMatStackIndex]; + listNode->transformInterpolated = gMatStackInterpolatedFixed[gMatStackIndex]; listNode->displayList = displayList; + listNode->displayListInterpolated = displayListInterpolated; listNode->next = 0; if (gCurGraphNodeMasterList->listHeads[layer] == 0) { gCurGraphNodeMasterList->listHeads[layer] = listNode; @@ -195,6 +246,10 @@ static void geo_append_display_list(void *displayList, s16 layer) { } } +static void geo_append_display_list(void *displayList, s16 layer) { + geo_append_display_list2(displayList, displayList, layer); +} + /** * Process the master list node. */ @@ -241,7 +296,9 @@ static void geo_process_perspective(struct GraphNodePerspective *node) { } if (node->fnNode.node.children != NULL) { u16 perspNorm; + Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); Mtx *mtx = alloc_display_list(sizeof(*mtx)); + f32 fovInterpolated; #ifdef VERSION_EU f32 aspect = ((f32) gCurGraphNodeRoot->width / (f32) gCurGraphNodeRoot->height) * 1.1f; @@ -250,9 +307,23 @@ static void geo_process_perspective(struct GraphNodePerspective *node) { #endif guPerspective(mtx, &perspNorm, node->fov, aspect, node->near, node->far, 1.0f); - gSPPerspNormalize(gDisplayListHead++, perspNorm); - gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(mtx), G_MTX_PROJECTION | G_MTX_LOAD | G_MTX_NOPUSH); + if (gGlobalTimer == node->prevTimestamp + 1 && gGlobalTimer != gLakituState.skipCameraInterpolationTimestamp) { + + fovInterpolated = (node->prevFov + node->fov) / 2.0f; + guPerspective(mtxInterpolated, &perspNorm, fovInterpolated, aspect, node->near, node->far, 1.0f); + gSPPerspNormalize(gDisplayListHead++, perspNorm); + + sPerspectivePos = gDisplayListHead; + sPerspectiveMtx = mtx; + gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(mtxInterpolated), + G_MTX_PROJECTION | G_MTX_LOAD | G_MTX_NOPUSH); + } else { + gSPPerspNormalize(gDisplayListHead++, perspNorm); + gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(mtx), G_MTX_PROJECTION | G_MTX_LOAD | G_MTX_NOPUSH); + } + node->prevFov = node->fov; + node->prevTimestamp = gGlobalTimer; gCurGraphNodeCamFrustum = node; geo_process_node_and_siblings(node->fnNode.node.children); @@ -297,6 +368,39 @@ static void geo_process_switch(struct GraphNodeSwitchCase *node) { } } +void interpolate_vectors(Vec3f res, Vec3f a, Vec3f b) { + res[0] = (a[0] + b[0]) / 2.0f; + res[1] = (a[1] + b[1]) / 2.0f; + res[2] = (a[2] + b[2]) / 2.0f; +} + +void interpolate_vectors_s16(Vec3s res, Vec3s a, Vec3s b) { + res[0] = (a[0] + b[0]) / 2; + res[1] = (a[1] + b[1]) / 2; + res[2] = (a[2] + b[2]) / 2; +} + +static s16 interpolate_angle(s16 a, s16 b) { + s32 absDiff = b - a; + if (absDiff < 0) { + absDiff = -absDiff; + } + if (absDiff >= 0x4000 && absDiff <= 0xC000) { + return b; + } + if (absDiff <= 0x8000) { + return (a + b) / 2; + } else { + return (a + b) / 2 + 0x8000; + } +} + +static void interpolate_angles(Vec3s res, Vec3s a, Vec3s b) { + res[0] = interpolate_angle(a[0], b[0]); + res[1] = interpolate_angle(a[1], b[1]); + res[2] = interpolate_angle(a[2], b[2]); +} + /** * Process a camera node. */ @@ -304,6 +408,9 @@ static void geo_process_camera(struct GraphNodeCamera *node) { Mat4 cameraTransform; Mtx *rollMtx = alloc_display_list(sizeof(*rollMtx)); Mtx *mtx = alloc_display_list(sizeof(*mtx)); + Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); + Vec3f posInterpolated; + Vec3f focusInterpolated; if (node->fnNode.func != NULL) { node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, gMatStack[gMatStackIndex]); @@ -314,12 +421,40 @@ static void geo_process_camera(struct GraphNodeCamera *node) { mtxf_lookat(cameraTransform, node->pos, node->focus, node->roll); mtxf_mul(gMatStack[gMatStackIndex + 1], cameraTransform, gMatStack[gMatStackIndex]); + + if (gGlobalTimer == node->prevTimestamp + 1 && gGlobalTimer != gLakituState.skipCameraInterpolationTimestamp) { + interpolate_vectors(posInterpolated, node->prevPos, node->pos); + interpolate_vectors(focusInterpolated, node->prevFocus, node->focus); + float magnitude = 0; + for (int i = 0; i < 3; i++) { + float diff = node->pos[i] - node->prevPos[i]; + magnitude += diff * diff; + } + if (magnitude > 500000) { + // Observed ~479000 in BBH when toggling R camera + // Can get over 3 million in VCUTM though... + vec3f_copy(posInterpolated, node->pos); + vec3f_copy(focusInterpolated, node->focus); + } + } else { + vec3f_copy(posInterpolated, node->pos); + vec3f_copy(focusInterpolated, node->focus); + } + vec3f_copy(node->prevPos, node->pos); + vec3f_copy(node->prevFocus, node->focus); + node->prevTimestamp = gGlobalTimer; + mtxf_lookat(cameraTransform, posInterpolated, focusInterpolated, node->roll); + mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], cameraTransform, gMatStackInterpolated[gMatStackIndex]); + gMatStackIndex++; mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); gMatStackFixed[gMatStackIndex] = mtx; + mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); + gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; if (node->fnNode.node.children != 0) { gCurGraphNodeCamera = node; node->matrixPtr = &gMatStack[gMatStackIndex]; + node->matrixPtrInterpolated = &gMatStackInterpolated[gMatStackIndex]; geo_process_node_and_siblings(node->fnNode.node.children); gCurGraphNodeCamera = NULL; } @@ -336,13 +471,17 @@ static void geo_process_translation_rotation(struct GraphNodeTranslationRotation Mat4 mtxf; Vec3f translation; Mtx *mtx = alloc_display_list(sizeof(*mtx)); + Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); vec3s_to_vec3f(translation, node->translation); mtxf_rotate_zxy_and_translate(mtxf, translation, node->rotation); mtxf_mul(gMatStack[gMatStackIndex + 1], mtxf, gMatStack[gMatStackIndex]); + mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], mtxf, gMatStackInterpolated[gMatStackIndex]); gMatStackIndex++; mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); gMatStackFixed[gMatStackIndex] = mtx; + mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); + gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; if (node->displayList != NULL) { geo_append_display_list(node->displayList, node->node.flags >> 8); } @@ -361,13 +500,17 @@ static void geo_process_translation(struct GraphNodeTranslation *node) { Mat4 mtxf; Vec3f translation; Mtx *mtx = alloc_display_list(sizeof(*mtx)); + Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); vec3s_to_vec3f(translation, node->translation); mtxf_rotate_zxy_and_translate(mtxf, translation, gVec3sZero); mtxf_mul(gMatStack[gMatStackIndex + 1], mtxf, gMatStack[gMatStackIndex]); + mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], mtxf, gMatStackInterpolated[gMatStackIndex]); gMatStackIndex++; mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); gMatStackFixed[gMatStackIndex] = mtx; + mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); + gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; if (node->displayList != NULL) { geo_append_display_list(node->displayList, node->node.flags >> 8); } @@ -385,12 +528,23 @@ static void geo_process_translation(struct GraphNodeTranslation *node) { static void geo_process_rotation(struct GraphNodeRotation *node) { Mat4 mtxf; Mtx *mtx = alloc_display_list(sizeof(*mtx)); + Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); + Vec3s rotationInterpolated; mtxf_rotate_zxy_and_translate(mtxf, gVec3fZero, node->rotation); mtxf_mul(gMatStack[gMatStackIndex + 1], mtxf, gMatStack[gMatStackIndex]); + if (gGlobalTimer == node->prevTimestamp + 1) { + interpolate_angles(rotationInterpolated, node->prevRotation, node->rotation); + mtxf_rotate_zxy_and_translate(mtxf, gVec3fZero, rotationInterpolated); + } + vec3s_copy(node->prevRotation, node->rotation); + node->prevTimestamp = gGlobalTimer; + mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], mtxf, gMatStackInterpolated[gMatStackIndex]); gMatStackIndex++; mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); gMatStackFixed[gMatStackIndex] = mtx; + mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); + gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; if (node->displayList != NULL) { geo_append_display_list(node->displayList, node->node.flags >> 8); } @@ -409,12 +563,16 @@ static void geo_process_scale(struct GraphNodeScale *node) { UNUSED Mat4 transform; Vec3f scaleVec; Mtx *mtx = alloc_display_list(sizeof(*mtx)); + Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); vec3f_set(scaleVec, node->scale, node->scale, node->scale); mtxf_scale_vec3f(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex], scaleVec); + mtxf_scale_vec3f(gMatStackInterpolated[gMatStackIndex + 1], gMatStackInterpolated[gMatStackIndex], scaleVec); gMatStackIndex++; mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); gMatStackFixed[gMatStackIndex] = mtx; + mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); + gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; if (node->displayList != NULL) { geo_append_display_list(node->displayList, node->node.flags >> 8); } @@ -433,21 +591,30 @@ static void geo_process_scale(struct GraphNodeScale *node) { static void geo_process_billboard(struct GraphNodeBillboard *node) { Vec3f translation; Mtx *mtx = alloc_display_list(sizeof(*mtx)); + Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); gMatStackIndex++; vec3s_to_vec3f(translation, node->translation); mtxf_billboard(gMatStack[gMatStackIndex], gMatStack[gMatStackIndex - 1], translation, gCurGraphNodeCamera->roll); + mtxf_billboard(gMatStackInterpolated[gMatStackIndex], gMatStackInterpolated[gMatStackIndex - 1], translation, + gCurGraphNodeCamera->roll); if (gCurGraphNodeHeldObject != NULL) { mtxf_scale_vec3f(gMatStack[gMatStackIndex], gMatStack[gMatStackIndex], gCurGraphNodeHeldObject->objNode->header.gfx.scale); + mtxf_scale_vec3f(gMatStackInterpolated[gMatStackIndex], gMatStackInterpolated[gMatStackIndex], + gCurGraphNodeHeldObject->objNode->header.gfx.scale); } else if (gCurGraphNodeObject != NULL) { mtxf_scale_vec3f(gMatStack[gMatStackIndex], gMatStack[gMatStackIndex], gCurGraphNodeObject->scale); + mtxf_scale_vec3f(gMatStackInterpolated[gMatStackIndex], gMatStackInterpolated[gMatStackIndex], + gCurGraphNodeObject->scale); } mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); gMatStackFixed[gMatStackIndex] = mtx; + mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); + gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; if (node->displayList != NULL) { geo_append_display_list(node->displayList, node->node.flags >> 8); } @@ -496,13 +663,39 @@ static void geo_process_generated_list(struct GraphNodeGenerated *node) { */ static void geo_process_background(struct GraphNodeBackground *node) { Gfx *list = NULL; + Gfx *listInterpolated = NULL; if (node->fnNode.func != NULL) { + Vec3f posCopy; + Vec3f focusCopy; + Vec3f posInterpolated; + Vec3f focusInterpolated; + + if (gGlobalTimer == node->prevCameraTimestamp + 1 && + gGlobalTimer != gLakituState.skipCameraInterpolationTimestamp) { + interpolate_vectors(posInterpolated, node->prevCameraPos, gLakituState.pos); + interpolate_vectors(focusInterpolated, node->prevCameraFocus, gLakituState.focus); + } else { + vec3f_copy(posInterpolated, gLakituState.pos); + vec3f_copy(focusInterpolated, gLakituState.focus); + } + vec3f_copy(node->prevCameraPos, gLakituState.pos); + vec3f_copy(node->prevCameraFocus, gLakituState.focus); + node->prevCameraTimestamp = gGlobalTimer; + list = node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, (struct AllocOnlyPool *) gMatStack[gMatStackIndex]); + vec3f_copy(posCopy, gLakituState.pos); + vec3f_copy(focusCopy, gLakituState.focus); + vec3f_copy(gLakituState.pos, posInterpolated); + vec3f_copy(gLakituState.focus, focusInterpolated); + listInterpolated = node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, NULL); + vec3f_copy(gLakituState.pos, posCopy); + vec3f_copy(gLakituState.focus, focusCopy); } if (list != 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) { #ifndef F3DEX_GBI_2E Gfx *gfxStart = alloc_display_list(sizeof(Gfx) * 7); @@ -527,61 +720,81 @@ 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; } } } - 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++; mtxf_to_mtx(matrixPtr, gMatStack[gMatStackIndex]); gMatStackFixed[gMatStackIndex] = matrixPtr; + mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); + gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; if (node->displayList != NULL) { geo_append_display_list(node->displayList, node->node.flags >> 8); } @@ -613,6 +826,17 @@ 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; gCurrAnimAttribute = segmented_to_virtual((void *) anim->index); gCurAnimData = segmented_to_virtual((void *) anim->values); @@ -631,8 +855,10 @@ void geo_set_animation_globals(struct GraphNodeObject_sub *node, s32 hasAnimatio */ static void geo_process_shadow(struct GraphNodeShadow *node) { Gfx *shadowList; + Gfx *shadowListInterpolated; Mat4 mtxf; Vec3f shadowPos; + Vec3f shadowPosInterpolated; Vec3f animOffset; f32 objScale; f32 shadowScale; @@ -640,6 +866,7 @@ static void geo_process_shadow(struct GraphNodeShadow *node) { f32 cosAng; struct GraphNode *geo; Mtx *mtx; + Mtx *mtxInterpolated; if (gCurGraphNodeCamera != NULL && gCurGraphNodeObject != NULL) { if (gCurGraphNodeHeldObject != NULL) { @@ -678,21 +905,57 @@ static void geo_process_shadow(struct GraphNodeShadow *node) { } } + if (gCurGraphNodeHeldObject != NULL) { + if (gGlobalTimer == gCurGraphNodeHeldObject->prevShadowPosTimestamp + 1) { + interpolate_vectors(shadowPosInterpolated, gCurGraphNodeHeldObject->prevShadowPos, shadowPos); + } else { + vec3f_copy(shadowPosInterpolated, shadowPos); + } + vec3f_copy(gCurGraphNodeHeldObject->prevShadowPos, shadowPos); + gCurGraphNodeHeldObject->prevShadowPosTimestamp = gGlobalTimer; + } else { + if (gGlobalTimer == gCurGraphNodeObject->prevShadowPosTimestamp + 1 && + gGlobalTimer != gCurGraphNodeObject->skipInterpolationTimestamp) { + interpolate_vectors(shadowPosInterpolated, gCurGraphNodeObject->prevShadowPos, shadowPos); + } else { + vec3f_copy(shadowPosInterpolated, shadowPos); + } + vec3f_copy(gCurGraphNodeObject->prevShadowPos, shadowPos); + gCurGraphNodeObject->prevShadowPosTimestamp = gGlobalTimer; + } + + extern u8 gInterpolatingSurfaces; + gInterpolatingSurfaces = TRUE; + shadowListInterpolated = create_shadow_below_xyz(shadowPosInterpolated[0], shadowPosInterpolated[1], + shadowPosInterpolated[2], shadowScale, + node->shadowSolidity, node->shadowType); + gInterpolatingSurfaces = FALSE; shadowList = create_shadow_below_xyz(shadowPos[0], shadowPos[1], shadowPos[2], shadowScale, node->shadowSolidity, node->shadowType); - if (shadowList != NULL) { + if (shadowListInterpolated != NULL && shadowList != NULL) { mtx = alloc_display_list(sizeof(*mtx)); + mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); gMatStackIndex++; + mtxf_translate(mtxf, shadowPos); mtxf_mul(gMatStack[gMatStackIndex], mtxf, *gCurGraphNodeCamera->matrixPtr); mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); gMatStackFixed[gMatStackIndex] = mtx; + + mtxf_translate(mtxf, shadowPosInterpolated); + mtxf_mul(gMatStackInterpolated[gMatStackIndex], mtxf, *gCurGraphNodeCamera->matrixPtrInterpolated); + mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); + gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; + if (gShadowAboveWaterOrLava == 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); } gMatStackIndex--; } @@ -789,31 +1052,101 @@ static int obj_is_in_view(struct GraphNodeObject *node, Mat4 matrix) { return TRUE; } +static void interpolate_matrix(Mat4 result, Mat4 a, Mat4 b) { + s32 i, j; + for (i = 0; i < 4; i++) { + for (j = 0; j < 4; j++) { + result[i][j] = (a[i][j] + b[i][j]) / 2.0f; + } + } +} + /** * Process an object node. */ static void geo_process_object(struct Object *node) { Mat4 mtxf; s32 hasAnimation = (node->header.gfx.node.flags & GRAPH_RENDER_HAS_ANIMATION) != 0; + Vec3f scaleInterpolated; if (node->header.gfx.unk18 == gCurGraphNodeRoot->areaIndex) { if (node->header.gfx.throwMatrix != NULL) { mtxf_mul(gMatStack[gMatStackIndex + 1], *node->header.gfx.throwMatrix, gMatStack[gMatStackIndex]); + if (gGlobalTimer == node->header.gfx.prevThrowMatrixTimestamp + 1 && + gGlobalTimer != node->header.gfx.skipInterpolationTimestamp) { + interpolate_matrix(mtxf, *node->header.gfx.throwMatrix, node->header.gfx.prevThrowMatrix); + mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], mtxf, + gMatStackInterpolated[gMatStackIndex]); + } else { + mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], (void *) node->header.gfx.throwMatrix, + gMatStackInterpolated[gMatStackIndex]); + } + mtxf_copy(node->header.gfx.prevThrowMatrix, *node->header.gfx.throwMatrix); + node->header.gfx.prevThrowMatrixTimestamp = gGlobalTimer; } else if (node->header.gfx.node.flags & GRAPH_RENDER_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; mtxf_scale_vec3f(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex + 1], node->header.gfx.scale); + mtxf_scale_vec3f(gMatStackInterpolated[gMatStackIndex + 1], gMatStackInterpolated[gMatStackIndex + 1], + scaleInterpolated); node->header.gfx.throwMatrix = &gMatStack[++gMatStackIndex]; + node->header.gfx.throwMatrixInterpolated = &gMatStackInterpolated[gMatStackIndex]; node->header.gfx.cameraToObject[0] = gMatStack[gMatStackIndex][3][0]; node->header.gfx.cameraToObject[1] = gMatStack[gMatStackIndex][3][1]; node->header.gfx.cameraToObject[2] = gMatStack[gMatStackIndex][3][2]; @@ -824,9 +1157,12 @@ static void geo_process_object(struct Object *node) { } if (obj_is_in_view(&node->header.gfx, gMatStack[gMatStackIndex])) { Mtx *mtx = alloc_display_list(sizeof(*mtx)); + Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); gMatStackFixed[gMatStackIndex] = mtx; + mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); + gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; if (node->header.gfx.sharedChild != NULL) { gCurGraphNodeObject = (struct GraphNodeObject *) node; node->header.gfx.sharedChild->parent = &node->header.gfx.node; @@ -837,11 +1173,16 @@ static void geo_process_object(struct Object *node) { if (node->header.gfx.node.children != NULL) { geo_process_node_and_siblings(node->header.gfx.node.children); } + } else { + node->header.gfx.prevThrowMatrixTimestamp = 0; + node->header.gfx.prevTimestamp = 0; + node->header.gfx.prevScaleTimestamp = 0; } gMatStackIndex--; gCurAnimType = ANIM_TYPE_NONE; node->header.gfx.throwMatrix = NULL; + node->header.gfx.throwMatrixInterpolated = NULL; } } @@ -868,6 +1209,8 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { Mat4 mat; Vec3f translation; Mtx *mtx = alloc_display_list(sizeof(*mtx)); + Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); + Vec3f scaleInterpolated; #ifdef F3DEX_GBI_2 gSPLookAt(gDisplayListHead++, &lookAt); @@ -883,6 +1226,14 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { translation[1] = node->translation[1] / 4.0f; translation[2] = node->translation[2] / 4.0f; + if (gGlobalTimer == node->objNode->header.gfx.prevScaleTimestamp + 1) { + interpolate_vectors(scaleInterpolated, node->objNode->header.gfx.prevScale, node->objNode->header.gfx.scale); + } else { + vec3f_copy(scaleInterpolated, node->objNode->header.gfx.scale); + } + vec3f_copy(node->objNode->header.gfx.prevScale, node->objNode->header.gfx.scale); + node->objNode->header.gfx.prevScaleTimestamp = gGlobalTimer; + mtxf_translate(mat, translation); mtxf_copy(gMatStack[gMatStackIndex + 1], *gCurGraphNodeObject->throwMatrix); gMatStack[gMatStackIndex + 1][3][0] = gMatStack[gMatStackIndex][3][0]; @@ -891,6 +1242,13 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { mtxf_mul(gMatStack[gMatStackIndex + 1], mat, gMatStack[gMatStackIndex + 1]); mtxf_scale_vec3f(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex + 1], node->objNode->header.gfx.scale); + mtxf_copy(gMatStackInterpolated[gMatStackIndex + 1], (void *) gCurGraphNodeObject->throwMatrixInterpolated); + gMatStackInterpolated[gMatStackIndex + 1][3][0] = gMatStackInterpolated[gMatStackIndex][3][0]; + gMatStackInterpolated[gMatStackIndex + 1][3][1] = gMatStackInterpolated[gMatStackIndex][3][1]; + gMatStackInterpolated[gMatStackIndex + 1][3][2] = gMatStackInterpolated[gMatStackIndex][3][2]; + mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], mat, gMatStackInterpolated[gMatStackIndex + 1]); + mtxf_scale_vec3f(gMatStackInterpolated[gMatStackIndex + 1], gMatStackInterpolated[gMatStackIndex + 1], + scaleInterpolated); if (node->fnNode.func != NULL) { node->fnNode.func(GEO_CONTEXT_HELD_OBJ, &node->fnNode.node, (struct AllocOnlyPool *) gMatStack[gMatStackIndex + 1]); @@ -898,12 +1256,15 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { gMatStackIndex++; mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); gMatStackFixed[gMatStackIndex] = mtx; + mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); + gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; gGeoTempState.type = gCurAnimType; gGeoTempState.enabled = gCurAnimEnabled; gGeoTempState.frame = gCurrAnimFrame; gGeoTempState.translationMultiplier = gCurAnimTranslationMultiplier; gGeoTempState.attribute = gCurrAnimAttribute; gGeoTempState.data = gCurAnimData; + gGeoTempState.prevFrame = gPrevAnimFrame; gCurAnimType = 0; gCurGraphNodeHeldObject = (void *) node; if (node->objNode->header.gfx.unk38.curAnim != NULL) { @@ -918,6 +1279,7 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { gCurAnimTranslationMultiplier = gGeoTempState.translationMultiplier; gCurrAnimAttribute = gGeoTempState.attribute; gCurAnimData = gGeoTempState.data; + gPrevAnimFrame = gGeoTempState.prevFrame; gMatStackIndex--; } @@ -1039,6 +1401,7 @@ void geo_process_root(struct GraphNodeRoot *node, Vp *b, Vp *c, s32 clearColor) if (node->node.flags & GRAPH_RENDER_ACTIVE) { Mtx *initialMatrix; Vp *viewport = alloc_display_list(sizeof(*viewport)); + Vp *viewportInterpolated = viewport; gDisplayListHeap = alloc_only_pool_init(main_pool_available() - sizeof(struct AllocOnlyPool), MEMORY_POOL_LEFT); @@ -1049,7 +1412,12 @@ void geo_process_root(struct GraphNodeRoot *node, Vp *b, Vp *c, s32 clearColor) vec3s_set(viewport->vp.vscale, node->width * 4, node->height * 4, 511); if (b != NULL) { clear_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; } @@ -1057,11 +1425,16 @@ void geo_process_root(struct GraphNodeRoot *node, Vp *b, Vp *c, s32 clearColor) clear_frame_buffer(clearColor); make_viewport_clip_rect(c); } + sPrevViewport = *viewport; mtxf_identity(gMatStack[gMatStackIndex]); mtxf_to_mtx(initialMatrix, gMatStack[gMatStackIndex]); gMatStackFixed[gMatStackIndex] = initialMatrix; - gSPViewport(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(viewport)); + + mtxf_identity(gMatStackInterpolated[gMatStackIndex]); + gMatStackInterpolatedFixed[gMatStackIndex] = initialMatrix; + + gSPViewport(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(viewportInterpolated)); gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(gMatStackFixed[gMatStackIndex]), G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH); gCurGraphNodeRoot = node; diff --git a/src/game/screen_transition.c b/src/game/screen_transition.c index b49ddaf..d6656af 100644 --- a/src/game/screen_transition.c +++ b/src/game/screen_transition.c @@ -16,6 +16,19 @@ u8 sTransitionColorFadeCount[4] = { 0 }; u16 sTransitionTextureFadeCount[2] = { 0 }; +static Gfx *sScreenTransitionVerticesPos[2]; +static Vtx *sScreenTransitionVertices; + +void patch_screen_transition_interpolated(void) { + if (sScreenTransitionVerticesPos[0] != NULL) { + gSPVertex(sScreenTransitionVerticesPos[0], VIRTUAL_TO_PHYSICAL(sScreenTransitionVertices), 8, 0); + gSPVertex(sScreenTransitionVerticesPos[1], VIRTUAL_TO_PHYSICAL(sScreenTransitionVertices), 4, 0); + sScreenTransitionVerticesPos[0] = NULL; + sScreenTransitionVerticesPos[1] = NULL; + sScreenTransitionVertices = NULL; + } +} + s32 set_and_reset_transition_fade_timer(s8 fadeTimer, u8 transTime) { s32 reset = FALSE; @@ -85,14 +98,29 @@ s32 render_fade_transition_into_color(s8 fadeTimer, u8 transTime, struct WarpTra return dl_transition_color(fadeTimer, transTime, transData, alpha); } +#if 0 + s16 calc_tex_transition_radius(s8 fadeTimer, s8 transTime, struct WarpTransitionData *transData) { f32 texRadius = transData->endTexRadius - transData->startTexRadius; f32 radiusTime = sTransitionColorFadeCount[fadeTimer] * texRadius / (f32)(transTime - 1); f32 result = transData->startTexRadius + radiusTime; - 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) { f32 startX = transData->startTexX; f32 startY = transData->startTexY; @@ -166,6 +194,8 @@ void *sTextureTransitionID[] = { texture_transition_bowser_half, }; +#if 0 + s32 render_textured_transition(s8 fadeTimer, s8 transTime, struct WarpTransitionData *transData, s8 texID, s8 transTexType) { f32 texTransTime = calc_tex_transition_time(fadeTimer, transTime, transData); u16 texTransPos = convert_tex_transition_angle_to_pos(transData); @@ -206,6 +236,56 @@ s32 render_textured_transition(s8 fadeTimer, s8 transTime, struct WarpTransition return set_and_reset_transition_fade_timer(fadeTimer, transTime); } +#else + +s32 render_textured_transition(s8 fadeTimer, s8 transTime, struct WarpTransitionData *transData, s8 texID, s8 transTexType) { + f32 texTransTime = calc_tex_transition_time(fadeTimer, transTime, transData); + u16 texTransPos = convert_tex_transition_angle_to_pos(transData); + s16 centerTransX = center_tex_transition_x(transData, texTransTime, texTransPos); + s16 centerTransY = center_tex_transition_y(transData, texTransTime, texTransPos); + s16 texTransRadius = calc_tex_transition_radius(fadeTimer, 1.0f, transTime, transData); + s16 texTransRadiusInterpolated = calc_tex_transition_radius(fadeTimer, 0.5f, transTime, transData); + Vtx *verts = alloc_display_list(8 * sizeof(*verts)); + Vtx *vertsInterpolated = alloc_display_list(8 * sizeof(*vertsInterpolated)); + + if (verts != NULL && vertsInterpolated != NULL) { + load_tex_transition_vertex(verts, fadeTimer, transData, centerTransX, centerTransY, texTransRadius, transTexType); + load_tex_transition_vertex(vertsInterpolated, fadeTimer, transData, centerTransX, centerTransY, texTransRadiusInterpolated, transTexType); + sScreenTransitionVertices = verts; + gSPDisplayList(gDisplayListHead++, dl_proj_mtx_fullscreen) + gDPSetCombineMode(gDisplayListHead++, G_CC_SHADE, G_CC_SHADE); + gDPSetRenderMode(gDisplayListHead++, G_RM_AA_OPA_SURF, G_RM_AA_OPA_SURF2); + sScreenTransitionVerticesPos[0] = gDisplayListHead; + gSPVertex(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(vertsInterpolated), 8, 0); + gSPDisplayList(gDisplayListHead++, dl_transition_draw_filled_region); + gDPPipeSync(gDisplayListHead++); + gDPSetCombineMode(gDisplayListHead++, G_CC_MODULATEIDECALA, G_CC_MODULATEIDECALA); + gDPSetRenderMode(gDisplayListHead++, G_RM_AA_XLU_SURF, G_RM_AA_XLU_SURF2); + gDPSetTextureFilter(gDisplayListHead++, G_TF_BILERP); + switch (transTexType) { + case TRANS_TYPE_MIRROR: + gDPLoadTextureBlock(gDisplayListHead++, sTextureTransitionID[texID], G_IM_FMT_IA, G_IM_SIZ_8b, 32, 64, 0, + G_TX_WRAP | G_TX_MIRROR, G_TX_WRAP | G_TX_MIRROR, 5, 6, G_TX_NOLOD, G_TX_NOLOD); + break; + case TRANS_TYPE_CLAMP: + gDPLoadTextureBlock(gDisplayListHead++, sTextureTransitionID[texID], G_IM_FMT_IA, G_IM_SIZ_8b, 64, 64, 0, + G_TX_CLAMP, G_TX_CLAMP, 6, 6, G_TX_NOLOD, G_TX_NOLOD); + break; + } + gSPTexture(gDisplayListHead++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_ON); + sScreenTransitionVerticesPos[1] = gDisplayListHead; + gSPVertex(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(vertsInterpolated), 4, 0); + gSPDisplayList(gDisplayListHead++, dl_draw_quad_verts_0123); + gSPTexture(gDisplayListHead++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_OFF); + gSPDisplayList(gDisplayListHead++, dl_screen_transition_end); + sTransitionTextureFadeCount[fadeTimer] += transData->texTimer; + } else { + } + return set_and_reset_transition_fade_timer(fadeTimer, transTime); +} + +#endif + int render_screen_transition(s8 fadeTimer, s8 transType, u8 transTime, struct WarpTransitionData *transData) { switch (transType) { case WARP_TRANSITION_FADE_FROM_COLOR: diff --git a/src/menu/intro_geo.c b/src/menu/intro_geo.c index 37c6752..d823d40 100644 --- a/src/menu/intro_geo.c +++ b/src/menu/intro_geo.c @@ -1,5 +1,6 @@ #include +#include "engine/math_util.h" #include "game/memory.h" #include "game/segment2.h" #include "game/segment7.h" @@ -70,6 +71,18 @@ s8 gameOverBackgroundTable[] = { s8 gameOverBackgroundFlipOrder[] = { 0x00, 0x01, 0x02, 0x03, 0x07, 0x0B, 0x0a, 0x09, 0x08, 0x04, 0x05, 0x06 }; +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) { struct GraphNode *graphNode; // sp4c Gfx *displayList; // sp48 @@ -80,6 +93,8 @@ Gfx *geo_title_screen(s32 sp50, struct GraphNode *sp54, UNUSED void *context) { f32 scaleX; // sp34 f32 scaleY; // sp30 f32 scaleZ; // sp2c + Vec3f scale; + Vec3f scaleInterpolated; graphNode = sp54; displayList = NULL; displayListIter = NULL; @@ -110,7 +125,11 @@ Gfx *geo_title_screen(s32 sp50, struct GraphNode *sp54, UNUSED void *context) { scaleY = 0.0f; scaleZ = 0.0f; } - guScale(scaleMat, scaleX, scaleY, scaleZ); + vec3f_set(scale, scaleX, scaleY, scaleZ); + interpolate_vectors(scaleInterpolated, sIntroScale, scale); + vec3f_set(sIntroScale, scaleX, scaleY, scaleZ); + guScale(scaleMat, scaleInterpolated[0], scaleInterpolated[1], scaleInterpolated[2]); + sIntroScalePos = displayListIter; gSPMatrix(displayListIter++, scaleMat, G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_PUSH); gSPDisplayList(displayListIter++, &intro_seg7_dl_0700B3A0); gSPPopMatrix(displayListIter++, G_MTX_MODELVIEW); diff --git a/src/pc/gfx/gfx_dxgi.cpp b/src/pc/gfx/gfx_dxgi.cpp index 0467495..fa4eb33 100644 --- a/src/pc/gfx/gfx_dxgi.cpp +++ b/src/pc/gfx/gfx_dxgi.cpp @@ -36,10 +36,10 @@ #ifdef VERSION_EU #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 using namespace Microsoft::WRL; // For ComPtr diff --git a/src/pc/gfx/gfx_sdl2.c b/src/pc/gfx/gfx_sdl2.c index aef2978..ff53b27 100644 --- a/src/pc/gfx/gfx_sdl2.c +++ b/src/pc/gfx/gfx_sdl2.c @@ -53,7 +53,7 @@ static void (*kb_all_keys_up)(void) = NULL; // whether to use timer for frame control static bool use_timer = true; // time between consequtive game frames -static const Uint32 frame_time = 1000 / FRAMERATE; +static const Uint32 frame_time = 1000 / (2 * FRAMERATE); static Uint32 frame_start = 0; const SDL_Scancode windows_scancode_table[] = { @@ -142,7 +142,7 @@ static inline void gfx_sdl_set_vsync(int mode) { if (mode > 1) { // try to detect refresh rate SDL_GL_SetSwapInterval(1); - const int vblanks = test_vsync(); + const int vblanks = test_vsync() / 2; if (vblanks) { printf("determined swap interval: %d\n", vblanks); SDL_GL_SetSwapInterval(vblanks); diff --git a/src/pc/pc_main.c b/src/pc/pc_main.c index ed6ee74..63679ad 100644 --- a/src/pc/pc_main.c +++ b/src/pc/pc_main.c @@ -83,6 +83,25 @@ void send_display_list(struct SPTask *spTask) { #define SAMPLES_LOW 528 #endif +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) { gfx_start_frame(); @@ -110,6 +129,11 @@ void produce_one_frame(void) { audio_api->play((u8 *)audio_buffer, 2 * num_audio_samples * 4); gfx_end_frame(); + + gfx_start_frame(); + patch_interpolations(); + send_display_list(gGfxSPTask); + gfx_end_frame(); } void audio_shutdown(void) {