Vehicle NavMesh Pathfinding

Apologies! Here it is in all it’s glory, or lack of…



// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "BehaviorTree/Tasks/BTTask_BlackboardBase.h"
#include "BehaviorTree/Blackboard/BlackboardKeyType_Object.h"
#include "BehaviorTree/Blackboard/BlackboardKeyType_Vector.h"
#include "BehaviorTree/Blackboard/BlackboardKeyType_Rotator.h"
#include "BehaviorTree/Services/BTService_DefaultFocus.h"
#include "BTTask_SteerTo.generated.h"

/**
 * Tells a BZ Pawn to Apply Steering towards a target location.
 */
UCLASS(Config=Game)
class BZGAME_API UBTTask_SteerTo : public UBTTask_BlackboardBase
{
	GENERATED_BODY()
	
protected:
	/* Success Condition Precision In Degrees */
	UPROPERTY(config, Category = Node, EditAnywhere, meta = (ClampMin = 0.0f))
	float YawAcceptanceRadius;
	UPROPERTY(config, Category = Node, EditAnywhere, meta = (ClampMin = 0.0f))
	float PitchAcceptanceRadius;

	UPROPERTY(config, Category = Node, EditAnywhere)
	bool AlignPitch;
	UPROPERTY(config, Category = Node, EditAnywhere)
	bool AlignYaw;

private:
	/* Cached Dot Precision */
	float YawPrecisionDot;
	float PitchPrecisionDot;

public:
	UBTTask_SteerTo(const FObjectInitializer& ObjectInitializer);

	virtual void PostInitProperties() override;
	virtual void PostLoad() override;

	virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
	virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
	virtual EBTNodeResult::Type AbortTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
	virtual void DescribeRuntimeValues(const UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, EBTDescriptionVerbosity::Type Verbosity, TArray<FString>& Values) const override;
	virtual FString GetStaticDescription() const override;

	virtual uint16 GetInstanceMemorySize() const override { return sizeof(FBTFocusMemory); }

protected:

	float GetYawPrecisionDot() const { return YawPrecisionDot; }
	float GetPitchPrecisionDot() const { return PitchPrecisionDot; }
	void CleanUp(AAIController& AIController, uint8* NodeMemory);	
};




// Fill out your copyright notice in the Description page of Project Settings.

#include "BZGame.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "AI/Tasks//BTTask_SteerTo.h"

