OLS/Source/ols/Private/Camera/OLSCameraMode.cpp
LongLy 57b53b9c0c Implemented OLSCameraMode.
Addressed @TODOs related to custom logs and OLSCameraMode
2025-01-20 14:08:07 -07:00

496 lines
12 KiB
C++

// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
#include "Camera/OLSCameraMode.h"
#include "Camera/OLSPlayerCameraManager.h"
#include "Camera/Components/OLSCameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Engine/Canvas.h"
#include "GameFramework/Character.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSCameraMode)
//////////////////////////////////////////////////////////////////////////
// FLyraCameraModeView
//////////////////////////////////////////////////////////////////////////
FOLSCameraModeView::FOLSCameraModeView()
: Location(ForceInit)
, Rotation(ForceInit)
, ControlRotation(ForceInit)
, FieldOfView(OLS_CAMERA_DEFAULT_FOV)
{
}
void FOLSCameraModeView::Blend(const FOLSCameraModeView& other, float otherWeight)
{
if (otherWeight <= 0.0f)
{
return;
}
else if (otherWeight >= 1.0f)
{
*this = other;
return;
}
Location = FMath::Lerp(Location, other.Location, otherWeight);
const FRotator deltaRotation = (other.Rotation - Rotation).GetNormalized();
Rotation = Rotation + (otherWeight * deltaRotation);
const FRotator deltaControlRotation = (other.ControlRotation - ControlRotation).GetNormalized();
ControlRotation = ControlRotation + (otherWeight * deltaControlRotation);
FieldOfView = FMath::Lerp(FieldOfView, other.FieldOfView, otherWeight);
}
//////////////////////////////////////////////////////////////////////////
// UOLSCameraMode
//////////////////////////////////////////////////////////////////////////
UOLSCameraMode::UOLSCameraMode()
{
FieldOfView = OLS_CAMERA_DEFAULT_FOV;
ViewPitchMin = OLS_CAMERA_DEFAULT_PITCH_MIN;
ViewPitchMax = OLS_CAMERA_DEFAULT_PITCH_MAX;
BlendTime = 0.5f;
BlendFunction = EOLSCameraModeBlendFunction::EaseOut;
BlendExponent = 4.0f;
BlendAlpha = 1.0f;
BlendWeight = 1.0f;
}
UOLSCameraComponent* UOLSCameraMode::GetOLSCameraComponent() const
{
return CastChecked<UOLSCameraComponent>(GetOuter());
}
UWorld* UOLSCameraMode::GetWorld() const
{
return HasAnyFlags(RF_ClassDefaultObject) ? nullptr : GetOuter()->GetWorld();
}
AActor* UOLSCameraMode::GetTargetActor() const
{
const UOLSCameraComponent* cameraComponent = GetOLSCameraComponent();
return cameraComponent->GetTargetActor();
}
const FOLSCameraModeView& UOLSCameraMode::GetCameraModeView() const
{
return View;
}
void UOLSCameraMode::OnActivation()
{
}
void UOLSCameraMode::OnDeactivation()
{
}
void UOLSCameraMode::UpdateCameraMode(float deltaTime)
{
UpdateView(deltaTime);
UpdateBlending(deltaTime);
}
float UOLSCameraMode::GetBlendTime() const
{
return BlendTime;
}
float UOLSCameraMode::GetBlendWeight() const
{
return BlendWeight;
}
void UOLSCameraMode::SetBlendWeight(float weight)
{
BlendWeight = FMath::Clamp(weight, 0.0f, 1.0f);
// Since we're setting the blend weight directly, we need to calculate the blend alpha to account for the blend function.
const float InvExponent = (BlendExponent > 0.0f) ? (1.0f / BlendExponent) : 1.0f;
switch (BlendFunction)
{
case EOLSCameraModeBlendFunction::Linear:
BlendAlpha = BlendWeight;
break;
case EOLSCameraModeBlendFunction::EaseIn:
BlendAlpha = FMath::InterpEaseIn(0.0f, 1.0f, BlendWeight, InvExponent);
break;
case EOLSCameraModeBlendFunction::EaseOut:
BlendAlpha = FMath::InterpEaseOut(0.0f, 1.0f, BlendWeight, InvExponent);
break;
case EOLSCameraModeBlendFunction::EaseInOut:
BlendAlpha = FMath::InterpEaseInOut(0.0f, 1.0f, BlendWeight, InvExponent);
break;
default:
checkf(false, TEXT("SetBlendWeight: Invalid BlendFunction [%d]\n"), (uint8)BlendFunction);
break;
}
}
FGameplayTag UOLSCameraMode::GetCameraTypeTag() const
{
return CameraTypeTag;
}
void UOLSCameraMode::DrawDebug(UCanvas* canvas) const
{
check(canvas);
FDisplayDebugManager& displayDebugManager = canvas->DisplayDebugManager;
displayDebugManager.SetDrawColor(FColor::White);
displayDebugManager.DrawString(FString::Printf(TEXT(" OLSCameraMode: %s (%f)"), *GetName(), BlendWeight));
}
FVector UOLSCameraMode::GetPivotLocation() const
{
const AActor* targetActor = GetTargetActor();
check(targetActor);
if (const APawn* targetPawn = Cast<APawn>(targetActor))
{
// Height adjustments for characters to account for crouching.
if (const ACharacter* targetCharacter = Cast<ACharacter>(targetPawn))
{
const ACharacter* targetCharacterCDO = targetCharacter->GetClass()->GetDefaultObject<ACharacter>();
check(targetCharacterCDO);
const UCapsuleComponent* capsuleComp = targetCharacter->GetCapsuleComponent();
check(capsuleComp);
const UCapsuleComponent* capsuleCompCDO = targetCharacterCDO->GetCapsuleComponent();
check(capsuleCompCDO);
const float defaultHalfHeight = capsuleCompCDO->GetUnscaledCapsuleHalfHeight();
const float actualHalfHeight = capsuleComp->GetUnscaledCapsuleHalfHeight();
const float heightAdjustment = (defaultHalfHeight - actualHalfHeight) + targetCharacterCDO->BaseEyeHeight;
return targetCharacter->GetActorLocation() + (FVector::UpVector * heightAdjustment);
}
return targetPawn->GetPawnViewLocation();
}
return targetActor->GetActorLocation();
}
FRotator UOLSCameraMode::GetPivotRotation() const
{
const AActor* targetActor = GetTargetActor();
check(targetActor);
if (const APawn* targetPawn = Cast<APawn>(targetActor))
{
return targetPawn->GetViewRotation();
}
return targetActor->GetActorRotation();
}
void UOLSCameraMode::UpdateView(float deltaTime)
{
FVector pivotLocation = GetPivotLocation();
FRotator pivotRotation = GetPivotRotation();
pivotRotation.Pitch = FMath::ClampAngle(pivotRotation.Pitch, ViewPitchMin, ViewPitchMax);
View.Location = pivotLocation;
View.Rotation = pivotRotation;
View.ControlRotation = View.Rotation;
View.FieldOfView = FieldOfView;
}
void UOLSCameraMode::UpdateBlending(float deltaTime)
{
if (BlendTime > 0.0f)
{
BlendAlpha += (deltaTime / BlendTime);
BlendAlpha = FMath::Min(BlendAlpha, 1.0f);
}
else
{
BlendAlpha = 1.0f;
}
const float exponent = (BlendExponent > 0.0f) ? BlendExponent : 1.0f;
switch (BlendFunction)
{
case EOLSCameraModeBlendFunction::Linear:
BlendWeight = BlendAlpha;
break;
case EOLSCameraModeBlendFunction::EaseIn:
BlendWeight = FMath::InterpEaseIn(0.0f, 1.0f, BlendAlpha, exponent);
break;
case EOLSCameraModeBlendFunction::EaseOut:
BlendWeight = FMath::InterpEaseOut(0.0f, 1.0f, BlendAlpha, exponent);
break;
case EOLSCameraModeBlendFunction::EaseInOut:
BlendWeight = FMath::InterpEaseInOut(0.0f, 1.0f, BlendAlpha, exponent);
break;
default:
checkf(false, TEXT("UpdateBlending: Invalid BlendFunction [%d]\n"), (uint8)BlendFunction);
break;
}
}
//////////////////////////////////////////////////////////////////////////
// UOLSCameraModeStack
//////////////////////////////////////////////////////////////////////////
UOLSCameraModeStack::UOLSCameraModeStack()
{
bIsStackActive = true;
}
void UOLSCameraModeStack::ActivateStack()
{
if (!IsStackActivate())
{
bIsStackActive = true;
// Notify camera modes that they are being activated.
for (UOLSCameraMode* cameraMode : CameraModeStack)
{
check(cameraMode);
cameraMode->OnActivation();
}
}
}
void UOLSCameraModeStack::DeactivateStack()
{
if (IsStackActivate())
{
bIsStackActive = false;
// Notify camera modes that they are being deactivated.
for (UOLSCameraMode* cameraMode : CameraModeStack)
{
check(cameraMode);
cameraMode->OnDeactivation();
}
}
}
bool UOLSCameraModeStack::IsStackActivate() const
{
return bIsStackActive;
}
void UOLSCameraModeStack::PushCameraMode(TSubclassOf<UOLSCameraMode> cameraModeClass)
{
if (!cameraModeClass)
{
return;
}
UOLSCameraMode* cameraMode = GetCameraModeInstance(cameraModeClass);
check(cameraMode);
int32 stackSize = CameraModeStack.Num();
if ((stackSize > 0) && (CameraModeStack[0] == cameraMode))
{
// Already top of stack.
return;
}
// See if it's already in the stack and remove it.
// Figure out how much it was contributing to the stack.
int32 existingStackIndex = INDEX_NONE;
float existingStackContribution = 1.0f;
for (int32 stackIndex = 0; stackIndex < stackSize; ++stackIndex)
{
if (CameraModeStack[stackIndex] == cameraMode)
{
existingStackIndex = stackIndex;
existingStackContribution *= cameraMode->GetBlendWeight();
break;
}
else
{
existingStackContribution *= (1.0f - CameraModeStack[stackIndex]->GetBlendWeight());
}
}
if (existingStackIndex != INDEX_NONE)
{
CameraModeStack.RemoveAt(existingStackIndex);
stackSize--;
}
else
{
existingStackContribution = 0.0f;
}
// Decide what initial weight to start with.
const bool shouldBlend = ((cameraMode->GetBlendTime() > 0.0f) && (stackSize > 0));
const float blendWeight = (shouldBlend ? existingStackContribution : 1.0f);
cameraMode->SetBlendWeight(blendWeight);
// Add new entry to top of stack.
CameraModeStack.Insert(cameraMode, 0);
// Make sure stack bottom is always weighted 100%.
CameraModeStack.Last()->SetBlendWeight(1.0f);
// Let the camera mode know if it's being added to the stack.
if (existingStackIndex == INDEX_NONE)
{
cameraMode->OnActivation();
}
}
bool UOLSCameraModeStack::EvaluateStack(float deltaTime, FOLSCameraModeView& outCameraModeView)
{
if (!IsStackActivate())
{
return false;
}
UpdateStack(deltaTime);
BlendStack(outCameraModeView);
return true;
}
void UOLSCameraModeStack::DrawDebug(UCanvas* canvas) const
{
check(canvas);
FDisplayDebugManager& displayDebugManager = canvas->DisplayDebugManager;
displayDebugManager.SetDrawColor(FColor::Green);
displayDebugManager.DrawString(FString(TEXT(" --- Camera Modes (Begin) ---")));
for (const UOLSCameraMode* cameraMode : CameraModeStack)
{
check(cameraMode);
cameraMode->DrawDebug(canvas);
}
displayDebugManager.SetDrawColor(FColor::Green);
displayDebugManager.DrawString(FString::Printf(TEXT(" --- Camera Modes (End) ---")));
}
void UOLSCameraModeStack::GetBlendInfo(float& outWeightOfTopLayer, FGameplayTag& outTagOfTopLayer) const
{
if (CameraModeStack.Num() == 0)
{
outWeightOfTopLayer = 1.0f;
outTagOfTopLayer = FGameplayTag();
return;
}
UOLSCameraMode* topEntry = CameraModeStack.Last();
check(topEntry);
outWeightOfTopLayer = topEntry->GetBlendWeight();
outTagOfTopLayer = topEntry->GetCameraTypeTag();
}
UOLSCameraMode* UOLSCameraModeStack::GetCameraModeInstance(TSubclassOf<UOLSCameraMode> cameraModeClass)
{
check(cameraModeClass);
// First see if we already created one.
for (UOLSCameraMode* cameraMode : CameraModeInstances)
{
if ((cameraMode != nullptr) && (cameraMode->GetClass() == cameraModeClass))
{
return cameraMode;
}
}
// Not found, so we need to create it.
UOLSCameraMode* newCameraMode = NewObject<UOLSCameraMode>(GetOuter(), cameraModeClass, NAME_None, RF_NoFlags);
check(newCameraMode);
CameraModeInstances.Add(newCameraMode);
return newCameraMode;
}
void UOLSCameraModeStack::UpdateStack(float deltaTime)
{
const int32 stackSize = CameraModeStack.Num();
if (stackSize <= 0)
{
return;
}
int32 removeCount = 0;
int32 removeIndex = INDEX_NONE;
for (int32 stackIndex = 0; stackIndex < stackSize; ++stackIndex)
{
UOLSCameraMode* cameraMode = CameraModeStack[stackIndex];
check(cameraMode);
cameraMode->UpdateCameraMode(deltaTime);
if (cameraMode->GetBlendWeight() >= 1.0f)
{
// Everything below this mode is now irrelevant and can be removed.
removeIndex = (stackIndex + 1);
removeCount = (stackSize - removeIndex);
break;
}
}
if (removeCount > 0)
{
// Let the camera modes know they being removed from the stack.
for (int32 stackIndex = removeIndex; stackIndex < stackSize; ++stackIndex)
{
UOLSCameraMode* cameraMode = CameraModeStack[stackIndex];
check(cameraMode);
cameraMode->OnDeactivation();
}
CameraModeStack.RemoveAt(removeIndex, removeCount);
}
}
void UOLSCameraModeStack::BlendStack(FOLSCameraModeView& outCameraModeView) const
{
const int32 stackSize = CameraModeStack.Num();
if (stackSize <= 0)
{
return;
}
// Start at the bottom and blend up the stack
const UOLSCameraMode* cameraMode = CameraModeStack[stackSize - 1];
check(cameraMode);
outCameraModeView = cameraMode->GetCameraModeView();
for (int32 stackIndex = (stackSize - 2); stackIndex >= 0; --stackIndex)
{
cameraMode = CameraModeStack[stackIndex];
check(cameraMode);
outCameraModeView.Blend(cameraMode->GetCameraModeView(), cameraMode->GetBlendWeight());
}
}