Recast Rasterization

To reduce nav link generation time, we enabled ai.nav.UseAsymetricBorderSizes, which cut generation time by about 50%. It’s been stable so far and appears safe, since it only removes buffer voxels on one side of a tile that should already be covered by neighboring tiles.

I’m considering an additional optimization in RecastRasterization.cpp (rcRasterizeTriangles) that could reduce time by another ~50%. If we skip triangles marked as RC_NULL_AREA, are there any risks or edge cases we should be aware of? For example, could some geometry stop carving nav as expected?

void rcRasterizeTriangles(rcContext* ctx, const rcReal* verts, const unsigned char* areas, const int nt,
						 rcHeightfield& solid, const int flagMergeThr, const int rasterizationFlags, const int* rasterizationMasks, //UE
						 const rcReal* vertsbmin, const rcReal* vertsbmax) //UE
{
	if (ctx)
		ctx->startTimer(RC_TIMER_RASTERIZE_TRIANGLES);
	
	const rcReal ics = 1.0f/solid.cs;
	const rcReal ich = 1.0f/solid.ch;
 
	const bool bAsFilledVerticalConvexVolume = rasterizationFlags & RC_RASTERIZE_AS_FILLED_CONVEX;
	if (!bAsFilledVerticalConvexVolume)
	{
		AddSpanInHeightfield Func;
		// Rasterize triangles.
		for (int i = 0; i < nt; ++i)
		{
			// MOD - BEGIN
			if (areas[i] == RC_NULL_AREA)
			{
				continue;
			}
			// MOD - END
 
			const rcReal* v0 = &verts[(i*3+0)*3];
			const rcReal* v1 = &verts[(i*3+1)*3];
			const rcReal* v2 = &verts[(i*3+2)*3];
			// Rasterize.
			rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr, rasterizationFlags, rasterizationMasks, Func); //UE
		}
	}
	else
	{
		AddSpanInTempColumns Func(ctx, solid, ics, vertsbmin, vertsbmax);
		if (Func.IsValid())
		{
			for (int i = 0; i < nt; ++i)
			{
				// MOD - BEGIN
				if (areas[i] == RC_NULL_AREA)
				{
					continue;
				}
				// MOD - END
 
				const rcReal* v0 = &verts[(i*3+0)*3];
				const rcReal* v1 = &verts[(i*3+1)*3];
				const rcReal* v2 = &verts[(i*3+2)*3];
				rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr, rasterizationFlags, rasterizationMasks, Func); //UE
			}
 
			Func.TransferColumnsToHeightfield(solid, flagMergeThr);
		}
	}
	
	if (ctx)
		ctx->stopTimer(RC_TIMER_RASTERIZE_TRIANGLES);
}

Steps to Reproduce
Enable bGenerateNavLinks with dynamic navigation on. After we rebuild the nav tiles, it can take quite a long time to then generate the nav links (causing load times to be very long).

Hi there,

Thank you for the detailed information.

Skipping RC_NULL_AREA triangles inside rcRasterizeTriangles() can be safe only if those triangles are strictly decorative, never intersect walkable surfaces,are not used as blockers, and are not part of any navigation modifiers.

In this specific case, skipping RC_NULL_AREA may provide performance benefits. However, it is not a generic solution and will not be suitable for all projects.

In Recast, RC_NULL_AREA means non-walkable surfaces. If you skip rasterizing them, those surfaces effectively cease to exist in the navigation build. This can cause issues if RC_NULL_AREA surfaces are being used for slope evaluation, blocking meshes, or dynamic obstacle geometry. In those cases, skipping them may unintentionally create walkable space underneath.

Regarding bGenerateNavLinks with dynamic navigation on, it represents a worst-case cost scenario. If links are being auto-generated across the entire map, long load times after a rebuild are expected.

If most of your geometry is static, prebuilding navigation links is almost always the preferred solution.

If dynamic navigation is required, I would like to suggest disabling automatic link generation, manually placing NavLinkProxy actors where needed, restricting dynamic link generation to specific gameplay zones

There is limited context about this specific case, but based on your description, the fact that enabling ai.nav.UseAsymetricBorderSizes reduced navmesh rebuild time by 50% suggests that tile border overlap was a significant cost factor.

Since nav link generation scales heavily with tile borders, reducing the number of tile edges that require link testing would likely be an effective next optimization step. Which means using larger tiles, fewer total tiles, reducing geometry fragmentation, limiting jump height or link search parameters.

I hope this helps clarify this case. Please feel free to reach out if you have any further questions.

Best regards,

Henry Liu

Thanks for the response Henry. We’ll play around with tile sizes and a more conservative approach for null areas.

Dennis

Thank you.

I will close this case now, but feel free to respond here if you have any follow-up questions.

Cheers,