UBTTask_SteerTo::UBTTask_SteerTo(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
	, PitchAcceptanceRadius(10.f)
	, YawAcceptanceRadius(10.f)
	, AlignPitch(false)
	, AlignYaw(true)
{
	NodeName = "SteerTo";
	bNotifyTick = true;

	// Accept Actors, Vectors & Rotators
	BlackboardKey.AddObjectFilter(this, GET_MEMBER_NAME_CHECKED(UBTTask_SteerTo, BlackboardKey), AActor::StaticClass());
	BlackboardKey.AddVectorFilter(this, GET_MEMBER_NAME_CHECKED(UBTTask_SteerTo, BlackboardKey));
	BlackboardKey.AddRotatorFilter(this, GET_MEMBER_NAME_CHECKED(UBTTask_SteerTo, BlackboardKey));
}

void UBTTask_SteerTo::PostInitProperties()
{
	Super::PostInitProperties();

	YawPrecisionDot = FMath::Cos(FMath::DegreesToRadians(YawAcceptanceRadius));
	PitchPrecisionDot = FMath::Cos(FMath::DegreesToRadians(PitchAcceptanceRadius));
}

void UBTTask_SteerTo::PostLoad()
{
	Super::PostLoad();

	YawPrecisionDot = FMath::Cos(FMath::DegreesToRadians(YawAcceptanceRadius));
	PitchPrecisionDot = FMath::Cos(FMath::DegreesToRadians(PitchAcceptanceRadius));
}

namespace
{
	FORCEINLINE_DEBUGGABLE float CalculateYawAngleDifferenceDot(const FVector& VectorA, const FVector& VectorB)
	{
		return VectorA.CosineAngle2D(VectorB);
	}
}

namespace
{
	FORCEINLINE_DEBUGGABLE float CalculatePitchAngleDifferenceDot(const FVector& VectorA, const FVector& VectorB)
	{
		// This may Suck Badly!
		FVector A = VectorA;
		FVector B = VectorB;

		A.Z = 0.f;
		B.Z = 0.f;
		A.Normalize();
		B.Normalize();
		return A | B;
	}
}

EBTNodeResult::Type UBTTask_SteerTo::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
	AAIController* AIController = OwnerComp.GetAIOwner();

	if (AIController == NULL || AIController->GetPawn() == NULL)
	{
		return EBTNodeResult::Failed;
	}

	FBTFocusMemory* MyMemory = (FBTFocusMemory*)NodeMemory;
	check(MyMemory);
	MyMemory->Reset();

	EBTNodeResult::Type Result = EBTNodeResult::Failed;

	APawn* Pawn = AIController->GetPawn();
	UBZGame_GameObjectComponent* PawnGOC = UBZGameplayStatics::GetGameObjectComponentFromActor(Pawn, true);
	ASSERTV_WR(PawnGOC != nullptr, Result, *FString::Printf(TEXT("UBTTask_SteerTo - Pawn Has no GameObjectComponent!")));
	FVector EyepointLocation, EyepointDirection;
	PawnGOC->GetEyepoint(EyepointLocation, EyepointDirection);
	const UBlackboardComponent* MyBlackboard = OwnerComp.GetBlackboardComponent();

	// Solve for Actor Target
	if (BlackboardKey.SelectedKeyType == UBlackboardKeyType_Object::StaticClass())
	{
		UObject* KeyValue = MyBlackboard->GetValue<UBlackboardKeyType_Object>(BlackboardKey.GetSelectedKeyID());
		AActor* ActorValue = Cast<AActor>(KeyValue);

		// TODO - Should Target Other Location
		if (ActorValue != NULL)
		{
			bool bYawMet = false;
			bool bPitchMet = false;

			if (AlignYaw)
			{
				const float YawAngleDiff = CalculateYawAngleDifferenceDot(EyepointDirection, (ActorValue->GetActorLocation() - EyepointLocation).GetSafeNormal());
				if (YawAngleDiff >= YawAcceptanceRadius)
				{
					bYawMet = true;
				}
			}
			else
			{
				bYawMet = true;
			}

			if (AlignPitch)
			{
				const float PitchAngleDiff = CalculatePitchAngleDifferenceDot(EyepointDirection, (ActorValue->GetActorLocation() - EyepointLocation).GetSafeNormal());
				if (PitchAngleDiff >= PitchAcceptanceRadius)
				{
					bPitchMet = true;
				}
			}
			else
			{
				bPitchMet = true;
			}

			if (bYawMet && bPitchMet)
			{
				Result = EBTNodeResult::Succeeded;
			}
			else
			{
				AIController->SetFocus(ActorValue, EAIFocusPriority::Gameplay);
				MyMemory->FocusActorSet = ActorValue;
				MyMemory->bActorSet = true;
				Result = EBTNodeResult::InProgress;
			}
		}
	}
	// Solve for Vector
	else if (BlackboardKey.SelectedKeyType == UBlackboardKeyType_Vector::StaticClass())
	{
		const FVector KeyValue = MyBlackboard->GetValue<UBlackboardKeyType_Vector>(BlackboardKey.GetSelectedKeyID());

		// Direction or Location in This?
		if (FAISystem::IsValidLocation(KeyValue))
		{
			bool bYawMet = false;
			bool bPitchMet = false;

			if (AlignYaw)
			{
				const float YawAngleDiff = CalculateYawAngleDifferenceDot(EyepointDirection, (KeyValue - EyepointLocation).GetSafeNormal());
				if (YawAngleDiff >= YawAcceptanceRadius)
				{
					bYawMet = true;
				}
			}
			else
			{
				bYawMet = true;
			}

			if (AlignPitch)
			{
				const float PitchAngleDiff = CalculatePitchAngleDifferenceDot(EyepointDirection, (KeyValue - EyepointLocation).GetSafeNormal());
				if (PitchAngleDiff >= PitchAcceptanceRadius)
				{
					bPitchMet = true;
				}
			}
			else
			{
				bPitchMet = true;
			}

			if (bYawMet && bPitchMet)
			{
				Result = EBTNodeResult::Succeeded;
			}
			else
			{
				AIController->SetFocalPoint(KeyValue, EAIFocusPriority::Gameplay);
				MyMemory->FocusLocationSet = KeyValue;
				Result = EBTNodeResult::InProgress;
			}
		}
	}
	// Solve For Rotation
	else if (BlackboardKey.SelectedKeyType == UBlackboardKeyType_Rotator::StaticClass())
	{
		const FRotator KeyValue = MyBlackboard->GetValue<UBlackboardKeyType_Rotator>(BlackboardKey.GetSelectedKeyID());

		if (FAISystem::IsValidRotation(KeyValue))
		{
			const FVector DirectionVector = KeyValue.Vector();

			bool bYawMet = false;
			bool bPitchMet = false;

			if (AlignYaw)
			{
				const float YawAngleDiff = CalculateYawAngleDifferenceDot(EyepointDirection, DirectionVector);
				if (YawAngleDiff >= YawAcceptanceRadius)
				{
					bYawMet = true;
				}
			}
			else
			{
				bYawMet = true;
			}

			if (AlignPitch)
			{
				const float PitchAngleDiff = CalculatePitchAngleDifferenceDot(EyepointDirection, DirectionVector);
				if (PitchAngleDiff >= PitchAcceptanceRadius)
				{
					bPitchMet = true;
				}
			}
			else
			{
				bPitchMet = true;
			}

			if (bYawMet && bPitchMet)
			{
				Result = EBTNodeResult::Succeeded;
			}
			else
			{
				const FVector FocalPoint = EyepointLocation + DirectionVector * (MAX_FLT / 2);
				AIController->SetFocalPoint(FocalPoint, EAIFocusPriority::Gameplay);
				MyMemory->FocusLocationSet = FocalPoint;
				Result = EBTNodeResult::InProgress;
			}
		}
	}

	return Result;
}

