I have a 2D grid in my game made up of nodes. I have enemies which follow players using the A* pathfinding algorithm (using the diagonal distance heuristic for H as diagonal movement is allowed).
The pathfinding works nearly all of the time however, when a player and an enemy are on exactly opposite (diagonally, vertically, or horizontally) sides of a wall, the enemy becomes stuck and stops moving.
From the below screenshot you can see the path found in this scenario, for some reason, a node in the opposite direction of the path is also being added to the path:
Below is my code for my F, G and H calculations (in my node class):
// Calculates distance cost from current node to an adjacent node.
public void CalculateG(Node nodeTarget)
{
// If the node is diagonal to the current node, its cost of movement is higher.
if (TileX != nodeTarget.TileX && TileY != nodeTarget.TileY)
{
intG = 14;
}
else intG = 10;
}
// Calculates H cost using the diagonal shortcut method.
public void CalculateH(Node nodeTarget)
{
int intXDiff = Math.Abs(TileX - nodeTarget.TileX);
int intYDiff = Math.Abs(TileY - nodeTarget.TileY);
if (intXDiff > intYDiff)
intH = 14 * intYDiff + 10 * (intXDiff - intYDiff);
else intH = 14 * intXDiff + 10 * (intYDiff - intXDiff);
}
public void CalculateF()
{
intF = intG + intH; // F = G + H
}
The code for my pathfinding class is shown below:
public class Path
{
private List<Node> PathOfNodes; // Stores the path of nodes to follow to the destination.
public List<Node> NodePath
{
get { return PathOfNodes; }
}
// Constructor takes the starting node, destination and the grid to generate the path.
public Path(Node Start, Node Destination, GridLayer grid)
{
List<Node> openNodes = new List<Node>(); // Creates a list of possible nodes for the path.
List<Node> closedNodes = new List<Node>(); // Creates a list of nodes confirmed for the path.
openNodes.Add(Start); // Step 1: Adds the current node to the possibilities list.
// Loops while the destination is not on the closed list and while the open nodes list is not empty.
while (!closedNodes.Contains(Destination) && openNodes.Any())
{
// Sorts the open list according to f scores.
openNodes.Sort((node, otherNode) => node.F.CompareTo(otherNode.F));
Node nodeCurrent = openNodes[0]; // The node with the lowest F score is set as the current node.
openNodes.Remove(nodeCurrent);
closedNodes.Add(nodeCurrent); // The current node is moved to the closed list.
// Creates a list containing all the nodes adjacent to the current node.
List<Node> adjacentNodes = AddAdjacentNodes(grid, nodeCurrent);
CheckAdjacentNodes(adjacentNodes, ref closedNodes, ref openNodes, nodeCurrent, Destination);
}
EstablishPath(closedNodes);
}
// Adds all the adjacent nodes from above the current node turning clockwise.
public List<Node> AddAdjacentNodes(GridLayer grid, Node nodeCurrent)
{
int intCol = nodeCurrent.TileX / nodeCurrent.TileWidth;
int intRow = nodeCurrent.TileY / nodeCurrent.TileHeight; // Gets the current node's indices.
List<Node> adjacentNodes = new List<Node>(); // Stores the nodes adjacent to the current node.
// The if statements check whether the node is within the grid before adding the node.
if (intRow - 1 >= 0)
adjacentNodes.Add(grid.Nodes[intCol, intRow - 1]); // Above
if ((intCol + 1 < 21 && intRow - 1 >= 0) && (grid.Nodes[intCol + 1, intRow].Traversable) && (grid.Nodes[intCol, intRow - 1].Traversable))
adjacentNodes.Add(grid.Nodes[intCol + 1, intRow - 1]); // Diagonally Right Up
if (intCol + 1 < 21)
adjacentNodes.Add(grid.Nodes[intCol + 1, intRow]); // Right
if (intCol + 1 < 21 && intRow + 1 < 12 && (grid.Nodes[intCol + 1, intRow].Traversable) && (grid.Nodes[intCol, intRow + 1].Traversable))
adjacentNodes.Add(grid.Nodes[intCol + 1, intRow + 1]); // Diagonally Right Down
if (intRow + 1 < 12)
adjacentNodes.Add(grid.Nodes[intCol, intRow + 1]); // Below
if (intCol - 1 >= 0 && intRow + 1 < 12 && (grid.Nodes[intCol - 1, intRow].Traversable) && (grid.Nodes[intCol, intRow + 1].Traversable))
adjacentNodes.Add(grid.Nodes[intCol - 1, intRow + 1]); // Diagonally Left Down
if (intCol - 1 >= 0)
adjacentNodes.Add(grid.Nodes[intCol - 1, intRow]); // Left
if (intCol - 1 >= 0 && intRow - 1 >= 0 && (grid.Nodes[intCol - 1, intRow].Traversable) && (grid.Nodes[intCol, intRow - 1].Traversable))
adjacentNodes.Add(grid.Nodes[intCol - 1, intRow - 1]); // Diagonally Left Up
return adjacentNodes;
}
// Checks the adjacent node list for nodes to be added to the open list/closed list.
private void CheckAdjacentNodes(List<Node> adjacentNodes, ref List<Node> closedNodes, ref List<Node> openNodes, Node nodeCurrent, Node destinationNode)
{
foreach (Node node in adjacentNodes)
{ // Checks each node to see if it is traversable and not already on the closed list.
if (node.Traversable && !closedNodes.Contains(node))
{
// If the node is not on the open list, add it, set its parent as the current node and calculate its F, G, and H values.
if (!openNodes.Contains(node))
{
openNodes.Add(node);
node.Parent = nodeCurrent;
node.CalculateG(nodeCurrent);
node.CalculateH(destinationNode);
node.CalculateF();
}
else // If the node was already on the open list...
{
// If its G cost of the node is lower than its parent + its own...
if (node.G < node.G + node.Parent.G)
{
// Make the node's parent the current node and recalculate its values.
node.Parent = nodeCurrent;
node.CalculateG(nodeCurrent.Parent);
node.CalculateF();
}
}
}
}
}
private void EstablishPath(List<Node> closedNodes)
{
PathOfNodes = new List<Node>(); // Stores the path for the entity to follow to its target.
for (int intNodeIndex = closedNodes.Count - 1; (intNodeIndex > 1); intNodeIndex--)
{
// Starting from the top of the closed list, add each node's parent to the path
// until the starting node is reached (index is 0).
PathOfNodes.Add(closedNodes[intNodeIndex].Parent);
}
PathOfNodes.Remove(null); // Remove the final null node (as the final node has no parent).
}
}
I believe there is a problem with the comparision between a node's G score and the G score of itself and its parent. I'm not sure if I am recalculating the G score using the correct adjacent node after the comparsion. If someone could make this clearer it would be really helpful. The article I followed was:
https://www.gamedev.net/resources/_/technical/artificial-intelligence/a-pathfinding-for-beginners-r2003
however, I don't think I have implemented this step into my code correctly:
If it is on the open list already, check to see if this path to that square is better, using G cost as the measure. A lower G cost means that this is a better path. If so, change the parent of the square to the current square, and recalculate the G and F scores of the square.
Please let me know of any additional information needed for this problem.
Thanks in advance.
Edit: I don't have any collision detection set up for enemies colliding with walls. The path is followed by the enemy moving towards the last node in the node path list.
Edit: My G calculation is wrong, the scores are not accumulated.
After correctly accumulating the G scores, it's now finding paths in all 4 directions (with the heuristic set to 0). I'm guessing this means that all the closed list nodes are being added to my final path meaning a problem in my establish path method.
The red numbers show the f score of the node, the blue numbers show the f score of the parent of the node (so you can tell which node is its parent).
After correctly accumulating the G scores, it's now finding paths in all 4 directions (with the heuristic set to 0).
Edit: I've fixed the problem. I'll submit an answer shortly but basically, my establish path method didn't do what it should. Instead of adding everything from the closed list, it was supposed to start at the end of the closed list follow through the chain of parents from the end until the starting node was added.
I do see some flaws in your code. This might not be the problem, but let's hope it is.
When you're checking if the node is better than other node in the openlist, you're doing something weird. That if-statement never even triggers, because something is never smaller than itself + a positive integer. Also you didn't even set the G-cost of the node yet. So therefore you can't check it either. This piece of code probably would result in a error (unless G has a standard value).
However I think this piece of code doesn't even get reached. Because I suspect that this piece of code allways triggers:
if (!openNodes.Contains(node))
You know why?
I can see the idea your trying to achieve, but there isn't another exact copy of the node in the openset. First of all because the nodes in the openset have their G, H and F cost set, and the node you're checking doesn't (so they're not the same). Thereby if the had the same data in them, I think it still wouldn't trigger because both nodes have another location in your computer (not 100% sure about this part). This also goes for checking if the node is in the closedset.
What you should be doing is check if there is a node in the openlist with the same location as the node you're checking. If the node is already in the list, than you should calculate the G-cost of the node we're dealing with. And check if the G-cost is smaller than the G-cost currently in the list, and change parent etc accordingly.
For the rest your code seems fine, although it's often hard to see mistakes in pathfinder. Hope this help, if you have any questions don't hesitate to ask.
First of all, thanks to J4stM4rt for identifying that I was making a useless comparison (integerA < integerA + positive integer) and Eric Lippert for suggesting that I should set the heuristic to 0 to identify the problem.
My first problem was that I was not accumulating the g-scores for the path. In my G score calculation method I needed to have intG = 10 (or 14) + targetNode.G.
The second problem was the problem mentioned by J4stM4rt in that I was checking if the node's g score was greater than itself + another node's g score which would always be true.
The final problem was in the establish path method, I was adding the parent of every node in the closed list to the path instead of starting at the end and adding each subsequent parent to the path until I reached the start. Below you can see the corrections to my code:
G-Score calculation:
// Calculates distance cost from start node to an adjacent node.
public void CalculateG(Node nodeTarget)
{ // The g-score is cumulative so the target node's score is also added.
// If the node is diagonal to the current node, its cost of movement is higher.
if (TileX != nodeTarget.TileX && TileY != nodeTarget.TileY)
{
intG = nodeTarget.G + 14;
}
else intG = nodeTarget.G + 10;
}
Establish path method:
private void EstablishPath(List<Node> closedNodes, Node nodeStart, Node nodeDestination)
{
PathOfNodes = new List<Node>(); // Stores the path for the entity to follow to its target.
// Start at the end of the path.
Node nodeToAdd = closedNodes[closedNodes.Count - 1];
while (!PathOfNodes.Contains(nodeStart))
{ // Add each node's parent until the start is reached.
PathOfNodes.Add(nodeToAdd);
nodeToAdd = nodeToAdd.Parent;
}
PathOfNodes.Remove(null); // Remove the final null node (as the final node had no parent).
}
G score comparison (this may still actually be wrong, I tried changing it to (node.G (cost of moving from the current node's parent to this node) < nodeCurrent.G (cost of moving from the current nodes parent to the current node) + calculation of g for moving from current node to this node) however, this resulted in my enemies becoming stuck again so I changed it to the following and it works seemingly ok:
if (node.G < nodeCurrent.G)
{
// Make the node's parent the current node's parent and recalculate its values.
node.Parent = nodeCurrent;
node.CalculateG(nodeCurrent);
node.CalculateF();
}
Related
I am trying to calculate difference in numbers between 2 Points, from a list of about 90 points. The code I have so far:
int positiveCounter = 0;
int positiveResetCounter = currentWayPointID;
int negativeCounter = 0;
int negativeResetCounter = currentWayPointID;
while ((currentWayPointID + positiveResetCounter) != pos)
{
positiveCounter++;
positiveResetCounter++;
if(positiveResetCounter > navigationTrack.AllWayPoints.Count)
{
positiveResetCounter = 0;
}
}
while((currentWayPointID+negativeResetCounter) != pos)
{
negativeCounter++;
negativeResetCounter--;
if(negativeResetCounter < 0)
{
negativeResetCounter = navigationTrack.AllWayPoints.Count;
}
}
if(positiveCounter <= negativeCounter)
{
MoveForward();
}
else if(negativeCounter < positiveCounter)
{
// MoveBack();
}
This works as intended but its too much for update to handle. How can I do this in less taxing way?
To give bit more context I have list of waypoints and vehicle that moves on each the vehicle moves to the point that is closest to my mouses position.
The path is circular so last waypoint connects first first(index 0).
I am trying to determine shortest path to each waypoint in order to go forward or back and the code above is my attempt at calculating which way to go.
I am not looking for a way to make it move as that already works.
I assume pos is the target index of the waypoint you want to reach.
instead of the while loops and index shifting you could simply compare the indexes directly:
Lets say you have the waypoint list like
[WP0, WP1, WP2, WP3, WP4, ... WPn]
so the available indexes are 0 to n, the list length n+1
Lets say currentWayPointID = n and pos = 2.
What you want to know is whether it is faster to go backwards or fowards. So you want to compare which difference is smaller:
going backwards
n - 2 // simply go steps backwards until reaching 2
or going forwards using a virtual extended list
(n+1) + 2 - n; // add the length of the list to the target index
or to visiualize it
[WP0, WP1, WP2, WP3, WP4, ... WPn]
index: 0, 1, 2, 3, 4, ... n
extended index: n+1+0, n+1+1, n+1+2, n+1+3, n+1+4, ... n+n+1
So in order to generalize that you only have to check first whether the currentwaypointID is before or after pos something like
bool isForwards = true;
if(currentwaypointID >= pos)
{
if(currentwaypointID - pos < navigationTrack.AllWayPoints.Count + pos - currentwaypointID)
{
isForwards = false;
}
}
else
{
if(pos - currentwaypointID > navigationTrack.AllWayPoints.Count + currentwaypointID - pos)
{
isForwards = false;
}
}
if(isForwards)
{
MoveForward();
}
else
{
MoveBack();
}
I am trying to repair graphs by deleting one edges. The only problem I am running in to is, when there are multiple cycles in the graph for example: 0 3, 2 3, 0 2, 1 2, 3 1. This can be fixed by extracting 3 1, but how do I let the program no that 3 1 is the edge that has to be removed?
Any suggestions? :)
Formatted code from comment...
...
else if (backedges.Count > 1)
{
foreach (Side side in backedges)
{
Node end = Side.node2;
Node begin = Side.node1;
List<Side> allsidesycle = new List<Side>();
while (begin != Side.node2)
{
end = begin;
begin = begin.pi;
Side be = new Side(begin, end);
allsidescycle.Add(be);
}
To find cycles you may want to use breadth first search (bfs).
Using bfs on unweighted (or equally weighted) graph grantees that then the first time a node is visited is the shortest path to that node.
If you visit it for the second time - you have a cycle. To remove it modify the graph by deleting that 2nd edge.
https://vimeo.com/70999946
I implemented a recursive path finding algorithm. This recursive algorithm works based on a pre-set of nodes connected together. Each node has four pointers containing further direction: Top, Button, Left and Right. The recursive algorithm simply walks through each node and looking for each of these four directions one by one toward reaching its final destination; an illustration, consider the following 7 nodes: A, B, C, D, E, F, G, H.
A (Button->D, Right->B)
B (Right->C, Left->B)
C (Left->B)
D (Button->G, Right->E, Top->A)
E (Right->F, Left->D)
F (Left->E)
G (Right->H, Top->D)
H (Left->G)
These nodes when comes to an overall view will display the following figure.
A—B—C
|
D—E—F
|
G—H
In this example, suppose the walker started node is Node A and wants to go to Node H as its final destination. Node A looks at its own Right, Button, Left and Top by order; its right pointed to Node B as a result he chooses to go to Node B; Node B in same pattern chooses to go to its right, Node C. When the walker reaches node C; as its Right, Top and Button is blocked, Node C reverts back to Node B. As well node B reverts back Node A. The walker comes back to the start point again. Then Node A goes to its button node base on the order; which means it goes to Node D. Node D goes to its right Node E and then Node F. As Node F is blocked; it goes back to Node E and Node D. Afterwards, Node D chooses to go its button, Node G according to walker order. From there Node G goes to Node H. Finally, the walker reaches its final destination.
Pseudocode: Recursive Path Finding Algorithm
ArrayList findPath(GameObject currentPoint , GameObject targetPoint , ArrayList InputArrayList)
{
1-Duplicate InputArrayList as tempArrayList
2-If the currentPoint equals to target Point return inputArrayList
//*** End Condition found target
3-If the Right side of the currentPoint is empty goto step 4
3.1- Add currentPoint to tempArrayList
//*** Call Right
3.2- tempArrayList = findPath(currentpoint.Right, targetPoint, tempArrayList);
3.3- If tempArrayList is not null return tempArrayList
4-If the Button side of the currentPoint is empty goto step 5
4.1- Add currentPoint to tempArrayList
//*** Call Button
4.2- tempArrayList = findPath(currentpoint.Button, targetPoint, tempArrayList);
4.3- If tempArrayList is not null return tempArrayList
5-If the Left side of the currentPoint is empty goto step 6
5.1- Add currentPoint to tempArrayList
//*** Call Left
5.2- tempArrayList = findPath(currentpoint.Left, targetPoint, tempArrayList);
5.3- If tempArrayList is not null return tempArrayList
6-If the Top side of the currentPoint is empty goto step 7
6.1- Add currentPoint to tempArrayList
//*** Call Top
6.2- tempArrayList = findPath(currentpoint.Top, targetPoint, tempArrayList);
6.3- If tempArrayList is not null return tempArrayList
7-Return null;
//*** End Condition does not found target
}
Note: The actual code is in C#, You can download it from this link.
Rise of problem in the case study:
As you understand this code; it has a weakness, to illustrate it; considering the following overall view of nodes, with the assumption that the start node is Node A and the final destination is Node H.
A—B—C
|
D—E—F—I
| | |
G—H—J—K
Though the best path solution is (A, D, G, H), The explained recursive path finding algorithm finds (A, D, E, F, I, K, J, H) as its solution; this really seems the Robot is a stupid robot :D !
Figure 1: The recursive path finding algorithm
Figure 2: The recursive path finding algorithm with the ability to learn
I resolved the problem by adding the learning ability for the nodes. You could see from this link the details of issue. But, I would wonder if anybody could revise the recursive algorithm to found the shortest path.
Thank you,
Why not simply compare it to Dijkstra and A* search?
Note by using recursion instead of a loop, you're likely to get a StackOverflow at 1025 recursions.
You are doing a depth-first search, when what you want to be doing is a breadth-first search. The latter will require a queue rather than recursion. The wiki page does a good job of explaining how to implement it, so I won't repeat that here.
From there, it would not be much work to implement A*, which should speed up your results. That will require a priority-queue rather than a queue; C# does not have a priority-queue in the base library, but, as luck would have it, I am the author of an optimized C# priority-queue implementation meant specifically for use in pathfinding.
Also, since you mentioned Unity, I'll point out that there are a number of pathfinding libraries built especially for Unity. This is probably the safest route, as efficient pathfinding in video games is non-trivial.
Here is a depth-first (quicker to write) version of what you need. I don't recommend this answer to path-finding. BlueRaja and Geoffrey's answers are much more generalizable, stable, and all around better algorithms for path finding. But I wanted to answer the OP's direct question.
To simplify my example, edges have cost/weight 1. The shortest path == path w/ least number of nodes to reach the target (Technically length == |path| - 1 since I'm counting nodes and not edges but whatever, the idea is the same). This code won't handle cycles, that's why other algorithms are a better idea.
public class PathNode {
public string Id;
public PathNode[] Children = new PathNode[4];
}
public class PathFinder : MonoBehaviour {
public List<PathNode> shortestPath = null;
/// <summary>
/// Sets shortest path to `candidatePath` if `candidatePath` is shorter
/// or no shortest path yet.
/// </summary>
public void SetShortestPath(List<PathNode> path){
if(shortestPath == null){
shortestPath = new List<PathNode>(path);
return;
}
if(path.Count < shortestPath.Count)
shortestPath = new List<PathNode>(path);
}
/// <summary>
/// depth-first shortest path
/// </summary>
public void FindShortestPath(PathNode target, List<PathNode> path){
PathNode next = path[path.Count-1]; //get next node from path
if(target == next){
SetShortestPath(path);
return;
}
// dfs - iterate children
foreach (PathNode node in next.Children){
if(node!=null){
path.Add(node);
FindShortestPath(target,path);
path.Remove(node);
}
}
}
public PathNode ExampleEdgeCreation(){
PathNode a = new PathNode{Id="A"};
a.Children[(int)Direction.Left] = new PathNode{Id="B"};
return a;
}
}
Assume that PathNode.Children[0] == PathNode.Children[Left] etc. I enumerated them in code but I wanted to keep this example small for SO.
I have to search a list of structs for every item whose XZPos is closer to Vector2 (or PointF) P. The list is ordered by XZPos' x and y. It'll look something like this:
Item 1 (XZPos: 0,0)Item 2 (XZPos: 0,1)Item 3 (XZPos: 0,2)...Item 12 (XZPos: 1,0)Item 13 (XZPos: 1,1)Item 14 (XZPos: 1,2)...2.249.984 elements later...
Now I have a point P (4,4) and I want a list of structs in the above list of every item closer to P than 5,66f. My algorithm searches every item in the list like this:
List<Node> res = new List<Node>();
for (int i = 0; i < Map.Length; i++)
{
Vector2 xzpos = new Vector2(Map[i].X, Map[i].Z);//Map is the list containing 2.250.000 elements
//neighbourlength = 5,66f in our example
if ((xzpos - pos).Length() <= neighbourlength && xzpos != pos)//looking for every item except the item which is P itself, therefore checking whether "xzpos != pos"
{
res.Add(new Node() { XZPos = xzpos, /*Other fields*/ });
}
}
return res.ToArray();
My problem is that it takes way too long to complete, and now I'm looking for a way to find the fields I'm looking for without searching the entire list. 22 seconds for a search is TOO LONG. If someone could help me get it to 1 or 2 seconds that would be very nice.
Thanks for your help, Alex
Your list is sorted, and you can use that to shrink the problem space. Instead of searching the whole list, search the subset of the list that spans x values within 5,66f, since anything that is farther than the maximum on one coordinate will be farther than you want no matter what the other coordinate is. Then if you store the start positions of each value in the list (i.e. in your example, "0" elements start at 1, and "1" elements start at 12), you can quickly get to the part of the list you care about. So instead of iterating through items 0 to 2 million, you could instead be iterating through items 175839 to 226835.
Example:
The List
1: (0,1)
2: (0,2)
3: (1,0)
4: (1,1)
5: (2,1)
6: (2,3)
7: (3,1)
8: (3,5)
9: (4,2)
10: (4,5)
11: (5,1)
12: (5,2)
13: (6,1)
14: (6,2)
15: (6,3)
16: (7,1)
17: (7,2)
Start Locations
(0,1)
(1,3)
(2,5)
(3,7)
(4,9)
(5,11)
(6,13)
(7,16)
If I have a point (3,5) and I want to search the list for points within 2 of it, I only need to iterate through the points where x is between 1 and 5. So I look at my start locations, and see that 1 starts at position 3 in the list, and 5 ends at position (13 - 1). So instead of iterating from 1 to 17, I only need to iterate from 3 to 12. If the range of values in your data is large but the distance to check is short, this will reduce the number of entries you need to iterate across greatly.
To the below solution there's a few assumptions. These assumptions are not stated in your question an the solution might need adjustment. If the assumptions hold this solution is extremely fast but requires you to keep the data in a different structure. However if you can change the structure then this will have a look of for each valid point in (almost) constant time. That is the time required to find M valid points in a set containing M point in total depends only on N.
The assumptions are:
Only positive integers are used for
values of X and Y
No duplicate points in the list
`private IEnumerable> GetPointsByDistance(int xOrigin, int yOrigin, double maxDistance){
var maxDist = (int) Math.Floor(maxDistance);
//Find the lowest possible value for X
var minX = Math.Min(0, xOrigin - maxDist);
//Find the highest possible value for X
var maxX = Math.Min(MaxX, xOrigin + maxDist);
for (var x = minX; x <= maxX; ++x){
//Get the possible values for Y with the current X
var ys = points[x];
if (ys.Length > 0){
//Calculate the max delta for Y
var maxYDist =(int)Math.Floor(Math.Sqrt(maxDistance*maxDistance - x*x));
//Find the lowest possible Y for the current X
var minY = Math.Min(0, yOrigin - maxYDist);
//Find the highest possible Y for the current X
var maxY = Math.Min(ys.Length, yOrigin + maxYDist);
for (var y = minY; y <= maxY; ++y){
//The value in the array will be true if a point with the combination (x,y,) exists
if (ys[y]){
yield return new KeyValuePair<int, int>(x, y);
}
}
}
}
}`
The internal representation of the point in this code is bool[][]. The value will be true if the indices of the two arrays is a point included in the set. E.g. points[0][1] will be true for if the set was the six points you've included in the post and points[0][3] would be false.
If the assumptions are not met the same idea can still be used but will need tweeking. If you post what the assumptions should be I'll update the answer to the question.
An assumption that does not have to be met is that the points are close to sequential. If they are not this will be rather greedy when it comes to memory. Changes can be made if the points are not (close) to sequential and again post the invariants if my assumptions are wrong and I'll update.
I have no idea what your original code is doing. I see you've defined x and y and never use them and you refer to pos but it's not defined. So, instead I'm going to take the verbal description of the problem your trying to solve
Now I have a point P (4,4) and I want a list of structs in the above list of every item closer to P than 5,66f.
and solve it. This is easy:
var nearbyNeighbors = Map.Where(
point => (point.X - 4) * (point.X - 4) +
(point.Z - 4) * (point.Z - 4) <
5.66f * 5.66f
);
foreach(var nearbyNeighbor in nearbyNeighbors) {
// do something with nearbyNeighbor
}
Here, I am assuming that you are using the standard Euclidean norm.
To utilize the fact that the collection is lexicographically sorted:
Binary search until |point.X - 4| >= 5.66. This splits the list in two contiguous sublists. Throw out the sublist with |point.X - 4| >= 5.66. From the remaining sublist, binary search until |point.Y - 4| >= 5.66 This splits the remaining sublist into two contiguous sublists. Throw out the sublist with |point.Y - 4| >= 5.66. Then search linearly through the remaining sublist.
This problem is related to this:
http://en.wikipedia.org/wiki/Nearest_neighbor_search
Take a look for sub-linear solutions.
Also take a look at this question
which data structure is appropriate to query "all points within distance d from point p"
I am having some trouble correctly implementing the Bentley-Ottmann algorithm in C#. I am trying to implement it according to the pseudocode here. I have posted my main code below. Assuming my BST and PriorityQueue classes are implemented correctly, do you see any problems with the code?
There are no errors, but not all intersection points are found, only some. My guess is that there's an error in the else part of the code (when the current event is an intersection point). I'm not sure what the pseudocode means by swapping the position of two segments in the BST. Is the way I do it fine? Because in the end, the two aren't really swapped in the BST. I can't just change their positions either, because that could break the BST properties.
Also, am I right in assuming that segments are ordered in the BST by the Y-coordinate of their left endpoint?
Another error I've noticed that I can't seem to be able to track down is that sometimes the point (0, 0) gets into eventList. (0, 0) is outputted by Geometry.Intersects in case there is no intersection, but in that case the if conditions should stop it from getting added. I have no idea how it gets in. If I print the contents of eventList after adding a point in, (0, 0) never shows up. If I print the contents after extracting and popping an element, (0, 0) sometimes shows up. Could this have anything to do with the Pop() method messing with the references, or is it definitely a problem in my PriorityQueue implementation?
If needed I can show my implementations for the BST and priority queue as well.
static class BentleyOttman
{
private static void AddIntersectionEvent(PriorityQueue eventList, Segment segEv, Segment segA, SegPoint i)
{
i.IntersectingSegments = new Tuple<Segment, Segment>(segEv, segA);
i.Type = SegmentPointType.IntersectionPoint;
eventList.Add(i);
}
public static void Solve(Panel surface, TextBox debug)
{
debug.Clear();
var segList = Generator.SegList;
PriorityQueue eventList = new PriorityQueue();
foreach (Segment s in segList)
{
eventList.Add(new SegPoint(s.A, s, SegmentPointType.LeftEndpoint));
eventList.Add(new SegPoint(s.B, s, SegmentPointType.RightEndpoint));
}
BST sweepLine = new BST();
while (!eventList.Empty)
{
SegPoint ev = eventList.Top();
eventList.Pop();
if (ev.Type == SegmentPointType.LeftEndpoint)
{
Segment segEv = ev.Segment;
sweepLine.Insert(segEv);
Segment segA = sweepLine.InorderPre(segEv);
Segment segB = sweepLine.InorderSuc(segEv);
SegPoint i = new SegPoint();
if (segA != null && Geometry.Intersects(segEv, segA, out i.Point))
{
AddIntersectionEvent(eventList, segA, segEv, i);
}
if (segB != null && Geometry.Intersects(segEv, segB, out i.Point))
{
AddIntersectionEvent(eventList, segEv, segB, i);
}
}
else if (ev.Type == SegmentPointType.RightEndpoint)
{
Segment segEv = ev.Segment;
Segment segA = sweepLine.InorderPre(segEv);
Segment segB = sweepLine.InorderSuc(segEv);
sweepLine.Remove(segEv);
SegPoint i = new SegPoint();
if (segA != null && segB != null && Geometry.Intersects(segA, segB, out i.Point))
{
AddIntersectionEvent(eventList, segA, segB, i);
}
}
else
{
Generator.DrawPoint(ev.Point, surface, Brushes.Red);
Segment seg1 = ev.IntersectingSegments.Item1;
Segment seg2 = ev.IntersectingSegments.Item2;
sweepLine.Remove(seg1);
sweepLine.Remove(seg2);
Segment t = new Segment(seg1);
seg1 = new Segment(seg2);
seg2 = new Segment(t);
sweepLine.Insert(seg1);
sweepLine.Insert(seg2);
Segment segA = sweepLine.InorderPre(seg2);
Segment segB = sweepLine.InorderSuc(seg1);
SegPoint i = new SegPoint();
if (segA != null && Geometry.Intersects(seg2, segA, out i.Point))
AddIntersectionEvent(eventList, segA, seg2, i);
if (segB != null && Geometry.Intersects(seg1, segB, out i.Point))
AddIntersectionEvent(eventList, seg1, segB, i);
}
}
}
}
I really cannot understand your code without some idea of what exactly the other classes do, but I can answer some of your other questions.
The segments are ordered in the BST by the Y coordinate of their intersection with the sweep line. So when we encounter a left endpoint we add the segment to the tree using the y coordinate of the left endpoint of the entering segment (comparing it with the Y coordinate of the intersection of the other segment with the sweep line). When we encounter a right endpoint we remove the segment from the tree. When we encounter an intersection, then the order of the intersections of the two segments with the sweep line switches, so we swap the two segments in the tree. For example consider the two segments
A = {(-1,1),(1,-1)} and
B = {(-1,-1),(1,1)}
When the X coordinate of the sweep line is less than 0 then the intersection of segment A with the sweep line is greater than the intersection of segment B with the sweep line. and if the sweep line is greater than 0 the reverse is true. (Draw a picture.)
It is probably instructive to draw a simple example, and trace what is going on step by step, drawing the sweep line for each event and labeling the segments in columns between the events, then keeping track of the BST and verifying that the BST keeps the same order as the segments in the region where it is valid. (I'm sorry if that is not as clear as it could be.)
Note: This assumes that your segments are in "general position", i.e. that no segment is vertical, no more than two segments intersect at a given point, etc. Dealing with segments not in general position is outlined on the wikipedia page