compare coordinate/directional objects - c#

I have a grid and get a direction to move. This direction can be
Vector2Int movementDirection = new Vector2Int(/* this can be
(0,1) // up
(0,-1) // down
(-1,0) // left
(1,0) // right
*/);
Vector2Int is a class from the Unity Framework!
https://docs.unity3d.com/ScriptReference/Vector2Int.html
When moving from bottom to top I want to check all the cells around my targetCell. But I don't want to check the bottom cell because this cell is where I come from.
When I move from the left to the right, I don't want to check the left cell.
So I went for this
private void CheckCells(Vector2Int movementDirection)
{
Vector2Int[] cellDirections = { Vector2Int.up, Vector2Int.down, Vector2Int.left, Vector2Int.right };
cellDirections.Where(direction => !direction.Equals(movementDirection)).ToArray();
for (int i = 0; i < cellDirections.Length; i++)
{
// check the other 3 cells
}
}
This array will still have a length of 4. It seems I can not compare a Vector2Int.up with (0,1)
I tried
!direction.Equals(movementDirection)
and
directon != movementDirection
How can I start a loop for just 3 of 4 directions? The given parameter should remove the fourth direction from the array.
Maybe I don't need an array?

This line :
cellDirections.Where(direction => !direction.Equals(movementDirection)).ToArray();
does nothing to your cellDirections variable, as you don't assign the result.
Either try :
cellDirections = cellDirections
.Where(direction => !direction.Equals(movementDirection)).ToArray();
or any other assignment :
var anyVar = cellDirections
.Where(direction => !direction.Equals(movementDirection)).ToArray();
Note that you could also just loop through your cellDirections and add an if inside your for :
foreach (var direction in cellDirections)
{
if(!direction.Equals(movementDirection))
{
// check the cells
}
}

Related

Check if lines forming a loop

Suppose I have a simple class with properties of a vertical line on a 2D Cartesian coordinate plane such as this:
public class VerticalLine
{
int x, y1, y2;
...
}
Assume that Y1 can be smaller or larger than Y2, but Y1 and Y2 never contain the same value.
In runtime, I will gather a collection(List) of vertical lines in similar format:
List<VerticalLine> lines = new List<VerticalLine>
{
new VerticalLine() { X=12, Y1=3, Y2=15 },
new VerticalLine() { X=23, Y1=23, Y2=5 },
new VerticalLine() { X=32, Y1=12, Y2=10 },
new VerticalLine() { X=37, Y1=15, Y2=12 },
...
};
Note that there are no specific order of the lines in the list.
By drawing imaginary horizontal lines from one end of vertical line to an end of another line, I need to check that if the lines in the above list will be able to connect and form a loop. In other words, pick a line in list, connect one end to an end of another line using a horizontal line, and then to next line and so on... until the last line connect back to the original line. See image for example.
Note that:
ALL the lines in the list must be used.
The order of the lines list is not important in finding the loop. Any line can be the first line.
The directions of lines are not important in finding the loop, meaning Y2 of one line may try to connect to Y1 or Y2 of the next line, as long as they share the same Y value.
In simple words, my problem is how to write a method to check if lines can form a loop.
My first thought of doing so will be using a recursive method to exhaustively try connecting the next line. But this way will involve a large amount of list manipulations, which can be slow. I wonder if there are 'better' way to achieve the same goal.
This is a working solution I have so far. It is simple but probably not the most efficient way of doing so. It is just a recursive and exhaustive method.
public bool CheckForLoop (List<VerticalLine> lines)
{
if (lines == null || lines.Count < 2)
return false;
VerticalLine head = lines[0];
return CheckForLoop(head.Y1, head.Y2, lines.GetRange(1, lines.Count - 1));
}
// helper method for recursion
private bool CheckForLoop(int start, int last, List<VerticalLine> remaining)
{
if (remaining.Count == 1)
{
return remaining[0].Y1 == start && remaining[0].Y2 == last || remaining[0].Y2 == start && remaining[0].Y1 == last;
}
foreach (var next in remaining)
{
bool foundMatching = false;
int newLast = 0;
if (next.Y1 == last)
{
foundMatching = true;
newLast = next.Y2;
} else if (next.Y2 == last)
{
foundMatching = true;
newLast = next.Y1;
}
if (foundMatching)
{
List<VerticalLine> remaining2 = new List<VerticalLine>(remaining);
remaining2.Remove(next);
if (CheckForLoop(start, newLast, remaining2))
{
return true;
}
}
}
return false;
}
What I don't like about this is that it requires a separate helper method for the recursion. I would be best if everything is done within the one method itself.

