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;