Skip to content
Snippets Groups Projects
Commit e95811d6 authored by Paul-Winpenny's avatar Paul-Winpenny
Browse files

Added node selection, and beginings for A* pathfinding.

parent 288b9693
No related branches found
No related tags found
No related merge requests found
using System; namespace Robobin.Models
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Robobin.Models
{ {
public class Node public class Node
{ {
public string Name { get; set; } public string Name { get; set; }
public float X { get; set; } public float X { get; set; }
public float Y { get; set; } public float Y { get; set; }
public List<Edge> Edges { get; set; } // Add edges to keep track of connections
public Node(string name, float x, float y) public Node(string name, float x, float y)
{ {
Name = name; Name = name;
X = x; X = x;
Y = y; Y = y;
Edges = new List<Edge>(); // Initialize the edges list
}
}
public class Edge
{
public Node TargetNode { get; set; }
public float Cost { get; set; } // Cost to traverse this edge
public Edge(Node targetNode, float cost)
{
TargetNode = targetNode;
Cost = cost;
} }
} }
...@@ -31,13 +39,35 @@ namespace Robobin.Models ...@@ -31,13 +39,35 @@ namespace Robobin.Models
Name = name; Name = name;
Nodes = nodes; Nodes = nodes;
AdjacencyMatrix = adjacencyMatrix; AdjacencyMatrix = adjacencyMatrix;
BuildEdges(); // Create edges based on the adjacency matrix
} }
public Graph( List<Node> nodes, (float, float)[,] adjacencyMatrix)
public Graph(List<Node> nodes, (float, float)[,] adjacencyMatrix)
{ {
Name = "Graph"; Name = "Graph";
Nodes = nodes; Nodes = nodes;
AdjacencyMatrix = adjacencyMatrix; AdjacencyMatrix = adjacencyMatrix;
BuildEdges(); // Create edges based on the adjacency matrix
} }
}
private void BuildEdges()
{
for (int i = 0; i < Nodes.Count; i++)
{
for (int j = 0; j < Nodes.Count; j++)
{
if (AdjacencyMatrix[i, j].magnitude > 0) // Check if there's a connection
{
float angle = AdjacencyMatrix[i, j].angle;
float x = Nodes[j].X; // Target node x
float y = Nodes[j].Y; // Target node y
Nodes[i].Edges.Add(new Edge(Nodes[j], AdjacencyMatrix[i, j].magnitude));
}
}
}
}
}
} }
...@@ -119,7 +119,7 @@ namespace RobobinApp.ViewModels ...@@ -119,7 +119,7 @@ namespace RobobinApp.ViewModels
// Add graphs to collection // Add graphs to collection
Graphs = new ObservableCollection<Graph> { graph1, graph2, graph3 }; Graphs = new ObservableCollection<Graph> { graph1, graph2, graph3 };
GraphNames = new ObservableCollection<string>(Graphs.Select(g => g.Name)); // Create list of names GraphNames = new ObservableCollection<string>(Graphs.Select(g => g.Name));
Graph = Graphs.First(); Graph = Graphs.First();
} }
......
using Microsoft.Maui.Graphics; using Microsoft.Maui.Graphics;
using Robobin.Models; using Robobin.Models;
using RobobinApp.Models; // Ensure you have the correct namespace for your Models using RobobinApp.Models;
using System;
using System.Linq;
namespace RobobinApp.Views namespace RobobinApp.Views
{ {
public class GraphicsDrawable : IDrawable public class GraphicsDrawable : IDrawable
{ {
private Graph _graph; private Graph _graph;
public const float BaseNodeRadius = 5f; // Make this public
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; // Color for the highlighted node
// Properties to keep track of scale and offset
public float CurrentScale { get; private set; } = 1.0f; // Initialize to default scale
public (float X, float Y) CurrentOffset { get; private set; } = (0, 0); // Initialize to default offset
private Node _highlightedNode; // Field to keep track of the highlighted node
public void SetGraph(Graph graph) public void SetGraph(Graph graph)
{ {
_graph = graph; _graph = graph ?? throw new ArgumentNullException(nameof(graph));
_highlightedNode = null; // Reset highlighted node when setting a new graph
} }
public void Draw(ICanvas canvas, RectF dirtyRect) public void Draw(ICanvas canvas, RectF dirtyRect)
{ {
if (_graph == null) return; if (_graph == null) return;
DrawBackground(canvas, dirtyRect);
canvas.FillColor = Color.FromHex("#2C2F33"); (float minX, float minY, float maxX, float maxY) = CalculateBounds();
CurrentScale = CalculateScale(dirtyRect, minX, minY, maxX, maxY); // Update CurrentScale
(float offsetX, float offsetY) = CalculateOffsets(dirtyRect, minX, minY, maxX, maxY, CurrentScale);
CurrentOffset = (offsetX, offsetY); // Update CurrentOffset
float nodeRadius = BaseNodeRadius * CurrentScale;
DrawConnections(canvas, CurrentScale, offsetX, offsetY);
DrawNodes(canvas, CurrentScale, offsetX, offsetY, nodeRadius, _highlightedNode);
}
private void DrawBackground(ICanvas canvas, RectF dirtyRect)
{
canvas.FillColor = BackgroundColor;
canvas.FillRectangle(dirtyRect); canvas.FillRectangle(dirtyRect);
}
private (float minX, float minY, float maxX, float maxY) CalculateBounds()
{
float minX = _graph.Nodes.Min(node => node.X); float minX = _graph.Nodes.Min(node => node.X);
float minY = _graph.Nodes.Min(node => node.Y); float minY = _graph.Nodes.Min(node => node.Y);
float maxX = _graph.Nodes.Max(node => node.X); float maxX = _graph.Nodes.Max(node => node.X);
float maxY = _graph.Nodes.Max(node => node.Y); 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 graphWidth = maxX - minX;
float graphHeight = maxY - minY; float graphHeight = maxY - minY;
float scaleX = dirtyRect.Width / graphWidth; float scaleX = dirtyRect.Width / graphWidth;
float scaleY = dirtyRect.Height / graphHeight; float scaleY = dirtyRect.Height / graphHeight;
float scale = Math.Min(scaleX, scaleY) * 0.8f; return Math.Min(scaleX, scaleY) * 0.8f; // Scale with some margin
}
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 offsetX = dirtyRect.Center.X - ((minX + maxX) * 0.5f) * scale;
float offsetY = dirtyRect.Center.Y - ((minY + maxY) * 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)
const float baseNodeRadius = 5; {
float nodeRadius = baseNodeRadius * scale; canvas.StrokeColor = ConnectionColor;
canvas.StrokeSize = 2;
foreach (var node in _graph.Nodes) foreach (var node in _graph.Nodes)
{ {
int nodeIndex = _graph.Nodes.IndexOf(node);
for (int j = 0; j < _graph.Nodes.Count; j++) for (int j = 0; j < _graph.Nodes.Count; j++)
{ {
var (magnitude, angle) = _graph.AdjacencyMatrix[_graph.Nodes.IndexOf(node), j]; var (magnitude, angle) = _graph.AdjacencyMatrix[nodeIndex, j];
if (magnitude > 0) if (magnitude > 0)
{ {
float targetX = (node.X + magnitude * MathF.Cos(MathF.PI * angle / 180)) * scale + offsetX; float targetX = (node.X + magnitude * MathF.Cos(MathF.PI * angle / 180)) * scale + offsetX;
float targetY = (node.Y + magnitude * MathF.Sin(MathF.PI * angle / 180)) * scale + offsetY; float targetY = (node.Y + magnitude * MathF.Sin(MathF.PI * angle / 180)) * scale + offsetY;
canvas.StrokeColor = Colors.Grey;
canvas.StrokeSize = 2;
canvas.DrawLine(node.X * scale + offsetX, node.Y * scale + offsetY, targetX, targetY); canvas.DrawLine(node.X * scale + offsetX, node.Y * scale + offsetY, targetX, targetY);
} }
} }
} }
}
// Draw nodes private void DrawNodes(ICanvas canvas, float scale, float offsetX, float offsetY, float nodeRadius, Node highlightedNode)
{
foreach (var node in _graph.Nodes) foreach (var node in _graph.Nodes)
{ {
float scaledX = node.X * scale + offsetX; float scaledX = node.X * scale + offsetX;
float scaledY = node.Y * scale + offsetY; float scaledY = node.Y * scale + offsetY;
canvas.FillColor = Colors.Red;
if (node.Name == "Home") // Use different color for highlighted node
{ canvas.FillColor = (highlightedNode != null && highlightedNode == node) ? HighlightedNodeColor : (node.Name == "Home" ? HomeNodeColor : DefaultNodeColor);
canvas.FillColor = Colors.Green;
}
canvas.FillCircle(scaledX, scaledY, nodeRadius); canvas.FillCircle(scaledX, scaledY, nodeRadius);
canvas.FontColor = Colors.White; canvas.FontColor = Colors.White;
canvas.DrawString(node.Name, scaledX + nodeRadius + 2, scaledY - nodeRadius, HorizontalAlignment.Left); canvas.DrawString(node.Name, scaledX + nodeRadius + 2, scaledY - nodeRadius, HorizontalAlignment.Left);
} }
} }
public Node FindNearestNode(float clickX, float clickY, float scale, float offsetX, float offsetY)
{
Node nearestNode = null;
float minDistance = float.MaxValue;
foreach (var node in _graph.Nodes)
{
float scaledX = node.X * scale + offsetX;
float scaledY = node.Y * scale + offsetY;
float distance = MathF.Sqrt(MathF.Pow(clickX - scaledX, 2) + MathF.Pow(clickY - scaledY, 2));
if (distance < minDistance)
{
minDistance = distance;
nearestNode = node;
}
}
return nearestNode;
}
public void HighlightNode(Node node)
{
_highlightedNode = node; // Store the node to highlight
}
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>(); // Cost from start to node
var fScore = new Dictionary<Node, float>(); // Estimated cost from start to target through node
foreach (var node in _graph.Nodes)
{
gScore[node] = float.MaxValue; // Initial cost is infinite
fScore[node] = float.MaxValue; // Initial cost is infinite
}
gScore[startNode] = 0;
fScore[startNode] = Heuristic(startNode, targetNode); // Initial heuristic
while (openSet.Count > 0)
{
// Find node with the lowest fScore
Node currentNode = openSet.OrderBy(node => fScore[node]).First();
if (currentNode == targetNode)
{
return ReconstructPath(cameFrom, currentNode);
}
openSet.Remove(currentNode);
// Check neighboring nodes
foreach (var edge in currentNode.Edges)
{
Node neighbor = edge.TargetNode; // Get the neighboring node
float tentativeGScore = gScore[currentNode] + edge.Cost; // Calculate tentative gScore
if (tentativeGScore < gScore[neighbor]) // A better path has been found
{
cameFrom[neighbor] = currentNode;
gScore[neighbor] = tentativeGScore;
fScore[neighbor] = gScore[neighbor] + Heuristic(neighbor, targetNode);
if (!openSet.Contains(neighbor))
{
openSet.Add(neighbor);
}
}
}
}
return new List<Node>(); // Return empty path if there is no path
}
private float Heuristic(Node a, Node b)
{
// Euclidean distance
return MathF.Sqrt(MathF.Pow(a.X - b.X, 2) + MathF.Pow(a.Y - b.Y, 2));
}
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(); // Reverse the path to start from the initial node
return totalPath;
}
} }
} }
...@@ -45,7 +45,13 @@ ...@@ -45,7 +45,13 @@
VerticalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
Grid.Column="1" Grid.Row="0"> Grid.Column="1" Grid.Row="0">
<GraphicsView x:Name="GraphicsView" <GraphicsView x:Name="GraphicsView"
Drawable="{StaticResource drawable}" /> Drawable="{StaticResource drawable}">
<GraphicsView.GestureRecognizers>
<TapGestureRecognizer Tapped="OnGraphicsViewTapped" />
</GraphicsView.GestureRecognizers>
</GraphicsView>
</Frame> </Frame>
<VerticalStackLayout StyleClass="sideFrame" <VerticalStackLayout StyleClass="sideFrame"
......
using Microsoft.Maui.Controls; using Microsoft.Maui.Controls;
using RobobinApp.ViewModels; using RobobinApp.ViewModels;
using RobobinApp.Models; using RobobinApp.Models;
using System.Diagnostics; using System.Diagnostics;
using RobobinApp.Views;
namespace RobobinApp.Views namespace RobobinApp.Views
{ {
...@@ -11,10 +12,8 @@ namespace RobobinApp.Views ...@@ -11,10 +12,8 @@ namespace RobobinApp.Views
{ {
InitializeComponent(); InitializeComponent();
// Get the drawable from resources
var drawable = (GraphicsDrawable)Resources["drawable"]; var drawable = (GraphicsDrawable)Resources["drawable"];
// Set the graph in the drawable using the existing BindingContext
if (BindingContext is MainPageViewModel viewModel) if (BindingContext is MainPageViewModel viewModel)
{ {
drawable.SetGraph(viewModel.Graph); drawable.SetGraph(viewModel.Graph);
...@@ -23,21 +22,57 @@ namespace RobobinApp.Views ...@@ -23,21 +22,57 @@ namespace RobobinApp.Views
private void OnGraphSelected(object sender, EventArgs e) private void OnGraphSelected(object sender, EventArgs e)
{ {
if (sender is Picker picker && BindingContext is MainPageViewModel viewModel) if (sender is Picker picker && BindingContext is MainPageViewModel viewModel)
{ {
// Update the selected graph
viewModel.SelectGraphCommand.Execute(picker.SelectedIndex); viewModel.SelectGraphCommand.Execute(picker.SelectedIndex);
// Refresh the drawable with the new graph
var drawable = (GraphicsDrawable)Resources["drawable"]; var drawable = (GraphicsDrawable)Resources["drawable"];
var newGraph = viewModel.Graphs.FirstOrDefault(g => g.Name == viewModel.Graph.Name); var newGraph = viewModel.Graphs.FirstOrDefault(g => g.Name == viewModel.Graph.Name);
drawable.SetGraph(newGraph);
// Invalidate the GraphicsView to redraw the updated graph
GraphicsView.Invalidate(); if (newGraph != null)
{
drawable.SetGraph(newGraph);
GraphicsView.Invalidate();
Debug.WriteLine("New Graph picked and drawn");
}
else
{
Debug.WriteLine("Graph not found!");
}
}
}
private void OnGraphicsViewTapped(object sender, TappedEventArgs e)
{
var tapPoint = e.GetPosition(GraphicsView);
var drawable = (GraphicsDrawable)Resources["drawable"];
float scale = drawable.CurrentScale;
var offset = drawable.CurrentOffset;
Debug.WriteLine("New Graph picked and drawn"); var tapPointX = (float)tapPoint.Value.X;
var tapPointY = (float)tapPoint.Value.Y;
var nearestNode = drawable.FindNearestNode(tapPointX, tapPointY, scale, offset.X, offset.Y);
drawable.HighlightNode(nearestNode);
if (nearestNode != null)
{
DisplayAlert("Node Clicked", $"You clicked on node: {nearestNode.Name}", "OK");
} }
else
{
DisplayAlert("No Node", "No node was clicked.", "OK");
}
GraphicsView.Invalidate();
} }
} }
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment