Find the point on navmesh closest to some FVector (not on navmesh)?

I’m building a world where AI controlled characters navigate to various locations, usually another Actor, and I ran into the issue where the goal Actor’s location is occasionally not on the NavMesh. This is affecting my call to UNavigationSystem::SimpleMoveToActor() and UNavigationSystem::SimpleMoveToLocation().

How can I calculate a new FVector, one located on the navmesh which I can supply to SimpleMoveToLocation, so that my characters can move to the closest navigable point?

This could help you: Best-fast method to detect if point is on navmesh - Programming & Scripting - Epic Developer Community Forums

You can manipulate with Extent argument to find point of navmesh in proximity of your target location.

That looked promising but it doesn’t seem to “project” a point on to the NavMesh in the (x,y) plane. It seems to project a point in the sky onto the ground (z axis) just fine, but I need to be able to project a point completely out of bounds (but on the same plane) back into bounds. Here’s how I was using it:

FVector newGoalVector = UNavigationSystem::ProjectPointToNavigation(goalActor, goalActor->GetActorLocation());

Internally, it looks like this particular call was assigning an INVALID_NAVEXTENT value to Extent. No joy. Then I tried:

UNavigationComponent* PFindComp = nullptr;
UPathFollowingComponent* PFollowComp = nullptr;
AActor* goal = citizen ? container->TargetGoal : nullptr;   // set goal.  Never null
CitizenController->InitNavigationControl(PFindComp, PFollowComp);
FVector Point = goal->GetActorLocation();
FNavLocation ProjectedPoint(Point);
UNavigationSystem* NavSys = UNavigationSystem::GetCurrent(GEngine->GetWorldFromContextObject(goal));
NavSys->ProjectPointToNavigation(Point, ProjectedPoint, PFindComp->GetQueryExtent() , PFindComp->GetNavData(), PFindComp->GetStoredQueryFilter());

Same thing… Still doesn’t project the way I need it to. I can’t find a lot of documentation on Extent (or these functions or how to use them). Any further advice?

Were you able to resolve this? I’d like to be able to find the nearest point on a nav mesh from a point that is off the nav mesh - after determining that it is off the nav mesh.

thanks

It’s sad that docs are so bad on that topic and there is no proper answer. Anyway to figure it out I’ve looked at the source of GetRandomPointInNavigableRadius - and basically what I think would be the best approach is to get all NavPolys in are with GetPolysInBox method and then you can iterate on those Polys and find closest points.

Can’t figure out how to use Project Point To Navigation either. Doesn’t do what I thought and no docs on it.

1 Like

I think Project Point To Navigation is just a simple node to basically do a trace to the navmesh and then offset the target point onto the navmesh if it’s not there already. Its used for minor adjustments and for detecting if the target location is or is not on the navmesh.

So if your AI is somehow moved off the Navmesh (let’s say thrown) then you could get the actor location of your AI, ProjectPointToNavigation, then from the Return value, plug it into a branch and from False you can run some extra code to get the nearest navigable location and make whatever adjustments necessary to get your character over there (like teleport them or Lerp them onto it). For example, you can use the GetRandomPointInNavigableRadius with a high radius value to grab a point that IS on the NavMesh and then lerp the actor there.

The issue is that RandomPointInNavigableRadius won’t return the nearest point, just one at random, so if you need the nearest point, you either have to set your radius to be small and hope that your character isn’t too far off, or do a bunch of checks, store them in an array and then figure out which one is closest to their current location with by getting the Vector of the point and the vector of your character and the subtracting the two points to see what the new length is. Basically, it’s a lot of extra work and there doesn’t seem to be an out-of-the-box node to get the nearest nav point.

Project Point to Navigation works fine.

				UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
				const FNavAgentProperties& AgentProps = Controller->GetNavAgentPropertiesRef();
				if (NavSys != nullptr)
				{
					FNavLocation ProjectedLocation;
					NavSys->ProjectPointToNavigation(Event.Location, ProjectedLocation, FVector(500.0f, 500.0f, 300.0f), &AgentProps);
					NearestMovableLocation = ProjectedLocation.Location;
				}

Note that the larger the radius you give it, the more impactful this call will be on CPU.

It’s a good idea to build your levels so that you don’t need to use this with a huge radius. 500x500x300 I would think should be more th an enough for most anything.

I thought Project Point to Navigation was for ensuring that a target location is in fact on the navmesh. As far as I know, that’s not going to help in the situation where your AI is outside of the navmesh and you need to find the nearest location to move them back over there.
If it does do this, I’ll laugh because I just spent a good hour or so designing my own way to do exactly this and it seems pretty efficient too :slight_smile:

1 Like

I’m not sure what else it could possibly do.

You give it a point anywhere in 3d space, and a 3d extent, and it asks Recast for the nearest poly to that point that is within that extent.

It does look like it doesn’t narrow it down to the nearest possible point, only the nearest poly, but that shouldn’t be a distinction that matters much to most, I would expect, as usually the polys are pretty small. I may also be wrong about that, because I’m not very adept at reading Recast code.

Great solution!

Looking at GetRandomReachablePointInRadius as an example, this should be the best way to do it I think:

FVector AAI_Controller_Human::GetClosestLocationOnNavMesh(const FVector Point)
{
	if (AGame::Instance() == nullptr)
		return FVector::Zero();

	if (ANavigation::Instance()->RecastMesh == nullptr)
		return FVector::Zero();
	
	//UNavigationSystemV1* Nav = UNavigationSystemV1::GetCurrent(GetWorld());

	const UE::Math::TVector4<double> a = UE::Math::TVector4<double>(Point.X - 1000, Point.Y - 1000, Point.Z - 1000, 0);
	const UE::Math::TVector4<double> b = UE::Math::TVector4<double>(Point.X + 1000, Point.Y + 1000, Point.Z + 1000, 0);
	const FBox Box = FBox(a, b);

	TArray<FNavPoly> Polies;
	ANavigation::Instance()->RecastMesh->GetPolysInBox(Box, Polies);

	
	float ClosestLength	= std::numeric_limits<double>::max();
	FVector ClosestPoint = FVector::Zero();
	
	for (int i = 0; i < Polies.Num(); i++)
	{
		FVector PointOnPoly;
		ANavigation::Instance()->RecastMesh->GetClosestPointOnPoly(Polies[i].Ref, Point, PointOnPoly);
		
		const float Length = (PointOnPoly - Point).SquaredLength();
		if (Length < ClosestLength)
		{
			ClosestLength	= Length;
			ClosestPoint	= PointOnPoly;
		}
	}

	return ClosestPoint;
}