-
Paul-Winpenny authoredPaul-Winpenny authored
CentreGraph.xaml.cs 10.78 KiB
using Microsoft.Maui.Graphics;
using Robobin.Models;
using RobobinApp.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace RobobinApp.Views
{
public class GraphicsDrawable : IDrawable
{
private Graph _graph;
private (float, float) _location;
public const float BaseNodeRadius = 5f; // Make this public if needed elsewhere
private static readonly Color DefaultNodeColor = Colors.Red;
private static readonly Color HomeNodeColor = Colors.Green;
private static readonly Color BackgroundColor = Color.FromHex("#2C2F33");
private static readonly Color ConnectionColor = Colors.Grey;
private static readonly Color HighlightedNodeColor = Colors.Yellow; // For the highlighted node
private static readonly Color PathColor = Colors.Blue; // For the path
private RectF storedDirtyRect;
public float CurrentScale { get; private set; } = 1.0f;
public (float X, float Y) CurrentOffset { get; private set; } = (0, 0);
private Node _highlightedNode;
private List<Node> _path;
public void SetGraph(Graph graph)
{
_graph = graph ?? throw new ArgumentNullException(nameof(graph));
_highlightedNode = null;
_path = null;
}
public void SetLocation((float, float) location)
{
_location = location;
}
public void Draw(ICanvas canvas, RectF dirtyRect)
{
storedDirtyRect = dirtyRect;
if (_graph == null) return;
DrawBackground(canvas, dirtyRect);
(float minX, float minY, float maxX, float maxY) = CalculateBounds();
CurrentScale = CalculateScale(dirtyRect, minX, minY, maxX, maxY);
(float offsetX, float offsetY) = CalculateOffsets(dirtyRect, minX, minY, maxX, maxY, CurrentScale);
CurrentOffset = (offsetX, offsetY);
float nodeRadius = BaseNodeRadius * CurrentScale;
DrawConnections(canvas, CurrentScale, offsetX, offsetY);
DrawPath(canvas, CurrentScale, offsetX, offsetY);
DrawNodes(canvas, CurrentScale, offsetX, offsetY, nodeRadius, _highlightedNode);
DrawLocation(canvas, CurrentScale, offsetX, offsetY);
}
private void DrawLocation(ICanvas canvas, float scale, float offsetX, float offsetY)
{
if (_location.Item1 == 0 && _location.Item2 == 0)
{
return;
}
Debug.WriteLine($"Drawing Location {_location.Item1} {_location.Item2}");
float scaledX = _location.Item1 * scale + offsetX;
float flippedY = storedDirtyRect.Height - (_location.Item2 * scale + offsetY);
canvas.FillColor = Colors.Yellow;
canvas.FillCircle(scaledX, flippedY, 5);
canvas.FontColor = Colors.White;
canvas.DrawString($"Robobin ({_location.Item1} {_location.Item2})", scaledX + 7, flippedY - 5, HorizontalAlignment.Left);
}
private void DrawBackground(ICanvas canvas, RectF dirtyRect)
{
canvas.FillColor = BackgroundColor;
canvas.FillRectangle(dirtyRect);
}
private (float minX, float minY, float maxX, float maxY) CalculateBounds()
{
float minX = _graph.Nodes.Min(node => node.X);
float minY = _graph.Nodes.Min(node => node.Y);
float maxX = _graph.Nodes.Max(node => node.X);
float maxY = _graph.Nodes.Max(node => node.Y);
return (minX, minY, maxX, maxY);
}
private float CalculateScale(RectF dirtyRect, float minX, float minY, float maxX, float maxY)
{
float graphWidth = maxX - minX;
float graphHeight = maxY - minY;
float scaleX = dirtyRect.Width / graphWidth;
float scaleY = dirtyRect.Height / graphHeight;
return Math.Min(scaleX, scaleY) * 0.8f;
}
private (float offsetX, float offsetY) CalculateOffsets(RectF dirtyRect, float minX, float minY, float maxX, float maxY, float scale)
{
float offsetX = dirtyRect.Center.X - ((minX + maxX) * 0.5f) * scale;
float offsetY = dirtyRect.Center.Y - ((minY + maxY) * 0.5f) * scale;
return (offsetX, offsetY);
}
private void DrawConnections(ICanvas canvas, float scale, float offsetX, float offsetY)
{
canvas.StrokeColor = ConnectionColor;
canvas.StrokeSize = 2;
foreach (var node in _graph.Nodes)
{
int nodeIndex = _graph.Nodes.IndexOf(node);
float startX = node.X * scale + offsetX;
float startY = storedDirtyRect.Height - (node.Y * scale + offsetY); // flip Y
for (int j = 0; j < _graph.Nodes.Count; j++)
{
var (magnitude, angle) = _graph.AdjacencyMatrix[nodeIndex, j];
if (magnitude > 0)
{
float targetX = (node.X + magnitude * MathF.Cos(MathF.PI * angle / 180)) * scale + offsetX;
float targetRawY = (node.Y + magnitude * MathF.Sin(MathF.PI * angle / 180)) * scale + offsetY;
float targetY = storedDirtyRect.Height - targetRawY; // flip Y
canvas.DrawLine(startX, startY, targetX, targetY);
}
}
}
}
private void DrawNodes(ICanvas canvas, float scale, float offsetX, float offsetY, float nodeRadius, Node highlightedNode)
{
foreach (var node in _graph.Nodes)
{
float scaledX = node.X * scale + offsetX;
float flippedY = storedDirtyRect.Height - (node.Y * scale + offsetY); // flip Y
canvas.FillColor = (highlightedNode == node)
? HighlightedNodeColor
: (node.Name == "Home" ? HomeNodeColor : DefaultNodeColor);
canvas.FillCircle(scaledX, flippedY, nodeRadius);
canvas.FontColor = Colors.White;
var nodeDetails = $"{node.Name}";
canvas.DrawString(nodeDetails, scaledX + nodeRadius + 2, flippedY - nodeRadius, HorizontalAlignment.Left);
}
}
public Node FindNearestNode(float clickX, float clickY, float scale, float offsetX, float offsetY)
{
// The coordinates given (clickX, clickY) are in the original coordinate system
// where the y-axis goes down. We must transform them similarly to how we did
// when drawing: flip the Y using storedDirtyRect.
float flippedClickY = clickY;
Node nearestNode = null;
float minDistance = float.MaxValue;
foreach (var node in _graph.Nodes)
{
float scaledX = node.X * scale + offsetX;
// Flip Y when comparing distances
float flippedY = storedDirtyRect.Height - (node.Y * scale + offsetY);
float dx = clickX - scaledX;
float dy = flippedClickY - flippedY;
float distance = MathF.Sqrt(dx * dx + dy * dy);
if (distance < minDistance)
{
minDistance = distance;
nearestNode = node;
}
}
return nearestNode;
}
public void HighlightNode(Node node)
{
_highlightedNode = node;
}
public List<Node> AStarPathfinding(Node startNode, Node targetNode)
{
var openSet = new List<Node> { startNode };
var cameFrom = new Dictionary<Node, Node>();
var gScore = new Dictionary<Node, float>();
var fScore = new Dictionary<Node, float>();
foreach (var node in _graph.Nodes)
{
gScore[node] = float.MaxValue;
fScore[node] = float.MaxValue;
}
gScore[startNode] = 0;
fScore[startNode] = Heuristic(startNode, targetNode);
while (openSet.Count > 0)
{
Node currentNode = openSet.OrderBy(node => fScore[node]).First();
if (currentNode == targetNode)
{
return ReconstructPath(cameFrom, currentNode);
}
openSet.Remove(currentNode);
foreach (var edge in currentNode.Edges)
{
Node neighbor = edge.TargetNode;
float tentativeGScore = gScore[currentNode] + edge.Cost;
if (tentativeGScore < gScore[neighbor])
{
cameFrom[neighbor] = currentNode;
gScore[neighbor] = tentativeGScore;
fScore[neighbor] = gScore[neighbor] + Heuristic(neighbor, targetNode);
if (!openSet.Contains(neighbor))
{
openSet.Add(neighbor);
}
}
}
}
return new List<Node>();
}
private float Heuristic(Node a, Node b)
{
return MathF.Sqrt((a.X - b.X) * (a.X - b.X) + (a.Y - b.Y) * (a.Y - b.Y));
}
private List<Node> ReconstructPath(Dictionary<Node, Node> cameFrom, Node currentNode)
{
var totalPath = new List<Node> { currentNode };
while (cameFrom.ContainsKey(currentNode))
{
currentNode = cameFrom[currentNode];
totalPath.Add(currentNode);
}
totalPath.Reverse();
return totalPath;
}
public Node GetHomeNode()
{
return _graph.Nodes.FirstOrDefault(n => n.Name == "Home");
}
public List<Node> Path
{
get => _path;
set => _path = value;
}
private void DrawPath(ICanvas canvas, float scale, float offsetX, float offsetY)
{
if (_path != null && _path.Count > 0)
{
canvas.StrokeColor = PathColor;
canvas.StrokeSize = 3;
for (int i = 0; i < _path.Count - 1; i++)
{
float startX = _path[i].X * scale + offsetX;
float startRawY = _path[i].Y * scale + offsetY;
float startY = storedDirtyRect.Height - startRawY; // flip Y
float endX = _path[i + 1].X * scale + offsetX;
float endRawY = _path[i + 1].Y * scale + offsetY;
float endY = storedDirtyRect.Height - endRawY; // flip Y
canvas.DrawLine(startX, startY, endX, endY);
}
}
}
}
}