Skip to content

Commit

Permalink
Further improvements to player update
Browse files Browse the repository at this point in the history
  • Loading branch information
Vrabbers committed Mar 2, 2024
1 parent ba87102 commit 391ce04
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 96 deletions.
2 changes: 0 additions & 2 deletions BnbnavNetClient.Linux/Program.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
using Avalonia;
using Avalonia.ReactiveUI;
using Avalonia.Svg.Skia;
using BnbnavNetClient.I18Next;
using BnbnavNetClient.Linux.TextToSpeech;
using BnbnavNetClient.Services.TextToSpeech;
using BnbnavNetClient.Settings;
using Splat;

namespace BnbnavNetClient.Linux;

Expand Down
1 change: 0 additions & 1 deletion BnbnavNetClient.Windows/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using BnbnavNetClient.I18Next;
using BnbnavNetClient.Services.TextToSpeech;
using BnbnavNetClient.Settings;
using Splat;

namespace BnbnavNetClient.Windows;

Expand Down
6 changes: 6 additions & 0 deletions BnbnavNetClient/Helpers/GeometryHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ namespace BnbnavNetClient.Helpers;

internal static class GeometryHelper
{
public static Rect SquareCenteredOn(Point point, double sideLength)
{
var offset = sideLength / 2;
return new Rect(point.X - offset, point.Y - offset, sideLength, sideLength);
}

public static bool LineIntersects(Point from, Point to, Rect bounds)
{
if (bounds.Contains(from) || bounds.Contains(to))
Expand Down
135 changes: 71 additions & 64 deletions BnbnavNetClient/Models/Player.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Diagnostics;
using System.Globalization;
using System.Transactions;
using Avalonia;
using Avalonia.Media;
using Avalonia.Threading;
Expand Down Expand Up @@ -60,7 +59,7 @@ public Point MarkerCoordinates
}
}

const int PosHistorySize = 8;
const int PosHistorySize = 12;
readonly Point[] _posHistory = new Point[PosHistorySize];
int _posHistoryIx;
DateTime _lastPosTime = DateTime.MinValue;
Expand All @@ -76,37 +75,43 @@ public Player(string name, MapService mapService)
_mapService = mapService;
Name = name;

_timer = new DispatcherTimer(DispatcherPriority.Background);
_timer.Tick += (_, _) =>
_timer = new DispatcherTimer(DispatcherPriority.Background)
{
var targetAngle = SnappedEdge is null ? Velocity.Angle : SnappedEdge.Line.Angle;
if (targetAngle < 0) targetAngle += 360;
Interval = TimeSpan.FromSeconds(1.0/20.0)
};
_timer.Tick += TimerOnTick;
_timer.Start();
}

Debug.Assert(MarkerAngle is >= 0 and < 360);
Debug.Assert(targetAngle is >= 0 and < 360);
void TimerOnTick(object? o, EventArgs eventArgs)
{
var targetAngle = SnappedEdge is null ? Velocity.Angle : SnappedEdge.Line.Angle;
if (targetAngle < 0)
targetAngle += 360;

Debug.Assert(MarkerAngle is >= 0 and < 360);
Debug.Assert(targetAngle is >= 0 and < 360);

var angleDifference = double.Ieee754Remainder(targetAngle - MarkerAngle, 360);
var angleDifference = double.Ieee754Remainder(targetAngle - MarkerAngle, 360);

Debug.Assert(angleDifference is >= -180 and <= 180);
Debug.Assert(angleDifference is >= -180 and <= 180);

if (double.Abs(angleDifference) < 0.1)
{
Moved = false;
return;
}
if (double.Abs(angleDifference) < 0.1)
{
Moved = false;
return;
}

var newAngle = double.Ieee754Remainder(MarkerAngle + angleDifference * 0.1, 360);
if (newAngle < 0)
newAngle += 360;
var newAngle = double.Ieee754Remainder(MarkerAngle + angleDifference * 0.2, 360);
if (newAngle < 0)
newAngle += 360;

MarkerAngle = newAngle;
MarkerAngle = newAngle;

Debug.Assert(MarkerAngle is >= 0 and < 360);
Debug.Assert(MarkerAngle is >= 0 and < 360);

Moved = true;
PlayerUpdateEvent?.Invoke(this, EventArgs.Empty);
};
_timer.Start();
Moved = true;
PlayerUpdateEvent?.Invoke(this, EventArgs.Empty);
}

public void GeneratePlayerText(FontFamily fontFamily)
Expand Down Expand Up @@ -150,63 +155,65 @@ public void HandlePlayerMovedEvent(PlayerMoved evt)

_lastPosTime = DateTime.Now;

if (SnappedEdge is not null)
if (SnappedEdge is not null && !CanSnapToEdge(SnappedEdge))
{
//Ensure the snapped edge is still valid
if (!CanSnapToEdge(SnappedEdge))
{
SnappedEdge = null;
}
SnappedEdge = null;
}