void UBTTask_SteerTo::TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
	AAIController* AIController = OwnerComp.GetAIOwner();

	if (AIController == NULL || AIController->GetPawn() == NULL)
	{
		FinishLatentTask(OwnerComp, EBTNodeResult::Failed);
	}
	else
	{
		UBZGame_GameObjectComponent* PawnsGOC = UBZGameplayStatics::GetGameObjectComponentFromActor(AIController->GetPawn(), true);
		if (PawnsGOC)
		{
			FVector EyeDirection, EyeLocation;
			PawnsGOC->GetEyepoint(EyeLocation, EyeDirection);

			const FVector FocalPoint = AIController->GetFocalPointForPriority(EAIFocusPriority::Gameplay);

			bool bYawMet = false;
			bool bPitchMet = false;

			if (AlignYaw)
			{
				const float YawAngleDiff = CalculateYawAngleDifferenceDot(EyeDirection, (FocalPoint - EyeLocation));
				if (YawAngleDiff >= YawAcceptanceRadius)
				{
					bYawMet = true;
				}
			}
			else
			{
				bYawMet = true;
			}

			if (AlignPitch)
			{
				const float PitchAngleDiff = CalculatePitchAngleDifferenceDot(EyeDirection, (FocalPoint - EyeLocation));
				if (PitchAngleDiff >= PitchAcceptanceRadius)
				{
					bPitchMet = true;
				}
			}
			else
			{
				bPitchMet = true;
			}

			if (bYawMet && bPitchMet)
			{
				CleanUp(*AIController, NodeMemory);
				FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
			}
		}
		else
		{
			FinishLatentTask(OwnerComp, EBTNodeResult::Failed);
		}
	}
}

void UBTTask_SteerTo::CleanUp(AAIController& AIController, uint8* NodeMemory)
{
	FBTFocusMemory* MyMemory = (FBTFocusMemory*)NodeMemory;
	check(MyMemory);

	bool bClearFocus = false;
	if (MyMemory->bActorSet)
	{
		bClearFocus = (MyMemory->FocusActorSet == AIController.GetFocusActorForPriority(EAIFocusPriority::Gameplay));
	}
	else
	{
		bClearFocus = (MyMemory->FocusLocationSet == AIController.GetFocalPointForPriority(EAIFocusPriority::Gameplay));
	}

	if (bClearFocus)
	{
		AIController.ClearFocus(EAIFocusPriority::Gameplay);
	}
}

EBTNodeResult::Type UBTTask_SteerTo::AbortTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
	AAIController* AIController = OwnerComp.GetAIOwner();

	if (AIController != NULL)
	{
		CleanUp(*AIController, NodeMemory);
	}

	return EBTNodeResult::Aborted;
}

void UBTTask_SteerTo::DescribeRuntimeValues(const UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, EBTDescriptionVerbosity::Type Verbosity, TArray<FString>& Values) const
{
	FString KeyDesc = BlackboardKey.SelectedKeyName.ToString();
	Values.Add(FString::Printf(TEXT("%s: %s"), *Super::GetStaticDescription(), *KeyDesc));

	AAIController* AIController = OwnerComp.GetAIOwner();

	if (AIController != NULL && AIController->GetPawn() != NULL)
	{
		UBZGame_GameObjectComponent* PawnsGOC = UBZGameplayStatics::GetGameObjectComponentFromActor(AIController->GetPawn(), true);
		if (PawnsGOC)
		{
			FVector EyeDirection, EyeLocation;
			PawnsGOC->GetEyepoint(EyeLocation, EyeDirection);

			const FVector FocalPoint = AIController->GetFocalPointForPriority(EAIFocusPriority::Gameplay);

			if (FocalPoint != FAISystem::InvalidLocation)
			{
				const float CurrentYawAngleRadians = CalculateYawAngleDifferenceDot(EyeDirection, (FocalPoint - EyeLocation).GetSafeNormal());
				const float CurrentPitchAngleRadians = CalculateYawAngleDifferenceDot(EyeDirection, (FocalPoint - EyeLocation).GetSafeNormal());
				Values.Add(FString::Printf(TEXT("Current Yaw: %.2f Current Pitch: %.2f"), FMath::DegreesToRadians(FMath::Acos(CurrentYawAngleRadians)), FMath::DegreesToRadians(FMath::Acos(CurrentPitchAngleRadians))));
			}
			else
			{
				Values.Add(TEXT("FocalPoint is an Invalid Location"));
			}
		}
		else
		{
			Values.Add(TEXT("GameObjectComponent is Invalid"));
		}
	}
	else
	{
		Values.Add(TEXT("Controller or Pawn is NULL"));
	}
}

FString UBTTask_SteerTo::GetStaticDescription() const
{
	FString KeyDesc = BlackboardKey.SelectedKeyName.ToString();
	return FString::Printf(TEXT("%s: %s"), *Super::GetStaticDescription(), *KeyDesc);
}