mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-04-27 09:48:58 -04:00
Navmesh generation is split into two steps: 1. Heightfield/Voxelfield Generation 2. Polygon Mesh Generation This PR focuses on Step 2. Step 2 still needs to run even when loading a navmesh from disk, since during baking we write the compressed Heightfield rather than the final polygon mesh. Step 2 is the cheaper of the two steps and already quite fast, but while profiling I noticed some additional optimization potential. This PR makes Step 2 roughly 20–30% faster, which will directly translate into faster navmesh loads from baked data. Looking at the whole pipeline (Step 1 + Step 2), I expect we'll see roughly a 10% improvement in our navmesh_gen benchmark. Optimizations primarily include: - Smarter caching of resources between tile generation runs - Reducing algorithmic complexity (O(n²) → O(n)) in some hot paths (at the cost of a small amount of memory) - Unrolling some loops - Loads of micro-optimizations
99 lines
2.8 KiB
C#
99 lines
2.8 KiB
C#
namespace Sandbox.Navigation.Generation;
|
|
|
|
[SkipHotload]
|
|
class NavMeshGenerator : IDisposable
|
|
{
|
|
// Created in init disposed of after generate
|
|
private CompactHeightfield chfWorkingCopy;
|
|
|
|
private Config cfg;
|
|
|
|
public void Init( Config config, CompactHeightfield inputChf )
|
|
{
|
|
cfg = config;
|
|
if ( chfWorkingCopy == null )
|
|
{
|
|
chfWorkingCopy = inputChf.Copy();
|
|
}
|
|
else
|
|
{
|
|
// Reuse memory
|
|
inputChf.CopyTo( chfWorkingCopy );
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if ( chfWorkingCopy != null )
|
|
{
|
|
chfWorkingCopy.Dispose();
|
|
chfWorkingCopy = null;
|
|
}
|
|
}
|
|
|
|
public void MarkArea( NavMeshAreaData area, int areaId )
|
|
{
|
|
var navTransform = NavMesh.ToNav( area.Transform );
|
|
var navBounds = NavMesh.ToNav( area.LocalBounds ).Transform( navTransform );
|
|
|
|
if ( area.Volume.Type == Volumes.SceneVolume.VolumeTypes.Box )
|
|
{
|
|
var navBox = NavMesh.ToNav( area.Volume.Box );
|
|
chfWorkingCopy.MarkBoxArea( navBox, navTransform, navBounds, areaId );
|
|
}
|
|
else if ( area.Volume.Type == Volumes.SceneVolume.VolumeTypes.Capsule )
|
|
{
|
|
var navCapsule = NavMesh.ToNav( area.Volume.Capsule );
|
|
chfWorkingCopy.MarkCapsuleArea( navCapsule, navTransform, navBounds, areaId );
|
|
}
|
|
else if ( area.Volume.Type == Volumes.SceneVolume.VolumeTypes.Sphere )
|
|
{
|
|
var navSpehere = NavMesh.ToNav( area.Volume.Sphere );
|
|
chfWorkingCopy.MarkSphereArea( navSpehere, navTransform, navBounds, areaId );
|
|
}
|
|
else if ( area.Volume.Type == Volumes.SceneVolume.VolumeTypes.Infinite )
|
|
{
|
|
var infiniteBounds = new BBox( new Vector3( float.MinValue ), new Vector3( float.MaxValue ) );
|
|
chfWorkingCopy.MarkBoxArea( infiniteBounds, Transform.Zero, infiniteBounds, areaId );
|
|
}
|
|
}
|
|
|
|
private List<int> prevCache = new( 512 );
|
|
|
|
// Cached pools for reuse across Generate() calls
|
|
private PolyMeshBuilder.PolyMeshBuilderContext polyMeshBuilderContext = new();
|
|
private RegionBuilder.RegionBuilderContext regionBuilderContext = new();
|
|
private ContourBuilder.ContourBuilderContext contourBuilderContext = new();
|
|
|
|
public PolyMesh Generate()
|
|
{
|
|
// According to recast docs good for tiles
|
|
if ( !RegionBuilder.BuildLayerRegions( chfWorkingCopy, cfg.BorderSize, cfg.MinRegionArea, prevCache, regionBuilderContext ) )
|
|
{
|
|
//Log.Warning( "buildNavigation: Could not build layer regions.\n" );
|
|
return null;
|
|
}
|
|
|
|
//
|
|
// Step 5. Trace and simplify region contours.
|
|
//
|
|
|
|
// Create contours.
|
|
ContourBuilder.BuildContours( chfWorkingCopy, cfg.MaxSimplificationError, cfg.MaxEdgeLen, contourBuilderContext );
|
|
|
|
if ( contourBuilderContext.ContourSet.Contours.Count == 0 )
|
|
{
|
|
//Log.Warning( "buildNavigation: No contours could be build for regions.\n" );
|
|
|
|
return null;
|
|
}
|
|
|
|
//
|
|
// Step 6. Build polygons mesh from contours.
|
|
//
|
|
|
|
// Build polygon navmesh from the contours.
|
|
return PolyMeshBuilder.BuildPolyMesh( contourBuilderContext.ContourSet, cfg.MaxVertsPerPoly, polyMeshBuilderContext );
|
|
}
|
|
}
|