World = evt.World;
}

public void StartCalculateSnappedEdge()
{
Task.Factory.StartNew(static obj =>
Task.Factory.StartNew(CalculateSnappedEdge, this, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
}

static void CalculateSnappedEdge(object? obj)
{
var self = (Player)obj!;

// if the lock is already taken, finish
if (!Monitor.TryEnter(self._snapMutex))
return;

try
{
var me = (Player)obj!;
Console.WriteLine($"Calculating snap edge for {me.Name}");
var shouldChangeEdge = self.SnappedEdge is null || (!self._mapService.CurrentRoute?.Edges.Contains(self.SnappedEdge) ?? false);

// if the lock is already taken, finish
if (!Monitor.TryEnter(me._snapMutex))
if (!shouldChangeEdge)
return;

try
Edge? snapEdge = null;
if (self.SnappedEdge is not null && self.CanSnapToEdge(self.SnappedEdge))
{
var shouldChangeEdge = me.SnappedEdge is null ||
(!me._mapService.CurrentRoute?.Edges.Contains(me.SnappedEdge) ?? false);
//TODO: Also change edge if the current route contains the edge to change to or if the current route does not contain the currently snapped edge
if (!shouldChangeEdge)
return;

Edge? snapEdge = null;
if (me._mapService.CurrentRoute is not null)
{
// If we're in a route, try finding edges in the route only.
snapEdge = me._mapService.CurrentRoute.Edges.FirstOrDefault(me.CanSnapToEdge);
}
else
{
// this is the more common and longer path (outside go mode) so we avoid LINQ here
foreach (var edge in me._mapService.Edges)
{
if (!me.CanSnapToEdge(edge.Value))
continue;

snapEdge = edge.Value;
break;
}
}

me.SnappedEdge = snapEdge;
// stay snapped!
return;
}
finally
else if (self._mapService.CurrentRoute is not null)
{
Monitor.Exit(me._snapMutex);
// If we're in a route, try finding edges in the route only.
snapEdge = self._mapService.CurrentRoute.Edges.FirstOrDefault(self.CanSnapToEdge);
}
}, this);
else
{
// this is the more common and longer path (outside go mode) so avoid LINQ here
foreach (var edge in self._mapService.Edges)
{
if (!self.CanSnapToEdge(edge.Value))
continue;

snapEdge = edge.Value;
break;
}
}

self.SnappedEdge = snapEdge;
}
finally
{
Monitor.Exit(self._snapMutex);
}
}

bool CanSnapToEdge(Edge edge)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Avalonia;
using BnbnavNetClient.I18Next.Services;
using Splat;

namespace BnbnavNetClient.Services.TextToSpeech;
Expand Down
58 changes: 30 additions & 28 deletions BnbnavNetClient/Views/MapView.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using System.Reactive;
using BnbnavNetClient.Services;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using BnbnavNetClient.Models;
using BnbnavNetClient.Helpers;
Expand All @@ -29,11 +28,17 @@ public partial class MapView : UserControl
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
IResourceDictionary _themeDict = default!;

Matrix _toScreenMtx = Matrix.Identity;
Matrix _toWorldMtx = Matrix.Identity;

public MapViewModel MapViewModel => (MapViewModel)DataContext!;

const int PlayerSize = 48;

