RealtimeStyleTransferRuntime/Source/LyraGame/Camera/LyraCameraMode_ThirdPerson.cpp

367 lines
13 KiB
C++
Raw Normal View History

2022-05-23 18:41:30 +00:00
// Copyright Epic Games, Inc. All Rights Reserved.
#include "LyraCameraMode_ThirdPerson.h"
#include "Curves/CurveVector.h"
#include "Engine/Canvas.h"
#include "GameFramework/CameraBlockingVolume.h"
#include "LyraCameraAssistInterface.h"
#include "GameFramework/Controller.h"
#include "GameFramework/Character.h"
namespace LyraCameraMode_ThirdPerson_Statics
{
static const FName NAME_IgnoreCameraCollision = TEXT("IgnoreCameraCollision");
}
ULyraCameraMode_ThirdPerson::ULyraCameraMode_ThirdPerson()
{
TargetOffsetCurve = nullptr;
PenetrationAvoidanceFeelers.Add(FLyraPenetrationAvoidanceFeeler(FRotator(+00.0f, +00.0f, 0.0f), 1.00f, 1.00f, 14.f, 0));
PenetrationAvoidanceFeelers.Add(FLyraPenetrationAvoidanceFeeler(FRotator(+00.0f, +16.0f, 0.0f), 0.75f, 0.75f, 00.f, 3));
PenetrationAvoidanceFeelers.Add(FLyraPenetrationAvoidanceFeeler(FRotator(+00.0f, -16.0f, 0.0f), 0.75f, 0.75f, 00.f, 3));
PenetrationAvoidanceFeelers.Add(FLyraPenetrationAvoidanceFeeler(FRotator(+00.0f, +32.0f, 0.0f), 0.50f, 0.50f, 00.f, 5));
PenetrationAvoidanceFeelers.Add(FLyraPenetrationAvoidanceFeeler(FRotator(+00.0f, -32.0f, 0.0f), 0.50f, 0.50f, 00.f, 5));
PenetrationAvoidanceFeelers.Add(FLyraPenetrationAvoidanceFeeler(FRotator(+20.0f, +00.0f, 0.0f), 1.00f, 1.00f, 00.f, 4));
PenetrationAvoidanceFeelers.Add(FLyraPenetrationAvoidanceFeeler(FRotator(-20.0f, +00.0f, 0.0f), 0.50f, 0.50f, 00.f, 4));
}
void ULyraCameraMode_ThirdPerson::UpdateView(float DeltaTime)
{
UpdateForTarget(DeltaTime);
UpdateCrouchOffset(DeltaTime);
FVector PivotLocation = GetPivotLocation() + CurrentCrouchOffset;
FRotator PivotRotation = GetPivotRotation();
PivotRotation.Pitch = FMath::ClampAngle(PivotRotation.Pitch, ViewPitchMin, ViewPitchMax);
View.Location = PivotLocation;
View.Rotation = PivotRotation;
View.ControlRotation = View.Rotation;
View.FieldOfView = FieldOfView;
// Apply third person offset using pitch.
if (!bUseRuntimeFloatCurves)
{
if (TargetOffsetCurve)
{
const FVector TargetOffset = TargetOffsetCurve->GetVectorValue(PivotRotation.Pitch);
View.Location = PivotLocation + PivotRotation.RotateVector(TargetOffset);
}
}
else
{
FVector TargetOffset(0.0f);
TargetOffset.X = TargetOffsetX.GetRichCurveConst()->Eval(PivotRotation.Pitch);
TargetOffset.Y = TargetOffsetY.GetRichCurveConst()->Eval(PivotRotation.Pitch);
TargetOffset.Z = TargetOffsetZ.GetRichCurveConst()->Eval(PivotRotation.Pitch);
View.Location = PivotLocation + PivotRotation.RotateVector(TargetOffset);
}
// Adjust final desired camera location to prevent any penetration
UpdatePreventPenetration(DeltaTime);
}
void ULyraCameraMode_ThirdPerson::UpdateForTarget(float DeltaTime)
{
if (const ACharacter* TargetCharacter = Cast<ACharacter>(GetTargetActor()))
{
if (TargetCharacter->bIsCrouched)
{
const ACharacter* TargetCharacterCDO = TargetCharacter->GetClass()->GetDefaultObject<ACharacter>();
const float CrouchedHeightAdjustment = TargetCharacterCDO->CrouchedEyeHeight - TargetCharacterCDO->BaseEyeHeight;
SetTargetCrouchOffset(FVector(0.f, 0.f, CrouchedHeightAdjustment));
return;
}
}
SetTargetCrouchOffset(FVector::ZeroVector);
}
void ULyraCameraMode_ThirdPerson::DrawDebug(UCanvas* Canvas) const
{
Super::DrawDebug(Canvas);
#if ENABLE_DRAW_DEBUG
FDisplayDebugManager& DisplayDebugManager = Canvas->DisplayDebugManager;
for (int i = 0; i < DebugActorsHitDuringCameraPenetration.Num(); i++)
{
DisplayDebugManager.DrawString(
FString::Printf(TEXT("HitActorDuringPenetration[%d]: %s")
, i
, *DebugActorsHitDuringCameraPenetration[i]->GetName()));
}
LastDrawDebugTime = GetWorld()->GetTimeSeconds();
#endif
}
void ULyraCameraMode_ThirdPerson::UpdatePreventPenetration(float DeltaTime)
{
if (!bPreventPenetration)
{
return;
}
AActor* TargetActor = GetTargetActor();
APawn* TargetPawn = Cast<APawn>(TargetActor);
AController* TargetController = TargetPawn ? TargetPawn->GetController() : nullptr;
ILyraCameraAssistInterface* TargetControllerAssist = Cast<ILyraCameraAssistInterface>(TargetController);
ILyraCameraAssistInterface* TargetActorAssist = Cast<ILyraCameraAssistInterface>(TargetActor);
TOptional<AActor*> OptionalPPTarget = TargetActorAssist ? TargetActorAssist->GetCameraPreventPenetrationTarget() : TOptional<AActor*>();
AActor* PPActor = OptionalPPTarget.IsSet() ? OptionalPPTarget.GetValue() : TargetActor;
ILyraCameraAssistInterface* PPActorAssist = OptionalPPTarget.IsSet() ? Cast<ILyraCameraAssistInterface>(PPActor) : nullptr;
const UPrimitiveComponent* PPActorRootComponent = Cast<UPrimitiveComponent>(PPActor->GetRootComponent());
if (PPActorRootComponent)
{
// Attempt at picking SafeLocation automatically, so we reduce camera translation when aiming.
// Our camera is our reticle, so we want to preserve our aim and keep that as steady and smooth as possible.
// Pick closest point on capsule to our aim line.
FVector ClosestPointOnLineToCapsuleCenter;
FVector SafeLocation = PPActor->GetActorLocation();
FMath::PointDistToLine(SafeLocation, View.Rotation.Vector(), View.Location, ClosestPointOnLineToCapsuleCenter);
// Adjust Safe distance height to be same as aim line, but within capsule.
float const PushInDistance = PenetrationAvoidanceFeelers[0].Extent + CollisionPushOutDistance;
float const MaxHalfHeight = PPActor->GetSimpleCollisionHalfHeight() - PushInDistance;
SafeLocation.Z = FMath::Clamp(ClosestPointOnLineToCapsuleCenter.Z, SafeLocation.Z - MaxHalfHeight, SafeLocation.Z + MaxHalfHeight);
float DistanceSqr;
PPActorRootComponent->GetSquaredDistanceToCollision(ClosestPointOnLineToCapsuleCenter, DistanceSqr, SafeLocation);
// Push back inside capsule to avoid initial penetration when doing line checks.
if (PenetrationAvoidanceFeelers.Num() > 0)
{
SafeLocation += (SafeLocation - ClosestPointOnLineToCapsuleCenter).GetSafeNormal() * PushInDistance;
}
// Then aim line to desired camera position
bool const bSingleRayPenetrationCheck = !bDoPredictiveAvoidance;
PreventCameraPenetration(*PPActor, SafeLocation, View.Location, DeltaTime, AimLineToDesiredPosBlockedPct, bSingleRayPenetrationCheck);
ILyraCameraAssistInterface* AssistArray[] = { TargetControllerAssist, TargetActorAssist, PPActorAssist };
if (AimLineToDesiredPosBlockedPct < ReportPenetrationPercent)
{
for (ILyraCameraAssistInterface* Assist : AssistArray)
{
if (Assist)
{
// camera is too close, tell the assists
Assist->OnCameraPenetratingTarget();
}
}
}
}
}
void ULyraCameraMode_ThirdPerson::PreventCameraPenetration(class AActor const& ViewTarget, FVector const& SafeLoc, FVector& CameraLoc, float const& DeltaTime, float& DistBlockedPct, bool bSingleRayOnly)
{
#if ENABLE_DRAW_DEBUG
DebugActorsHitDuringCameraPenetration.Reset();
#endif
float HardBlockedPct = DistBlockedPct;
float SoftBlockedPct = DistBlockedPct;
FVector BaseRay = CameraLoc - SafeLoc;
FRotationMatrix BaseRayMatrix(BaseRay.Rotation());
FVector BaseRayLocalUp, BaseRayLocalFwd, BaseRayLocalRight;
BaseRayMatrix.GetScaledAxes(BaseRayLocalFwd, BaseRayLocalRight, BaseRayLocalUp);
float DistBlockedPctThisFrame = 1.f;
int32 const NumRaysToShoot = bSingleRayOnly ? FMath::Min(1, PenetrationAvoidanceFeelers.Num()) : PenetrationAvoidanceFeelers.Num();
FCollisionQueryParams SphereParams(SCENE_QUERY_STAT(CameraPen), false, nullptr/*PlayerCamera*/);
SphereParams.AddIgnoredActor(&ViewTarget);
//TODO ILyraCameraTarget.GetIgnoredActorsForCameraPentration();
//if (IgnoreActorForCameraPenetration)
//{
// SphereParams.AddIgnoredActor(IgnoreActorForCameraPenetration);
//}
FCollisionShape SphereShape = FCollisionShape::MakeSphere(0.f);
UWorld* World = GetWorld();
for (int32 RayIdx = 0; RayIdx < NumRaysToShoot; ++RayIdx)
{
FLyraPenetrationAvoidanceFeeler& Feeler = PenetrationAvoidanceFeelers[RayIdx];
if (Feeler.FramesUntilNextTrace <= 0)
{
// calc ray target
FVector RayTarget;
{
FVector RotatedRay = BaseRay.RotateAngleAxis(Feeler.AdjustmentRot.Yaw, BaseRayLocalUp);
RotatedRay = RotatedRay.RotateAngleAxis(Feeler.AdjustmentRot.Pitch, BaseRayLocalRight);
RayTarget = SafeLoc + RotatedRay;
}
// cast for world and pawn hits separately. this is so we can safely ignore the
// camera's target pawn
SphereShape.Sphere.Radius = Feeler.Extent;
ECollisionChannel TraceChannel = ECC_Camera; //(Feeler.PawnWeight > 0.f) ? ECC_Pawn : ECC_Camera;
// do multi-line check to make sure the hits we throw out aren't
// masking real hits behind (these are important rays).
// MT-> passing camera as actor so that camerablockingvolumes know when it's the camera doing traces
FHitResult Hit;
const bool bHit = World->SweepSingleByChannel(Hit, SafeLoc, RayTarget, FQuat::Identity, TraceChannel, SphereShape, SphereParams);
#if ENABLE_DRAW_DEBUG
if (World->TimeSince(LastDrawDebugTime) < 1.f)
{
DrawDebugSphere(World, SafeLoc, SphereShape.Sphere.Radius, 8, FColor::Red);
DrawDebugSphere(World, bHit ? Hit.Location : RayTarget, SphereShape.Sphere.Radius, 8, FColor::Red);
DrawDebugLine(World, SafeLoc, bHit ? Hit.Location : RayTarget, FColor::Red);
}
#endif // ENABLE_DRAW_DEBUG
Feeler.FramesUntilNextTrace = Feeler.TraceInterval;
const AActor* HitActor = Hit.GetActor();
if (bHit && HitActor)
{
bool bIgnoreHit = false;
if (HitActor->ActorHasTag(LyraCameraMode_ThirdPerson_Statics::NAME_IgnoreCameraCollision))
{
bIgnoreHit = true;
SphereParams.AddIgnoredActor(HitActor);
}
// Ignore CameraBlockingVolume hits that occur in front of the ViewTarget.
if (!bIgnoreHit && HitActor->IsA<ACameraBlockingVolume>())
{
const FVector ViewTargetForwardXY = ViewTarget.GetActorForwardVector().GetSafeNormal2D();
const FVector ViewTargetLocation = ViewTarget.GetActorLocation();
const FVector HitOffset = Hit.Location - ViewTargetLocation;
const FVector HitDirectionXY = HitOffset.GetSafeNormal2D();
const float DotHitDirection = FVector::DotProduct(ViewTargetForwardXY, HitDirectionXY);
if (DotHitDirection > 0.0f)
{
bIgnoreHit = true;
// Ignore this CameraBlockingVolume on the remaining sweeps.
SphereParams.AddIgnoredActor(HitActor);
}
else
{
#if ENABLE_DRAW_DEBUG
DebugActorsHitDuringCameraPenetration.AddUnique(TObjectPtr<const AActor>(HitActor));
#endif
}
}
if (!bIgnoreHit)
{
float const Weight = Cast<APawn>(Hit.GetActor()) ? Feeler.PawnWeight : Feeler.WorldWeight;
float NewBlockPct = Hit.Time;
NewBlockPct += (1.f - NewBlockPct) * (1.f - Weight);
// Recompute blocked pct taking into account pushout distance.
NewBlockPct = ((Hit.Location - SafeLoc).Size() - CollisionPushOutDistance) / (RayTarget - SafeLoc).Size();
DistBlockedPctThisFrame = FMath::Min(NewBlockPct, DistBlockedPctThisFrame);
// This feeler got a hit, so do another trace next frame
Feeler.FramesUntilNextTrace = 0;
#if ENABLE_DRAW_DEBUG
DebugActorsHitDuringCameraPenetration.AddUnique(TObjectPtr<const AActor>(HitActor));
#endif
}
}
if (RayIdx == 0)
{
// don't interpolate toward this one, snap to it
// assumes ray 0 is the center/main ray
HardBlockedPct = DistBlockedPctThisFrame;
}
else
{
SoftBlockedPct = DistBlockedPctThisFrame;
}
}
else
{
--Feeler.FramesUntilNextTrace;
}
}
if (bResetInterpolation)
{
DistBlockedPct = DistBlockedPctThisFrame;
}
else if (DistBlockedPct < DistBlockedPctThisFrame)
{
// interpolate smoothly out
if (PenetrationBlendOutTime > DeltaTime)
{
DistBlockedPct = DistBlockedPct + DeltaTime / PenetrationBlendOutTime * (DistBlockedPctThisFrame - DistBlockedPct);
}
else
{
DistBlockedPct = DistBlockedPctThisFrame;
}
}
else
{
if (DistBlockedPct > HardBlockedPct)
{
DistBlockedPct = HardBlockedPct;
}
else if (DistBlockedPct > SoftBlockedPct)
{
// interpolate smoothly in
if (PenetrationBlendInTime > DeltaTime)
{
DistBlockedPct = DistBlockedPct - DeltaTime / PenetrationBlendInTime * (DistBlockedPct - SoftBlockedPct);
}
else
{
DistBlockedPct = SoftBlockedPct;
}
}
}
DistBlockedPct = FMath::Clamp<float>(DistBlockedPct, 0.f, 1.f);
if (DistBlockedPct < (1.f - ZERO_ANIMWEIGHT_THRESH))
{
CameraLoc = SafeLoc + (CameraLoc - SafeLoc) * DistBlockedPct;
}
}
void ULyraCameraMode_ThirdPerson::SetTargetCrouchOffset(FVector NewTargetOffset)
{
CrouchOffsetBlendPct = 0.0f;
InitialCrouchOffset = CurrentCrouchOffset;
TargetCrouchOffset = NewTargetOffset;
}
void ULyraCameraMode_ThirdPerson::UpdateCrouchOffset(float DeltaTime)
{
if (CrouchOffsetBlendPct < 1.0f)
{
CrouchOffsetBlendPct = FMath::Min(CrouchOffsetBlendPct + DeltaTime * CrouchOffsetBlendMultiplier, 1.0f);
CurrentCrouchOffset = FMath::InterpEaseInOut(InitialCrouchOffset, TargetCrouchOffset, CrouchOffsetBlendPct, 1.0f);
}
else
{
CurrentCrouchOffset = TargetCrouchOffset;
CrouchOffsetBlendPct = 1.0f;
}
}