From 56bb7ba38420527731a4ce9f3c418f8a82e83223 Mon Sep 17 00:00:00 2001
From: Vrabbers <25323861+Vrabbers@users.noreply.github.com>
Date: Fri, 14 Jun 2024 07:23:01 -0300
Subject: [PATCH] No edge duplication
---
BnbnavNetClient/BnbnavNetClient.csproj | 1 +
BnbnavNetClient/Models/Edge.cs | 18 +++++-
BnbnavNetClient/Models/IntRect.cs | 3 +
BnbnavNetClient/Models/MapBin.cs | 80 +++++++++++++++++++-------
BnbnavNetClient/Models/Node.cs | 16 ++++++
BnbnavNetClient/Views/MapView.axaml.cs | 62 ++++++++++++--------
6 files changed, 133 insertions(+), 47 deletions(-)
diff --git a/BnbnavNetClient/BnbnavNetClient.csproj b/BnbnavNetClient/BnbnavNetClient.csproj
index ce6fff7..a917ca5 100644
--- a/BnbnavNetClient/BnbnavNetClient.csproj
+++ b/BnbnavNetClient/BnbnavNetClient.csproj
@@ -25,6 +25,7 @@
+
diff --git a/BnbnavNetClient/Models/Edge.cs b/BnbnavNetClient/Models/Edge.cs
index 2f833fb..7239b7f 100644
--- a/BnbnavNetClient/Models/Edge.cs
+++ b/BnbnavNetClient/Models/Edge.cs
@@ -35,4 +35,20 @@ public void Deconstruct(out string id, out Road road, out Node from, out Node to
};
}
-public class TemporaryEdge(Road road, Node from, Node to) : Edge("somegeneratedid", road, from, to);
\ No newline at end of file
+public class TemporaryEdge(Road road, Node from, Node to) : Edge("somegeneratedid", road, from, to);
+
+public sealed class EdgeComparer : IComparer
+{
+ public static readonly EdgeComparer Instance = new();
+
+ public int Compare(Edge? x, Edge? y)
+ {
+ if (ReferenceEquals(x, y))
+ return 0;
+ if (ReferenceEquals(null, y))
+ return 1;
+ if (ReferenceEquals(null, x))
+ return -1;
+ return string.Compare(x.Id, y.Id, StringComparison.Ordinal);
+ }
+}
\ No newline at end of file
diff --git a/BnbnavNetClient/Models/IntRect.cs b/BnbnavNetClient/Models/IntRect.cs
index c327594..f9d0bdb 100644
--- a/BnbnavNetClient/Models/IntRect.cs
+++ b/BnbnavNetClient/Models/IntRect.cs
@@ -7,4 +7,7 @@ public bool Contains(int x, int y) =>
public IntRect Expand(int amt) =>
new(Left - amt, Top - amt, Right + amt, Bottom + amt);
+
+ public (int Left, int Top) IntersectTopLeft(IntRect rect) =>
+ (int.Max(Left, rect.Left), int.Max(Top, rect.Top));
}
\ No newline at end of file
diff --git a/BnbnavNetClient/Models/MapBin.cs b/BnbnavNetClient/Models/MapBin.cs
index 47745d3..18645a2 100644
--- a/BnbnavNetClient/Models/MapBin.cs
+++ b/BnbnavNetClient/Models/MapBin.cs
@@ -1,5 +1,6 @@
-using Avalonia;
-using DynamicData;
+using CommunityToolkit.HighPerformance;
+using System.Collections;
+using Avalonia.Platform;
namespace BnbnavNetClient.Models;
@@ -8,14 +9,19 @@ public sealed class MapBins
public class Bin
{
public List Nodes { get; } = [];
+
+ public List EdgeRects { get; } = [];
public List Edges { get; } = [];
+ public required IntRect Bounds { get; init; }
+
+ public BinAttachedRenderTarget? RenderTarget { get; set; }
}
public const int BinSideLength = 256;
private readonly Bin?[,] _bins;
- private int BinsXLength => _bins.GetLength(0);
- private int BinsYLength => _bins.GetLength(1);
+ private int BinsXLength => _bins.GetLength(1);
+ private int BinsYLength => _bins.GetLength(0);
public IntRect Bounds { get; private set; }
@@ -26,7 +32,7 @@ public MapBins(IntRect bounds, IEnumerable nodes, IEnumerable edges)
var yLength = Bounds.Bottom - Bounds.Top;
var xNumBins = xLength / BinSideLength;
var yNumBins = yLength / BinSideLength;
- _bins = new Bin?[xNumBins + 1, yNumBins + 1];
+ _bins = new Bin?[yNumBins + 1, xNumBins + 1];
foreach (var node in nodes)
{
@@ -38,6 +44,15 @@ public MapBins(IntRect bounds, IEnumerable nodes, IEnumerable edges)
Insert(edge);
}
}
+
+ private IntRect BoundsForBin(int x, int y)
+ {
+ var left = Bounds.Left + BinSideLength * x;
+ var right = left + BinSideLength;
+ var top = Bounds.Top + BinSideLength * y;
+ var bottom = top + BinSideLength;
+ return new IntRect(left, top, right, bottom);
+ }
public void InsertNode(Node node)
{
@@ -49,8 +64,8 @@ public void InsertNode(Node node)
var binX = x / BinSideLength;
var binY = y / BinSideLength;
- ref var bin = ref _bins[binX, binY];
- bin ??= new Bin();
+ ref var bin = ref _bins[binY, binX];
+ bin ??= new Bin { Bounds = BoundsForBin(binX, binY) };
bin.Nodes.Add(node);
}
@@ -67,36 +82,57 @@ public void Insert(Edge edge)
var endX = (expanded.Right - Bounds.Left + BinSideLength / 2) / BinSideLength;
var endY = (expanded.Bottom - Bounds.Top + BinSideLength / 2) / BinSideLength;
- for (var i = startX; i <= endX; i++)
+ for (var j = startY; j <= endY; j++)
{
- for (var j = startY; j <= endY; j++)
+ for (var i = startX; i <= endX; i++)
{
- ref var bin = ref _bins[i, j];
- bin ??= new Bin();
+ ref var bin = ref _bins[j, i];
+ bin ??= new Bin { Bounds = BoundsForBin(i, j) };
bin.Edges.Add(edge);
+ bin.EdgeRects.Add(expanded);
}
}
}
- public void Query(IntRect rect, List nodes, List edges)
+ public Span2D Query(IntRect queryRect)
{
- var startX = (rect.Left - Bounds.Left - BinSideLength / 2) / BinSideLength;
- var startY = (rect.Top - Bounds.Top - BinSideLength / 2) / BinSideLength;
- var endX = (rect.Right - Bounds.Left + BinSideLength / 2) / BinSideLength;
- var endY = (rect.Bottom - Bounds.Top + BinSideLength / 2) / BinSideLength;
+ var startX = (queryRect.Left - Bounds.Left - BinSideLength / 2) / BinSideLength;
+ var startY = (queryRect.Top - Bounds.Top - BinSideLength / 2) / BinSideLength;
+ var endX = (queryRect.Right - Bounds.Left + BinSideLength / 2) / BinSideLength;
+ var endY = (queryRect.Bottom - Bounds.Top + BinSideLength / 2) / BinSideLength;
- for (var i = startX; i <= endX; i++)
+ return new Span2D(_bins, startY, startX, endY - startY, endX - startX);
+ }
+
+ public void Query(IntRect queryRect, List nodes, List edges)
+ {
+ var startX = (queryRect.Left - Bounds.Left - BinSideLength / 2) / BinSideLength;
+ var startY = (queryRect.Top - Bounds.Top - BinSideLength / 2) / BinSideLength;
+ var endX = (queryRect.Right - Bounds.Left + BinSideLength / 2) / BinSideLength;
+ var endY = (queryRect.Bottom - Bounds.Top + BinSideLength / 2) / BinSideLength;
+
+ for (var j = startY; j <= endY; j++)
{
- for (var j = startY; j <= endY; j++)
+ for (var i = startX; i <= endX; i++)
{
- ref var bin = ref _bins[i, j];
+ var bin = _bins[j, i];
if (bin is null)
continue;
+
+ var binRect = bin.Bounds;
+
foreach (var node in bin.Nodes)
nodes.Add(node);
- foreach (var edge in bin.Edges)
- edges.Add(edge);
+
+ for (var k = 0; k < bin.Edges.Count; k++)
+ {
+ var (top, left) = bin.EdgeRects[k].IntersectTopLeft(queryRect);
+ if (binRect.Contains(top, left))
+ edges.Add(bin.Edges[k]);
+ }
}
}
}
-}
\ No newline at end of file
+}
+
+public record BinAttachedRenderTarget(IRenderTarget RenderTarget);
\ No newline at end of file
diff --git a/BnbnavNetClient/Models/Node.cs b/BnbnavNetClient/Models/Node.cs
index 3b81e1f..bd40990 100644
--- a/BnbnavNetClient/Models/Node.cs
+++ b/BnbnavNetClient/Models/Node.cs
@@ -49,4 +49,20 @@ public TemporaryNode(ISearchable original) : base(
}
ISearchable? OriginalSearchable { get; }
+}
+
+public sealed class NodeComparer : IComparer
+{
+ public static readonly NodeComparer Instance = new();
+
+ public int Compare(Node? x, Node? y)
+ {
+ if (ReferenceEquals(x, y))
+ return 0;
+ if (ReferenceEquals(null, y))
+ return 1;
+ if (ReferenceEquals(null, x))
+ return -1;
+ return string.Compare(x.Id, y.Id, StringComparison.Ordinal);
+ }
}
\ No newline at end of file
diff --git a/BnbnavNetClient/Views/MapView.axaml.cs b/BnbnavNetClient/Views/MapView.axaml.cs
index 6f101ac..0562792 100644
--- a/BnbnavNetClient/Views/MapView.axaml.cs
+++ b/BnbnavNetClient/Views/MapView.axaml.cs
@@ -20,24 +20,25 @@ namespace BnbnavNetClient.Views;
public partial class MapView : UserControl
{
- bool _pointerPressing;
- bool _disablePan;
- Point _pointerPrevPosition;
- Point _currentPointerPosition;
- Vector _viewVelocity = Vector.Zero;
- readonly List _pointerVelocities = [];
+ private bool _pointerPressing;
+ private bool _disablePan;
+ private Point _pointerPrevPosition;
+ private Point _currentPointerPosition;
+ private Vector _viewVelocity = Vector.Zero;
+
+ private readonly List _pointerVelocities = [];
// This list is averaged to get smooth panning.
// For some reason, using the proper method, (i.e. ResourceDictionary.ThemeDictionaries) does not seem to work here.
// This is a pretty crap solution, so if we find a better way it would probably be worthwhile implementing it
public IResourceDictionary ThemeDict { get; private set; }= default!;
-
- Matrix _toScreenMtx = Matrix.Identity;
- Matrix _toWorldMtx = Matrix.Identity;
+
+ private Matrix _toScreenMtx = Matrix.Identity;
+ private Matrix _toWorldMtx = Matrix.Identity;
public MapViewModel MapViewModel => (MapViewModel)DataContext!;
- const int PlayerSize = 48;
+ private const int PlayerSize = 48;
public MapView()
{
@@ -283,7 +284,7 @@ protected override void OnInitialized()
}));
}
- void InertialPan(TimeSpan time)
+ private void InertialPan(TimeSpan time)
{
if (_viewVelocity.Length < 0.1)
return;
@@ -291,8 +292,8 @@ void InertialPan(TimeSpan time)
_viewVelocity /= 1.1;
TopLevel.GetTopLevel(this)?.RequestAnimationFrame(InertialPan);
}
-
- void UpdateContextMenuItems()
+
+ private void UpdateContextMenuItems()
{
var seenEdges = new List();
MapViewModel.ContextMenuItems.Clear();
@@ -378,7 +379,7 @@ void UpdateContextMenuItems()
public List SpiedNodes { get; set; } = [];
- void UpdateFollowMeState()
+ private void UpdateFollowMeState()
{
var loggedInPlayer = MapViewModel.MapService.LoggedInPlayer;
if (loggedInPlayer is null) return;
@@ -397,7 +398,7 @@ void UpdateFollowMeState()
}
}
- void PanTo(Point worldCoords, double xOffset = 0.5, double yOffset = 0.5) =>
+ private void PanTo(Point worldCoords, double xOffset = 0.5, double yOffset = 0.5) =>
MapViewModel.Pan = worldCoords - new Point(Bounds.Size.Width * xOffset, Bounds.Size.Height * yOffset) / MapViewModel.Scale;
public IEnumerable HitTest(Point point)
@@ -428,7 +429,7 @@ protected override Size ArrangeOverride(Size finalSize)
return base.ArrangeOverride(finalSize);
}
- void UpdateDrawnItems(Rect? boundsRect = null)
+ private void UpdateDrawnItems(Rect? boundsRect = null)
{
var mapService = MapViewModel.MapService;
@@ -442,11 +443,7 @@ void UpdateDrawnItems(Rect? boundsRect = null)
_drawnEdges.Clear();
_drawnNodes.Clear();
- var worldTl = ToWorld(bounds.TopLeft);
- var worldBr = ToWorld(bounds.BottomRight);
-
- var intBounds = new IntRect((int)double.Floor(worldTl.X), (int)double.Floor(worldTl.Y),
- (int)double.Ceiling(worldBr.X), (int)double.Ceiling(worldBr.Y));
+ var intBounds = ToWorldIntBounds(bounds);
mapService.MapBins.Query(intBounds, _drawnNodes, _drawnEdges);
@@ -461,7 +458,25 @@ void UpdateDrawnItems(Rect? boundsRect = null)
InvalidateVisual();
}
- Pen PenForRoadType(RoadType type) => (Pen)(type switch
+ private IntRect ToWorldIntBounds(Rect bounds)
+ {
+ var worldTl = ToWorld(bounds.TopLeft);
+ var worldBr = ToWorld(bounds.BottomRight);
+
+ var intBounds = new IntRect((int)double.Floor(worldTl.X), (int)double.Floor(worldTl.Y),
+ (int)double.Ceiling(worldBr.X), (int)double.Ceiling(worldBr.Y));
+ return intBounds;
+ }
+
+ private Rect ToScreenBounds(IntRect bounds)
+ {
+ var tl = ToScreen(new Point(bounds.Top, bounds.Left));
+ var br = ToScreen(new Point(bounds.Bottom, bounds.Right));
+
+ return new Rect(tl, br);
+ }
+
+ private Pen PenForRoadType(RoadType type) => (Pen)(type switch
{
RoadType.Local => ThemeDict["LocalRoadPen"]!,
RoadType.Main => ThemeDict["MainRoadPen"]!,
@@ -486,8 +501,7 @@ public void DrawEdge(DrawingContext context, RoadType roadType, Point from, Poin
var diffPoint = to - from;
var angle = double.Atan2(diffPoint.Y, diffPoint.X);
- var matrix = Matrix.Identity *
- Matrix.CreateRotation(angle) *
+ var matrix = Matrix.CreateRotation(angle) *
Matrix.CreateTranslation(from);
pen.Thickness = ThicknessForRoadType(roadType) * MapViewModel.Scale;