refactor nav coverage into clustered windows

Replace region-based runtime pathing with interest-cluster coverage windows so active nav areas stay contiguous and spawn anchors participate in initial coverage.
This commit is contained in:
Alexander Borisov
2026-04-08 13:52:00 +03:00
parent 7282621211
commit 0b380def78
11 changed files with 680 additions and 219 deletions
@@ -0,0 +1,59 @@
using System.Collections.Generic;
using InfiniteWorld.VoxelWorld;
using InfiniteWorld.VoxelWorld.Contracts;
using UnityEngine;
namespace VoxelWorldScene
{
public sealed class SceneWorldInterestReader : IWorldInterestReader
{
private readonly VoxelWorldGenerator worldGenerator;
private VoxelWorldSpawnAnchor[] spawnAnchors;
private int lastAnchorRefreshFrame = -1;
public SceneWorldInterestReader(VoxelWorldGenerator worldGenerator)
{
this.worldGenerator = worldGenerator;
}
public int InterestVersion => worldGenerator != null ? worldGenerator.InterestVersion : 0;
public void GetInterestPoints(List<WorldInterestPoint> results)
{
if (results == null)
{
return;
}
worldGenerator?.GetInterestPoints(results);
RefreshSpawnAnchors();
if (spawnAnchors == null)
{
return;
}
for (int i = 0; i < spawnAnchors.Length; i++)
{
VoxelWorldSpawnAnchor anchor = spawnAnchors[i];
if (anchor == null || !anchor.isActiveAndEnabled)
{
continue;
}
results.Add(new WorldInterestPoint(anchor.transform.position, anchor.Priority, WorldInterestKind.SpawnAnchor));
}
}
private void RefreshSpawnAnchors()
{
if (lastAnchorRefreshFrame == Time.frameCount)
{
return;
}
lastAnchorRefreshFrame = Time.frameCount;
spawnAnchors = Object.FindObjectsByType<VoxelWorldSpawnAnchor>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6f1f0155f1e6452486d2f44f9dcefd5a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -30,8 +30,9 @@ namespace VoxelWorldScene
builder.RegisterMessagePipe();
builder.RegisterInstance(config);
builder.RegisterInstance(worldGenerator).As<IChunkNavSourceReader>().As<IWorldInterestReader>().AsSelf();
builder.RegisterEntryPoint<VoxelWorldNavMeshService>();
builder.RegisterInstance(worldGenerator).As<IChunkNavSourceReader>().AsSelf();
builder.Register<SceneWorldInterestReader>(Lifetime.Singleton).As<IWorldInterestReader>();
builder.RegisterEntryPoint<VoxelWorldNavMeshService>().AsSelf();
builder.RegisterBuildCallback(ResolvePublishers);
}
@@ -48,4 +49,5 @@ namespace VoxelWorldScene
resolver.Resolve<IPublisher<WorldInterestChangedMessage>>());
}
}
}
@@ -40,11 +40,6 @@ namespace VoxelWorldScene
return explicitStreamTarget;
}
if (currentStreamTarget != null)
{
return currentStreamTarget;
}
CameraFollow[] cameraFollows = FindObjectsByType<CameraFollow>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
for (int i = 0; i < cameraFollows.Length; i++)
{
@@ -55,6 +50,16 @@ namespace VoxelWorldScene
}
}
VoxelWorldSpawnAnchor[] spawnAnchors = FindObjectsByType<VoxelWorldSpawnAnchor>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
for (int i = 0; i < spawnAnchors.Length; i++)
{
VoxelWorldSpawnAnchor anchor = spawnAnchors[i];
if (anchor != null && anchor.isActiveAndEnabled)
{
return anchor.transform;
}
}
return null;
}
@@ -0,0 +1,12 @@
using UnityEngine;
namespace VoxelWorldScene
{
[DisallowMultipleComponent]
public sealed class VoxelWorldSpawnAnchor : MonoBehaviour
{
[SerializeField, Min(0.01f)] private float priority = 2f;
public float Priority => priority;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7a0a7758ae4541b39ed0b5d1fe912869
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: