Files
GTASource/game/camera/switch/SwitchCamera.cpp
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

1114 lines
35 KiB
C++

//
// camera/switch/SwitchCamera.cpp
//
// Copyright (C) 1999-2012 Rockstar Games. All Rights Reserved.
//
#include "camera/switch/SwitchCamera.h"
#include "fwmaths/angle.h"
#include "fwsys/timer.h"
#include "camera/CamInterface.h"
#include "camera/gameplay/GameplayDirector.h"
#include "camera/helpers/ControlHelper.h"
#include "camera/helpers/FrameInterpolator.h"
#include "camera/system/CameraManager.h"
#include "camera/system/CameraMetadata.h"
#include "peds/ped.h"
#include "renderer/PostProcessFX.h"
#include "scene/playerswitch/PlayerSwitchInterface.h"
#include "scene/world/GameWorldHeightMap.h"
CAMERA_OPTIMISATIONS()
INSTANTIATE_RTTI_CLASS(camSwitchCamera,0xDECCDF37)
PF_PAGE(camSwitchCameraPage, "Camera: Switch Camera");
PF_GROUP(camSwitchCameraMetrics);
PF_LINK(camSwitchCameraPage, camSwitchCameraMetrics);
PF_VALUE_FLOAT(switchCameraPhase, camSwitchCameraMetrics);
PF_VALUE_FLOAT(switchCameraBlendLevel, camSwitchCameraMetrics);
#if __BANK
enum eSwitchOrientationOptions
{
SOO_DEFAULT = 0,
SOO_USE_START_HEADING,
SOO_USE_FINAL_HEADING,
SOO_USE_MAP_NORTH,
SOO_USE_TRAVEL_DIRECTION_AS_UP,
SOO_USE_TRAVEL_DIRECTION_AS_RIGHT,
SOO_ROTATE_ON_ASCENT_ONLY,
SOO_ROTATE_ON_DESCENT_ONLY,
SOO_ROTATE_ASCENT_AND_DESCENT,
SOO_MAX
};
static const char* g_DebugSwitchOrientationOptions[SOO_MAX] =
{
"DEFAULT",
"USE INITIAL HEADING",
"USE FINAL HEADING",
"USE MAP NORTH",
"CAMERA UP IS TRAVEL DIRECTION",
"CAMERA RIGHT IS TRAVEL DIRECTION",
"ROTATE ON ASCENT ONLY",
"ROTATE ON DESCENT ONLY",
"ROTATE DURING ASCENT AND DESCENT"
};
s32 camSwitchCamera::ms_DebugOrientationOption = (s32)SOO_DEFAULT;
#endif // __BANK
camSwitchCamera::camSwitchCamera(const camBaseObjectMetadata& metadata)
: camBaseCamera(metadata)
, m_Metadata(static_cast<const camSwitchCameraMetadata&>(metadata))
, m_StartPosition(g_InvalidPosition)
, m_EndPosition(g_InvalidPosition)
, m_EstablishingShotMetadata(NULL)
, m_State(INACTIVE)
, m_NumAscentCuts(1)
, m_NumDescentCuts(1)
, m_StateDuration(0)
, m_StateStartTime(0)
, m_InterpolateOutDurationToApply(0)
, m_ControlFlags(0)
, m_StartHeading(0.0f)
, m_EndHeading(0.0f)
, m_AscentFloorHeight(0.0f)
, m_DescentFloorHeight(0.0f)
, m_CeilingHeight(0.0f)
, m_InitialFovForPan(m_Metadata.m_Fov)
, m_InitialMotionBlurMaxVelocityScale(0.0f)
, m_IsInitialised(false)
, m_ShouldApplyCutBehaviour(false)
, m_ShouldBlendInCutBehaviour(false)
, m_HasBehaviourFinished(false)
, m_WasSourcePedInVehicle(false)
, m_IsUsingFirstPersonEstablishShot(false)
, m_ShouldReportAsFinishedForFPSCutback(false)
{
}
camSwitchCamera::~camSwitchCamera()
{
//Ensure any intro gameplay behaviour is stopped.
//TODO: We would ideally manage the gameplay Switch behaviour within the player Switch manager, rather than here.
camGameplayDirector* gameplayDirector = camInterface::GetGameplayDirectorSafe();
if(gameplayDirector)
{
gameplayDirector->StopSwitchBehaviour();
}
}
void camSwitchCamera::Init(const CPed& sourcePed, const CPed& destinationPed, const CPlayerSwitchParams& switchParams)
{
const fwTransform& destinationPedTransform = destinationPed.GetTransform();
const bool bStartFromCameraPosRatherThanPed = ((switchParams.m_controlFlags & CPlayerSwitchInterface::CONTROL_FLAG_START_FROM_CAMPOS) != 0);
m_StartPosition = bStartFromCameraPosRatherThanPed ? camInterface::GetPos() : VEC3V_TO_VECTOR3(sourcePed.GetTransform().GetPosition());
m_StartHeading = ComputeStartHeading();
m_EndPosition = VEC3V_TO_VECTOR3(destinationPedTransform.GetPosition());
m_EndHeading = destinationPedTransform.GetHeading();
m_ControlFlags = switchParams.m_controlFlags;
m_AscentFloorHeight = switchParams.m_fLowestHeightAscent;
m_DescentFloorHeight = switchParams.m_fLowestHeightDescent;
m_CeilingHeight = switchParams.m_fCeilingHeight;
m_NumAscentCuts = cameraVerifyf(switchParams.m_numJumpsAscent > 0, "A Switch must include at least one ascent cut. Defaulting to one") ?
switchParams.m_numJumpsAscent : 1;
m_NumDescentCuts = cameraVerifyf(switchParams.m_numJumpsDescent > 0, "A Switch must include at least one descent cut. Defaulting to one") ?
switchParams.m_numJumpsDescent : 1;
m_IsInitialised = true;
m_InterpolateOutDurationToApply = 0;
m_HasBehaviourFinished = true;
m_WasSourcePedInVehicle = sourcePed.GetIsInVehicle();
}
void camSwitchCamera::OverrideDestination(const Matrix34& worldMatrix)
{
if(m_EstablishingShotMetadata)
{
//We already have an establishing shot, so we must maintain the associated end position and heading.
return;
}
m_EndPosition = worldMatrix.d;
m_EndHeading = camFrame::ComputeHeadingFromMatrix(worldMatrix);
//Validate that the descent floor height is still safe for the revised end position.
//TODO: Remove this once the player Switch manager recomputes the Switch parameters and reapplies them to this camera.
const float maxWorldHeightAtEndPosition = CGameWorldHeightMap::GetMaxHeightFromWorldHeightMap(
m_EndPosition.x - m_Metadata.m_HalfBoxExtentForHeightMapQuery,
m_EndPosition.y - m_Metadata.m_HalfBoxExtentForHeightMapQuery,
m_EndPosition.x + m_Metadata.m_HalfBoxExtentForHeightMapQuery,
m_EndPosition.y + m_Metadata.m_HalfBoxExtentForHeightMapQuery);
const float minSafeCutHeight = maxWorldHeightAtEndPosition + m_Metadata.m_MinDistanceAboveHeightMapForFirstCut;
m_DescentFloorHeight = Max(m_DescentFloorHeight, minSafeCutHeight);
}
void camSwitchCamera::SetEstablishingShotMetadata(const CPlayerSwitchEstablishingShotMetadata* metadata)
{
m_EstablishingShotMetadata = metadata;
if(!m_EstablishingShotMetadata)
{
return;
}
//Apply any alternate shot specified for first-person on-foot, as required.
const atHashString firstPersonShotName = m_EstablishingShotMetadata->m_OverriddenShotForFirstPersonOnFoot;
if(firstPersonShotName.IsNotNull())
{
const s32 onFootViewMode = camControlHelper::GetViewModeForContext(camControlHelperMetadataViewMode::ON_FOOT);
if(onFootViewMode == camControlHelperMetadataViewMode::FIRST_PERSON)
{
const CPlayerSwitchEstablishingShotMetadata* firstPersonShotMetadata = CPlayerSwitchEstablishingShot::FindShotMetaData(firstPersonShotName);
if(cameraVerifyf(firstPersonShotMetadata, "An overridden Switch establishing shot for first-person could not be found (name = %s, hash = %u)",
(firstPersonShotName.TryGetCStr() ? firstPersonShotName.TryGetCStr() : "Unknown"), firstPersonShotName.GetHash()))
{
m_EstablishingShotMetadata = firstPersonShotMetadata;
m_IsUsingFirstPersonEstablishShot = true;
}
}
}
//We need to descend to the establishing shot, so override the end position and heading.
m_EndPosition = m_EstablishingShotMetadata->m_Position;
m_EndHeading = m_EstablishingShotMetadata->m_Orientation.z * DtoR;
//Validate that the descent floor height is still safe for the revised end position.
//TODO: Remove this once the player Switch manager recomputes the Switch parameters and reapplies them to this camera.
const float maxWorldHeightAtEndPosition = CGameWorldHeightMap::GetMaxHeightFromWorldHeightMap(
m_EndPosition.x - m_Metadata.m_HalfBoxExtentForHeightMapQuery,
m_EndPosition.y - m_Metadata.m_HalfBoxExtentForHeightMapQuery,
m_EndPosition.x + m_Metadata.m_HalfBoxExtentForHeightMapQuery,
m_EndPosition.y + m_Metadata.m_HalfBoxExtentForHeightMapQuery);
const float minSafeCutHeight = maxWorldHeightAtEndPosition + m_Metadata.m_MinDistanceAboveHeightMapForFirstCut;
m_DescentFloorHeight = Max(m_DescentFloorHeight, minSafeCutHeight);
//NOTE: We must skip the outro states when interpolating out of the establishing shot.
if(m_EstablishingShotMetadata->m_InterpolateOutDuration > 0)
{
m_ControlFlags |= CPlayerSwitchInterface::CONTROL_FLAG_SKIP_OUTRO;
}
}
bool camSwitchCamera::PerformIntro()
{
if(!cameraVerifyf(m_IsInitialised, "A Switch camera has been requested to Start when it has not been initialised"))
{
m_State = INACTIVE;
return false;
}
if(m_ControlFlags & CPlayerSwitchInterface::CONTROL_FLAG_SKIP_INTRO)
{
m_HasBehaviourFinished = true;
}
else
{
m_HasBehaviourFinished = false;
//TODO: We would ideally manage the gameplay Switch behaviour within the player Switch manager, rather than here.
const s32 switchType = g_PlayerSwitch.GetSwitchType();
camInterface::GetGameplayDirector().StartSwitchBehaviour(switchType, true);
}
m_State = INTRO;
return true;
}
void camSwitchCamera::PerformAscentCut(u32 cutIndex)
{
//Ensure any intro gameplay behaviour is stopped.
//TODO: We would ideally manage the gameplay Switch behaviour within the player Switch manager, rather than here.
camInterface::GetGameplayDirector().StopSwitchBehaviour();
const u32 cutIndexToApply = cameraVerifyf(cutIndex < m_NumAscentCuts, "Invalid ascent cut index (%u, %u cuts)",
cutIndex, m_NumAscentCuts) ? cutIndex : (m_NumAscentCuts - 1);
camFrame cutFrame;
ComputeFrameForAscentCut(cutIndexToApply, cutFrame);
m_Frame.CloneFrom(cutFrame);
m_StateDuration = m_Metadata.m_CutEffectDuration;
m_StateStartTime = fwTimer::GetTimeInMilliseconds_NonScaledClipped();
m_State = ASCENT_CUT;
m_ShouldApplyCutBehaviour = true;
m_ShouldBlendInCutBehaviour = false;
m_HasBehaviourFinished = false;
}
void camSwitchCamera::ComputeFrameForAscentCut(u32 cutIndex, camFrame& frame) const
{
float desiredHeading = (m_WasSourcePedInVehicle || (m_ControlFlags & CPlayerSwitchInterface::CONTROL_FLAG_UNKNOWN_DEST)) ? m_StartHeading : m_EndHeading;
#if __BANK
DebugOverrideCameraHeading(desiredHeading, cutIndex, true);
#endif // __BANK
ComputeBaseFrame(frame, desiredHeading);
Vector3 cutPosition;
ComputeAscentCutPosition(cutIndex, cutPosition);
frame.SetPosition(cutPosition);
frame.GetFlags().SetFlag(camFrame::Flag_HasCutPosition);
}
void camSwitchCamera::PerformLookUp()
{
m_StateDuration = m_Metadata.m_LookUpDuration;
m_StateStartTime = fwTimer::GetTimeInMilliseconds_NonScaledClipped();
m_State = LOOK_UP;
m_HasBehaviourFinished = false;
}
void camSwitchCamera::PerformLookDown()
{
m_StateDuration = m_Metadata.m_LookDownDuration;
m_StateStartTime = fwTimer::GetTimeInMilliseconds_NonScaledClipped();
m_State = LOOK_DOWN;
m_HasBehaviourFinished = false;
}
void camSwitchCamera::PerformPan(u32 duration)
{
m_StateDuration = duration;
m_StateStartTime = fwTimer::GetTimeInMilliseconds_NonScaledClipped();
m_InitialFovForPan = m_Frame.GetFov();
m_State = PAN;
m_HasBehaviourFinished = false;
m_InitialMotionBlurMaxVelocityScale = PostFX::GetMotionBlurMaxVelocityScale();
}
void camSwitchCamera::PerformDescentCut(u32 cutIndex)
{
const u32 cutIndexToApply = cameraVerifyf(cutIndex < m_NumDescentCuts, "Invalid descent cut index (%u, %u cuts)",
cutIndex, m_NumDescentCuts) ? cutIndex : (m_NumDescentCuts - 1);
camFrame cutFrame;
ComputeFrameForDescentCut(cutIndexToApply, cutFrame);
m_Frame.CloneFrom(cutFrame);
m_StateDuration = m_Metadata.m_CutEffectDuration;
m_StateStartTime = fwTimer::GetTimeInMilliseconds_NonScaledClipped();
m_State = DESCENT_CUT;
m_ShouldApplyCutBehaviour = true;
//NOTE: We blend in the cut behaviour on the topmost descent cut in order to smooth the transition from the pan.
m_ShouldBlendInCutBehaviour = (cutIndex == (m_NumDescentCuts - 1));
m_HasBehaviourFinished = false;
}
void camSwitchCamera::ComputeFrameForDescentCut(u32 cutIndex, camFrame& frame) const
{
float desiredHeading = (m_WasSourcePedInVehicle || (m_ControlFlags & CPlayerSwitchInterface::CONTROL_FLAG_UNKNOWN_DEST)) ? m_StartHeading : m_EndHeading;
#if __BANK
DebugOverrideCameraHeading(desiredHeading, cutIndex, false);
#endif // __BANK
ComputeBaseFrame(frame, desiredHeading);
Vector3 cutPosition;
ComputeDescentCutPosition(cutIndex, cutPosition);
frame.SetPosition(cutPosition);
frame.GetFlags().SetFlag(camFrame::Flag_HasCutPosition);
}
void camSwitchCamera::PerformEstablishingShot()
{
m_State = ESTABLISHING_SHOT_HOLD;
if(cameraVerifyf(m_EstablishingShotMetadata, "The Switch camera has no establishing shot metadata with which to compose a frame"))
{
camFrame establishingShotFrame;
ComputeBaseFrameForEstablishingShot(*m_EstablishingShotMetadata, establishingShotFrame);
Matrix34 startWorldMatrix;
ComputeWorldMatrixForEstablishingShotStart(*m_EstablishingShotMetadata, startWorldMatrix);
establishingShotFrame.SetWorldMatrix(startWorldMatrix, false);
float fov = establishingShotFrame.GetFov();
fov *= m_Metadata.m_MaxFovScalingForEstablishingShotInHold;
establishingShotFrame.SetFov(fov);
establishingShotFrame.GetFlags().SetFlag(camFrame::Flag_HasCutPosition);
m_Frame.CloneFrom(establishingShotFrame);
m_StateDuration = m_Metadata.m_EstablishingShotInHoldDuration;
m_StateStartTime = fwTimer::GetTimeInMilliseconds_NonScaledClipped();
m_HasBehaviourFinished = false;
}
else
{
m_HasBehaviourFinished = true;
}
}
bool camSwitchCamera::ComputeFrameForEstablishingShotStart(camFrame& frame) const
{
if(!cameraVerifyf(m_EstablishingShotMetadata, "The Switch camera has no establishing shot metadata with which to compose a frame"))
{
return false;
}
ComputeBaseFrameForEstablishingShot(*m_EstablishingShotMetadata, frame);
Matrix34 worldMatrix;
ComputeWorldMatrixForEstablishingShotStart(*m_EstablishingShotMetadata, worldMatrix);
frame.SetWorldMatrix(worldMatrix, false);
return true;
}
bool camSwitchCamera::ComputeFrameForEstablishingShotEnd(camFrame& frame) const
{
if(!cameraVerifyf(m_EstablishingShotMetadata, "The Switch camera has no establishing shot metadata with which to compose a frame"))
{
return false;
}
ComputeBaseFrameForEstablishingShot(*m_EstablishingShotMetadata, frame);
Matrix34 worldMatrix;
ComputeWorldMatrixForEstablishingShotEnd(*m_EstablishingShotMetadata, worldMatrix);
frame.SetWorldMatrix(worldMatrix, false);
return true;
}
void camSwitchCamera::PerformOutroHold()
{
if(m_ControlFlags & CPlayerSwitchInterface::CONTROL_FLAG_SKIP_OUTRO)
{
if(m_IsUsingFirstPersonEstablishShot)
{
camManager::TriggerFirstPersonFlashEffect(camManager::CAM_PUSH_IN_FX_NEUTRAL, 0, "Switch Fall back");
m_HasBehaviourFinished = false;
m_StateStartTime = fwTimer::GetTimeInMilliseconds_NonScaledClipped();
}
else
{
m_HasBehaviourFinished = true;
}
}
else
{
if(m_IsUsingFirstPersonEstablishShot)
{
camManager::TriggerFirstPersonFlashEffect(camManager::CAM_PUSH_IN_FX_NEUTRAL, 0, "Switch Fall back");
m_HasBehaviourFinished = false;
m_StateStartTime = fwTimer::GetTimeInMilliseconds_NonScaledClipped();
}
else
{
//Ensure that any shake is cleaned up immediately.
StopShaking();
m_HasBehaviourFinished = false;
//TODO: We would ideally manage the gameplay Switch behaviour within the player Switch manager, rather than here.
camGameplayDirector& gameplayDirector = camInterface::GetGameplayDirector();
const s32 switchType = g_PlayerSwitch.GetSwitchType();
gameplayDirector.StartSwitchBehaviour(switchType, false);
gameplayDirector.SetSwitchBehaviourPauseState(true);
m_StateDuration = m_Metadata.m_OutroHoldDuration;
m_StateStartTime = fwTimer::GetTimeInMilliseconds_NonScaledClipped();
}
}
m_State = OUTRO_HOLD;
}
void camSwitchCamera::PerformOutroSwoop()
{
//Ensure that any shake is cleaned up immediately.
StopShaking();
if(m_ControlFlags & CPlayerSwitchInterface::CONTROL_FLAG_SKIP_OUTRO)
{
m_HasBehaviourFinished = true;
}
else
{
m_HasBehaviourFinished = false;
//TODO: We would ideally manage the gameplay Switch behaviour within the player Switch manager, rather than here.
camInterface::GetGameplayDirector().SetSwitchBehaviourPauseState(false);
}
m_State = OUTRO_SWOOP;
}
bool camSwitchCamera::Update()
{
bool hasSucceeded = false;
switch(m_State)
{
case INACTIVE:
break;
case INTRO:
hasSucceeded = UpdateIntro();
break;
case ASCENT_CUT:
hasSucceeded = UpdateAscentCut();
break;
case LOOK_UP:
hasSucceeded = UpdateLookUp();
break;
case LOOK_DOWN:
hasSucceeded = UpdateLookDown();
break;
case PAN:
hasSucceeded = UpdatePan();
break;
case DESCENT_CUT:
hasSucceeded = UpdateDescentCut();
break;
case ESTABLISHING_SHOT_HOLD:
hasSucceeded = UpdateEstablishingShotHold();
break;
case ESTABLISHING_SHOT:
hasSucceeded = UpdateEstablishingShot();
break;
case OUTRO_HOLD:
hasSucceeded = UpdateOutroHold();
break;
case OUTRO_SWOOP:
hasSucceeded = UpdateOutroSwoop();
break;
}
if(m_ShouldReportAsFinishedForFPSCutback)
{
camInterface::GetGameplayDirector().SetSwitchCameraHasTerminated(true);
}
return hasSucceeded;
}
bool camSwitchCamera::UpdateIntro()
{
//TODO: We would ideally manage the gameplay Switch behaviour within the player Switch manager, rather than here.
m_HasBehaviourFinished = camInterface::GetGameplayDirector().IsSwitchBehaviourFinished();
//Don't render during the intro, as this is handled by the gameplay director.
return false;
}
bool camSwitchCamera::UpdateAscentCut()
{
//Report completion immediately, as we do not need to wait for the dynamic cut effect.
m_HasBehaviourFinished = true;
if(m_ShouldApplyCutBehaviour)
{
const float phase = ComputeStatePhase(m_StateDuration);
const float initialFov = m_Metadata.m_MaxFovScalingForCutEffect * m_Metadata.m_Fov;
const float blendLevel = m_ShouldBlendInCutBehaviour ? SlowInOut(phase) : SlowOut(phase);
const float fovToApply = Lerp(blendLevel, initialFov, m_Metadata.m_Fov);
m_Frame.SetFov(fovToApply);
PF_SET(switchCameraPhase, phase);
PF_SET(switchCameraBlendLevel, blendLevel);
}
return true;
}
bool camSwitchCamera::UpdateLookUp()
{
const float phase = ComputeStatePhase(m_StateDuration);
float heading;
float pitch;
float roll;
m_Frame.ComputeHeadingPitchAndRoll(heading, pitch, roll);
const float phaseToApply = SlowInOut(phase);
pitch = Lerp(phaseToApply, m_Metadata.m_DefaultPitch, m_Metadata.m_DesiredPitchWhenLookingUp);
pitch *= DtoR;
m_Frame.SetWorldMatrixFromHeadingPitchAndRoll(heading, pitch, roll);
m_HasBehaviourFinished = (phase >= (1.0f - SMALL_FLOAT));
return true;
}
bool camSwitchCamera::UpdateLookDown()
{
const float phase = ComputeStatePhase(m_StateDuration);
float heading;
float pitch;
float roll;
m_Frame.ComputeHeadingPitchAndRoll(heading, pitch, roll);
const float phaseToApply = SlowInOut(phase);
pitch = Lerp(phaseToApply, m_Metadata.m_DesiredPitchWhenLookingUp, m_Metadata.m_DefaultPitch);
pitch *= DtoR;
m_Frame.SetWorldMatrixFromHeadingPitchAndRoll(heading, pitch, roll);
m_HasBehaviourFinished = (phase >= (1.0f - SMALL_FLOAT));
return true;
}
bool camSwitchCamera::UpdatePan()
{
float desiredHeading = (m_WasSourcePedInVehicle || (m_ControlFlags & CPlayerSwitchInterface::CONTROL_FLAG_UNKNOWN_DEST)) ? m_StartHeading : m_EndHeading;
#if __BANK
DebugOverrideCameraHeading(desiredHeading, (m_NumAscentCuts - 1), true);
#endif // __BANK
camFrame panFrame;
ComputeBaseFrame(panFrame, desiredHeading);
panFrame.SetMotionBlurStrength(m_Metadata.m_MotionBlurStrengthForPan);
const float phase = ComputeStatePhase(m_StateDuration);
const float blendLevel = SlowInOut(SlowInOut(phase));
PF_SET(switchCameraPhase, phase);
PF_SET(switchCameraBlendLevel, blendLevel);
Vector3 panStartPosition;
ComputeAscentCutPosition(m_NumAscentCuts - 1, panStartPosition);
Vector3 panEndPosition;
ComputeDescentCutPosition(m_NumDescentCuts - 1, panEndPosition);
Vector3 panPosition;
panPosition.Lerp(blendLevel, panStartPosition, panEndPosition);
panFrame.SetPosition(panPosition);
//Interpolate to the default FOV across the pan, so that we may apply the full FOV change again at the highest descent position.
const float fovToApply = Lerp(blendLevel, m_InitialFovForPan, m_Metadata.m_Fov);
panFrame.SetFov(fovToApply);
m_Frame.CloneFrom(panFrame);
if(phase >= (1.0f - SMALL_FLOAT))
{
//Restore the initial motion blur settings.
PostFX::SetMotionBlurMaxVelocityScale(m_InitialMotionBlurMaxVelocityScale);
m_HasBehaviourFinished = true;
}
else
{
//Request custom motion blur settings.
PostFX::SetMotionBlurMaxVelocityScale(m_Metadata.m_MotionBlurMaxVelocityScaleForPan);
}
return true;
}
bool camSwitchCamera::UpdateDescentCut()
{
//Report completion immediately, as we do not need to wait for the dynamic cut effect.
m_HasBehaviourFinished = true;
if(m_ShouldApplyCutBehaviour)
{
const float phase = ComputeStatePhase(m_StateDuration);
const float targetFov = m_Metadata.m_MaxFovScalingForCutEffect * m_Metadata.m_Fov;
const float blendLevel = m_ShouldBlendInCutBehaviour ? SlowInOut(phase) : SlowOut(phase);
const float fovToApply = Lerp(blendLevel, m_Metadata.m_Fov, targetFov);
m_Frame.SetFov(fovToApply);
PF_SET(switchCameraPhase, phase);
PF_SET(switchCameraBlendLevel, blendLevel);
}
return true;
}
bool camSwitchCamera::UpdateEstablishingShotHold()
{
if(!cameraVerifyf(m_EstablishingShotMetadata, "The Switch camera has no establishing shot metadata with which to compose a frame"))
{
m_HasBehaviourFinished = true;
return true;
}
//Release any existing camera shake.
StopShaking(false);
camFrame establishingShotFrame;
ComputeBaseFrameForEstablishingShot(*m_EstablishingShotMetadata, establishingShotFrame);
const float phase = ComputeStatePhase(m_StateDuration);
const float targetFov = establishingShotFrame.GetFov();
const float initialFov = m_Metadata.m_MaxFovScalingForEstablishingShotInHold * targetFov;
const float fovToApply = Lerp(SlowIn(phase), initialFov, targetFov);
m_Frame.SetFov(fovToApply);
const bool isHoldFinished = (phase >= (1.0f - SMALL_FLOAT));
if(isHoldFinished)
{
//Transition to the establishing shot state, rather than reporting that the current behaviour has finished.
//NOTE: We inhibit the shake if we are using a catch-up exit, otherwise we may observe a pop when catch-up is triggered.
if(!m_EstablishingShotMetadata->m_ShouldIntepolateToCatchUp && m_Metadata.m_EstablishingShotShakeRef)
{
Shake(m_Metadata.m_EstablishingShotShakeRef, m_Metadata.m_EstablishingShotShakeAmplitude);
}
m_State = ESTABLISHING_SHOT;
m_StateDuration = m_Metadata.m_EstablishingShotSwoopDuration + m_EstablishingShotMetadata->m_HoldDuration +
m_EstablishingShotMetadata->m_InterpolateOutDuration;
m_StateStartTime = fwTimer::GetTimeInMilliseconds_NonScaledClipped();
}
return true;
}
bool camSwitchCamera::UpdateEstablishingShot()
{
if(!cameraVerifyf(m_EstablishingShotMetadata, "The Switch camera has no establishing shot metadata with which to compose a frame"))
{
m_HasBehaviourFinished = true;
return true;
}
camGameplayDirector& gameplayDirector = camInterface::GetGameplayDirector();
if(m_EstablishingShotMetadata->m_ShouldInhibitFirstPersonOnFoot)
{
gameplayDirector.DisableFirstPersonThisUpdate();
}
const float statePhase = ComputeStatePhase(m_StateDuration);
m_HasBehaviourFinished = (statePhase >= (1.0f - SMALL_FLOAT));
const bool shouldInterpolateOut = (m_EstablishingShotMetadata->m_InterpolateOutDuration > 0);
const float swoopPhase = ComputeStatePhase(m_Metadata.m_EstablishingShotSwoopDuration);
const float swoopOrientationBlendLevel = shouldInterpolateOut ? SlowOut(SlowInOut(swoopPhase)) : SlowInOut(swoopPhase);
const float swoopPositionBlendLevel = shouldInterpolateOut ? SlowOut(SlowOut(swoopPhase)) : SlowOut(swoopPhase);
camFrame establishingShotFrame;
ComputeBaseFrameForEstablishingShot(*m_EstablishingShotMetadata, establishingShotFrame);
Matrix34 startWorldMatrix;
ComputeWorldMatrixForEstablishingShotStart(*m_EstablishingShotMetadata, startWorldMatrix);
Matrix34 endWorldMatrix;
ComputeWorldMatrixForEstablishingShotEnd(*m_EstablishingShotMetadata, endWorldMatrix);
Matrix34 blendedWorldMatrix;
camFrame::SlerpOrientation(swoopOrientationBlendLevel, startWorldMatrix, endWorldMatrix, blendedWorldMatrix);
blendedWorldMatrix.d.Lerp(swoopPositionBlendLevel, startWorldMatrix.d, endWorldMatrix.d);
establishingShotFrame.SetWorldMatrix(blendedWorldMatrix, false);
m_Frame.CloneFrom(establishingShotFrame);
if(shouldInterpolateOut)
{
const float swoopAndHoldPhase = ComputeStatePhase(m_Metadata.m_EstablishingShotSwoopDuration + m_EstablishingShotMetadata->m_HoldDuration);
if(swoopAndHoldPhase > (1.0f - SMALL_FLOAT))
{
if(m_InterpolateOutDurationToApply == 0)
{
if(m_EstablishingShotMetadata->m_ShouldIntepolateToCatchUp)
{
//Clone the active gameplay frame and override the position, orientation and FOV.
const camFrame& gameplayFrame = gameplayDirector.GetFrameNoPostEffects();
camFrame catchUpFrame;
catchUpFrame.CloneFrom(gameplayFrame);
Matrix34 catchUpWorldMatrix;
ComputeWorldMatrixForEstablishingShotCatchUp(*m_EstablishingShotMetadata, catchUpWorldMatrix);
catchUpFrame.SetWorldMatrix(catchUpWorldMatrix, false);
catchUpFrame.SetFov(m_EstablishingShotMetadata->m_CatchUpFov);
gameplayDirector.CatchUpFromFrame(catchUpFrame);
}
else
{
float desiredRelativeHeading = 0.0f;
float desiredRelativePitch = 0.0f;
if(m_EstablishingShotMetadata->m_ShouldApplyGameplayCameraOrientation)
{
//Apply the custom gameplay camera orientation settings specified for the establishing shot.
desiredRelativeHeading = m_EstablishingShotMetadata->m_GameplayRelativeHeading * DtoR;
desiredRelativePitch = m_EstablishingShotMetadata->m_GameplayRelativePitch * DtoR;
}
else
{
//Align the gameplay camera roughly with the establishing shot.
const CPed* newPed = g_PlayerSwitch.GetLongRangeMgr().GetNewPed();
if(newPed)
{
const CVehicle* vehicle = newPed->GetVehiclePedInside();
const Matrix34 attachParentMatrix = MAT34V_TO_MATRIX34(vehicle ? vehicle->GetMatrix() : newPed->GetMatrix());
const Vector3 shotToAttachParent = attachParentMatrix.d - endWorldMatrix.d;
const float distanceToAttachParent = shotToAttachParent.Mag();
if(distanceToAttachParent >= SMALL_FLOAT)
{
Vector3 front;
front.InvScale(shotToAttachParent, distanceToAttachParent);
attachParentMatrix.UnTransform3x3(front);
desiredRelativeHeading = camFrame::ComputeHeadingFromFront(front);
}
}
}
gameplayDirector.SetUseScriptHeading(desiredRelativeHeading);
gameplayDirector.SetUseScriptPitch(desiredRelativePitch, 1.0f);
}
}
m_InterpolateOutDurationToApply = m_EstablishingShotMetadata->m_InterpolateOutDuration;
}
}
return true;
}
bool camSwitchCamera::UpdateOutroHold()
{
if(m_ControlFlags & CPlayerSwitchInterface::CONTROL_FLAG_SKIP_OUTRO)
{
if(m_IsUsingFirstPersonEstablishShot)
{
const float statePhase = ComputeStatePhase(300);
m_HasBehaviourFinished = (statePhase >= (1.0f - SMALL_FLOAT));
camGameplayDirector& gameplayDirector = camInterface::GetGameplayDirector();
if(m_EstablishingShotMetadata->m_ShouldInhibitFirstPersonOnFoot && !m_HasBehaviourFinished)
{
gameplayDirector.DisableFirstPersonThisUpdate();
}
if(m_HasBehaviourFinished && m_IsUsingFirstPersonEstablishShot)
{
m_ShouldReportAsFinishedForFPSCutback = true;
}
}
else
{
m_HasBehaviourFinished = true;
}
//Continue to render the current frame if we're skipping the outro, so that we don't risk seeing a frame of gameplay
//before cutting to something else.
return true;
}
else
{
//Wait the predefined hold time.
const float phase = ComputeStatePhase(m_StateDuration);
m_HasBehaviourFinished = (phase >= (1.0f - SMALL_FLOAT));
//Don't render during the outro, as this is handled by another director.
return false;
}
}
bool camSwitchCamera::UpdateOutroSwoop()
{
//TODO: We would ideally manage the gameplay Switch behaviour within the player Switch manager, rather than here.
m_HasBehaviourFinished = camInterface::GetGameplayDirector().IsSwitchBehaviourFinished();
//Don't render during the outro, as this is handled by another director.
return false;
}
float camSwitchCamera::ComputeStartHeading() const
{
const camFrame& gameplayFrame = camInterface::GetGameplayDirector().GetFrame();
const Matrix34& gameplayWorldMatrix = gameplayFrame.GetWorldMatrix();
float heading;
float pitch;
float roll;
camFrame::ComputeHeadingPitchAndRollFromMatrix(gameplayWorldMatrix, heading, pitch, roll);
//Take account of any roll.
float startHeading = heading + roll;
startHeading = fwAngle::LimitRadianAngle(startHeading);
return startHeading;
}
void camSwitchCamera::ComputeAscentCutPosition(u32 cutIndex, Vector3& cutPosition) const
{
cutPosition.Set(m_StartPosition);
const float phase = (m_NumAscentCuts > 1) ? (static_cast<float>(cutIndex) / static_cast<float>(m_NumAscentCuts - 1)) : 1.0f;
cutPosition.z = Lerp(phase, m_AscentFloorHeight, m_CeilingHeight);
}
void camSwitchCamera::ComputeDescentCutPosition(u32 cutIndex, Vector3& cutPosition) const
{
cutPosition.Set(m_EndPosition);
const float phase = (m_NumDescentCuts > 1) ? (static_cast<float>(cutIndex) / static_cast<float>(m_NumDescentCuts - 1)) : 1.0f;
cutPosition.z = Lerp(phase, m_DescentFloorHeight, m_CeilingHeight);
}
float camSwitchCamera::ComputeStatePhase(u32 duration) const
{
if(duration == 0)
{
return 1.0f;
}
const u32 currentTime = fwTimer::GetTimeInMilliseconds_NonScaledClipped();
const u32 timeSinceStart = currentTime - m_StateStartTime;
float phase = static_cast<float>(timeSinceStart) / static_cast<float>(duration);
phase = Clamp(phase, 0.0f, 1.0f);
return phase;
}
void camSwitchCamera::ComputeBaseFrame(camFrame& frame, float desiredHeading) const
{
frame.SetWorldMatrixFromHeadingPitchAndRoll(desiredHeading, m_Metadata.m_DefaultPitch * DtoR, 0.0f);
frame.SetFov(m_Metadata.m_Fov);
frame.SetNearClip(m_Metadata.m_NearClip);
frame.SetMotionBlurStrength(m_Metadata.m_MotionBlurStrength);
frame.GetFlags().SetFlag(camFrame::Flag_ShouldOverrideStreamingFocus);
}
void camSwitchCamera::ComputeBaseFrameForEstablishingShot(const CPlayerSwitchEstablishingShotMetadata& shotMetadata, camFrame& frame) const
{
frame.SetFov(shotMetadata.m_Fov);
frame.SetNearClip(m_Metadata.m_NearClip);
// frame.SetMotionBlurStrength(m_Metadata.m_MotionBlurStrength);
frame.GetFlags().SetFlag(camFrame::Flag_ShouldOverrideStreamingFocus);
}
void camSwitchCamera::ComputeWorldMatrixForEstablishingShotStart(const CPlayerSwitchEstablishingShotMetadata& shotMetadata, Matrix34& worldMatrix) const
{
Vector3 orientation;
orientation.SetScaled(shotMetadata.m_Orientation, DtoR);
orientation.x = m_Metadata.m_DefaultPitch * DtoR;
Vector3 startOffset(m_Metadata.m_EstablishingShotRelativeStartOffset);
startOffset.RotateZ(orientation.z);
worldMatrix.FromEulersYXZ(orientation);
worldMatrix.d = shotMetadata.m_Position + startOffset;
}
void camSwitchCamera::ComputeWorldMatrixForEstablishingShotEnd(const CPlayerSwitchEstablishingShotMetadata& shotMetadata, Matrix34& worldMatrix) const
{
Vector3 orientation;
orientation.SetScaled(shotMetadata.m_Orientation, DtoR);
worldMatrix.FromEulersYXZ(orientation);
worldMatrix.d = shotMetadata.m_Position;
}
void camSwitchCamera::ComputeWorldMatrixForEstablishingShotCatchUp(const CPlayerSwitchEstablishingShotMetadata& shotMetadata, Matrix34& worldMatrix) const
{
Vector3 orientation;
orientation.SetScaled(shotMetadata.m_CatchUpOrientation, DtoR);
worldMatrix.FromEulersYXZ(orientation);
worldMatrix.d = shotMetadata.m_CatchUpPosition;
}
void camSwitchCamera::GetTransition(Vec3V_InOut vPos0, Vec3V_InOut vPos1, float& fFov0, float& fFov1) const
{
if (g_PlayerSwitch.GetLongRangeMgr().GetState() == CPlayerSwitchMgrLong::STATE_PAN)
{
camFrame panFrameStart;
camFrame panFrameEnd;
ComputeFrameForAscentCut(m_NumAscentCuts-1, panFrameStart);
ComputeFrameForDescentCut(m_NumDescentCuts-1, panFrameEnd);
vPos0 = VECTOR3_TO_VEC3V(panFrameStart.GetPosition());
vPos1 = VECTOR3_TO_VEC3V(panFrameEnd.GetPosition());
fFov0 = camInterface::GetFov();
fFov1 = fFov0;
}
else
{
vPos0 = VECTOR3_TO_VEC3V(camInterface::GetPos());
vPos1 = vPos0;
fFov0 = m_Metadata.m_Fov * m_Metadata.m_MaxFovScalingForCutEffect;
fFov1 = m_Metadata.m_Fov;
}
}
void camSwitchCamera::GetTransitionFov(float& fFov0, float& fFov1) const
{
if (g_PlayerSwitch.GetLongRangeMgr().GetState() == CPlayerSwitchMgrLong::STATE_PAN)
{
fFov0 = camInterface::GetFov();
fFov1 = fFov0;
}
else
{
fFov0 = m_Metadata.m_Fov * m_Metadata.m_MaxFovScalingForCutEffect;
fFov1 = m_Metadata.m_Fov;
}
}
void camSwitchCamera::SetDest(const CPed& destinationPed, const CPlayerSwitchParams& switchParams)
{
const fwTransform& destinationPedTransform = destinationPed.GetTransform();
m_EndPosition = VEC3V_TO_VECTOR3(destinationPedTransform.GetPosition());
m_EndHeading = destinationPedTransform.GetHeading();
m_ControlFlags = switchParams.m_controlFlags;
m_DescentFloorHeight = switchParams.m_fLowestHeightDescent;
m_NumDescentCuts = cameraVerifyf(switchParams.m_numJumpsDescent > 0,
"A Switch must include at least one descent cut. Defaulting to one") ? switchParams.m_numJumpsDescent : 1;
}
#if __BANK
void camSwitchCamera::DebugOverrideCameraHeading(float& desiredHeading, u32 cutIndex, bool ascent) const
{
switch (ms_DebugOrientationOption)
{
case SOO_USE_START_HEADING:
desiredHeading = m_StartHeading;
break;
case SOO_USE_FINAL_HEADING:
//desiredHeading = (cutIndex != 0 || !ascent) ? m_EndHeading : m_StartHeading;
desiredHeading = m_EndHeading;
break;
case SOO_USE_MAP_NORTH:
desiredHeading = 0.0f;
break;
case SOO_USE_TRAVEL_DIRECTION_AS_UP:
desiredHeading = DebugComputePanDirectionHeading();
break;
case SOO_USE_TRAVEL_DIRECTION_AS_RIGHT:
desiredHeading = DebugComputePanDirectionHeading();
desiredHeading += HALF_PI;
break;
case SOO_ROTATE_ON_ASCENT_ONLY:
if (ascent)
{
const float phase = (m_NumAscentCuts > 1) ? (static_cast<float>(cutIndex) /
static_cast<float>(m_NumAscentCuts - 1)) : 1.0f;
desiredHeading = fwAngle::LerpTowards(m_StartHeading, m_EndHeading, phase);
}
else
{
desiredHeading = m_EndHeading;
}
break;
case SOO_ROTATE_ON_DESCENT_ONLY:
if (ascent)
{
desiredHeading = m_StartHeading;
}
else
{
const float phase = (m_NumDescentCuts > 1) ? (1.0f - (static_cast<float>(cutIndex) /
static_cast<float>(m_NumDescentCuts - 1))) : 1.0f;
desiredHeading = fwAngle::LerpTowards(m_StartHeading, m_EndHeading, phase);
}
break;
case SOO_ROTATE_ASCENT_AND_DESCENT:
if (ascent)
{
//Rotate halfway towards the destination heading during ascent.
const float phase = (m_NumAscentCuts > 1) ? (static_cast<float>(cutIndex) /
(static_cast<float>(m_NumAscentCuts - 1) * 2.0f)) : 0.5f;
desiredHeading = fwAngle::LerpTowards(m_StartHeading, m_EndHeading, phase);
}
else
{
//Complete the remaining half of the rotation towards the destination heading during descent.
const float phase = (m_NumDescentCuts > 1) ? (1.0f - (static_cast<float>(cutIndex) /
(static_cast<float>(m_NumDescentCuts - 1) * 2.0f))) : 0.5f;
desiredHeading = fwAngle::LerpTowards(m_StartHeading, m_EndHeading, phase);
}
break;
case SOO_DEFAULT:
default:
break;
}
}
float camSwitchCamera::DebugComputePanDirectionHeading() const
{
Vector3 panDirection = m_EndPosition - m_StartPosition;
panDirection.NormalizeSafe(YAXIS);
const float heading = camFrame::ComputeHeadingFromFront(panDirection);
return heading;
}
void camSwitchCamera::AddWidgets(bkBank& bank)
{
bank.PushGroup("Switch camera");
{
bank.AddCombo("Orientation options", &ms_DebugOrientationOption, SOO_MAX, g_DebugSwitchOrientationOptions);
}
bank.PopGroup(); //Switch camera
}
#endif // __BANK