I am trying to spawn the neighbours of my path finder but when doing so the original path (path) blue also becomes covered up. I have tried switching of the order of spawn but this does not seem to help the problem. Quite new to Unity and would appreciate any insight into how I might go about spawning the path the neighbours without losing the original blue path.
Obtaining path neighbours; spawning the path + neighbours
for (int i = 1; i < path.Count; i++)
{
var pathCellPosition = path[i];
pathCellPosition.Position = new Vector3(pathCellPosition.Position.x, pathPrefab.transform.position.y, pathCellPosition.Position.z);
List<Node> neighbours = GetNeighbours(pathCellPosition);
var p = Instantiate(pathPrefab, pathCellPosition.Position, Quaternion.identity, PathCells);
foreach (Node n in neighbours)
{
neighbourPrefab.GetComponentInChildren<Text>().text = n.Cost.ToString();
Instantiate(neighbourPrefab, n.Position, Quaternion.identity, p.transform);
}
}
Original path
Path getting covered by neighbours
Implemented suggestions fixing part of the problem
The tiles around now seem to appear but when applying the same solution to implement neighbours of neighbours it seems to cause an overlap with the original neighbouring nodes.
Code changes:
HashSet<Node> visitedNodes = new HashSet<Node>();
for (int i = 1; i < path.Count; i++)
{
var pathCellPosition = path[i];
visitedNodes.Add(path[i]);
pathCellPosition.Position = new Vector3(pathCellPosition.Position.x, pathPrefab.transform.position.y, pathCellPosition.Position.z);
List<Node> neighbours = GetNeighbours(path[i]);
int fCost = DistanceFromStart(pathCellPosition) + HCostDistanceFromEndNode(pathCellPosition) + pathCellPosition.Cost;
pathPrefab.GetComponentInChildren<Text>().text = fCost.ToString();
var p = Instantiate(pathPrefab, pathCellPosition.Position, Quaternion.identity, PathCells);
for(int x = 0; x < neighbours.Count; x++)
{
var node = neighbours[x];
if (visitedNodes.Contains(node))
{
continue;
}
visitedNodes.Add(node);
fCost = DistanceFromStart(node) + HCostDistanceFromEndNode(node) + node.Cost;
neighbourPrefab.GetComponentInChildren<Text>().text = fCost.ToString();
Instantiate(neighbourPrefab, node.Position, Quaternion.identity, p.transform);
List<Node> NeighBourOfNeighbourNodes = GetNeighbours(node);
if(NeighBourOfNeighbourNodes.Count > 0)
{
for (int y = 0; y < NeighBourOfNeighbourNodes.Count; y++)
{
var neighbourNode = NeighBourOfNeighbourNodes[y];
if (visitedNodes.Contains(neighbourNode))
{
continue;
}
visitedNodes.Add(neighbourNode);
fCost = DistanceFromStart(neighbourNode) + HCostDistanceFromEndNode(neighbourNode) + neighbourNode.Cost;
nofn.GetComponentInChildren<Text>().text = fCost.ToString();
Instantiate(nofn, neighbourNode.Position, Quaternion.identity, p.transform);
}
}
}
If I don't draw neighbour of neighbours, this looks right. But if I do neighbour of neighbours the purple ones don't appear as much as they should.
Before:
After:
A typical simple solution is to check if the node is part of the path before adding. A HashSet can be good for this, but you will need something that can be reliably compared.
Perhaps something like this:
var visitedNodes = new HashSet<Node>(path);
for (int i = 1; i < path.Count; i++)
{
var pathCellPosition = path[i];
pathCellPosition.Position = new Vector3(pathCellPosition.Position.x, pathPrefab.transform.position.y, pathCellPosition.Position.z);
List<Node> neighbours = GetNeighbours(pathCellPosition);
var p = Instantiate(pathPrefab, pathCellPosition.Position, Quaternion.identity, PathCells);
foreach (Node n in neighbours)
{
if(visitedNodes.Contains(n))
continue;
visitedNodes.Add(n);
neighbourPrefab.GetComponentInChildren<Text>().text = n.Cost.ToString();
Instantiate(neighbourPrefab, n.Position, Quaternion.identity, p.transform);
}
}
You can also use the same approach to ensure a neighbour is not added twice.
If you want multiple levels from the node you might need to do something like a breath first search, ideally keeping track of the distance from the original path. The following generic code should traverse the graph in a breadth first manner, just use the path as the first parameter, and the GetNeighbours method for the second.
public static IEnumerable<(T Node, int Distance)> GetNeighborsWithDistance<T>(IEnumerable<T> self, Func<T, IEnumerable<T>> selector)
{
var stack = new Queue<(T Node, int Distance)>();
var visited = new HashSet<T>();
foreach (var node in self)
{
stack.Enqueue((node, 0));
}
while (stack.Count > 0)
{
var current = stack.Dequeue();
if(visited.Contains(current.Node))
continue;
yield return current;
visited.Add(current.Node);
foreach (var child in selector(current.Node))
{
stack.Enqueue((child, current.Distance+ 1));
}
}
}
This should be guaranteed to return nodes in order of distance from the original path, so just do .TakeWhile(p => p.Distance < DesiredDistanceFromOriginalPath) to get as many nodes as you want.
Related
So how can I update the position every time I call the StartRandomizingRightSpikePosition
private bool CheckOverlap(GameObject o1, GameObject o2)
{
return spikeRight.Select(t => t.GetComponent<Collider>().bounds.Intersects(t.GetComponent<Collider>().bounds)).FirstOrDefault();
}
public void StartRandomizingRightSpikesPosition()
{
foreach (var t in spikeRight)
{
foreach (var t1 in spikeRight)
{
if (t == t1) continue;
if (!CheckOverlap(t, t1)) continue;
yPosition = Random.Range(-7, 7);
var position = t1.transform.position;
desiredPosition = new Vector3(position.x, yPosition, position.z);
t1.transform.position = desiredPosition;
Debug.Log(t.gameObject + " intersects " + t1.gameObject);
}
}
}
The short answer is yes but I'm not sure you would want too. I'm not sure you're going to find a way to do this efficiently and you might be better off finding a way to generate the objects such that this step is not necessary.
I can't tell from your question how the objects are actually stored so I'm going to provide some sample code that just deals with a simple array of Rectangles. You should be able to adapt it to your specifics.
I tried to make it slightly more efficient by not checking both t1 == t and t == t1.
const int Bounds = 1000;
static bool RemovedOverlappingRects(Rectangle[] rects)
{
for (int pos = 0; pos < rects.Length; ++pos)
{
for (int check = pos +1; check < rects.Length; ++check)
{
var r1 = rects[pos];
var r2 = rects[check];
if (r1.IntersectsWith(r2))
{
r2.Y = Rng.Next(1, Bounds);
rects[check] = r2;
Console.WriteLine($"{pos} overlaps with {check}");
return true;
}
}
}
return false;
}
Once we've randomly generated a new rectangle we have to start over. Which means invoking the above method in a loop.
var rects = GetRandomeRects(20).ToArray();
while (RemovedOverlappingRects(rects))
;
Because of the random movement I'm not certain you can guarantee this will always end. If you can deal with the non-random look of the results changing it to stack the overlaps would I believe always finish. That would be this:
r2.Y = r1.Y + r1.Height + 1;
in place of
r2.Y = Rng.Next(1, Bounds);
But even then you're still dealing with a very unpredictable run time due to the random input.
Maybe someone else can show us a more efficient way...
I am trying to build a graph out of multipolygons. The code works fine when I don't have large amount of vertices but with 1M it is not satisfying. Any performance improvement suggestions?
public Graph(List<Polygon> PolygonsSet)
{
edges = new List<Edge>();
graphID = Guid.NewGuid();
//Setting up Graph instance by adding vertices, edges and polygons
foreach (Polygon Polygon in PolygonsSet)
{
List<Vertex> vertices = Polygon.vertices;
// Clear pre-existing edges in the case this is an updating process.
Polygon.edges.Clear();
//If there is only one polygon, treat it as boundary
if (PolygonsSet.Count() == 1)
{
Polygon.isBoundary = true;
}
//If first and last point of vertices list are the same, remove last.
if (vertices.First().Equals(vertices.Last()) && vertices.Count() > 1)
{
vertices = vertices.Take(vertices.Count() - 1).ToList();
}
//For each point, creates vertex and associated edge and adds them
//to the polygons Dictionary
int vertexCount = vertices.Count();
// If valid polygon
if (vertexCount >= 3)
{
int newId = GetNextId();
for (var j = 0; j < vertexCount; j++)
{
int next_index = (j + 1) % vertexCount;
Vertex vertex = vertices[j];
Vertex next_vertex = vertices[next_index];
Edge edge = new Edge(vertex, next_vertex);
//If is a valid polygon, add id to vertex and
//edge to vertices dictionary
if (vertexCount > 2)
{
vertex.polygonId = newId;
next_vertex.polygonId = newId;
Polygon gPol = new Polygon();
if (polygons.TryGetValue(newId, out gPol))
{
gPol.edges.Add(edge);
}
else
{
Polygon.edges.Add(edge);
Polygon.id = newId;
polygons.Add(newId, Polygon);
}
}
AddEdge(edge);
}
}
}
}
and AddEdge method is;
public void AddEdge(Edge edge)
{
List<Edge> startEdgesList = new List<Edge>();
List<Edge> endEdgesList = new List<Edge>();
if (graph.TryGetValue(edge.StartVertex, out startEdgesList))
{
if (!startEdgesList.Contains(edge)) { startEdgesList.Add(edge); }
}
else
{
graph.Add(edge.StartVertex, new List<Edge>() { edge });
}
if (graph.TryGetValue(edge.EndVertex, out endEdgesList))
{
if (!endEdgesList.Contains(edge)) { endEdgesList.Add(edge); }
}
else
{
graph.Add(edge.EndVertex, new List<Edge>() { edge });
}
if (!edges.Contains(edge)) { edges.Add(edge); }
}
Code works fine, my only concern is performance.
I tried to simplify the polygons and even used convex hull to reduce the workload but in some cases I need to use the polygon as it is.
So any help will be appreciated...
In the code lines starting with if (vertexCount > 2), you test the vertext count, but the count did not change since the last test if (vertexCount >= 3). Drop this second if.
Then you create a new polygon with Polygon gPol = new Polygon();. This polygon gets replaced immediately afterwards by the out parameter in polygons.TryGetValue(newId, out gPol).
Either TryGetValue yields true, then gPol become the polygon found in the collection, or TryGetValue yields false and gPol becomes null. Do not assign gPol
Polygon gPol;
if (polygons.TryGetValue(newId, out gPol)) ...
Or use the C# 7.0 syntax
if (polygons.TryGetValue(newId, out Polygon gPol)) ...
In the else case, you should create a new polygon (because gPol is null). However, you can simplify this code, because the edge is added in both cases:
if (!polygons.TryGetValue(newId, out Polygon gPol)) {
gPol = new Polygon { id = newId };
polygons.Add(newId, gPol);
}
gPol.edges.Add(edge);
You also seem to confuse Polygon with gPol.
Since newId gets created before the for-loop, you can move the code finding or creating the polyon out of the loop
int vertexCount = vertices.Count();
if (vertexCount >= 3)
{
int newId = GetNextId();
if (!polygons.TryGetValue(newId, out Polygon gPol)) {
gPol = new Polygon { id = newId };
polygons.Add(newId, gPol);
}
for (var j = 0; j < vertexCount; j++)
{
int next_index = (j + 1) % vertexCount;
Vertex vertex = vertices[j];
Vertex next_vertex = vertices[next_index];
Edge edge = new Edge(vertex, next_vertex);
vertex.polygonId = newId;
next_vertex.polygonId = newId;
gPol.edges.Add(edge);
AddEdge(edge);
}
}
In AddEdge you are repeating the same error of overwriting the lists just created by TryGetValue.
So if I have two vertices in a graph, and they are connected through more than one edge while having the same shortest path between them (i.e if I have node A and node B and they are connected directly through three edges (there are 3 shortest paths between them each of distance 1) so the count should return 3) How can I modify the BFS algorithm to achieve that? This is my code, it only compute the shortest path between 2 nodes but not the number of these shortest paths.
public void BFSDegree(Graph g, string s, string p)
{
Queue<string> q = new Queue<string>();
dist.Add(s, 0);
q.Enqueue(s);
while (q.Count() != 0)
{
string j = q.Dequeue();
foreach (string h in g.adjacentTo(j))
{
if (!dist.ContainsKey(h))
{
q.Enqueue(h);
dist.Add(h, 1 + dist[j]);
}
if (j == p)
{
Console.WriteLine(" " + dist[j]);
return;
}
}
}
}
If a node u has x shortest paths, then an adjacent node v discovered thru it, would have x times y shortest paths, where y is the number of edges from u to v. Moreover, if v is reachable thru other adjacent nodes (with the same path length), then its count of shortest paths would be the sum of all the xy factors computed for each parent.
So the algorithm would be quite different than your prototype. I would suggest a main loop which increases the current length in each iteration, and then process the queue by looking at all the unvisited adjacent nodes of the nodes in the queue, computing the sum of xy factors for each of these adjacent nodes, and then clearing the queue and enqueing all the adjacent nodes (and marking them as visied) for the next iteration. In the first iteration the path length is 0 and the queue contains only the source node.
public void BFSDegree(Graph g, string s, string p)
{
Queue<string> q = new Queue<string>();
HashMap<string, int> path_counts = new HashMap<string, int>();
path_counts.put(s, 1);
q.Enqueue(s);
while (q.size()>0)
{
HashMap<string, int> adj_nodes = new HashMap<string, int>();
foreach (string j in q)
{
foreach (string h in g.adjacentTo(j))
{
if (!path_counts.ContainsKey(h))
{
int count = 0;
if (adj_nodes.containsKey(h))
count=adj_nodes.get(h);
count += path_counts.get(j);
adj_nodes.put(h, count);
}
}
}
if (adj_nodes.containsKey(p))
{
Console.WriteLine(" " + adj_nodes.get(p));
return;
}
path_counts.putAll(adj_nodes);
q.clear();
q.addAll(adj_nodes.keySet());
}
}
Before the foreach, init a variable int pathCount = 0;
Then, instead of the return; increment pathCount.
After the foreach, check if pathCount > 0 and if so, return it. Of course you have to change your return type to int.
I'm trying to implement an A star searching method for a college work, so i kinda need to make it from scratch, but I'm having some trouble making it work the correct way.
Here's a picture of my problem:
As you can see, it does find a path, but not the easiest one.
Here is my implement:
public List<node> updateAstar(){
//clear all the lists
openedNodes.Clear();
closedNodes.Clear();
nodesToLook.Clear();
//check if starpos and endpos are okay
if(startPos!=null && endPos!=null){
float F;
node currentNote=Grid.getNodeAtPos(startPos);
openedNodes.Add(currentNote);
int i = 0;
int size = 100;
while(currentNote.type!=tilesType.END){
if(i<=size){ //debugging purpose. prevent infinite loop
nodesToLook.Clear();
foreach(node nearNode in currentNote.getNearestTiles()){
if(closedNodes.Find(r => ((r.pos.x==nearNode.pos.x)&&(r.pos.y==nearNode.pos.y)))==null){
nodesToLook.Add(nearNode);
}
}
float bestValue=float.PositiveInfinity;
node bestNode=new node();
foreach(node lookingNode in nodesToLook){
//check if current node is not on the closed list
if((closedNodes.Find(r => ((r.pos.x==lookingNode.pos.x)&&(r.pos.y==lookingNode.pos.y)))==null)
&&(openedNodes.Find(r => ((r.pos.x==lookingNode.pos.x)&&(r.pos.y==lookingNode.pos.y)))==null)
&& lookingNode.type!=tilesType.BLOCK){
//calculate F=G+H
//assume path number is 0 for the question purpose
F=lookingNode.G[pathNumber]+lookingNode.H[pathNumber];
if(F<bestValue){
bestValue=F;
bestNode=lookingNode;
}else
closedNodes.Add(lookingNode);
}
}
openedNodes.Add(bestNode);
currentNote=bestNode;
i++;
}else{
Debug.Log("Error getting better path");
break;
}
}
}else Debug.Log("Current path does not have an startpos nor endpos");
return openedNodes;
}
Here is how I instantiate each node (I save it on a matrix):
coordinate posAux=new coordinate();
this.myNodes=new node[columnNumber,lineNumber];
this.lineNumber=lineNumber;
this.columnNumber=columnNumber;
for(int y=0;y<lineNumber;y++){ // Y Desce = linhas
for(int x=0; x<columnNumber; x++){ // X vai pro lado = colunas
//create a node based on matrix position
posAux.Set(x, y);
tilesType type;
node current=new node(posAux);
//update up and left nodes
//"nodeDireita" means rightNode and "nodeEsquerda" means left node
if(x-1>=0){
current.nodeEsquerda=myNodes[x-1, y];
myNodes[x-1, y].nodeDireita=current;
}
if(y-1>=0){
current.nodeAcima=myNodes[x, y-1];
current.nodeAcima.nodeAbaixo=current;
}
//UNity stuff to set type of node visually based on what object is in it
Collider[] colliders;
if((colliders = Physics.OverlapSphere(coordinate.gridToUnity(posAux), 3f)).Length >0){
foreach(Collider collider in colliders){
objScript obj = collider.gameObject.GetComponent<objScript>();
current.type=obj.type;
if(current.type==tilesType.START){
path Path = new path (obj.pos, obj.posEnd, this);
addPath (Path);
Path.numeroPath=paths.IndexOf(Path);
}
}
}
myNodes[x,y]=current;
}
}
//adicionar vetor[] para H e G com numero de paths nos nodes
//create a vector for multiple paths in each node
int numeroPaths = paths.Count;
for (int y = 0; y < lineNumber; y++) {
for (int x = 0; x < columnNumber; x++) {
myNodes [x, y].H=new float[numeroPaths];
myNodes [x, y].G=new float[numeroPaths];
}
}
//adicionar Heuristica e G para cada node em cada path
//calculate heuristic and G for each node in each path
foreach (path Path in paths) {
coordinate start=Path.startPos, end=Path.endPos;
int numeroPath=paths.IndexOf(Path);
for (int y = 0; y < lineNumber; y++) {
for (int x = 0; x < columnNumber; x++) {
coordinate pos = myNodes [x, y].pos;
//G e H as manhattan distance
/*Mathf.Sqrt(Mathf.Pow((start.x - pos.x), 2) + Mathf.Pow((start.y - pos.y), 2)); euclidian-does not apply x.x */
myNodes [x, y].H[numeroPath]=Mathf.Abs(pos.x-end.x) + Mathf.Abs(pos.y-end.y);
myNodes [x, y].G[numeroPath]=Mathf.Abs(start.x-pos.x) + Mathf.Abs(start.y-pos.y);
}
}
}
Code refs:
--node is a custom class that holds "G" and "H" that I use the Manhattan formula to define, "x", "y", "BLOCK" or "NORMAL"(availability of the position)
--openedNodes is a List that I put the correct nodes for the path
--closedNodes are the nodes I checked, but have bigger "F" values;
--nodesToLook are the neighbor nodes for checking.
I appreciate any help.
Thanks.
Since you haven't posted your whole code, i have not the slightest clue what you are doing with your nodes, but what i can see:
You are not updating G. G is not a heuristic, it is the actual cost of reaching that node. Aka: nextTile.G = currentTile.G + distanceBetween(currentTile,nextTile)
You are only adding the best option to the open list. So instead of checking all 4, you only check 1
i can go on, but your whole algorithm does not work like A*, at all. Fixing your code means rewriting it completely.
The algorithm is realy easy. The pseudocode on wikipedia can be copied and implemented directly. It just seems you missed a couple of steps there and implemented a lot of things incorrectly.
Small bit of background first. I am developing a system that generates a "route" between locations. Locations have a pre-defined list of neighbours not limited to those adjacent to it. The search can safely assume that by picking the closest neighbour (numerically) to the target destination, it is making the optimal move towards it.
I have working code as shown below:
public Route GetRoute(int StartPoint, int Destination)
{
Route returnRoute = new Route();
returnRoute.steps = new List<int>();
bool locationReached = false;
int selectedNeighbour;
int distanceFromTarget;
int currentPoint = StartPoint; // set the current point to the start point
while (!locationReached)
{
selectedNeighbour = 0;
distanceFromTarget = 5000; // nominal amount guaranteed to be overwritten
var neighbours = locations.FirstOrDefault(l => l.LocationID == currentPoint).Neighbours;
for (int i = 0; i < neighbours.Length; i++)
{
// get the current neighbours, then check proximity
int currentNeighbour = neighbours[i];
int tempDistance = Math.Abs( currentNeighbour - Destination );
// if nearer than previous neighbour, set it as the chosen location
if ( tempDistance < distanceFromTarget )
{
distanceFromTarget = tempDistance;
selectedNeighbour = currentNeighbour;
// if the selected neighbour is the destination, we're done
if ( selectedNeighbour == Destination )
locationReached = true;
}
} // for
// add the selected neighbour if we found one
if ( selectedNeighbour != 0 )
{
currentPoint = selectedNeighbour;
returnRoute.steps.Add(selectedNeighbour);
}
else
{
Debug.Log ("No Route Found");
return returnRoute;
}
} // while
return returnRoute;
}
My question is regarding the loop of the neighbours (int[]) variable. How can this best be optimised? I've seen some use of linq and ordering, but also comments that this approach might be inefficient. I need efficiency over neatness here.
Many thanks.