// 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(TEXT("RootComponent")); SpringArmComponent = CreateDefaultSubobject(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> AttachedChildren = SpringArmComponent->GetAttachChildren(); for (TObjectPtr AttachedChild : AttachedChildren) { UCameraComponent* Camera = Cast(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& 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& 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& PolygonPoints) { FVector2D ClosestPoint; float ClosestDistanceSq = TNumericLimits::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 AUniversalCamera::GetActorsLocation2D(TArray Actors) { TArray ReturnActors; for (AActor*& Actor : Actors) { FVector Location = Actor->GetActorLocation(); ReturnActors.Add(FVector2D(Location.X, Location.Y)); } return ReturnActors; } TArray AUniversalCamera::GetSettingsSaveFormat() { TArray ObjectData; FMemoryWriter MemoryWriter(ObjectData, true); FObjectAndNameAsStringProxyArchive Ar(MemoryWriter, false); Ar.ArNoDelta = true; this->Serialize(Ar); return ObjectData; } void AUniversalCamera::LoadSettingsSaveFormat(TArray 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(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(UGameplayStatics::LoadGameFromSlot(SlotName, UserIndex)); if (IsValid(UCSave) && UCSave->bIsValidSavePosition) { LoadPositionSaveFormat(UCSave->SavedPosition); } } void AUniversalCamera::QuickSaveSettings(TArray 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(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); } }