2024-02-05 05:12:34 +01:00
# include "patches.h"
# include "transform_ids.h"
2024-03-02 19:33:09 +01:00
# include "z64cutscene.h"
2024-03-03 05:23:29 +01:00
# include "overlays/kaleido_scope/ovl_kaleido_scope/z_kaleido_scope.h"
# include "overlays/gamestates/ovl_file_choose/z_file_select.h"
2024-03-02 19:33:09 +01:00
2024-05-04 01:55:34 +02:00
// #define PRINT_CAMERA_INFO
2024-03-03 05:23:29 +01:00
static bool camera_interpolation_forced = false ;
static bool camera_skip_interpolation_forced = false ;
static bool camera_ignore_tracking = false ;
static bool in_kaleido = false ;
static bool prev_in_kaleido = false ;
2024-03-02 19:33:09 +01:00
2024-03-27 23:47:15 +01:00
static bool camera_skipped = false ;
void set_camera_skipped ( bool skipped ) {
camera_skipped = skipped ;
}
void clear_camera_skipped ( ) {
camera_skipped = false ;
}
bool camera_was_skipped ( ) {
return camera_skipped ;
}
2024-03-03 05:23:29 +01:00
void camera_pre_play_update ( PlayState * play ) {
}
2024-03-02 19:33:09 +01:00
2024-03-03 05:23:29 +01:00
void camera_post_play_update ( PlayState * play ) {
// Track whether the game is in kaleido.
prev_in_kaleido = in_kaleido ;
2024-03-02 19:33:09 +01:00
2024-03-03 05:23:29 +01:00
if ( ( play - > pauseCtx . state ! = 0 ) | | ( play - > pauseCtx . debugEditor ! = DEBUG_EDITOR_NONE ) ) {
in_kaleido = true ;
}
else {
in_kaleido = false ;
2024-05-04 01:55:34 +02:00
if ( play - > activeCamId > = 0 & & play - > activeCamId < NUM_CAMS ) {
Camera * active_cam = play - > cameraPtrs [ play - > activeCamId ] ;
# ifdef PRINT_CAMERA_INFO
recomp_printf ( " active_cam->setting %d active_cam->mode %d play->sceneId %d \n " , active_cam - > setting , active_cam - > mode , play - > sceneId ) ;
# endif
// Dedicated section for workarounds where the heuristic fails to detect the large amount of movements the camera does in these particular areas.
bool force_interpolation = false ;
2024-05-06 05:09:33 +02:00
if ( active_cam - > setting = = CAM_SET_BOSS_MAJORA ) {
// Majora's Mask final fight. All cameras should transition smoothly during this fight while not in a cutscene.
force_interpolation = true ;
}
else if ( active_cam - > setting = = CAM_SET_NORMAL0 | | active_cam - > setting = = CAM_SET_DUNGEON0 ) {
2024-05-04 01:55:34 +02:00
force_interpolation =
// Pirates' Fortress Moat. Pushing the switch that unlocks the fortress will cause very large camera movement.
play - > sceneId = = SCENE_TORIDE | |
// Z-targetting an actor.
active_cam - > mode = = CAM_MODE_FOLLOWTARGET | |
// Z-targetting nothing.
2024-05-06 05:09:33 +02:00
active_cam - > mode = = CAM_MODE_TARGET | |
// Z-targetting in battle.
active_cam - > mode = = CAM_MODE_BATTLE ;
2024-05-04 01:55:34 +02:00
}
// TODO: This setting claims "Smoothly and gradually return camera to Player after a cutscene "CONNECT0"". It might be worth it to enable this globally regardless of the scene.
else if ( active_cam - > setting = = CAM_SET_CONNECT0 ) {
// Stone tower and inverted stone tower. The block puzzles will cause very large camera movement after they're activated.
force_interpolation = play - > sceneId = = SCENE_F40 | | play - > sceneId = = SCENE_F41 ;
}
else if ( active_cam - > setting = = CAM_SET_FREE0 ) {
// Cutscene after Majora fight. The camera zooms out while facing the moon with a very large movement.
force_interpolation = play - > sceneId = = SCENE_00KEIKOKU & & play - > csCtx . scriptIndex = = 0 & & play - > csCtx . curFrame < = 98 & & gSaveContext . sceneLayer = = 8 ;
}
if ( force_interpolation ) {
force_camera_interpolation ( ) ;
}
}
2024-03-03 05:23:29 +01:00
}
2024-03-02 19:33:09 +01:00
}
2024-02-05 05:12:34 +01:00
s32 View_ApplyPerspective ( View * view ) ;
s32 View_ApplyOrtho ( View * view ) ;
2024-03-03 05:23:29 +01:00
void force_camera_interpolation ( ) {
camera_interpolation_forced = true ;
}
void force_camera_skip_interpolation ( ) {
camera_skip_interpolation_forced = true ;
}
void force_camera_ignore_tracking ( ) {
camera_ignore_tracking = true ;
}
void KaleidoScope_SetView ( PauseContext * pauseCtx , f32 eyeX , f32 eyeY , f32 eyeZ ) {
Vec3f eye ;
Vec3f at ;
Vec3f up ;
eye . x = eyeX ;
eye . y = eyeY ;
eye . z = eyeZ ;
at . x = at . y = at . z = 0.0f ;
up . x = up . z = 0.0f ;
up . y = 1.0f ;
// @recomp Force interpolation for this view and skip tracking positions.
force_camera_interpolation ( ) ;
force_camera_ignore_tracking ( ) ;
View_LookAt ( & pauseCtx - > view , & eye , & at , & up ) ;
View_Apply ( & pauseCtx - > view ,
VIEW_ALL | VIEW_FORCE_VIEWING | VIEW_FORCE_VIEWPORT | VIEW_FORCE_PROJECTION_PERSPECTIVE ) ;
}
void FileSelect_SetView ( FileSelectState * this , f32 eyeX , f32 eyeY , f32 eyeZ ) {
Vec3f eye ;
Vec3f lookAt ;
Vec3f up ;
eye . x = eyeX ;
eye . y = eyeY ;
eye . z = eyeZ ;
lookAt . x = lookAt . y = lookAt . z = 0.0f ;
up . x = up . z = 0.0f ;
up . y = 1.0f ;
// @recomp Force interpolation for this view and skip tracking positions.
force_camera_interpolation ( ) ;
force_camera_ignore_tracking ( ) ;
View_LookAt ( & this - > view , & eye , & lookAt , & up ) ;
View_Apply ( & this - > view , VIEW_ALL | VIEW_FORCE_VIEWING | VIEW_FORCE_VIEWPORT | VIEW_FORCE_PROJECTION_PERSPECTIVE ) ;
}
bool should_interpolate_perspective ( Vec3f * eye , Vec3f * at ) {
static Vec3f prev_eye = { 0 , 0 , 0 } ;
static Vec3f prev_at = { 0 , 0 , 0 } ;
static Vec3f eye_velocity = { 0 , 0 , 0 } ;
static Vec3f at_velocity = { 0 , 0 , 0 } ;
Vec3f predicted_eye ;
Vec3f predicted_at ;
// Predict the new eye and at positions based on the previous velocity and positions.
Math_Vec3f_Sum ( & prev_eye , & eye_velocity , & predicted_eye ) ;
Math_Vec3f_Sum ( & prev_at , & at_velocity , & predicted_at ) ;
// Calculate the current velocities from the previous and current positions.
Math_Vec3f_Diff ( eye , & prev_eye , & eye_velocity ) ;
Math_Vec3f_Diff ( at , & prev_at , & at_velocity ) ;
// Compare the predicted positions to the real positions.
float eye_dist = Math_Vec3f_DistXYZ ( & predicted_eye , eye ) ;
float at_dist = Math_Vec3f_DistXYZ ( & predicted_at , at ) ;
// Compare the velocities of the eye and at positions.
float velocity_diff = Math_Vec3f_DistXYZ ( & eye_velocity , & at_velocity ) ;
// Update the tracking for the previous positions with the new ones.
prev_eye = * eye ;
prev_at = * at ;
// These numbers are all picked via testing.
// If the velocity of both positions was the same, then they're moving together and should interpolate.
if ( velocity_diff < = 3.0f & & eye_dist < = 100.0f & & at_dist < = 100.0f ) {
return true ;
}
// If the focus or position are basically the same across frames and the eye didn't move too far then it should probably be interpolated.
if ( at_dist < = 10.0f & & eye_dist < = 300.0f ) {
return true ;
}
if ( eye_dist < = 10.0f & & at_dist < = 300.0f ) {
return true ;
}
2024-03-27 23:47:15 +01:00
if ( velocity_diff > 50.0f | | at_dist > 50.0f | | eye_dist > 300.0f ) {
eye_velocity . x = 0.0f ;
eye_velocity . y = 0.0f ;
eye_velocity . z = 0.0f ;
at_velocity . x = 0.0f ;
at_velocity . y = 0.0f ;
at_velocity . z = 0.0f ;
2024-03-03 05:23:29 +01:00
return false ;
}
return true ;
}
2024-02-05 05:12:34 +01:00
/**
* Apply view to POLY_OPA_DISP , POLY_XLU_DISP ( and OVERLAY_DISP if ortho )
*/
void View_Apply ( View * view , s32 mask ) {
mask = ( view - > flags & mask ) | ( mask > > 4 ) ;
2024-03-03 05:23:29 +01:00
// @recomp Determine if the camera should be interpolated this frame.
bool interpolate_camera = false ;
2024-02-05 05:12:34 +01:00
if ( mask & VIEW_PROJECTION_ORTHO ) {
View_ApplyOrtho ( view ) ;
} else {
View_ApplyPerspective ( view ) ;
2024-03-03 05:23:29 +01:00
// @recomp Determine if interpolation should occur based on the new eye and at positions.
if ( ! camera_ignore_tracking ) {
interpolate_camera = should_interpolate_perspective ( & view - > eye , & view - > at ) ;
}
2024-02-05 05:12:34 +01:00
}
2024-03-03 05:23:29 +01:00
camera_ignore_tracking = false ;
// Force skipping interpolation if the previous view was kaleido and this one isn't.
if ( prev_in_kaleido & & ! in_kaleido ) {
camera_skip_interpolation_forced = true ;
}
// @recomp Apply camera interpolation overrides.
if ( camera_skip_interpolation_forced ) {
interpolate_camera = false ;
}
else if ( camera_interpolation_forced ) {
interpolate_camera = true ;
}
2024-02-05 05:12:34 +01:00
// @recomp Tag the camera matrices
GraphicsContext * gfxCtx = view - > gfxCtx ;
OPEN_DISPS ( gfxCtx ) ;
2024-03-02 19:33:09 +01:00
if ( interpolate_camera ) {
// Simple interpolation works much better for cameras because they orbit around a focus.
2024-03-12 23:01:58 +01:00
gEXMatrixGroupSimple ( POLY_OPA_DISP + + , CAMERA_TRANSFORM_ID , G_EX_NOPUSH , G_MTX_PROJECTION ,
2024-03-29 01:36:16 +01:00
G_EX_COMPONENT_INTERPOLATE , G_EX_COMPONENT_INTERPOLATE , G_EX_COMPONENT_INTERPOLATE , G_EX_COMPONENT_INTERPOLATE , G_EX_COMPONENT_INTERPOLATE , G_EX_ORDER_LINEAR , G_EX_EDIT_NONE ) ;
2024-03-12 23:01:58 +01:00
gEXMatrixGroupSimple ( POLY_XLU_DISP + + , CAMERA_TRANSFORM_ID , G_EX_NOPUSH , G_MTX_PROJECTION ,
2024-03-29 01:36:16 +01:00
G_EX_COMPONENT_INTERPOLATE , G_EX_COMPONENT_INTERPOLATE , G_EX_COMPONENT_INTERPOLATE , G_EX_COMPONENT_INTERPOLATE , G_EX_COMPONENT_INTERPOLATE , G_EX_ORDER_LINEAR , G_EX_EDIT_NONE ) ;
2024-03-02 19:33:09 +01:00
}
else {
// Skip interpolation for this frame.
2024-03-12 23:01:58 +01:00
gEXMatrixGroupSimple ( POLY_OPA_DISP + + , CAMERA_TRANSFORM_ID , G_EX_NOPUSH , G_MTX_PROJECTION ,
2024-03-29 01:36:16 +01:00
G_EX_COMPONENT_SKIP , G_EX_COMPONENT_SKIP , G_EX_COMPONENT_SKIP , G_EX_COMPONENT_SKIP , G_EX_COMPONENT_INTERPOLATE , G_EX_ORDER_LINEAR , G_EX_EDIT_NONE ) ;
2024-03-12 23:01:58 +01:00
gEXMatrixGroupSimple ( POLY_XLU_DISP + + , CAMERA_TRANSFORM_ID , G_EX_NOPUSH , G_MTX_PROJECTION ,
2024-03-29 01:36:16 +01:00
G_EX_COMPONENT_SKIP , G_EX_COMPONENT_SKIP , G_EX_COMPONENT_SKIP , G_EX_COMPONENT_SKIP , G_EX_COMPONENT_INTERPOLATE , G_EX_ORDER_LINEAR , G_EX_EDIT_NONE ) ;
2024-03-02 19:33:09 +01:00
}
2024-03-27 23:47:15 +01:00
// Record whether the camera was skipped for later use.
set_camera_skipped ( ! interpolate_camera ) ;
2024-03-03 05:23:29 +01:00
camera_interpolation_forced = false ;
camera_skip_interpolation_forced = false ;
2024-02-26 07:42:57 +01:00
2024-02-05 05:12:34 +01:00
CLOSE_DISPS ( gfxCtx ) ;
}
2024-04-26 05:44:44 +02:00
typedef s32 ( * CameraUpdateFunc ) ( Camera * ) ;
typedef struct {
/* 0x0 */ s16 val ;
/* 0x2 */ s16 param ;
} CameraModeValue ; // size = 0x4
typedef struct {
/* 0x0 */ s16 funcId ;
/* 0x2 */ s16 numValues ;
/* 0x4 */ CameraModeValue * values ;
} CameraMode ; // size = 0x8
typedef struct {
/* 0x0 */ u32 validModes ;
/* 0x4 */ u32 flags ;
/* 0x8 */ CameraMode * cameraModes ;
} CameraSetting ; // size = 0xC
extern CameraSetting sCameraSettings [ ] ;
extern s32 sCameraInterfaceFlags ;
s32 Camera_BgCheck ( Camera * camera , Vec3f * from , Vec3f * to ) ;
Vec3s * Camera_GetBgCamOrActorCsCamFuncData ( Camera * camera , u32 camDataId ) ;
f32 Camera_GetFocalActorHeight ( Camera * camera ) ;
f32 Camera_ScaledStepToCeilF ( f32 target , f32 cur , f32 stepScale , f32 minDiff ) ;
s16 Camera_ScaledStepToCeilS ( s16 target , s16 cur , f32 stepScale , s16 minDiff ) ;
void Camera_ScaledStepToCeilVec3f ( Vec3f * target , Vec3f * cur , f32 xzStepScale , f32 yStepScale , f32 minDiff ) ;
void Camera_SetFocalActorAtOffset ( Camera * camera , Vec3f * focalActorPos ) ;
void Camera_SetUpdateRatesSlow ( Camera * camera ) ;
Vec3f Camera_Vec3sToVec3f ( Vec3s * src ) ;
# define RELOAD_PARAMS(camera) ((camera->animState == 0) || (camera->animState == 10) || (camera->animState == 20))
# define CAM_RODATA_SCALE(x) ((x)*100.0f)
# define CAM_RODATA_UNSCALE(x) ((x)*0.01f)
# define GET_NEXT_RO_DATA(values) ((values++)->val)
# define GET_NEXT_SCALED_RO_DATA(values) CAM_RODATA_UNSCALE(GET_NEXT_RO_DATA(values))
/**
* Used for many fixed - based camera settings i . e . camera is fixed in rotation , and often position ( but not always )
*/
// @recomp Modified to not force interpolation while panning.
s32 Camera_Fixed1 ( Camera * camera ) {
s32 pad [ 2 ] ;
s32 yawDiff ;
VecGeo eyeOffset ;
VecGeo eyeAtOffset ;
VecGeo sp7C ;
u32 negOne ;
Vec3f adjustedPos ;
BgCamFuncData * bgCamFuncData ;
Vec3f * eye = & camera - > eye ;
Vec3f * at = & camera - > at ;
PosRot * focalActorPosRot = & camera - > focalActorPosRot ;
f32 focalActorHeight = Camera_GetFocalActorHeight ( camera ) ;
CameraModeValue * values ;
PosRot * targetHome ;
PosRot * targetWorld ;
VecGeo sp44 ;
Fixed1ReadOnlyData * roData = & camera - > paramData . fixd1 . roData ;
Fixed1ReadWriteData * rwData = & camera - > paramData . fixd1 . rwData ;
sp7C = OLib_Vec3fDiffToVecGeo ( at , eye ) ;
if ( ! RELOAD_PARAMS ( camera ) ) {
} else {
values = sCameraSettings [ camera - > setting ] . cameraModes [ camera - > mode ] . values ;
bgCamFuncData = ( BgCamFuncData * ) Camera_GetBgCamOrActorCsCamFuncData ( camera , camera - > bgCamIndex ) ;
rwData - > eyePosRotTarget . pos = Camera_Vec3sToVec3f ( & bgCamFuncData - > pos ) ;
rwData - > eyePosRotTarget . rot = bgCamFuncData - > rot ;
rwData - > fov = bgCamFuncData - > fov ;
rwData - > focalActor = camera - > focalActor ;
roData - > unk_00 = GET_NEXT_SCALED_RO_DATA ( values ) * focalActorHeight ;
roData - > unk_04 = GET_NEXT_SCALED_RO_DATA ( values ) ;
roData - > fov = GET_NEXT_RO_DATA ( values ) ;
roData - > interfaceFlags = GET_NEXT_RO_DATA ( values ) ;
if ( roData - > interfaceFlags & FIXED1_FLAG_4 ) {
if ( camera - > target = = NULL ) {
return false ;
}
targetHome = & camera - > target - > home ;
targetWorld = & camera - > target - > world ;
sp44 = OLib_Vec3fDiffToVecGeo ( & targetHome - > pos , & rwData - > eyePosRotTarget . pos ) ;
sp44 . yaw = targetWorld - > rot . y + ( s16 ) ( sp44 . yaw - targetHome - > rot . y ) ;
rwData - > eyePosRotTarget . pos = OLib_AddVecGeoToVec3f ( & targetWorld - > pos , & sp44 ) ;
yawDiff = ( s16 ) ( rwData - > eyePosRotTarget . rot . y - targetHome - > rot . y ) ;
rwData - > eyePosRotTarget . rot . y = targetWorld - > rot . y + yawDiff ;
}
}
negOne = - 1 ;
if ( rwData - > focalActor ! = camera - > focalActor ) {
camera - > animState = 20 ;
}
if ( rwData - > fov = = ( s32 ) negOne ) {
rwData - > fov = roData - > fov * 100 ;
} else if ( rwData - > fov < = 360 ) {
rwData - > fov * = 100 ;
}
sCameraInterfaceFlags = roData - > interfaceFlags ;
if ( camera - > animState = = 0 ) {
camera - > animState + + ;
Camera_SetUpdateRatesSlow ( camera ) ;
if ( rwData - > fov ! = ( s32 ) negOne ) {
roData - > fov = CAM_RODATA_UNSCALE ( rwData - > fov ) ;
}
if ( bgCamFuncData - > unk_0E ! = ( s32 ) negOne ) {
roData - > unk_04 = CAM_RODATA_UNSCALE ( bgCamFuncData - > unk_0E ) ;
}
}
// @recomp Camera interpolation should always apply on this mode unless something else modified it externally.
// We check for the approach percentage as well to detect when it wants to force an interpolation to the next position.
static Vec3f lastEye = { } ;
static Vec3f lastAt = { } ;
if ( OLib_Vec3fDist ( eye , & lastEye ) < 1e-6 f & & OLib_Vec3fDist ( at , & lastAt ) < 1e-6 f & & ( roData - > unk_04 < 0.999f ) ) {
force_camera_interpolation ( ) ;
}
eyeAtOffset = OLib_Vec3fDiffToVecGeo ( eye , at ) ;
Camera_ScaledStepToCeilVec3f ( & rwData - > eyePosRotTarget . pos , eye , roData - > unk_04 , roData - > unk_04 , 0.2f ) ;
adjustedPos = focalActorPosRot - > pos ;
adjustedPos . y + = focalActorHeight ;
camera - > dist = OLib_Vec3fDist ( & adjustedPos , eye ) ;
eyeOffset . r = camera - > dist ;
eyeOffset . pitch =
Camera_ScaledStepToCeilS ( rwData - > eyePosRotTarget . rot . x * - 1 , eyeAtOffset . pitch , roData - > unk_04 , 5 ) ;
eyeOffset . yaw = Camera_ScaledStepToCeilS ( rwData - > eyePosRotTarget . rot . y , eyeAtOffset . yaw , roData - > unk_04 , 5 ) ;
* at = OLib_AddVecGeoToVec3f ( eye , & eyeOffset ) ;
camera - > eyeNext = * eye ;
Camera_BgCheck ( camera , eye , at ) ;
camera - > fov = Camera_ScaledStepToCeilF ( roData - > fov , camera - > fov , roData - > unk_04 , 0.1f ) ;
camera - > roll = 0 ;
camera - > atLerpStepScale = 0.0f ;
Camera_SetFocalActorAtOffset ( camera , & focalActorPosRot - > pos ) ;
camera - > roll = Camera_ScaledStepToCeilS ( rwData - > eyePosRotTarget . rot . z , camera - > roll , roData - > unk_04 , 5 ) ;
// @recomp
lastEye = * eye ;
lastAt = * at ;
return 1 ;
}