第一次提交
This commit is contained in:
@@ -0,0 +1,290 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GCMS_CameraMode_WithPenetrationAvoidance.h"
|
||||
#include "Engine/Canvas.h"
|
||||
#include "GameFramework/Pawn.h"
|
||||
#include "Components/PrimitiveComponent.h"
|
||||
#include "GameFramework/Controller.h"
|
||||
#include "Engine/HitResult.h"
|
||||
#include "GCMS_CameraAssistInterface.h"
|
||||
#include "GameFramework/CameraBlockingVolume.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GCMS_CameraMode_WithPenetrationAvoidance)
|
||||
|
||||
namespace GMS_CameraMode_WithPenetrationAvoidance_Statics
|
||||
{
|
||||
static const FName NAME_IgnoreCameraCollision = TEXT("IgnoreCameraCollision");
|
||||
}
|
||||
|
||||
|
||||
UGCMS_CameraMode_WithPenetrationAvoidance::UGCMS_CameraMode_WithPenetrationAvoidance()
|
||||
{
|
||||
PenetrationAvoidanceFeelers.Add(FGCMS_CameraPenetrationAvoidanceFeeler(FRotator(+00.0f, +00.0f, 0.0f), 1.00f, 1.00f, 14.f, 0));
|
||||
PenetrationAvoidanceFeelers.Add(FGCMS_CameraPenetrationAvoidanceFeeler(FRotator(+00.0f, +16.0f, 0.0f), 0.75f, 0.75f, 00.f, 3));
|
||||
PenetrationAvoidanceFeelers.Add(FGCMS_CameraPenetrationAvoidanceFeeler(FRotator(+00.0f, -16.0f, 0.0f), 0.75f, 0.75f, 00.f, 3));
|
||||
PenetrationAvoidanceFeelers.Add(FGCMS_CameraPenetrationAvoidanceFeeler(FRotator(+00.0f, +32.0f, 0.0f), 0.50f, 0.50f, 00.f, 5));
|
||||
PenetrationAvoidanceFeelers.Add(FGCMS_CameraPenetrationAvoidanceFeeler(FRotator(+00.0f, -32.0f, 0.0f), 0.50f, 0.50f, 00.f, 5));
|
||||
PenetrationAvoidanceFeelers.Add(FGCMS_CameraPenetrationAvoidanceFeeler(FRotator(+20.0f, +00.0f, 0.0f), 1.00f, 1.00f, 00.f, 4));
|
||||
PenetrationAvoidanceFeelers.Add(FGCMS_CameraPenetrationAvoidanceFeeler(FRotator(-20.0f, +00.0f, 0.0f), 0.50f, 0.50f, 00.f, 4));
|
||||
}
|
||||
|
||||
void UGCMS_CameraMode_WithPenetrationAvoidance::UpdatePreventPenetration(float DeltaTime)
|
||||
{
|
||||
if (!bPreventPenetration)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AActor* TargetActor = GetTargetActor();
|
||||
|
||||
APawn* TargetPawn = Cast<APawn>(TargetActor);
|
||||
AController* TargetController = TargetPawn ? TargetPawn->GetController() : nullptr;
|
||||
IGCMS_CameraAssistInterface* TargetControllerAssist = Cast<IGCMS_CameraAssistInterface>(TargetController);
|
||||
|
||||
IGCMS_CameraAssistInterface* TargetActorAssist = Cast<IGCMS_CameraAssistInterface>(TargetActor);
|
||||
|
||||
TOptional<AActor*> OptionalPPTarget = TargetActorAssist ? TargetActorAssist->GetCameraPreventPenetrationTarget() : TOptional<AActor*>();
|
||||
AActor* PPActor = OptionalPPTarget.IsSet() ? OptionalPPTarget.GetValue() : TargetActor;
|
||||
IGCMS_CameraAssistInterface* PPActorAssist = OptionalPPTarget.IsSet() ? Cast<IGCMS_CameraAssistInterface>(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(bSingleRayPenetrationCheck, DeltaTime, PPActor, SafeLocation, View.Location, AimLineToDesiredPosBlockedPct);
|
||||
|
||||
IGCMS_CameraAssistInterface* AssistArray[] = {TargetControllerAssist, TargetActorAssist, PPActorAssist};
|
||||
|
||||
if (AimLineToDesiredPosBlockedPct < ReportPenetrationPercent)
|
||||
{
|
||||
for (IGCMS_CameraAssistInterface* Assist : AssistArray)
|
||||
{
|
||||
if (Assist)
|
||||
{
|
||||
// camera is too close, tell the assists
|
||||
Assist->OnCameraPenetratingTarget();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGCMS_CameraMode_WithPenetrationAvoidance::PreventCameraPenetration(bool bSingleRayOnly, const float& DeltaTime, const AActor* ViewTarget, FVector const& SafeLoc, FVector& CameraLoc,
|
||||
float& DistBlockedPct)
|
||||
{
|
||||
#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)
|
||||
{
|
||||
FGCMS_CameraPenetrationAvoidanceFeeler& 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(GMS_CameraMode_WithPenetrationAvoidance_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 UGCMS_CameraMode_WithPenetrationAvoidance::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
|
||||
}
|
||||
Reference in New Issue
Block a user