C# + Unity - Getting the MIN value from a variables inside a List of Classes [duplicate]

This question already has answers here:
How to use LINQ to select object with minimum or maximum property value
(20 answers)
Return Item from List with Minimum Value
(3 answers)
Closed 2 years ago.
I have a list of this Class:
public class EachEnemy
{
public GameObject enemy;
public Vector3 coords;
public float distance;
}
And they calculate the 'distance' to a Ship.
As the enemies spawn and move, the distance variables changes all the time.
I need to find which is the smallest of those distances inside the List of that Class, and its index so I can target the Enemy object.
You can use Linq OrderBy like
using System.Linq;
...
// Wherever you get this from
List<EachEnemy> allEnemies;
// Sorted by the distance to this object
var sortedEnemies = allEnemies.OrderBy(enemy => (transform.position - enemy.coords).sqrMagnitude);
// Or depending on how your class is used simply
//var sortedEnemies = allEnemies.OrderBy(enemy => enemy.distance);
// This would return null if the list was empty
// Or the first element of the sorted lost -> smallest distance
var nearestEnemy = sortedEnemies.FirstOrDefault();
Note that using
OrderBy(enemy => (transform.position - enemy.coords).sqrMagnitude);
Would be more efficient than actual calculating the distances first since it skips using a square root on all the vector sqrMagnitudes. Since a > b also implies a² > b² and we know that a magnitude is always positive it is enough to know the sqrMagnitudes of all the delta vectors in order to sort them.
The advantage of this would be that you can also get the second and third closest enemies etc.
float smallestDistance = eachEnemyList[0].distance;
int index = 0;
for(int i = 1; i < eachEnemyList.Count; i++)
{
if(eachEnemyList[i].distance < smallestDistance)
{
smallestDistance = eachEnemyList[i].distance;
index = i;
}
}
This is probably not perfectly optimized but should do just fine. This gives you both the smallest distance and the index of the EachEnemy class containing the smallest distance.
You can go over each item in the list and save value outside the loop, and also save the index outside the loop, e.g.
EachEnemy someFuncname(List<EachEnemy> list)
{
int index = 0;
EachEnemy ea = null;
float distance = 9999 ///
foreach (var Enemy in list) {
if (Enemy.Distance < distance) {
ea = Enemy;
}
return ea;
}
You can also return the index in the same way.

Unity Finding difference between A and B in a list efficiently

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();
}

Get Series value from a mouse click

I use Microsoft.DataVisualization.Charting and want to get the value of the point when i click on it.
My problem: i want exactly that value i clicked, even if its only a value calculated by the Chart and between 2 points.
Example: 3 points: P(0;3), P(1;6), P(3;12)
When i click at x-Value 2 i want to get 9 as result if the line is linear.
Currently i do that:
HitTestResult[] hits = chart.HitTest(e.X, e.Y, false, ChartElementType.PlottingArea);
//DataInformation save the DateTime and Value for later use
DataInformation[] dinfo = new DataInformation[hits.Length];
foreach (ChartArea area in chart.ChartAreas)
{
area.CursorX.LineWidth = 0; //clear old lines
}
for (int i = 0; i < hits.Length; i++) //for all hits
{
if (hits[i].ChartElementType == ChartElementType.PlottingArea)
{
//val saves the x-value clicked in the ChartArea
double val = hits[i].ChartArea.AxisX.PixelPositionToValue(e.X);
DataPoint pt = chart.Series[hits[i].ChartArea.Name].Points.Last(elem => elem.XValue < val);
dinfo[i].caption = hits[i].ChartArea.Name;
dinfo[i].value = pt.YValues[0].ToString();
//hits[i].ChartArea.CursorX.Position = pt.XValue;
}
}
This show the right values for every existing data point but not that clicked point.
How can i get the exact value?
It seems, there is no way to get the exact value. I changed to OxyPlot. OxyPlot can show the data much faster and you can get the exact value for any point.

