Skip to content
Snippets Groups Projects
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);
                }
            }
        }
    }
}