Files
ProjectEleri/Plugins/UniversalCameraPlugin/Source/RTSCameraPlugin/Private/UniversalCamera.cpp
2026-07-11 10:43:52 +02:00

1952 lines
65 KiB
C++

// Universal Camera Plugin - Mathieu Jacq 2021
#include "UniversalCamera.h"
#include "Camera/CameraComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Kismet/KismetMathLibrary.h"
#include "DrawDebugHelpers.h"
#include "AsyncNode.h"
#include "Curves/CurveFloat.h"
#include "Net/UnrealNetwork.h"
#include "TimerManager.h"
#include "Logging/MessageLog.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Serialization/MemoryWriter.h"
#include "UniversalCameraSaveGame.h"
#include "Math/Box.h"
#include "Engine/Engine.h"
#include "Components/SplineComponent.h"
#include "Serialization/ObjectAndNameAsStringProxyArchive.h"
#define LOG_WARNING_MESSAGE(Message) \
{ \
FMessageLog MessageLog(FName("PIE")); \
MessageLog.Warning(FText::FromString(Message)); \
}
FVector FTargetSettings_WithTemplates::GetTargetLocation(bool& IsValid, FRotator& Rotation)
{
IsValid = false;
switch (Type)
{
case 0:
if (TargetSettings.IsValidActor())
{
IsValid = true;
Rotation = TargetSettings.GetActorRotation();
return TargetSettings.GetActorLocation();
}
else return FVector(0.f, 0.f, 0.f);
case 1:
if (TargetSettings.IsValidSceneComponent())
{
IsValid = true;
Rotation = TargetSettings.GetSceneComponentRotation();
return TargetSettings.GetSceneComponentLocation();
}
else return FVector(0.f, 0.f, 0.f);
case 2:
if (TargetSettings.IsValidSocket())
{
IsValid = true;
Rotation = TargetSettings.GetSocketRotation();
return TargetSettings.GetSocketLocation();
}
else return FVector(0.f, 0.f, 0.f);
}
return FVector(0.f, 0.f, 0.f);
}
// Sets default values
AUniversalCamera::AUniversalCamera()
{
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.bTickEvenWhenPaused = true;
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));
SpringArmComponent = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraSpringArm"));
if (SpringArmComponent && RootComponent)
{
SpringArmComponent->SetupAttachment(RootComponent);
SpringArmComponent->TargetArmLength = 400.0f;
SpringArmComponent->bDoCollisionTest = false;
}
}
void AUniversalCamera::GetLifetimeReplicatedProps(TArray< FLifetimeProperty >& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AUniversalCamera, DesiredLocation);
DOREPLIFETIME(AUniversalCamera, DesiredSocketOffset);
DOREPLIFETIME(AUniversalCamera, DesiredTargetOffset);
DOREPLIFETIME(AUniversalCamera, DesiredRotation);
DOREPLIFETIME(AUniversalCamera, DesiredZoom);
DOREPLIFETIME(AUniversalCamera, DesiredRotationOffset);
}
// Called when the game starts or when spawned
void AUniversalCamera::BeginPlay()
{
#if WITH_EDITOR
LOG_VerifyStartingSettings();
#endif
if (GetWorld()) LastDeltaTime = GetWorld()->GetDeltaSeconds();
SetDesiredLocation(OverrideStartingLocation ? StartingLocation : GetActorLocation(), true, true);
SetDesiredYaw(OverrideStartingYaw ? StartingYaw : GetActorRotation().Yaw, true, true);
SetDesiredPitch(OverrideStartingPitch ? StartingPitch : GetActorRotation().Pitch, true, true);
if (SpringArmComponent)
{
SetDesiredSocketOffset(OverrideStartingSocketOffset ? StartingSocketOffset : SpringArmComponent->SocketOffset, true, true);
SetDesiredTargetOffset(OverrideStartingTargetOffset ? StartingTargetOffset : SpringArmComponent->TargetOffset, true, true);
SetDesiredZoom(OverrideStartingZoom ? StartingZoom : SpringArmComponent->TargetArmLength, true, true);
}
Super::BeginPlay();
}
#if WITH_EDITOR
void AUniversalCamera::LOG_VerifyStartingSettings()
{
if (!SpringArmComponent)
{
LOG_WARNING_MESSAGE("Couldn't find the SpringArmComponent.");
return;
}
// Verify that a CameraComponent is attached to the SpringArmComponent
UCameraComponent* AttachedCamera = GetCameraComponent();
if (!AttachedCamera)
{
LOG_WARNING_MESSAGE("Please attach a Camera or a CineCamera to the SpringArmComponent of your Universal Camera class!");
}
// Verify that DoCollisionTest is disabled
if (SpringArmComponent->bDoCollisionTest)
{
LOG_WARNING_MESSAGE("Please disable 'DoCollisionTest' in the SpringArmComponent of your Universal Camera and use the 'SpringArmCollisions' feature in your Universal Camera defaults instead.");
}
// Verify that OriginArmCollisions probe size is greater that SpringArmCollisions probe size
if (EnabledFeatures & OriginCollisions && EnabledFeatures & SpringArmCollisions && (SpringArmCollisionsProbeSize > OriginCollisionsProbeSize))
{
LOG_WARNING_MESSAGE("Spring Arm Collisions Probe Size is greater than Origin Collisions Probe Size, which could result in Spring Arm Collisions triggering when the Origin is near geometry.");
}
}
#endif
// Called every frame
void AUniversalCamera::Tick(float DeltaTime)
{
const float DeltaTimeCorrective = DeltaTime * GetTimeDilationCorrectiveValue();
LastLocation = GetActorLocation();
if (EnabledFeatures & EdgeScrolling) EdgeScrollingTick_Internal();
if (IsScreenSliding) ScreenSlidingTick();
if (IsTraveling) TravelTaskTick(DeltaTimeCorrective);
if (bIsFollowingAnyActor) FollowTargetTick();
// Update Z Desired Location if using Terrain Height Adaptation
if ((EnabledFeatures & TerrainHeightAdaptation) && !IsPreciseMouseDragging && !IsFollowingAnyActor())
{
ApplyTerrainHeightAdaptationTick();
}
if (IsPreciseMouseDragging)
{
PreciseMouseDragTick();
}
// LAG
if (EnabledFeatures & LagMovement)
{
FVector LagLocation = Lag_GetTickDesiredLocation(DeltaTimeCorrective);
SetActorLocation(LagLocation);
}
if (EnabledFeatures & LagOffset)
{
if (SpringArmComponent)
{
SpringArmComponent->SocketOffset = Lag_GetTickDesiredSocketOffset(DeltaTimeCorrective);
SpringArmComponent->TargetOffset = Lag_GetTickDesiredTargetOffset(DeltaTimeCorrective);
}
else
{
LOG_WARNING_MESSAGE("Couldn't find the SpringArmComponent.");
}
}
if (EnabledFeatures & LagMovement)
{
FVector CurrentLocation = GetActorLocation();
SetActorLocation(FVector(CurrentLocation.X, CurrentLocation.Y, Lag_GetTickDesiredHeight(DeltaTimeCorrective)));
}
if (EnabledFeatures & LagRotation)
{
SetActorRotation(Lag_GetTickDesiredRotation(DeltaTimeCorrective));
}
UpdateSpringArmLength(DeltaTimeCorrective);
if (DebugPrintDesiredPosition) PrintDesiredPosition();
// Update Velocity
Velocity = (GetActorLocation() - LastLocation) / LastDeltaTime;
if (GetWorld()) LastDeltaTime = GetWorld()->GetDeltaSeconds();
LastTimeDilation = UGameplayStatics::GetGlobalTimeDilation(GetWorld());
Super::Tick(DeltaTime);
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (DrawDebugSpheres)
{
FVector CurrentLocation = GetActorLocation();
DrawDebugSphere(GetWorld(), GetActorLocation(), 5.f, 8, FColor::Green);
if (EnabledFeatures & LagMovement)
{
DrawDebugSphere(GetWorld(), DesiredLocation, 5.f, 8, FColor::Yellow);
DrawDebugDirectionalArrow(GetWorld(), CurrentLocation, DesiredLocation, 7.5f, FColor::Yellow);
}
if (DesiredTargetOffset.Length() > KINDA_SMALL_NUMBER)
{
DrawDebugSphere(GetWorld(), GetDesiredTargetOffsetWorldLocation(), 5.f, 8, FColor::Orange);
DrawDebugDirectionalArrow(GetWorld(), DesiredLocation, GetDesiredTargetOffsetWorldLocation(), 7.5f, FColor::Orange);
}
if (DesiredSocketOffset.Length() > KINDA_SMALL_NUMBER)
{
DrawDebugSphere(GetWorld(), GetDesiredSocketOffsetWorldLocation(), 5.f, 8, FColor::Red);
DrawDebugDirectionalArrow(GetWorld(), GetDesiredTargetOffsetWorldLocation(), GetDesiredSocketOffsetWorldLocation(), 7.5f, FColor::Red);
}
}
#endif
}
// Called to bind functionality to input
void AUniversalCamera::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
}
UCameraComponent* AUniversalCamera::GetCameraComponent()
{
if (!SpringArmComponent)
{
LOG_WARNING_MESSAGE("Couldn't find the SpringArmComponent.");
return nullptr;
}
TArray<TObjectPtr<USceneComponent>> AttachedChildren = SpringArmComponent->GetAttachChildren();
for (TObjectPtr<USceneComponent> AttachedChild : AttachedChildren)
{
UCameraComponent* Camera = Cast<UCameraComponent>(AttachedChild);
if (IsValid(Camera))
{
return Camera;
}
}
return nullptr;
}
void AUniversalCamera::UpdateSpringArmLength(float DeltaTime)
{
if (!SpringArmComponent)
{
LOG_WARNING_MESSAGE("Couldn't find the SpringArmComponent.");
return;
}
float NewTargetArmLength = SpringArmComponent->TargetArmLength;
if (EnabledFeatures & LagZoom)
{
NewTargetArmLength = Lag_GetTickDesiredZoom(DeltaTime);
}
if (EnabledFeatures & SpringArmCollisions && (NewTargetArmLength != 0.0f))
{
FVector OriginLoc = GetActorLocation();
FVector CameraLoc = OriginLoc - GetActorRotation().Vector() * NewTargetArmLength;
FHitResult HitResult;
AActor* ActorToIgnore = IsFollowingAnyActor() ? FollowTargetSettings.TargetSettings.GetOwnerActor() : GetOwner();
FCollisionQueryParams QueryParams(SCENE_QUERY_STAT(SpringArm), false, ActorToIgnore);
GetWorld()->SweepSingleByChannel(HitResult, OriginLoc, CameraLoc, FQuat::Identity, SpringArmCollisionsChannel, FCollisionShape::MakeSphere(SpringArmCollisionsProbeSize), QueryParams);
if (HitResult.bBlockingHit)
{
NewTargetArmLength = (HitResult.Location - OriginLoc).Length();
}
}
SpringArmComponent->TargetArmLength = NewTargetArmLength;
}
void AUniversalCamera::EnableFeatures(int32 FeaturesToEnable)
{
EnabledFeatures |= FeaturesToEnable;
}
void AUniversalCamera::DisableFeatures(int32 FeaturesToDisable)
{
// Disable ScreenSliding if active
if ((FeaturesToDisable & ScreenSliding) && (EnabledFeatures & ScreenSliding) && IsScreenSliding)
{
ToggleScreenSliding(false);
}
// Stop Following if active
if ((FeaturesToDisable & EEnabledFeatures::FollowTarget) && (EnabledFeatures & EEnabledFeatures::FollowTarget) && IsFollowingAnyActor())
{
StopFollowing_Internal(bShouldResetOffsetIfFailed, StopFollowingReason_DisabledFeature);
}
// Disable PreciseMouseDrag if active
if ((FeaturesToDisable & PreciseMouseDrag) && (EnabledFeatures & PreciseMouseDrag) && IsPreciseMouseDragging)
{
TogglePreciseMouseDrag(false);
}
if ((FeaturesToDisable & LagMovement) && (EnabledFeatures & LagMovement))
{
SetDesiredLocation(GetActorLocation());
}
if ((FeaturesToDisable & LagOffset) && (EnabledFeatures & LagOffset))
{
if (SpringArmComponent)
{
SetDesiredSocketOffset(SpringArmComponent->SocketOffset);
SetDesiredTargetOffset(SpringArmComponent->TargetOffset);
}
else
{
LOG_WARNING_MESSAGE("Couldn't find the SpringArmComponent.");
}
}
if ((FeaturesToDisable & LagRotation) && (EnabledFeatures & LagRotation))
{
SetDesiredRotation(GetActorRotation());
}
if ((FeaturesToDisable & LagZoom) && (EnabledFeatures & LagZoom))
{
if (SpringArmComponent)
{
SetDesiredZoom(SpringArmComponent->TargetArmLength);
}
else
{
LOG_WARNING_MESSAGE("Couldn't find the SpringArmComponent.");
}
}
EnabledFeatures = ~(~EnabledFeatures | FeaturesToDisable);
}
bool AUniversalCamera::AreFeaturesEnabled(int32 FeaturesToTest)
{
return (EnabledFeatures & FeaturesToTest);
}
void AUniversalCamera::StartTraveling_Internal(UAsyncNode* AsyncNode)
{
if (!IsValid(AsyncNode)) return;
CurrentTravelTask = AsyncNode;
IsTraveling = true;
}
void AUniversalCamera::TravelTaskTick(float DeltaTime)
{
if (!IsValid(CurrentTravelTask))
{
EndTravelTask(true);
return;
}
CurrentTravelTask->TryUpdateLocationDirectionAndValidity();
CurrentTravelTask->TryUpdateYawDirectionAndValidity();
CurrentTravelTask->TryUpdatePitchDirectionAndValidity();
CurrentTravelTask->TryUpdateRollDirectionAndValidity();
CurrentTravelTask->TryUpdateZoomDirectionAndValidity();
if (!CurrentTravelTask->AreValidSettings())
{
EndTravelTask(true);
return;
}
CurrentTravelTask->ElapsedTime += DeltaTime;
float CurvePosition = CurrentTravelTask->ElapsedTime / CurrentTravelTask->MaxDuration;
float CurveValue = (CurrentTravelTask->m_Curve != nullptr) ? CurrentTravelTask->m_Curve->GetFloatValue(CurvePosition) : CurvePosition;
UpdateCameraTransform(CurveValue);
if (CurvePosition >= 1.f)
{
EndTravelTask(false);
}
}
void AUniversalCamera::UpdateCameraTransform(float CurveValue)
{
const bool UseSpline = CurrentTravelTask->LocationSettings.TargetMod == ETargetMod::TargetMod_Spline;
if (UseSpline)
{
USplineComponent* SplineComponent = CurrentTravelTask->LocationSettings.SplineComponent;
if (SplineComponent)
{
const float SplineLength = SplineComponent->GetSplineLength();
SetDesiredLocation_Internal(SplineComponent->GetLocationAtDistanceAlongSpline(CurveValue * SplineLength, ESplineCoordinateSpace::World));
}
}
else
{
if (CurrentTravelTask->IsValidLocation)
{
SetDesiredLocation_Internal(CurrentTravelTask->StartingLocation + CurrentTravelTask->LocationDirection * CurveValue, CurrentTravelTask->m_IgnoreLag, CurrentTravelTask->m_IgnoreRestrictions);
}
}
if (CurrentTravelTask->IsValidOffset)
{
if (CurrentTravelTask->OffsetSettings.OffsetMod == 1)
{
SetDesiredTargetOffset_Internal(CurrentTravelTask->StartingOffset + CurrentTravelTask->OffsetDirection * CurveValue, CurrentTravelTask->m_IgnoreLag, CurrentTravelTask->m_IgnoreRestrictions);
}
else
{
SetDesiredSocketOffset_Internal(CurrentTravelTask->StartingOffset + CurrentTravelTask->OffsetDirection * CurveValue, CurrentTravelTask->m_IgnoreLag, CurrentTravelTask->m_IgnoreRestrictions);
}
}
if (CurrentTravelTask->IsValidYaw)
{
SetDesiredYaw_Internal(CurrentTravelTask->StartingYaw + CurrentTravelTask->YawDirection * CurveValue, CurrentTravelTask->m_IgnoreLag, CurrentTravelTask->m_IgnoreRestrictions);
}
if (CurrentTravelTask->IsValidPitch)
{
SetDesiredPitch_Internal(CurrentTravelTask->StartingPitch + CurrentTravelTask->PitchDirection * CurveValue, CurrentTravelTask->m_IgnoreLag, CurrentTravelTask->m_IgnoreRestrictions);
}
if (CurrentTravelTask->IsValidRoll)
{
SetDesiredRoll_Internal(CurrentTravelTask->StartingRoll + CurrentTravelTask->RollDirection * CurveValue, CurrentTravelTask->m_IgnoreLag, CurrentTravelTask->m_IgnoreRestrictions);
}
if (CurrentTravelTask->IsValidZoom)
{
SetDesiredZoom_Internal(CurrentTravelTask->StartingZoom + CurrentTravelTask->ZoomDirection * CurveValue, CurrentTravelTask->m_IgnoreLag, CurrentTravelTask->m_IgnoreRestrictions);
}
}
void AUniversalCamera::EndTravelTask(const bool Abort)
{
IsTraveling = false;
if (Abort && IsValid(CurrentTravelTask))
{
CurrentTravelTask->OnAborted.Broadcast();
}
else if (IsValid(CurrentTravelTask))
{
CurrentTravelTask->OnReachedDestination.Broadcast();
}
}
void AUniversalCamera::SetDesiredPosition(FVector NewLocation, FVector NewSocketOffset, FVector NewTargetOffset, float NewYaw, float NewPitch, float NewRoll, float NewZoom, bool IgnoreLag, bool IgnoreRestrictions)
{
SetDesiredLocation(NewLocation, IgnoreLag, IgnoreRestrictions);
SetDesiredSocketOffset(NewSocketOffset, IgnoreLag, IgnoreRestrictions);
SetDesiredTargetOffset(NewTargetOffset, IgnoreLag, IgnoreRestrictions);
SetDesiredYaw(NewYaw, IgnoreLag, IgnoreRestrictions);
SetDesiredPitch(NewPitch, IgnoreLag, IgnoreRestrictions);
SetDesiredRoll(NewRoll, IgnoreLag, IgnoreRestrictions);
SetDesiredZoom(NewZoom, IgnoreLag, IgnoreRestrictions);
}
void AUniversalCamera::SetDesiredLocation_Internal(FVector NewLocation, bool IgnoreLag, bool IgnoreRestrictions)
{
if (!IgnoreRestrictions) NewLocation = GetCorrectedDestinationFromRestrictions(NewLocation);
if (!IgnoreRestrictions && EnabledFeatures & OriginCollisions)
{
bool DumBool;
NewLocation = GetCorrectedDestinationFromOriginCollisions(DesiredLocation, NewLocation, DumBool);
}
DesiredLocation = NewLocation;
if (!(EnabledFeatures & EEnabledFeatures::LagMovement) || IgnoreLag)
{
LastLocation = GetActorLocation();
LastDeltaTime = GetWorld()->GetDeltaSeconds();
SetActorLocation(DesiredLocation, true);
}
OnDesiredLocationChanged.Broadcast(DesiredLocation);
}
void AUniversalCamera::SetDesiredLocation(FVector NewLocation, bool IgnoreLag, bool IgnoreRestrictions)
{
if (IsTraveling && IsValid(CurrentTravelTask) && (CurrentTravelTask->m_LockAllMovement || CurrentTravelTask->IsValidLocation)) return;
SetDesiredLocation_Internal(NewLocation, IgnoreLag, IgnoreRestrictions);
}
FVector AUniversalCamera::GetClampedLocation(FVector Location)
{
if (LocationConstrainType == ELocationConstrainType::Raw)
{
float X = Location.X;
float Y = Location.Y;
float Z = Location.Z;
ClampCustomValue(ConstrainLocationMinimum.X, ConstrainLocationMaximum.X, &X, MinimumLocation.X, MaximumLocation.X);
ClampCustomValue(ConstrainLocationMinimum.Y, ConstrainLocationMaximum.Y, &Y, MinimumLocation.Y, MaximumLocation.Y);
ClampCustomValue(ConstrainLocationMinimum.Z, ConstrainLocationMaximum.Z, &Z, MinimumLocation.Z, MaximumLocation.Z);
return FVector(X, Y, Z);
}
else if (LocationConstrainType == ELocationConstrainType::Sphere)
{
FVector ToLocation = Location - SphereOrigin;
float DistanceSquared = ToLocation.SizeSquared();
if (DistanceSquared > FMath::Square(SphereRadius))
{
ToLocation.Normalize();
Location = SphereOrigin + ToLocation * SphereRadius;
}
return Location;
}
return Location;
}
void AUniversalCamera::SetDesiredTargetOffset_Internal(FVector NewTargetOffset, bool IgnoreLag, bool IgnoreRestrictions)
{
if (!SpringArmComponent)
{
LOG_WARNING_MESSAGE("Couldn't find the SpringArmComponent.");
return;
}
if (!IgnoreRestrictions) NewTargetOffset = GetClampedOffset(NewTargetOffset);
if (!IgnoreRestrictions && EnabledFeatures & OriginCollisions)
{
bool DumBool;
NewTargetOffset = GetCorrectedDestinationFromOriginCollisions(GetDesiredTargetOffsetWorldLocation(), DesiredLocation + NewTargetOffset, DumBool) - DesiredLocation;
}
DesiredTargetOffset = NewTargetOffset;
if (!(EnabledFeatures & EEnabledFeatures::LagOffset) || IgnoreLag)
SpringArmComponent->TargetOffset = DesiredTargetOffset;
OnDesiredTargetOffsetChanged.Broadcast(DesiredTargetOffset);
}
void AUniversalCamera::SetDesiredTargetOffset(FVector NewTargetOffset, bool IgnoreLag, bool IgnoreRestrictions)
{
if (IsTraveling && IsValid(CurrentTravelTask) && (CurrentTravelTask->m_LockAllMovement || (CurrentTravelTask->IsValidOffset && CurrentTravelTask->OffsetSettings.OffsetMod == 1))) return;
SetDesiredTargetOffset_Internal(NewTargetOffset, IgnoreLag, IgnoreRestrictions);
}
void AUniversalCamera::SetDesiredSocketOffset_Internal(FVector NewSocketOffset, bool IgnoreLag, bool IgnoreRestrictions)
{
if (!SpringArmComponent)
{
LOG_WARNING_MESSAGE("Couldn't find the SpringArmComponent.");
return;
}
if (!IgnoreRestrictions) NewSocketOffset = GetClampedOffset(NewSocketOffset);
if (!IgnoreRestrictions && EnabledFeatures & OriginCollisions)
{
bool DumBool;
FVector NewWorldSocketOffset = GetCorrectedDestinationFromOriginCollisions(GetDesiredSocketOffsetWorldLocation(), GetDesiredTargetOffsetWorldLocation() + FRotationMatrix(DesiredRotation).TransformVector(NewSocketOffset), DumBool);
FVector RelativeLoc = FTransform(DesiredRotation, GetDesiredTargetOffsetWorldLocation()).InverseTransformPosition(NewWorldSocketOffset);
NewSocketOffset = RelativeLoc;
}
DesiredSocketOffset = NewSocketOffset;
if (!(EnabledFeatures & EEnabledFeatures::LagOffset) || IgnoreLag)
SpringArmComponent->SocketOffset = DesiredSocketOffset;
OnDesiredSocketOffsetChanged.Broadcast(DesiredSocketOffset);
}
void AUniversalCamera::SetDesiredSocketOffset(FVector NewSocketOffset, bool IgnoreLag, bool IgnoreRestrictions)
{
if (IsTraveling && IsValid(CurrentTravelTask) && (CurrentTravelTask->m_LockAllMovement || (CurrentTravelTask->IsValidOffset && CurrentTravelTask->OffsetSettings.OffsetMod == 2))) return;
SetDesiredSocketOffset_Internal(NewSocketOffset, IgnoreLag, IgnoreRestrictions);
}
FVector AUniversalCamera::GetClampedOffset(FVector Offset)
{
if (OffsetConstrainType == EConstrainType::CustomConstrain)
{
// UE5.0 compatibility / Replaces FVector member references
float X = Offset.X;
float Y = Offset.Y;
float Z = Offset.Z;
ClampCustomValue(ConstrainOffsetMinimum.X, ConstrainOffsetMaximum.X, &X, MinimumOffset.X, MaximumOffset.X);
ClampCustomValue(ConstrainOffsetMinimum.Y, ConstrainOffsetMaximum.Y, &Y, MinimumOffset.Y, MaximumOffset.Y);
ClampCustomValue(ConstrainOffsetMinimum.Z, ConstrainOffsetMaximum.Z, &Z, MinimumOffset.Z, MaximumOffset.Z);
Offset = FVector(X, Y, Z);
}
else if (OffsetConstrainType == EConstrainType::MaxDistance)
{
if (Offset.Length() > OffsetMaxDistance)
{
FVector Direction = Offset;
Direction.Normalize();
Offset = Direction * OffsetMaxDistance;
}
}
return Offset;
}
void AUniversalCamera::SetDesiredYaw_Internal(float NewYaw, bool IgnoreLag, bool IgnoreRestrictions)
{
float NewYawOffset = DesiredRotationOffset.Yaw;
if (!IgnoreRestrictions)
{
NewYaw = GetClampedYaw(NewYaw);
NewYawOffset = GetClampedYaw(NewYaw + DesiredRotationOffset.Yaw) - NewYaw;
}
if (!IgnoreRestrictions && EnabledFeatures & OriginCollisions && DesiredSocketOffset.Length() > KINDA_SMALL_NUMBER)
{
bool DidHit;
FVector NewWorldSocketOffset = GetCorrectedDestinationFromOriginCollisions(GetDesiredSocketOffsetWorldLocation(), GetDesiredTargetOffsetWorldLocation() + FRotationMatrix(FRotator(DesiredRotation.Pitch, NewYaw, DesiredRotation.Roll)).TransformVector(DesiredSocketOffset), DidHit);
if (DidHit)
{
return;
}
}
DesiredRotation.Yaw = NewYaw;
DesiredRotationOffset.Yaw = NewYawOffset;
if (!(EnabledFeatures & EEnabledFeatures::LagRotation) || IgnoreLag)
SetActorRotation(GetDesiredRotationWithOffset());
OnDesiredRotationChanged.Broadcast(DesiredRotation);
OnDesiredRotationOffsetChanged.Broadcast(DesiredRotationOffset);
}
void AUniversalCamera::SetDesiredYaw(float NewYaw, bool IgnoreLag, bool IgnoreRestrictions)
{
if (IsTraveling && IsValid(CurrentTravelTask) && (CurrentTravelTask->m_LockAllMovement || CurrentTravelTask->IsValidYaw)) return;
SetDesiredYaw_Internal(NewYaw, IgnoreLag, IgnoreRestrictions);
}
void AUniversalCamera::SetDesiredYawOffset(float NewYawOffset)
{
DesiredRotationOffset.Yaw = NewYawOffset;
SetDesiredYaw(DesiredRotation.Yaw);
}
float AUniversalCamera::GetClampedYaw(float Yaw)
{
Yaw = UKismetMathLibrary::NormalizeAxis(Yaw);
ClampCustomValue(ConstrainYaw, ConstrainYaw, &Yaw, MinimumYaw, MaximumYaw, ReverseYawRestriction);
return Yaw;
}
void AUniversalCamera::SetDesiredPitch_Internal(float NewPitch, bool IgnoreLag, bool IgnoreRestrictions)
{
float NewPitchOffset = DesiredRotationOffset.Pitch;
if (!IgnoreRestrictions)
{
NewPitch = GetClampedPitch(NewPitch);
NewPitchOffset = GetClampedPitch(NewPitch + DesiredRotationOffset.Pitch) - NewPitch;
}
if (!IgnoreRestrictions && EnabledFeatures & OriginCollisions && DesiredSocketOffset.Length() > KINDA_SMALL_NUMBER)
{
bool DidHit;
FVector NewWorldSocketOffset = GetCorrectedDestinationFromOriginCollisions(GetDesiredSocketOffsetWorldLocation(), GetDesiredTargetOffsetWorldLocation() + FRotationMatrix(FRotator(NewPitch, DesiredRotation.Yaw, DesiredRotation.Roll)).TransformVector(DesiredSocketOffset), DidHit);
if (DidHit)
{
return;
}
}
DesiredRotation.Pitch = NewPitch;
DesiredRotationOffset.Pitch = NewPitchOffset;
if (!(EnabledFeatures & EEnabledFeatures::LagRotation) || IgnoreLag)
SetActorRotation(GetDesiredRotationWithOffset());
OnDesiredRotationChanged.Broadcast(DesiredRotation);
OnDesiredRotationOffsetChanged.Broadcast(DesiredRotationOffset);
}
void AUniversalCamera::SetDesiredPitch(float NewPitch, bool IgnoreLag, bool IgnoreRestrictions)
{
if (IsTraveling && IsValid(CurrentTravelTask) && (CurrentTravelTask->m_LockAllMovement || CurrentTravelTask->IsValidPitch)) return;
SetDesiredPitch_Internal(NewPitch, IgnoreLag, IgnoreRestrictions);
}
void AUniversalCamera::SetDesiredPitchOffset(float NewPitchOffset)
{
DesiredRotationOffset.Pitch = NewPitchOffset;
SetDesiredPitch(DesiredRotation.Pitch);
}
float AUniversalCamera::GetClampedPitch(float Pitch)
{
Pitch = UKismetMathLibrary::NormalizeAxis(Pitch);
ClampCustomValue(ConstrainPitch, ConstrainPitch, &Pitch, MinimumPitch, MaximumPitch, ReversePitchRestriction);
return Pitch;
}
void AUniversalCamera::SetDesiredRoll_Internal(float NewRoll, bool IgnoreLag, bool IgnoreRestrictions)
{
float NewRollOffset = DesiredRotationOffset.Roll;
if (!IgnoreRestrictions)
{
NewRoll = GetClampedRoll(NewRoll);
NewRollOffset = GetClampedRoll(NewRoll + DesiredRotationOffset.Roll) - NewRoll;
}
if (!IgnoreRestrictions && EnabledFeatures & OriginCollisions && DesiredSocketOffset.Length() > KINDA_SMALL_NUMBER)
{
bool DidHit;
FVector NewWorldSocketOffset = GetCorrectedDestinationFromOriginCollisions(GetDesiredSocketOffsetWorldLocation(), GetDesiredTargetOffsetWorldLocation() + FRotationMatrix(FRotator(DesiredRotation.Pitch, DesiredRotation.Yaw, NewRoll)).TransformVector(DesiredSocketOffset), DidHit);
if (DidHit)
{
return;
}
}
DesiredRotation.Roll = NewRoll;
DesiredRotationOffset.Roll = NewRollOffset;
if (!(EnabledFeatures & EEnabledFeatures::LagRotation) || IgnoreLag)
SetActorRotation(GetDesiredRotationWithOffset());
OnDesiredRotationChanged.Broadcast(DesiredRotation);
OnDesiredRotationOffsetChanged.Broadcast(DesiredRotationOffset);
}
void AUniversalCamera::SetDesiredRoll(float NewRoll, bool IgnoreLag, bool IgnoreRestrictions)
{
if (IsTraveling && IsValid(CurrentTravelTask) && (CurrentTravelTask->m_LockAllMovement || CurrentTravelTask->IsValidRoll)) return;
SetDesiredRoll_Internal(NewRoll, IgnoreLag, IgnoreRestrictions);
}
void AUniversalCamera::SetDesiredRollOffset(float NewRollOffset)
{
DesiredRotationOffset.Roll = NewRollOffset;
SetDesiredRoll(DesiredRotation.Roll);
}
float AUniversalCamera::GetClampedRoll(float Roll)
{
Roll = UKismetMathLibrary::NormalizeAxis(Roll);
ClampCustomValue(ConstrainRoll, ConstrainRoll, &Roll, MinimumRoll, MaximumRoll, ReverseRollRestriction);
return Roll;
}
void AUniversalCamera::SetDesiredRotation(FRotator NewRotation, bool IgnoreLag, bool IgnoreRestrictions)
{
SetDesiredYaw(NewRotation.Yaw, IgnoreLag, IgnoreRestrictions);
SetDesiredPitch(NewRotation.Pitch, IgnoreLag, IgnoreRestrictions);
SetDesiredRoll(NewRotation.Roll, IgnoreLag, IgnoreRestrictions);
}
void AUniversalCamera::SetDesiredRotationOffset(FRotator NewRotationOffset)
{
DesiredRotationOffset = NewRotationOffset;
SetDesiredRotation(DesiredRotation);
}
void AUniversalCamera::SetDesiredZoom_Internal(float NewZoom, bool IgnoreLag, bool IgnoreRestrictions)
{
if (!SpringArmComponent)
{
LOG_WARNING_MESSAGE("Couldn't find the SpringArmComponent.");
return;
}
if (!IgnoreRestrictions) NewZoom = GetClampedZoom(NewZoom);
DesiredZoom = NewZoom;
if (!(EnabledFeatures & EEnabledFeatures::LagZoom) || IgnoreLag)
{
SpringArmComponent->TargetArmLength = DesiredZoom;
}
OnDesiredZoomChanged.Broadcast(DesiredZoom);
}
void AUniversalCamera::SetDesiredZoom(float NewZoom, bool IgnoreLag, bool IgnoreRestrictions)
{
if (IsTraveling && IsValid(CurrentTravelTask) && (CurrentTravelTask->m_LockAllMovement || CurrentTravelTask->IsValidZoom)) return;
SetDesiredZoom_Internal(NewZoom, IgnoreLag, IgnoreRestrictions);
}
FVector AUniversalCamera::GetOriginLocationFromCameraLocation(FVector CameraLocation) const
{
return CameraLocation - DesiredTargetOffset - FRotationMatrix(DesiredRotation).TransformVector(DesiredSocketOffset) + DesiredRotation.Vector() * DesiredZoom;
}
float AUniversalCamera::GetClampedZoom(float Zoom)
{
ClampCustomValue(ConstrainZoomMinimum, ConstrainZoomMaximum, &Zoom, MinimumZoom, MaximumZoom);
return Zoom;
}
void AUniversalCamera::EdgeScrollingTick_Internal()
{
APlayerController* PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0);
if (!IsValid(PlayerController)) return;
// Get Mouse Position
float MouseX, MouseY;
if (!PlayerController->GetMousePosition(MouseX, MouseY)) return;
// Get Screen Size
int32 SizeX, SizeY;
PlayerController->GetViewportSize(SizeX, SizeY);
float RightMultiplier = 0.f;
float ForwardMultiplier = 0.f;
float XSensitivity = EdgeScrollingSensitivity * SizeX;
float YSensitivity = EdgeScrollingSensitivity * SizeY;
float RightAxis = 0.f;
float ForwardAxis = 0.f;
bool bTargetOffset = false;
bool bSocketOffset = false;
bool bShouldEdgeScroll = false;
switch (EdgeScrollMode)
{
case ScrollMovement:
bTargetOffset = false;
bSocketOffset = false;
break;
case ScrollSocketOffset:
bTargetOffset = true;
bSocketOffset = false;
break;
case ScrollTargetOffset:
bTargetOffset = false;
bSocketOffset = true;
break;
}
if (MouseX < XSensitivity)
{
RightAxis = -1.f;
RightMultiplier = UseEdgeScrollingStrength ? 1.0f - MouseX / XSensitivity : 1.0f;
bShouldEdgeScroll = true;
}
else if (MouseX > SizeX - XSensitivity)
{
RightAxis = 1.f;
RightMultiplier = UseEdgeScrollingStrength ? 1.0f - (SizeX - MouseX) / XSensitivity : 1.0f;
bShouldEdgeScroll = true;
}
if (MouseY < YSensitivity)
{
ForwardAxis = 1.f;
ForwardMultiplier = UseEdgeScrollingStrength ? 1.0f - MouseY / YSensitivity : 1.0f;
bShouldEdgeScroll = true;
}
else if (MouseY > SizeY - YSensitivity)
{
ForwardAxis = -1.f;
ForwardMultiplier = UseEdgeScrollingStrength ? 1.0f - (SizeY - MouseY) / YSensitivity : 1.0f;
bShouldEdgeScroll = true;
}
FVector2D DirectionalMultiplier(FMath::Abs(RightAxis), FMath::Abs(ForwardAxis));
if (NormalizeEdgeScrollingDirection) DirectionalMultiplier.Normalize();
DirectionalMultiplier.X *= RightMultiplier * EdgeScrollingSpeedMultiplier;
DirectionalMultiplier.Y *= ForwardMultiplier * EdgeScrollingSpeedMultiplier;
if (bShouldEdgeScroll)
{
EdgeScrollingTick(RightAxis, ForwardAxis, DirectionalMultiplier.X, DirectionalMultiplier.Y, bTargetOffset, bSocketOffset);
}
if (!IsEdgeScrolling && bShouldEdgeScroll)
{
IsEdgeScrolling = true;
OnBeginEdgeScrolling();
}
if (IsEdgeScrolling && !bShouldEdgeScroll)
{
IsEdgeScrolling = false;
OnEndEdgeScrolling();
}
ESimplifiedMovementDirection SimplifiedMovementDirection = MovementDirection_None;
if (IsEdgeScrolling)
{
if (RightAxis > 0.f)
{
if (ForwardAxis > 0.f)
{
SimplifiedMovementDirection = Direction_RightForward;
}
else if (ForwardAxis < 0.f)
{
SimplifiedMovementDirection = Direction_RightBackward;
}
else
{
SimplifiedMovementDirection = Direction_Right;
}
}
else if (RightAxis < 0.f)
{
if (ForwardAxis > 0.f)
{
SimplifiedMovementDirection = Direction_LeftForward;
}
else if (ForwardAxis < 0.f)
{
SimplifiedMovementDirection = Direction_LeftBackward;
}
else
{
SimplifiedMovementDirection = Direction_Left;
}
}
else
{
if (ForwardAxis > 0.f)
{
SimplifiedMovementDirection = Direction_Forward;
}
else if (ForwardAxis < 0.f)
{
SimplifiedMovementDirection = Direction_Backward;
}
}
FVector2D Direction = FVector2D(ForwardAxis * ForwardMultiplier, RightAxis * RightMultiplier);
OnUpdateEdgeScrolling(SimplifiedMovementDirection, Direction);
}
}
void AUniversalCamera::EdgeScrollingTick_Implementation(float RightAxis, float ForwardAxis, float RightMultiplier, float ForwardMultiplier, bool bTargetOffset, bool bSocketOffset)
{
MoveRight(RightAxis, RightMultiplier, bTargetOffset, bSocketOffset);
MoveForward(ForwardAxis, ForwardMultiplier, bTargetOffset, bSocketOffset);
}
FVector AUniversalCamera::Lag_GetTickDesiredLocation_Implementation(float DeltaTime)
{
FVector CurrentLocation = GetActorLocation();
FVector2D TickDesiredLocation = FMath::Vector2DInterpTo(FVector2D(CurrentLocation.X, CurrentLocation.Y), FVector2D(DesiredLocation.X, DesiredLocation.Y), DeltaTime, MovementLagSpeed);
return FVector(TickDesiredLocation.X, TickDesiredLocation.Y, CurrentLocation.Z);
}
FVector AUniversalCamera::Lag_GetTickDesiredSocketOffset_Implementation(float DeltaTime)
{
if (!SpringArmComponent)
{
LOG_WARNING_MESSAGE("Couldn't find the SpringArmComponent.");
return DesiredSocketOffset;
}
FVector CurrentOffset = SpringArmComponent->SocketOffset;
FVector TickDesiredOffset = FMath::VInterpTo(CurrentOffset, DesiredSocketOffset, DeltaTime, OffsetLagSpeed);
return TickDesiredOffset;
}
FVector AUniversalCamera::Lag_GetTickDesiredTargetOffset_Implementation(float DeltaTime)
{
if (!SpringArmComponent)
{
LOG_WARNING_MESSAGE("Couldn't find the SpringArmComponent.");
return DesiredTargetOffset;
}
FVector CurrentOffset = SpringArmComponent->TargetOffset;
FVector TickDesiredOffset = FMath::VInterpTo(CurrentOffset, DesiredTargetOffset, DeltaTime, OffsetLagSpeed);
return TickDesiredOffset;
}
float AUniversalCamera::Lag_GetTickDesiredHeight_Implementation(float DeltaTime)
{
float TickDesiredHeight = FMath::FInterpTo(GetActorLocation().Z, DesiredLocation.Z, DeltaTime, HeightLagSpeed);
return TickDesiredHeight;
}
FRotator AUniversalCamera::Lag_GetTickDesiredRotation_Implementation(float DeltaTime)
{
FRotator TickDesiredRotation = FRotator(FMath::QInterpTo(FQuat(GetActorRotation()), FQuat(GetDesiredRotationWithOffset()), DeltaTime, RotationLagSpeed));
return TickDesiredRotation;
}
float AUniversalCamera::Lag_GetTickDesiredZoom_Implementation(float DeltaTime)
{
if (!SpringArmComponent)
{
LOG_WARNING_MESSAGE("Couldn't find the SpringArmComponent.");
return DesiredZoom;
}
float TickDesiredZoom = FMath::FInterpTo(SpringArmComponent->TargetArmLength, DesiredZoom, DeltaTime, ZoomLagSpeed);
return TickDesiredZoom;
}
float AUniversalCamera::GetScaledValue(float AxisValue, float Multiplier, float Speed, float ScalingValue, float Min, float Max) const
{
return AxisValue * Multiplier * (Max > 0 ? FMath::Clamp(Speed * ScalingValue, Min, Max) : FMath::Max(Speed * ScalingValue, Min));
}
void AUniversalCamera::MoveForward(float AxisValue, float Multiplier, bool MoveTargetOffset, bool MoveSocketOffset)
{
if (IsFollowingAnyActor() && !(MoveSocketOffset || MoveTargetOffset)) return;
if (!(EnabledFeatures & EEnabledFeatures::Movement)) return;
AxisValue = FMath::Clamp(AxisValue, -1.f, 1.f);
FVector ForwardVector = UseAlternativeForwardVector ? GetAlternativeForwardVector(DesiredRotation) : UKismetMathLibrary::GetForwardVector(DesiredRotation);
if (MoveSocketOffset)
{
ForwardVector = DesiredRotation.UnrotateVector(ForwardVector);
if (ApplyOffsetScaling) SetDesiredSocketOffset(DesiredSocketOffset + ForwardVector * GetTickMultiplier() * GetScaledValue(AxisValue, Multiplier, OffsetSpeed, GetMovementScaling(), MinimumMovementSpeed, MaximumMovementSpeed));
else SetDesiredSocketOffset(DesiredSocketOffset + ForwardVector * GetTickMultiplier() * AxisValue * Multiplier * OffsetSpeed);
}
else if (MoveTargetOffset)
{
if (ApplyOffsetScaling) SetDesiredTargetOffset(DesiredTargetOffset + ForwardVector * GetTickMultiplier() * GetScaledValue(AxisValue, Multiplier, OffsetSpeed, GetMovementScaling(), MinimumMovementSpeed, MaximumMovementSpeed));
else SetDesiredTargetOffset(DesiredTargetOffset + ForwardVector * GetTickMultiplier() * AxisValue * Multiplier * OffsetSpeed);
}
else
{
if (ApplyMovementScaling) SetDesiredLocation(DesiredLocation + ForwardVector * GetTickMultiplier() * GetScaledValue(AxisValue, Multiplier, MovementSpeed, GetMovementScaling(), MinimumMovementSpeed, MaximumMovementSpeed));
else SetDesiredLocation(DesiredLocation + ForwardVector * GetTickMultiplier() * AxisValue * Multiplier * MovementSpeed);
}
}
void AUniversalCamera::MoveRight(float AxisValue, float Multiplier, bool MoveTargetOffset, bool MoveSocketOffset)
{
if (IsFollowingAnyActor() && !(MoveSocketOffset || MoveTargetOffset)) return;
if (!(EnabledFeatures & EEnabledFeatures::Movement)) return;
AxisValue = FMath::Clamp(AxisValue, -1.f, 1.f);
FVector RightVector = UseAlternativeRightVector ? GetAlternativeRightVector(DesiredRotation) : UKismetMathLibrary::GetRightVector(DesiredRotation);
if (MoveSocketOffset)
{
RightVector = FVector(0.f, 1.f, 0.f);
if (ApplyOffsetScaling) SetDesiredSocketOffset(DesiredSocketOffset + RightVector * GetTickMultiplier() * GetScaledValue(AxisValue, Multiplier, OffsetSpeed, GetMovementScaling(), MinimumMovementSpeed, MaximumMovementSpeed));
else SetDesiredSocketOffset(DesiredSocketOffset + RightVector * GetTickMultiplier() * AxisValue * Multiplier * OffsetSpeed);
}
else if (MoveTargetOffset)
{
if (ApplyOffsetScaling) SetDesiredTargetOffset(DesiredTargetOffset + RightVector * GetTickMultiplier() * GetScaledValue(AxisValue, Multiplier, OffsetSpeed, GetMovementScaling(), MinimumMovementSpeed, MaximumMovementSpeed));
else SetDesiredTargetOffset(DesiredTargetOffset + RightVector * GetTickMultiplier() * AxisValue * Multiplier * OffsetSpeed);
}
else
{
if (ApplyMovementScaling) SetDesiredLocation(DesiredLocation + RightVector * GetTickMultiplier() * GetScaledValue(AxisValue, Multiplier, MovementSpeed, GetMovementScaling(), MinimumMovementSpeed, MaximumMovementSpeed));
else SetDesiredLocation(DesiredLocation + RightVector * GetTickMultiplier() * AxisValue * Multiplier * MovementSpeed);
}
}
void AUniversalCamera::MoveUp(float AxisValue, float Multiplier, bool MoveTargetOffset, bool MoveSocketOffset)
{
if (IsFollowingAnyActor() && !(MoveSocketOffset || MoveTargetOffset)) return;
if (!(EnabledFeatures & EEnabledFeatures::Movement)) return;
AxisValue = FMath::Clamp(AxisValue, -1.f, 1.f);
FVector UpVector = UseAlternativeUpVector ? GetAlternativeUpVector(DesiredRotation) : UKismetMathLibrary::GetUpVector(DesiredRotation);
if (MoveSocketOffset)
{
UpVector = DesiredRotation.UnrotateVector(UpVector);
if (ApplyOffsetScaling) SetDesiredSocketOffset(DesiredSocketOffset + UpVector * GetTickMultiplier() * GetScaledValue(AxisValue, Multiplier, OffsetSpeed, GetMovementScaling(), MinimumMovementSpeed, MaximumMovementSpeed));
else SetDesiredSocketOffset(DesiredSocketOffset + UpVector * GetTickMultiplier() * AxisValue * Multiplier * OffsetSpeed);
}
else if (MoveTargetOffset)
{
if (ApplyOffsetScaling) SetDesiredTargetOffset(DesiredTargetOffset + UpVector * GetTickMultiplier() * GetScaledValue(AxisValue, Multiplier, OffsetSpeed, GetMovementScaling(), MinimumMovementSpeed, MaximumMovementSpeed));
else SetDesiredTargetOffset(DesiredTargetOffset + UpVector * GetTickMultiplier() * AxisValue * Multiplier * OffsetSpeed);
}
else
{
if (ApplyMoveUpScaling) SetDesiredLocation(DesiredLocation + UpVector * GetTickMultiplier() * GetScaledValue(AxisValue, Multiplier, MovementSpeed, GetMovementScaling(), MinimumMovementSpeed, MaximumMovementSpeed));
else SetDesiredLocation(DesiredLocation + UpVector * GetTickMultiplier() * AxisValue * Multiplier * MovementSpeed);
}
}
void AUniversalCamera::Move(float ForwardAxis, float RightAxis, float UpAxis, float Multiplier, bool NormalizeDirection, bool MoveTargetOffset, bool MoveSocketOffset)
{
FVector AxisDirection = FVector(ForwardAxis, RightAxis, UpAxis);
if (NormalizeDirection) AxisDirection.Normalize();
MoveForward(AxisDirection.X, Multiplier, MoveTargetOffset, MoveSocketOffset);
MoveRight(AxisDirection.Y, Multiplier, MoveTargetOffset, MoveSocketOffset);
MoveUp(AxisDirection.Z, Multiplier, MoveTargetOffset, MoveSocketOffset);
}
void AUniversalCamera::RotateYaw(float AxisValue, float Multiplier, bool RotateOffset)
{
if (!(EnabledFeatures & EEnabledFeatures::Rotation)) return;
AxisValue = FMath::Clamp(AxisValue, -1.f, 1.f);
float NewYawOffset = DesiredRotationOffset.Yaw - GetTickMultiplier() * AxisValue * RotateSpeed * Multiplier;
float NewYaw = DesiredRotation.Yaw - GetTickMultiplier() * AxisValue * RotateSpeed * Multiplier;
if (RotateOffset)
{
SetDesiredYawOffset(NewYawOffset);
}
else
{
SetDesiredYaw(NewYaw);
}
}
void AUniversalCamera::RotatePitch(float AxisValue, float Multiplier, bool RotateOffset)
{
if (!(EnabledFeatures & EEnabledFeatures::Rotation)) return;
AxisValue = FMath::Clamp(AxisValue, -1.f, 1.f);
float NewPitchOffset = DesiredRotationOffset.Pitch - GetTickMultiplier() * AxisValue * RotateSpeed * Multiplier;
float NewPitch = DesiredRotation.Pitch - GetTickMultiplier() * AxisValue * RotateSpeed * Multiplier;
if (RotateOffset)
{
SetDesiredPitchOffset(NewPitchOffset);
}
else
{
SetDesiredPitch(NewPitch);
}
}
void AUniversalCamera::RotateRoll(float AxisValue, float Multiplier, bool RotateOffset)
{
if (!(EnabledFeatures & EEnabledFeatures::Rotation)) return;
AxisValue = FMath::Clamp(AxisValue, -1.f, 1.f);
float NewRollOffset = DesiredRotationOffset.Roll - GetTickMultiplier() * AxisValue * RotateSpeed * Multiplier;
float NewRoll = DesiredRotation.Roll - GetTickMultiplier() * AxisValue * RotateSpeed * Multiplier;
if (RotateOffset)
{
SetDesiredRollOffset(NewRollOffset);
}
else
{
SetDesiredRoll(NewRoll);
}
}
void AUniversalCamera::Rotate(float YawAxis, float PitchAxis, float RollAxis, float Multiplier, bool RotateOffset)
{
RotateYaw(YawAxis, Multiplier, RotateOffset);
RotatePitch(PitchAxis, Multiplier, RotateOffset);
RotateRoll(RollAxis, Multiplier, RotateOffset);
}
void AUniversalCamera::ZoomIn(float AxisValue, float Multiplier)
{
if (!(EnabledFeatures & EEnabledFeatures::Zoom)) return;
SetDesiredZoom(DesiredZoom - GetZoomInValue(AxisValue, Multiplier));
}
float AUniversalCamera::GetZoomInValue(float AxisValue, float Multiplier)
{
return UseZoomScaling ? GetScaledValue(AxisValue, Multiplier, ZoomSpeed, GetZoomInScaling(), MinimumZoomSpeed, MaximumZoomSpeed) :
AxisValue * Multiplier * ZoomSpeed;
}
float AUniversalCamera::GetZoomInValueFromDistance(float AxisValue, float Multiplier, float Distance)
{
return UseZoomScaling ? GetScaledValue(AxisValue, Multiplier, ZoomSpeed, Distance / ZoomReference, MinimumZoomSpeed, MaximumZoomSpeed) * GetTickMultiplier() :
AxisValue * Multiplier * ZoomSpeed * GetTickMultiplier();
}
float AUniversalCamera::GetMovementScaling_Implementation()
{
if (ScalingTraceToGround)
{
APlayerController* PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0);
if (!IsValid(PlayerController)) return 0.f;
FCollisionObjectQueryParams Params;
for (ECollisionChannel Channel : TerrainHeightAdaptationChannels) { Params.AddObjectTypesToQuery(Channel); }
FHitResult HitResult;
FVector Start = DesiredLocation;
FVector End = DesiredLocation - FVector(0.0f, 0.0f, PlayerController->HitResultTraceDistance);
bool DidHit = GetWorld()->LineTraceSingleByObjectType(HitResult, Start, End, Params);
if (DidHit)
{
GroundLevel = HitResult.ImpactPoint.Z;
}
}
return UseZoomLevel ? DesiredZoom / ScalingDistanceReference : (DesiredLocation.Z - GroundLevel) / ScalingDistanceReference;
}
float AUniversalCamera::GetZoomInScaling_Implementation()
{
return DesiredZoom / ZoomReference;
}
FVector AUniversalCamera::GetCorrectedDestinationFromOriginCollisions(FVector Location, FVector Destination, bool& DidHit)
{
FHitResult HitResult;
FCollisionQueryParams QueryParams(SCENE_QUERY_STAT(Origin), false, GetOwner());
GetWorld()->SweepSingleByChannel(HitResult, Location, Destination, FQuat::Identity, OriginCollisionsChannel, FCollisionShape::MakeSphere(OriginCollisionsProbeSize), QueryParams);
if (HitResult.bBlockingHit)
{
FVector BA = Destination - HitResult.Location;
FVector AH = HitResult.ImpactNormal;
float BALength = BA.Length();
BA.Normalize();
Destination += HitResult.ImpactNormal * (FMath::Abs(FVector::DotProduct(BA, AH) * BALength) + 1.f);
}
else return Destination;
GetWorld()->SweepSingleByChannel(HitResult, Location, Destination, FQuat::Identity, OriginCollisionsChannel, FCollisionShape::MakeSphere(OriginCollisionsProbeSize), QueryParams);
if (HitResult.bBlockingHit)
{
return HitResult.Location + HitResult.ImpactNormal;
}
else return Destination;
}
float AUniversalCamera::GetTerrainHeightAdaptationValue_Implementation(bool& IsValidLoc)
{
IsValidLoc = false;
float HeightValue = DesiredLocation.Z;
APlayerController* PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0);
if (!IsValid(PlayerController)) return HeightValue;
if (TerrainHeightAdaptationChannels.Num() == 0) return HeightValue;
FHitResult HitResult;
float ControllerTraceDistance = PlayerController->HitResultTraceDistance;
FVector Start = CanGoBelowSurfaces ? DesiredLocation + FVector(0.0f, 0.0f, GetDesiredCameraWorldLocation().Z) : DesiredLocation + FVector(0.0f, 0.0f, ControllerTraceDistance);
FVector End = DesiredLocation - FVector(0, 0, ControllerTraceDistance);
FCollisionObjectQueryParams Params;
for (ECollisionChannel Channel : TerrainHeightAdaptationChannels) { Params.AddObjectTypesToQuery(Channel); }
// Check under and above Origin
bool DidHit = GetWorld()->SweepSingleByObjectType(HitResult, Start, End, FQuat::Identity, Params, FCollisionShape::MakeSphere(ZProbeRadius));
if (DidHit)
{
if (DistanceAdaptation && HitResult.ImpactPoint.Z <= GetDesiredCameraWorldLocation().Z)
{
if (DistanceBypassValue < DesiredZoom) return HeightValue;
}
if (DesiredLocation.Z - HitResult.ImpactPoint.Z != KINDA_SMALL_NUMBER)
{
IsValidLoc = true;
HeightValue = HitResult.ImpactPoint.Z + OriginDistanceFromGround;
}
// Check if camera has vision on origin if asked
if (AlwaysSeeOrigin)
{
DidHit = GetWorld()->SweepSingleByObjectType(HitResult, GetDesiredCameraWorldLocation(), DesiredLocation, FQuat::Identity, Params, FCollisionShape::MakeSphere(ZProbeRadius));
if (DidHit)
{
IsValidLoc = true;
HeightValue = HitResult.ImpactPoint.Z + OriginDistanceFromGround;
}
}
}
return HeightValue;
}
void AUniversalCamera::ApplyTerrainHeightAdaptationTick()
{
bool IsValidLoc = false;
float NewZLoc = GetTerrainHeightAdaptationValue(IsValidLoc);
if (IsValidLoc)
{
FVector NewLocation = DesiredLocation;
NewLocation.Z = NewZLoc;
SetDesiredLocation(NewLocation);
// Update the MouseDragOrigin
PreciseMouseDragOrigin.Z = DesiredLocation.Z;
}
}
FVector2D AUniversalCamera::GetMousePositionRatio(APlayerController* PlayerController, FVector2D& RawPosition)
{
if (!IsValid(PlayerController)) return FVector2D(0.f, 0.f);
float OriginX, OriginY;
PlayerController->GetMousePosition(OriginX, OriginY);
FVector2D Origin(OriginX, OriginY);
RawPosition = Origin;
int32 SizeX, SizeY;
PlayerController->GetViewportSize(SizeX, SizeY);
FVector2D Size(SizeX, SizeY);
return Origin / Size;
}
FVector AUniversalCamera::GetImpactPointUnderCursor(APlayerController* PlayerController, ECollisionChannel TraceChannel, bool TraceComplex, bool& DidHit)
{
DidHit = false;
if (!IsValid(PlayerController)) return FVector(0.f, 0.f, 0.f);
FHitResult HitResult;
PlayerController->GetHitResultUnderCursor(TraceChannel, TraceComplex, HitResult);
DidHit = HitResult.IsValidBlockingHit();
if (DidHit) return HitResult.ImpactPoint;
else return FVector(0.f, 0.f, 0.f);
}
void AUniversalCamera::ToggleScreenSliding(bool Activate)
{
if (Activate && EnabledFeatures & ScreenSliding)
{
APlayerController* PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0);
if (!IsValid(PlayerController)) return;
FVector2D RawPosition(0.f, 0.f);
ScreenSlidingOriginRatio = GetMousePositionRatio(PlayerController, RawPosition);
IsScreenSliding = true;
OnBeginScreenSliding(RawPosition);
}
else
{
IsScreenSliding = false;
OnEndScreenSliding();
}
}
void AUniversalCamera::ScreenSlidingTick()
{
if (ScreenSlidingType == ScreenSliding_None) return;
APlayerController* PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0);
if (!IsValid(PlayerController)) return;
FVector2D RawPosition;
FVector2D CurrentMousePositionRatio = GetMousePositionRatio(PlayerController, RawPosition);
FVector2D Delta = CurrentMousePositionRatio - ScreenSlidingOriginRatio;
float StrengthX = FMath::Min(FMath::Abs(Delta.X), ScreenSlidingMaxRatio) * ScreenSlidingMaxSpeed / ScreenSlidingMaxRatio;
float StrengthY = FMath::Min(FMath::Abs(Delta.Y), ScreenSlidingMaxRatio) * ScreenSlidingMaxSpeed / ScreenSlidingMaxRatio;
FVector2D Direction = CurrentMousePositionRatio - ScreenSlidingOriginRatio;
Direction.Normalize();
OnUpdateScreenSliding(Direction);
float AxisX = Delta.X > 0 ? 1.0f : -1.0f;
if (ScreenSlidingInvertXAxis) AxisX *= -1.0f;
float AxisY = Delta.Y > 0 ? -1.0f : 1.0f;
if (ScreenSlidingInvertYAxis) AxisY *= -1.0f;
switch (ScreenSlidingType)
{
case SlideCamera:
MoveRight(AxisX, StrengthX);
MoveForward(AxisY, StrengthY);
return;
case SlideSocketOffset:
MoveRight(AxisX, StrengthX, true, false);
MoveForward(AxisY, StrengthY, true, false);
return;
case SlideTargetOffset:
MoveRight(AxisX, StrengthX, false, true);
MoveForward(AxisY, StrengthY, false, true);
return;
case SlideYaw:
RotateYaw(AxisX, StrengthX);
return;
case SlidePitch:
RotatePitch(AxisY, StrengthY);
return;
case SlideYawAndPitch:
RotateYaw(AxisX, StrengthX);
RotatePitch(AxisY, StrengthY);
return;
};
}
void AUniversalCamera::FollowTarget(FTargetSettings_WithTemplates TargetSettings, FConstrainVector2 FollowAxis, FBoolRotation FollowRotations, bool IgnoreRestrictions, bool ResetOffsetIfPrematurelyEnded)
{
if (!(EnabledFeatures & EEnabledFeatures::FollowTarget)) return;
if (!FollowAxis.IsValid()) return;
switch (TargetSettings.Type)
{
case 0:
if (!TargetSettings.TargetSettings.IsValidActor()) return;
break;
case 1:
if (!TargetSettings.TargetSettings.IsValidSceneComponent()) return;
break;
case 2:
if (!TargetSettings.TargetSettings.IsValidSocket()) return;
break;
}
if (IsTraveling && IsValid(CurrentTravelTask)) EndTravelTask(false);
FollowTargetSettings = TargetSettings;
FollowedAxis = FollowAxis;
FollowedRotations = FollowRotations;
bShouldResetOffsetIfFailed = ResetOffsetIfPrematurelyEnded;
bIsFollowingAnyActor = true;
bFollowTargetIgnoreRestrictions = IgnoreRestrictions;
}
void AUniversalCamera::FollowTargetTick()
{
bool IsValidTarget;
FRotator TargetRotation;
FVector TargetLocation = FollowTargetSettings.GetTargetLocation(IsValidTarget, TargetRotation);
if (!IsValidTarget)
{
StopFollowing_Internal(bShouldResetOffsetIfFailed, StopFollowingReason_InvalidTarget);
return;
}
FVector NewDesiredLocation = DesiredLocation;
if (FollowedAxis.X) NewDesiredLocation.X = TargetLocation.X;
if (FollowedAxis.Y) NewDesiredLocation.Y = TargetLocation.Y;
if (FollowedAxis.Z) NewDesiredLocation.Z = TargetLocation.Z;
SetDesiredLocation(NewDesiredLocation, false, bFollowTargetIgnoreRestrictions);
FRotator NewDesiredRotation = DesiredRotation;
if (FollowedRotations.Yaw) NewDesiredRotation.Yaw = TargetRotation.Yaw;
if (FollowedRotations.Pitch) NewDesiredRotation.Pitch = TargetRotation.Pitch;
if (FollowedRotations.Roll) NewDesiredRotation.Roll = TargetRotation.Roll;
SetDesiredRotation(NewDesiredRotation, false, bFollowTargetIgnoreRestrictions);
}
void AUniversalCamera::StopFollowing_Internal(bool bResetOffset, EStopFollowingReason Reason)
{
bIsFollowingAnyActor = false;
FollowTargetSettings.Empty();
OnStoppedFollowing(Reason);
if (bResetOffset)
{
ResetSocketOffset();
ResetTargetOffset();
ResetRotationOffset();
}
}
FVector AUniversalCamera::GetCorrectedDestinationFromDotsShape(const FVector& Destination, const TArray<FVector2D>& PolygonPoints)
{
// Convert the 3D destination point to a 2D point
FVector2D Destination2D(Destination.X, Destination.Y);
// Check if the destination point is inside the polygon
if (IsPointInPolygon(Destination2D, PolygonPoints))
{
// If the destination point is inside the polygon, return it as is
return Destination;
}
else
{
// If the destination point is outside the polygon, find the closest point on the polygon
FVector2D ClosestPoint = GetClosestPointOnPolygon(Destination2D, PolygonPoints);
// Set the Z coordinate of the corrected destination to the original Z coordinate of the destination
FVector CorrectedDestination(ClosestPoint.X, ClosestPoint.Y, Destination.Z);
return CorrectedDestination;
}
}
bool AUniversalCamera::IsPointInPolygon(const FVector2D& Point, const TArray<FVector2D>& PolygonPoints)
{
int32 NumVertices = PolygonPoints.Num();
bool Inside = false;
for (int32 i = 0, j = NumVertices - 1; i < NumVertices; j = i++)
{
if (((PolygonPoints[i].Y > Point.Y) != (PolygonPoints[j].Y > Point.Y)) &&
(Point.X < (PolygonPoints[j].X - PolygonPoints[i].X) * (Point.Y - PolygonPoints[i].Y) / (PolygonPoints[j].Y - PolygonPoints[i].Y) + PolygonPoints[i].X))
{
Inside = !Inside;
}
}
return Inside;
}
FVector2D AUniversalCamera::GetClosestPointOnPolygon(const FVector2D& Point, const TArray<FVector2D>& PolygonPoints)
{
FVector2D ClosestPoint;
float ClosestDistanceSq = TNumericLimits<float>::Max();
for (int32 i = 0; i < PolygonPoints.Num(); i++)
{
FVector2D EdgeStart = PolygonPoints[i];
FVector2D EdgeEnd = PolygonPoints[(i + 1) % PolygonPoints.Num()];
FVector2D EdgeDirection = (EdgeEnd - EdgeStart).GetSafeNormal();
float DistanceAlongEdge = FVector2D::DotProduct(Point - EdgeStart, EdgeDirection);
FVector2D ClosestPointOnEdge = EdgeStart + EdgeDirection * FMath::Clamp(DistanceAlongEdge, 0.f, (EdgeEnd - EdgeStart).Size());
float DistanceSq = (Point - ClosestPointOnEdge).SizeSquared();
if (DistanceSq < ClosestDistanceSq)
{
ClosestPoint = ClosestPointOnEdge;
ClosestDistanceSq = DistanceSq;
}
}
return ClosestPoint;
}
FVector AUniversalCamera::GetCorrectedDestinationFromRestrictions(FVector Destination)
{
Destination = GetClampedLocation(Destination);
if (ConstrainLocationToShape)
{
Destination = GetCorrectedDestinationFromDotsShape(Destination, ConstrainDots);
}
return Destination;
}
TArray<FVector2D> AUniversalCamera::GetActorsLocation2D(TArray<AActor*> Actors)
{
TArray<FVector2D> ReturnActors;
for (AActor*& Actor : Actors)
{
FVector Location = Actor->GetActorLocation();
ReturnActors.Add(FVector2D(Location.X, Location.Y));
}
return ReturnActors;
}
TArray<uint8> AUniversalCamera::GetSettingsSaveFormat()
{
TArray<uint8> ObjectData;
FMemoryWriter MemoryWriter(ObjectData, true);
FObjectAndNameAsStringProxyArchive Ar(MemoryWriter, false);
Ar.ArNoDelta = true;
this->Serialize(Ar);
return ObjectData;
}
void AUniversalCamera::LoadSettingsSaveFormat(TArray<uint8> SettingsSaveFormat)
{
FMemoryReader MemoryReader(SettingsSaveFormat, true);
FObjectAndNameAsStringProxyArchive Ar(MemoryReader, false);
Ar.ArIsSaveGame = true;
Ar.ArNoDelta = true;
this->Serialize(Ar);
}
FUniversalCameraPositionSaveFormat AUniversalCamera::GetPositionSaveFormat()
{
FUniversalCameraPositionSaveFormat SaveFormat;
SaveFormat.DesiredLocation = DesiredLocation;
SaveFormat.DesiredSocketOffset = DesiredSocketOffset;
SaveFormat.DesiredTargetOffset = DesiredTargetOffset;
SaveFormat.DesiredRotation = DesiredRotation;
SaveFormat.DesiredRotationOffset = DesiredRotationOffset;
SaveFormat.DesiredZoom = DesiredZoom;
return SaveFormat;
}
void AUniversalCamera::LoadPositionSaveFormat(FUniversalCameraPositionSaveFormat PositionSaveFormat)
{
SetDesiredPosition(PositionSaveFormat.DesiredLocation, PositionSaveFormat.DesiredSocketOffset, PositionSaveFormat.DesiredTargetOffset, PositionSaveFormat.DesiredRotation.Yaw, PositionSaveFormat.DesiredRotation.Pitch, PositionSaveFormat.DesiredRotation.Roll, PositionSaveFormat.DesiredZoom, true, true);
SetDesiredRotationOffset(PositionSaveFormat.DesiredRotationOffset);
}
void AUniversalCamera::InitUCSaveGameInstance()
{
if (!IsValid(UCSaveGameInstance))
{
UCSaveGameInstance = Cast<UUniversalCameraSaveGame>(UGameplayStatics::CreateSaveGameObject(UUniversalCameraSaveGame::StaticClass()));
}
}
void AUniversalCamera::QuickSavePosition(FUniversalCameraPositionSaveFormat PositionSaveFormat, const FString SlotName, const int32 UserIndex)
{
InitUCSaveGameInstance();
UCSaveGameInstance->SavedPosition = PositionSaveFormat;
UCSaveGameInstance->bIsValidSavePosition = true;
UGameplayStatics::SaveGameToSlot(UCSaveGameInstance, SlotName, UserIndex);
}
void AUniversalCamera::QuickLoadPosition(const FString SlotName, const int32 UserIndex)
{
UUniversalCameraSaveGame* UCSave = Cast<UUniversalCameraSaveGame>(UGameplayStatics::LoadGameFromSlot(SlotName, UserIndex));
if (IsValid(UCSave) && UCSave->bIsValidSavePosition)
{
LoadPositionSaveFormat(UCSave->SavedPosition);
}
}
void AUniversalCamera::QuickSaveSettings(TArray<uint8> SettingsSaveFormat, const FString SlotName, const int32 UserIndex)
{
InitUCSaveGameInstance();
UCSaveGameInstance->SavedSettings = SettingsSaveFormat;
UCSaveGameInstance->bIsValidSaveSettings = true;
UGameplayStatics::SaveGameToSlot(UCSaveGameInstance, SlotName, UserIndex);
}
void AUniversalCamera::QuickLoadSettings(const FString SlotName, const int32 UserIndex)
{
UUniversalCameraSaveGame* UCSave = Cast<UUniversalCameraSaveGame>(UGameplayStatics::LoadGameFromSlot(SlotName, UserIndex));
if (IsValid(UCSave) && UCSave->bIsValidSaveSettings)
{
LoadSettingsSaveFormat(UCSave->SavedSettings);
}
}
bool AUniversalCamera::GetActorFocusLocation_Implementation(AActor* Actor, FVector& Location, float& IdealZoom)
{
if (!Actor) return false;
FVector Origin, BoxExtent;
Actor->GetActorBounds(false, Origin, BoxExtent);
Location = Origin;
float Radius = BoxExtent.Size();
IdealZoom = Radius * 2.0f;
return true;
}
FVector AUniversalCamera::FindVectorAtZ(bool& Intersect, FVector Direction, float Z)
{
Intersect = false;
if (Direction.Z == 0.f) return FVector(0.f, 0.f, 0.f);
Intersect = true;
Direction.Normalize();
float Multiplier = Z / Direction.Z;
return Direction * Multiplier;
}
void AUniversalCamera::ZoomAtLocation(float AxisValue, FVector Location, float Multiplier, bool LockZoomLevel)
{
if (AxisValue == 0.f) return;
AxisValue = FMath::Clamp(AxisValue, -1.f, 1.f);
FVector Direction = Location - GetDesiredCameraWorldLocation();
Direction.Normalize();
float ScalingDistance = (Location - DesiredLocation).Length();
if (LockZoomLevel)
{
SetDesiredLocation(DesiredLocation + GetZoomInValueFromDistance(AxisValue, Multiplier, ScalingDistance) * Direction);
return;
}
else
{
ZoomIn(AxisValue, Multiplier);
}
if (Direction.Z > -0.1f) return;
float ZDirection = GetDesiredCameraWorldLocation().Z - Location.Z;
bool Intersect;
FVector NewCameraLoc = Location + FindVectorAtZ(Intersect, Direction, ZDirection);
if (Intersect)
{
FVector NewOriginDesiredLoc = GetOriginLocationFromCameraLocation(NewCameraLoc);
SetDesiredLocation(NewOriginDesiredLoc);
}
}
void AUniversalCamera::ZoomAtCursor(float AxisValue, ECollisionChannel CollisionChannel, bool& DidHit, float Multiplier, bool LockZoomLevel)
{
APlayerController* PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0);
if (!IsValid(PlayerController)) return;
FVector ImpactPoint = GetImpactPointUnderCursor(PlayerController, CollisionChannel, false, DidHit);
if (DidHit)
{
ZoomAtLocation(AxisValue, ImpactPoint, Multiplier, LockZoomLevel);
}
else
{
ZoomIn(AxisValue, Multiplier);
}
}
void AUniversalCamera::TogglePreciseMouseDrag(bool Activate)
{
if (Activate && (EnabledFeatures & EEnabledFeatures::PreciseMouseDrag))
{
APlayerController* PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0);
if (!IsValid(PlayerController)) return;
bool DidHit = false;
if (PreciseMouseDragMode == EPreciseMouseDragMode::TraceToGround)
{
PreciseMouseDragOrigin = GetImpactPointUnderCursor(PlayerController, PreciseMouseDragCollisionChannel, PreciseMouseDragTraceComplex, DidHit);
}
else if (PreciseMouseDragMode == EPreciseMouseDragMode::Plane)
{
FVector WorldPosition, Direction;
FVector2D MousePosition;
PlayerController->GetMousePosition(MousePosition.X, MousePosition.Y);
UGameplayStatics::DeprojectScreenToWorld(PlayerController, MousePosition, WorldPosition, Direction);
// Create the plane from the point and normal
FPlane DragPlane = FPlane(PreciseMouseDragPoint, PreciseMouseDragNormal);
// Calculate the intersection of the ray with the plane
PreciseMouseDragOrigin = FMath::LinePlaneIntersection(WorldPosition, WorldPosition + Direction * 10000, DragPlane);
DidHit = true; // Assuming the plane is always intersected
}
IsPreciseMouseDragging = DidHit;
if (IsPreciseMouseDragging)
{
OnBeginMouseDrag();
}
}
else
{
IsPreciseMouseDragging = false;
OnEndMouseDrag();
}
}
void AUniversalCamera::PreciseMouseDragTick()
{
APlayerController* PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0);
if (!IsValid(PlayerController)) return;
FVector2D MousePosition;
PlayerController->GetMousePosition(MousePosition.X, MousePosition.Y);
FVector WorldPosition, Direction;
UGameplayStatics::DeprojectScreenToWorld(PlayerController, MousePosition, WorldPosition, Direction);
FVector P = WorldPosition + (DesiredLocation - GetActorLocation());
FVector M = FMath::LinePlaneIntersection(P, P + Direction * 10000, FPlane(PreciseMouseDragOrigin, FVector::UpVector));
if (!M.ContainsNaN()) // Check for valid intersection point
{
FVector Delta = M - PreciseMouseDragOrigin;
FVector NewLocation = DesiredLocation - Delta;
SetDesiredLocation(NewLocation, PreciseMouseDragIgnoreMovementLag);
OnUpdateMouseDrag(Delta);
}
}
/*
void AUniversalCamera::PreciseMouseDragTick()
{
APlayerController* PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0);
if (!IsValid(PlayerController)) return;
FVector2D MousePosition;
PlayerController->GetMousePosition(MousePosition.X, MousePosition.Y);
FVector WorldPosition;
FVector Direction;
if (PreciseMouseDragMode == EPreciseMouseDragMode::Plane)
{
// ???
}
else if (PreciseMouseDragMode == EPreciseMouseDragMode::TraceToGround)
{
UGameplayStatics::DeprojectScreenToWorld(PlayerController, MousePosition, WorldPosition, Direction);
}
FVector P = WorldPosition + (DesiredLocation - GetActorLocation());
FVector D = Direction;
FVector M;
M.Z = PreciseMouseDragOrigin.Z;
M.X = P.X + (PreciseMouseDragOrigin.Z - P.Z) * D.X / D.Z;
M.Y = P.Y + (PreciseMouseDragOrigin.Z - P.Z) * D.Y / D.Z;
FVector Delta = M - PreciseMouseDragOrigin;
FVector NewLocation = DesiredLocation - Delta;
SetDesiredLocation(NewLocation, PreciseMouseDragIgnoreMovementLag);
OnUpdateMouseDrag(Delta);
}
*/
void AUniversalCamera::RotateYawAroundPivot(FVector Pivot, float AxisValue, float Multiplier)
{
AxisValue = FMath::Clamp(AxisValue, -1.f, 1.f);
FVector Direction = DesiredLocation - Pivot;
float OldYaw = DesiredRotation.Yaw;
SetDesiredYaw(DesiredRotation.Yaw + GetTickMultiplier() * AxisValue * RotateSpeed * Multiplier);
float Delta = DesiredRotation.Yaw - OldYaw;
Direction = Direction.RotateAngleAxis(Delta, FVector(0.0f, 0.0f, 1.0f));
SetDesiredLocation(Pivot + Direction);
}
void AUniversalCamera::RotatePitchAroundPivot(FVector Pivot, float AxisValue, float Multiplier, bool LockZAxis)
{
AxisValue = FMath::Clamp(AxisValue, -1.f, 1.f);
FVector Direction = DesiredLocation - Pivot;
float OldPitch = DesiredRotation.Pitch;
SetDesiredPitch(DesiredRotation.Pitch + GetTickMultiplier() * AxisValue * RotateSpeed * Multiplier);
float Delta = OldPitch - DesiredRotation.Pitch;
Direction = Direction.RotateAngleAxis(Delta, UKismetMathLibrary::GetRightVector(DesiredRotation));
float DesiredZ = DesiredLocation.Z;
FVector NewOriginLocation = Pivot + Direction;
if (LockZAxis)
{
if ((DesiredRotation.Pitch < 8.f && DesiredRotation.Pitch > -8.f) || DesiredRotation.Pitch > 172.f || DesiredRotation.Pitch < -172.f) return;
float DirectionMultiplier = (DesiredZ - NewOriginLocation.Z) / DesiredRotation.Vector().Z;
NewOriginLocation = NewOriginLocation + DesiredRotation.Vector() * DirectionMultiplier;
SetDesiredZoom(DesiredZoom + DirectionMultiplier);
}
SetDesiredLocation(NewOriginLocation, false, true);
}
void AUniversalCamera::PrintDesiredPosition(const float Duration)
{
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
GEngine->AddOnScreenDebugMessage(0, Duration, FColor::Yellow, FString::Printf(TEXT("Desired Location: %s"), *DesiredLocation.ToString()));
GEngine->AddOnScreenDebugMessage(1, Duration, FColor::Yellow, FString::Printf(TEXT("Desired Yaw: %f + %f"), DesiredRotation.Yaw, DesiredRotationOffset.Yaw));
GEngine->AddOnScreenDebugMessage(2, Duration, FColor::Yellow, FString::Printf(TEXT("Desired Pitch: %f + %f"), DesiredRotation.Pitch, DesiredRotationOffset.Pitch));
GEngine->AddOnScreenDebugMessage(3, Duration, FColor::Yellow, FString::Printf(TEXT("Desired Roll: %f + %f"), DesiredRotation.Roll, DesiredRotationOffset.Roll));
GEngine->AddOnScreenDebugMessage(4, Duration, FColor::Yellow, FString::Printf(TEXT("Desired Zoom: %f"), DesiredZoom));
GEngine->AddOnScreenDebugMessage(5, Duration, FColor::Yellow, FString::Printf(TEXT("Desired Socket offset: %s"), *DesiredSocketOffset.ToString()));
GEngine->AddOnScreenDebugMessage(6, Duration, FColor::Yellow, FString::Printf(TEXT("Desired Target offset: %s"), *DesiredTargetOffset.ToString()));
#endif
}
FVector AUniversalCamera::GetLinePlaneIntersection(FVector Direction, FVector Origin, FVector PlaneNormal, FVector PlaneCoord, bool& Intersect)
{
float d = PlaneNormal.Dot(PlaneCoord);
if (PlaneNormal.Dot(Direction) == 0) {
Intersect = false;
return FVector(0.f, 0.f, 0.f); // No intersection, the line is parallel to the plane
}
float x = (d - PlaneNormal.Dot(Origin)) / PlaneNormal.Dot(Direction);
// return contact point
Direction.Normalize();
FVector Contact = Origin + Direction * x;
Intersect = true;
return Contact;
}
FVector AUniversalCamera::GetAlternativeForwardVector(FRotator Rotation)
{
Rotation.Pitch = 0.f;
Rotation.Roll = 0.f;
return UKismetMathLibrary::GetForwardVector(Rotation);
}
FVector AUniversalCamera::GetAlternativeRightVector(FRotator Rotation)
{
Rotation.Pitch = 0.f;
Rotation.Roll = 0.f;
return UKismetMathLibrary::GetRightVector(Rotation);
}
FVector AUniversalCamera::GetAlternativeUpVector(FRotator Rotation)
{
return FVector::UpVector;
}
void AUniversalCamera::ClampCustomValue(bool ConstrainMinimum, bool ConstrainMaximum, float* ValueToClamp, float Minimum, float Maximum, bool ReverseRestrictions)
{
if (!(ConstrainMinimum || ConstrainMaximum)) return;
if (ReverseRestrictions)
{
if ((*ValueToClamp < Maximum) && (*ValueToClamp > Minimum))
{
float ClampedValue = FMath::Abs(*ValueToClamp - Maximum) > FMath::Abs(*ValueToClamp - Minimum) ? Minimum : Maximum;
*ValueToClamp = ClampedValue;
}
}
else
{
*ValueToClamp = FMath::Clamp(*ValueToClamp, ConstrainMinimum ? Minimum : *ValueToClamp, ConstrainMaximum ? Maximum : *ValueToClamp);
}
}