Skip to content

Commit

Permalink
No edge duplication
Browse files Browse the repository at this point in the history
  • Loading branch information
Vrabbers committed Jun 14, 2024
1 parent c0ff645 commit 56bb7ba
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 47 deletions.
1 change: 1 addition & 0 deletions BnbnavNetClient/BnbnavNetClient.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<PackageReference Include="Avalonia.Fonts.Inter" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.ReactiveUI" Version="$(AvaloniaVersion)" />
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)" />
<PackageReference Include="CommunityToolkit.HighPerformance" Version="8.2.2" />

<PackageReference Include="JetBrains.Annotations" Version="2022.3.1" />
<PackageReference Include="ReactiveUI.Fody" Version="19.5.41" />
Expand Down
18 changes: 17 additions & 1 deletion BnbnavNetClient/Models/Edge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
public class TemporaryEdge(Road road, Node from, Node to) : Edge("somegeneratedid", road, from, to);

public sealed class EdgeComparer : IComparer<Edge>
{
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);
}
}
3 changes: 3 additions & 0 deletions BnbnavNetClient/Models/IntRect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
80 changes: 58 additions & 22 deletions BnbnavNetClient/Models/MapBin.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Avalonia;
using DynamicData;
using CommunityToolkit.HighPerformance;
using System.Collections;
using Avalonia.Platform;

namespace BnbnavNetClient.Models;

Expand All @@ -8,14 +9,19 @@ public sealed class MapBins
public class Bin
{
public List<Node> Nodes { get; } = [];

public List<IntRect> EdgeRects { get; } = [];
public List<Edge> 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; }

Expand All @@ -26,7 +32,7 @@ public MapBins(IntRect bounds, IEnumerable<Node> nodes, IEnumerable<Edge> 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)
{
Expand All @@ -38,6 +44,15 @@ public MapBins(IntRect bounds, IEnumerable<Node> nodes, IEnumerable<Edge> 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)
{
Expand All @@ -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);
}

Expand All @@ -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<Node> nodes, List<Edge> edges)
public Span2D<Bin?> 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<Bin?>(_bins, startY, startX, endY - startY, endX - startX);
}

public void Query(IntRect queryRect, List<Node> nodes, List<Edge> 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]);
}
}
}
}
}
}

public record BinAttachedRenderTarget(IRenderTarget RenderTarget);
16 changes: 16 additions & 0 deletions BnbnavNetClient/Models/Node.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,20 @@ public TemporaryNode(ISearchable original) : base(
}

ISearchable? OriginalSearchable { get; }
}

public sealed class NodeComparer : IComparer<Node>
{
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);
}
}
62 changes: 38 additions & 24 deletions BnbnavNetClient/Views/MapView.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Point> _pointerVelocities = [];
private bool _pointerPressing;
private bool _disablePan;
private Point _pointerPrevPosition;
private Point _currentPointerPosition;
private Vector _viewVelocity = Vector.Zero;

private readonly List<Point> _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()
{
Expand Down Expand Up @@ -283,16 +284,16 @@ protected override void OnInitialized()
}));
}

void InertialPan(TimeSpan time)
private void InertialPan(TimeSpan time)
{
if (_viewVelocity.Length < 0.1)
return;
MapViewModel.Pan += _viewVelocity / MapViewModel.Scale;
_viewVelocity /= 1.1;
TopLevel.GetTopLevel(this)?.RequestAnimationFrame(InertialPan);
}
void UpdateContextMenuItems()

private void UpdateContextMenuItems()
{
var seenEdges = new List<Edge>();
MapViewModel.ContextMenuItems.Clear();
Expand Down Expand Up @@ -378,7 +379,7 @@ void UpdateContextMenuItems()

public List<Node> SpiedNodes { get; set; } = [];

void UpdateFollowMeState()
private void UpdateFollowMeState()
{
var loggedInPlayer = MapViewModel.MapService.LoggedInPlayer;
if (loggedInPlayer is null) return;
Expand All @@ -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<MapItem> HitTest(Point point)
Expand Down Expand Up @@ -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;

Expand All @@ -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);

Expand All @@ -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"]!,
Expand All @@ -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;
Expand Down

0 comments on commit 56bb7ba

Please sign in to comment.