Grouping horizontal and vertical lines into table (C#)

A simple line class is define as two PointF members containing the start and end coordinates:
public class Line {
PointF s, e;
}
I have two lists containing all horizontal and vertical lines that appear on a drawing canvas and form one or more tables.
List<Line> AllHorizontalLines;
List<Line> AllVerticalLines;
I need to group these lines so that lines belonging to one table are captured in a single group, thus the grouping function would have a signature like this:
List<List<Line>> GroupLines(List<Line> hor, List<Line> ver)
{
}
For simplicity we are assuming that there are only "simple" tables on the page, i.e. no nested table are there. However there can be merged cells, so we have to ignore small horizontal and vertical lines that are smaller than the full height of the parent table. For further simplicity, assume that both input lists are sorted (horizontal lines w.r.t. Y-axis and vertical lines w.r.t. X-axis).
Is there any known algorithm to solve this problem? Or can anyone help me devise one?
Here is my suggested approach:
Clean both lists so there aren't any fully contained 'small' lines.
Pick any line.
Take all the lines that touch (intersect) this line.
For each of these lines take all the lines that touch them.
Continue until you can't find any more touching lines.
You now have a group.
Pick a line from those that remain and repeat until there are no more lines left.
Code:
public static IEnumerable<IEnumerable<Line>> Group(IEnumerable<Line> horizontalLines, IEnumerable<Line> verticalLines)
{
// Clean the input lists here. I'll leave the implementation up to you.
var ungroupedLines = new HashSet<Line>(horizontalLines.Concat(verticalLines));
var groupedLines = new List<List<Line>>();
while (ungroupedLines.Count > 0)
{
var group = new List<Line>();
var unprocessedLines = new HashSet<Line>();
unprocessedLines.Add(ungroupedLines.TakeFirst());
while (unprocessedLines.Count > 0)
{
var line = unprocessedLines.TakeFirst();
group.Add(line);
unprocessedLines.AddRange(ungroupedLines.TakeIntersectingLines(line));
}
groupedLines.Add(group);
}
return groupedLines;
}
public static class GroupingExtensions
{
public static T TakeFirst<T>(this HashSet<T> set)
{
var item = set.First();
set.Remove(item);
return item;
}
public static IEnumerable<Line> TakeIntersectingLines(this HashSet<Line> lines, Line line)
{
var intersectedLines = lines.Where(l => l.Intersects(line)).ToList();
lines.RemoveRange(intersectedLines);
return intersectedLines;
}
public static void RemoveRange<T>(this HashSet<T> set, IEnumerable<T> itemsToRemove)
{
foreach(var item in itemsToRemove)
{
set.Remove(item);
}
}
public static void AddRange<T>(this HashSet<T> set, IEnumerable<T> itemsToAdd)
{
foreach(var item in itemsToAdd)
{
set.Add(item);
}
}
}
Add this method to Line
public bool Intersects(Line other)
{
// Whether this line intersects the other line or not.
// I'll leave the implementation up to you.
}
Notes:
If this code runs too slowly you might need to scan horizontally, picking up connected lines as you go. Might also be worth looking at this.
Specialised:
public static IEnumerable<IEnumerable<Line>> Group(IEnumerable<Line> horizontalLines, IEnumerable<Line> verticalLines)
{
// Clean the input lists here. I'll leave the implementation up to you.
var ungroupedHorizontalLines = new HashSet<Line>(horizontalLines);
var ungroupedVerticalLines = new HashSet<Line>(verticalLines);
var groupedLines = new List<List<Line>>();
while (ungroupedHorizontalLines.Count + ungroupedVerticalLines.Count > 0)
{
var group = new List<Line>();
var unprocessedHorizontalLines = new HashSet<Line>();
var unprocessedVerticalLines = new HashSet<Line>();
if (ungroupedHorizontalLines.Count > 0)
{
unprocessedHorizontalLines.Add(ungroupedHorizontalLines.TakeFirst());
}
else
{
unprocessedVerticalLines.Add(ungroupedVerticalLines.TakeFirst());
}
while (unprocessedHorizontalLines.Count + unprocessedVerticalLines.Count > 0)
{
while (unprocessedHorizontalLines.Count > 0)
{
var line = unprocessedHorizontalLines.TakeFirst();
group.Add(line);
unprocessedVerticalLines.AddRange(ungroupedVerticalLines.TakeIntersectingLines(line));
}
while (unprocessedVerticalLines.Count > 0)
{
var line = unprocessedVerticalLines.TakeFirst();
group.Add(line);
unprocessedHorizontalLines.AddRange(ungroupedHorizontalLines.TakeIntersectingLines(line));
}
}
groupedLines.Add(group);
}
return groupedLines;
}
This assumes no lines overlap as it doesn't check if horizontal lines touch other horizontal lines (same for vertical).
You can probably remove the if-else. That's just there in case there are vertical lines not attached to horizontal lines.
The following seems to work:
Set up a dictionary mapping bounding rectangles to a list of lines in each rectangle.
For each line in both your input lists (we don't care about the direction)
Create a bounding rectangle out of the line
Check if the line crosses any existing bounding rectangle(s).
If so, merge the lines from those rectangle(s), add the current line, delete the touched rectangles, calculate a new bounding rectangle, and check again.
Otherwise, add this new rectangle to the dictionary and delete the old one(s).
Return the list of lines from each rectangle.
Here's the code I've got:
public static IEnumerable<IEnumerable<Line>> GroupLines(IEnumerable<Line> lines)
{
var grouped = new Dictionary<Rectangle, IEnumerable<Line>>();
foreach (var line in lines)
{
var boundedLines = new List<Line>(new[] { line });
IEnumerable<Rectangle> crossedRectangles;
var boundingRectangle = CalculateRectangle(boundedLines);
while (
(crossedRectangles = grouped.Keys
.Where(r => Crosses(boundingRectangle, r))
.ToList()
).Any())
{
foreach (var crossedRectangle in crossedRectangles)
{
boundedLines.AddRange(grouped[crossedRectangle]);
grouped.Remove(crossedRectangle);
}
boundingRectangle = CalculateRectangle(boundedLines);
}
grouped.Add(boundingRectangle, boundedLines);
}
return grouped.Values;
}
public static bool Crosses(Rectangle r1, Rectangle r2)
{
return !(r2.Left > r1.Right ||
r2.Right < r1.Left ||
r2.Top > r1.Bottom ||
r2.Bottom < r1.Top);
}
public static Rectangle CalculateRectangle(IEnumerable<Line> lines)
{
Rectangle rtn = new Rectangle
{
Left = int.MaxValue,
Right = int.MinValue,
Top = int.MaxValue,
Bottom = int.MinValue
};
foreach (var line in lines)
{
if (line.P1.X < rtn.Left) rtn.Left = line.P1.X;
if (line.P2.X < rtn.Left) rtn.Left = line.P2.X;
if (line.P1.X > rtn.Right) rtn.Right = line.P1.X;
if (line.P2.X > rtn.Right) rtn.Right = line.P2.X;
if (line.P1.Y < rtn.Top) rtn.Top = line.P1.Y;
if (line.P2.Y < rtn.Top) rtn.Top = line.P2.Y;
if (line.P1.Y > rtn.Bottom) rtn.Bottom = line.P1.Y;
if (line.P2.Y > rtn.Bottom) rtn.Bottom = line.P2.Y;
}
return rtn;
}
OK. I finally devised an efficient algorithm myself. Here are the steps for any future reader.
Define a grand collection of Tuple<List<Line>, List<Line>>. Each tuple in this collection will represent one table (all horizontal and vertical lines of that table).
Find all vertical lines that touch a horizontal line's starting point at one end and another horizontal line's starting point at the other end. These will be the left-most lines of each table.
Now for each left-line (call it LV):
a. locate all the vertical lines that have same starting and ending Y as LV and thus belong to the same table. Add these "sister" lines to a list called MyVSisters.
b. Locate the right-most vertical sister line's X-coordinate (call it RVX).
c. Now locate all the horizontal lines that stretch from the LV's X to RVX and have their Y between LV's Y1 and Y2. Add these lines to a collection called MyHSisters.
d. Add the tuple MyHSisters and MyVSisters to the grand collection.
Return grand collection.
I'm not marking it as answer yet. I'll wait for response from both of the guys and compare performance and accuracy of all 3 before deciding which one is the best answer.

Categories