|
|
|
@@ -14,9 +14,11 @@ namespace InfiniteWorld.VoxelWorld.NavMesh
|
|
|
|
|
{
|
|
|
|
|
private readonly IChunkNavSourceReader chunkNavSourceReader;
|
|
|
|
|
private readonly IWorldInterestReader worldInterestReader;
|
|
|
|
|
private readonly INavCoverageHintReader navCoverageHintReader;
|
|
|
|
|
private readonly ISubscriber<ChunkNavGeometryReadyMessage> chunkReadySubscriber;
|
|
|
|
|
private readonly ISubscriber<ChunkNavGeometryRemovedMessage> chunkRemovedSubscriber;
|
|
|
|
|
private readonly ISubscriber<WorldInterestChangedMessage> worldInterestChangedSubscriber;
|
|
|
|
|
private readonly ISubscriber<NavCoverageHintChangedMessage> navCoverageHintChangedSubscriber;
|
|
|
|
|
private readonly VoxelWorldNavMeshConfig config;
|
|
|
|
|
private readonly Dictionary<int, NavCoverageWindowRuntime> coverageWindows = new Dictionary<int, NavCoverageWindowRuntime>();
|
|
|
|
|
private readonly Queue<int> dirtyCoverageWindowIds = new Queue<int>();
|
|
|
|
@@ -28,7 +30,7 @@ namespace InfiniteWorld.VoxelWorld.NavMesh
|
|
|
|
|
private readonly List<NavCoverageWindowSnapshot> coverageWindowSnapshots = new List<NavCoverageWindowSnapshot>(8);
|
|
|
|
|
private readonly List<DesiredCoverageWindow> desiredCoverageWindows = new List<DesiredCoverageWindow>(8);
|
|
|
|
|
private readonly List<ClusterAccumulator> clusterAccumulators = new List<ClusterAccumulator>(8);
|
|
|
|
|
private readonly List<IDisposable> subscriptions = new List<IDisposable>(3);
|
|
|
|
|
private readonly List<IDisposable> subscriptions = new List<IDisposable>(4);
|
|
|
|
|
|
|
|
|
|
private int nextCoverageWindowId = 1;
|
|
|
|
|
private int? activeBuildWindowId;
|
|
|
|
@@ -36,16 +38,20 @@ namespace InfiniteWorld.VoxelWorld.NavMesh
|
|
|
|
|
public VoxelWorldNavMeshService(
|
|
|
|
|
IChunkNavSourceReader chunkNavSourceReader,
|
|
|
|
|
IWorldInterestReader worldInterestReader,
|
|
|
|
|
INavCoverageHintReader navCoverageHintReader,
|
|
|
|
|
ISubscriber<ChunkNavGeometryReadyMessage> chunkReadySubscriber,
|
|
|
|
|
ISubscriber<ChunkNavGeometryRemovedMessage> chunkRemovedSubscriber,
|
|
|
|
|
ISubscriber<WorldInterestChangedMessage> worldInterestChangedSubscriber,
|
|
|
|
|
ISubscriber<NavCoverageHintChangedMessage> navCoverageHintChangedSubscriber,
|
|
|
|
|
VoxelWorldNavMeshConfig config)
|
|
|
|
|
{
|
|
|
|
|
this.chunkNavSourceReader = chunkNavSourceReader;
|
|
|
|
|
this.worldInterestReader = worldInterestReader;
|
|
|
|
|
this.navCoverageHintReader = navCoverageHintReader;
|
|
|
|
|
this.chunkReadySubscriber = chunkReadySubscriber;
|
|
|
|
|
this.chunkRemovedSubscriber = chunkRemovedSubscriber;
|
|
|
|
|
this.worldInterestChangedSubscriber = worldInterestChangedSubscriber;
|
|
|
|
|
this.navCoverageHintChangedSubscriber = navCoverageHintChangedSubscriber;
|
|
|
|
|
this.config = config ?? new VoxelWorldNavMeshConfig();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -54,6 +60,7 @@ namespace InfiniteWorld.VoxelWorld.NavMesh
|
|
|
|
|
subscriptions.Add(chunkReadySubscriber.Subscribe(OnChunkNavGeometryReady));
|
|
|
|
|
subscriptions.Add(chunkRemovedSubscriber.Subscribe(OnChunkNavGeometryRemoved));
|
|
|
|
|
subscriptions.Add(worldInterestChangedSubscriber.Subscribe(OnWorldInterestChanged));
|
|
|
|
|
subscriptions.Add(navCoverageHintChangedSubscriber.Subscribe(OnNavCoverageHintChanged));
|
|
|
|
|
|
|
|
|
|
RefreshInterestPoints();
|
|
|
|
|
SyncCoverageWindows();
|
|
|
|
@@ -154,10 +161,18 @@ namespace InfiniteWorld.VoxelWorld.NavMesh
|
|
|
|
|
MarkAllCoverageWindowsDirty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OnNavCoverageHintChanged(NavCoverageHintChangedMessage message)
|
|
|
|
|
{
|
|
|
|
|
RefreshInterestPoints();
|
|
|
|
|
SyncCoverageWindows();
|
|
|
|
|
MarkAllCoverageWindowsDirty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RefreshInterestPoints()
|
|
|
|
|
{
|
|
|
|
|
interestPoints.Clear();
|
|
|
|
|
worldInterestReader.GetInterestPoints(interestPoints);
|
|
|
|
|
navCoverageHintReader.GetHintPoints(interestPoints);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void SyncCoverageWindows()
|
|
|
|
@@ -857,4 +872,136 @@ namespace InfiniteWorld.VoxelWorld.NavMesh
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public sealed class NavCoverageHintService : ITickable, INavCoverageHintRegistry, INavCoverageHintReader
|
|
|
|
|
{
|
|
|
|
|
private readonly IChunkNavSourceReader chunkNavSourceReader;
|
|
|
|
|
private readonly VoxelWorldNavMeshConfig config;
|
|
|
|
|
private readonly IPublisher<NavCoverageHintChangedMessage> hintChangedPublisher;
|
|
|
|
|
private readonly Dictionary<int, HintEntry> hints = new Dictionary<int, HintEntry>();
|
|
|
|
|
private readonly List<int> expiredHintOwnerIds = new List<int>(8);
|
|
|
|
|
|
|
|
|
|
private int hintVersion;
|
|
|
|
|
|
|
|
|
|
public NavCoverageHintService(
|
|
|
|
|
IChunkNavSourceReader chunkNavSourceReader,
|
|
|
|
|
VoxelWorldNavMeshConfig config,
|
|
|
|
|
IPublisher<NavCoverageHintChangedMessage> hintChangedPublisher)
|
|
|
|
|
{
|
|
|
|
|
this.chunkNavSourceReader = chunkNavSourceReader;
|
|
|
|
|
this.config = config ?? new VoxelWorldNavMeshConfig();
|
|
|
|
|
this.hintChangedPublisher = hintChangedPublisher;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int HintVersion => hintVersion;
|
|
|
|
|
|
|
|
|
|
public void Tick()
|
|
|
|
|
{
|
|
|
|
|
if (hints.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float now = Time.time;
|
|
|
|
|
expiredHintOwnerIds.Clear();
|
|
|
|
|
foreach (KeyValuePair<int, HintEntry> pair in hints)
|
|
|
|
|
{
|
|
|
|
|
if (pair.Value.ExpireAt > now)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
expiredHintOwnerIds.Add(pair.Key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (expiredHintOwnerIds.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < expiredHintOwnerIds.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
hints.Remove(expiredHintOwnerIds[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NotifyHintsChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SetLinearHint(int ownerId, Vector3 from, Vector3 to, float priority, float ttlSeconds)
|
|
|
|
|
{
|
|
|
|
|
if (ownerId == 0)
|
|
|
|
|
{
|
|
|
|
|
ownerId = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float expireAt = Time.time + Mathf.Max(0.1f, ttlSeconds);
|
|
|
|
|
WorldInterestPoint[] points = BuildLinearHintPoints(from, to, Mathf.Max(0.01f, priority));
|
|
|
|
|
hints[ownerId] = new HintEntry(points, expireAt);
|
|
|
|
|
NotifyHintsChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void ClearHint(int ownerId)
|
|
|
|
|
{
|
|
|
|
|
if (!hints.Remove(ownerId))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NotifyHintsChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void GetHintPoints(List<WorldInterestPoint> results)
|
|
|
|
|
{
|
|
|
|
|
if (results == null)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentNullException(nameof(results));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (KeyValuePair<int, HintEntry> pair in hints)
|
|
|
|
|
{
|
|
|
|
|
WorldInterestPoint[] points = pair.Value.Points;
|
|
|
|
|
for (int i = 0; i < points.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
results.Add(points[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private WorldInterestPoint[] BuildLinearHintPoints(Vector3 from, Vector3 to, float priority)
|
|
|
|
|
{
|
|
|
|
|
float chunkWorldSize = Mathf.Max(1f, chunkNavSourceReader.ChunkWorldSize);
|
|
|
|
|
float spacing = Mathf.Max(chunkWorldSize, config.clusterMergeDistanceInChunks * chunkWorldSize * 0.75f);
|
|
|
|
|
float distance = Vector3.Distance(from, to);
|
|
|
|
|
int segmentCount = Mathf.Max(1, Mathf.CeilToInt(distance / Mathf.Max(0.01f, spacing)));
|
|
|
|
|
int pointCount = segmentCount + 1;
|
|
|
|
|
WorldInterestPoint[] points = new WorldInterestPoint[pointCount];
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < pointCount; i++)
|
|
|
|
|
{
|
|
|
|
|
float t = pointCount == 1 ? 1f : i / (float)(pointCount - 1);
|
|
|
|
|
Vector3 position = Vector3.Lerp(from, to, t);
|
|
|
|
|
points[i] = new WorldInterestPoint(position, priority, WorldInterestKind.TransientNavHint);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return points;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void NotifyHintsChanged()
|
|
|
|
|
{
|
|
|
|
|
hintVersion++;
|
|
|
|
|
hintChangedPublisher?.Publish(new NavCoverageHintChangedMessage(hintVersion));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private readonly struct HintEntry
|
|
|
|
|
{
|
|
|
|
|
public HintEntry(WorldInterestPoint[] points, float expireAt)
|
|
|
|
|
{
|
|
|
|
|
Points = points;
|
|
|
|
|
ExpireAt = expireAt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public WorldInterestPoint[] Points { get; }
|
|
|
|
|
public float ExpireAt { get; }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|