public MapView()
{
_i18N = Locator.Current.GetService<IAvaloniaI18Next>()!;
Expand Down Expand Up @@ -223,7 +228,8 @@ protected override void OnInitialized()
foreach (var (name, player) in prop.Value)
{
if ((MapViewModel.FollowMeEnabled && name == MapViewModel.LoggedInUsername) ||
(player.World == MapViewModel.ChosenWorld && Bounds.Contains(ToScreen(player.Point))))
(player.World == MapViewModel.ChosenWorld &&
Bounds.Intersects(GeometryHelper.SquareCenteredOn(ToScreen(player.Point), PlayerSize))))
{
if (!player.Moved)
continue;
Expand Down Expand Up @@ -453,20 +459,20 @@ void UpdateDrawnItems(Rect? boundsRect = null)

Pen PenForRoadType(RoadType type) => (Pen)(type switch
{
RoadType.Local => ThemeDict["LocalRoadPen"]!,
RoadType.Main => ThemeDict["MainRoadPen"]!,
RoadType.Highway => ThemeDict["HighwayRoadPen"]!,
RoadType.Expressway => ThemeDict["ExpresswayRoadPen"]!,
RoadType.Motorway => ThemeDict["MotorwayRoadPen"]!,
RoadType.Footpath => ThemeDict["FootpathRoadPen"]!,
RoadType.Waterway => ThemeDict["WaterwayRoadPen"]!,
RoadType.Private => ThemeDict["PrivateRoadPen"]!,
RoadType.Roundabout => ThemeDict["RoundaboutRoadPen"]!,
RoadType.DuongWarp => ThemeDict["DuongWarpRoadPen"]!,
_ => ThemeDict["UnknownRoadPen"]!,
RoadType.Local => _themeDict["LocalRoadPen"]!,
RoadType.Main => _themeDict["MainRoadPen"]!,
RoadType.Highway => _themeDict["HighwayRoadPen"]!,
RoadType.Expressway => _themeDict["ExpresswayRoadPen"]!,
RoadType.Motorway => _themeDict["MotorwayRoadPen"]!,
RoadType.Footpath => _themeDict["FootpathRoadPen"]!,
RoadType.Waterway => _themeDict["WaterwayRoadPen"]!,
RoadType.Private => _themeDict["PrivateRoadPen"]!,
RoadType.Roundabout => _themeDict["RoundaboutRoadPen"]!,
RoadType.DuongWarp => _themeDict["DuongWarpRoadPen"]!,
_ => _themeDict["UnknownRoadPen"]!,
});

public double ThicknessForRoadType(RoadType type) => (double)(type == RoadType.Motorway ? ThemeDict["MotorwayThickness"]! : ThemeDict["RoadThickness"]!);
public double ThicknessForRoadType(RoadType type) => (double)(type == RoadType.Motorway ? _themeDict["MotorwayThickness"]! : _themeDict["RoadThickness"]!);

public void DrawEdge(DrawingContext context, RoadType roadType, Point from, Point to, bool drawGhost = false, bool drawRoute = false)
{
Expand Down Expand Up @@ -508,7 +514,7 @@ public void DrawLandmark(DrawingContext context, Landmark landmark, Rect rect)
if (scale < lowerScaleBound || scale > higherScaleBound) return;

var text = new FormattedText(landmark.Name, CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
new Typeface(FontFamily), size * scale, (Brush)ThemeDict["ForegroundBrush"]!);
new Typeface(FontFamily), size * scale, (Brush)_themeDict["ForegroundBrush"]!);

context.DrawText(text, rect.Center - new Point(text.Width / 2, text.Height / 2));
return;
Expand All @@ -522,15 +528,12 @@ public void DrawLandmark(DrawingContext context, Landmark landmark, Rect rect)

context.DrawSvgUrl(landmark.IconUrl, rect);
}

// 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
IResourceDictionary ThemeDict => (IResourceDictionary)this.FindResource(ActualThemeVariant.ToString())!;

[SuppressMessage("ReSharper", "PossibleLossOfFraction")]
public override void Render(DrawingContext context)
{
context.FillRectangle((Brush)ThemeDict["BackgroundBrush"]!, Bounds);
_themeDict = (IResourceDictionary)this.FindResource(ActualThemeVariant.ToString())!;

context.FillRectangle((Brush)_themeDict["BackgroundBrush"]!, Bounds);

var noRender = new List<MapItem>();
noRender.AddRange(MapViewModel.MapEditorService.EditController.ItemsNotToRender);
Expand Down Expand Up @@ -574,10 +577,10 @@ public override void Render(DrawingContext context)

if (MapViewModel.IsInEditMode)
{
var nodeBorder = (Pen)ThemeDict["NodeBorder"]!;
var nodeBrush = (Brush)ThemeDict["NodeFill"]!;
var spiedBorder = (Pen)ThemeDict["SpiedNodeBorder"]!;
var spiedBrush = (Brush)ThemeDict["SpiedNodeFill"]!;
var nodeBorder = (Pen)_themeDict["NodeBorder"]!;
var nodeBrush = (Brush)_themeDict["NodeFill"]!;
var spiedBorder = (Pen)_themeDict["SpiedNodeBorder"]!;
var spiedBrush = (Brush)_themeDict["SpiedNodeFill"]!;
foreach (var (rect, node) in DrawnNodes)
{
if (noRender.Contains(node)) continue;
Expand All @@ -603,13 +606,12 @@ public override void Render(DrawingContext context)
foreach (var player in MapViewModel.MapService.Players.Values
.Where(player => player.World == MapViewModel.ChosenWorld && Bounds.Contains(ToScreen(player.Point))))
{
const int playerSize = 48;
var rect = new Rect(ToScreen(player.MarkerCoordinates) - new Point(playerSize, playerSize) / 2, new Size(playerSize, playerSize));
var rect = GeometryHelper.SquareCenteredOn(ToScreen(player.MarkerCoordinates), PlayerSize);
const string? uriString = "avares://BnbnavNetClient/Assets/playermark.svg";
context.DrawSvgUrl(uriString, rect, -player.MarkerAngle + MapViewModel.Rotation);

//Draw the player name
var textBrush = (Brush)ThemeDict["ForegroundBrush"]!;
var textBrush = (Brush)_themeDict["ForegroundBrush"]!;

if (player.PlayerText is null)
{
Expand Down

0 comments on commit 391ce04

Please sign in to comment.