I've been looking at this for far too long, hopefully someone can help.
The general jist is;
I have a SortedSet of player objects (sorted by Y position), each player contains a sortedset of 'Polygon' objects which in turn contain a list of three point objects, obviously forming a triangle.
This polygon set is sorted by area of said polygon, smallest to largest.
What I want to do is iterate through the set of players and;
I. Assign the point at index[1] (the 'peak' of the triangle) from the first object in the polygon SortedSet to another variable inside the player.
II. Once the point at index[1] has been assigned to a player, I need to iterate through every player and remove any instance of a polygon that contains the current point at ElementAt(1) so no subsequent players in the 'parent' iteration can be assigned a polygon that contains that Point.
It's probably important to note that the point at polygonPoints[1] is based on the enemy position so they're consistent across all players, this is why I'm trying to use it as my 'reference' point to remove any polygon objects that contain said point.
It's a convoluted explanation but hopefully you've been able to follow.
Right now I've got the first part working, but the second part is proving to be a serious pain - I've tried remove and removewhere (using criteria that should remove everything) and the set stubbornly stays the same length no matter what I do.
For reference here's the latest iteration of code that I'm wrangling with.
List<PointF> duplicates = new List<PointF>();
foreach (Player p in playerSet)
{
//Assign lowest cost polygon to current player, then remove all polygons containing the point at p.triangleSet.ElementAt(0).polygonPoints[1] from every other player so it can't be assigned to any subsequent player.
p.currentAttackingPoint = p.triangleSet.ElementAt(0).polygonPoints[1];
//I use this method to keep track of which 'attacking points' have been assigned.
add(p.currentAttackingPoint);
//Then, in theory, I use this method to remove all polygons that contain any point in the duplicates list from every other player. Obviously this is proving to be the troublesome aspect.
remove(duplicates);
}
...
private void add(PointF i)
{
duplicates.Add(i);
}
private void remove(List<PointF> dupes)
{
foreach(PointF p in dupes)
{
foreach (Player l in playerSet)
{
//Outputs 100
textBox3.AppendText(l.triangleSet.Count.ToString() + "\r\n");
l.triangleSet.RemoveWhere(e => e.polygonPoints[1] == p);
//l.pressingTriangleSet.RemoveWhere(e => e.polygonPoints[1].X > 0); --Doesn't work either, despite it being true of every point in the set.
//Still 100
textBox3.AppendText(l.triangleSet.Count.ToString() + "\r\n");
}
}
}
Please bear in mind that the set of polygons inside each player, whilst the same length all have largely different content, they're based on the position of the player the position of the enemy and another arbitrary fact about the game state - so I can't just remove the first polygon from each player's set.
I'm thinking about converting each set to a list once they've been created because this is bizarre, it's got to be some order of operations issue that I'm overlooking.
Purely for my own sanity I knocked this out http://pastie.org/private/ikq5lhhacxxvfaoervauw and it works exactly as you'd expect it to.
EDIT - For anyone who finds this looking for a solution to a similar problem, don't waste your time using sortedsets to sort a collection of objects, you can use OrderBy to achieve the same thing with lists.
This is a longshot but sometimes comparing floats and double is a bit tricky because of precision. For example a point 1.0 might be represented as 1.0000001 because of rounding errors. It is possible that your points are not compared correctly
Try something like
e.polygonPoints[1].X - p.X < 0.00001
&& p.X - e.polygonPoints[1].X < 0.00001
&& e.polygonPoints[1].Y - p.Y < 0.00001
&& p.Y - e.polygonPoints[1].Y < 0.00001
I would have assumed that PointF equality operator would take care of that but it may not
Related
The program uses vertices in R^2 as nodes and there are edges between nodes, and ultimately more is built from there. There are a high number of circuitous ways that a point (x,y) in R^2 might be reached that may rely on layer after layer of trig functions. So it makes sense to define one vertex as the canonical vertex for all points in a square with side length 2*epsilon centered at the point. So various calculations happen, out comes a point (x,y) where I wish to put a vertex, a vertex factory checks to see if there is already vertex deemed as canonical that should be used for this point, if so it returns that vertex, if not a new vertex is created with the coordinates of the point and that vertex is now deemed canonical. I know this can lead to ambiguities given the possibility for overlap of the squares but that is immaterial for the moment, epsilon can be set to make the probability of such a case vanishingly small.
Clearly a list of canonical vertices must be kept.
I have working implementations using List<Vertex> and HashSet<Vertex>, however the vertex creation process seems to scale poorly when the number of vertices grows to over over 100,000 and incredibly poorly if the number gets anywhere near 1,000,000.
I have no idea how to efficiently implement the VertexFactory. Right now Vertex has method IsEquivalentTo(Vertex v) and returns true if v is contained in the square around the instance vertex calling it, false otherwise.
So the the vertex creation process looks like this:
Some point (x,y) get calculated and requests a new vertex from the vertex manager.
VertexManager creates a temporary vertex temp then uses a foreach to iterate over every vertex in the container using IsEquivalentTo(temp) to find a match and return it, if no match is found then add temp to the container and return it. I should state that if a match is found obviously it breaks out of the foreach loop.
I may be way off but my first guess would be put an order on the vertices such as
v1 < v2 iff ( (v1.X < v2.X) || ( (v1.X == v2.X) && (v1.Y < v2.Y) ) )
and then store the vertices in a sorted container. But to be honest I do not know enough about about the various containers to know which is the most appropriate for the purpose, standard C# best practices, etc
Edit:
I cannot mark the comment as an answer so thanks to GSerg whose comment guided me to kd-trees. This is exactly what I was looking for.
Thanks
Essentially, I have a function designed to test if one object is positioned entirely above another while taking the collider bounds into account:
public static bool isAbove(GameObject a, GameObject b)
{
Collider2D colliderA = a.GetComponent<Collider2D>();
Collider2D colliderB = b.GetComponent<Collider2D>();
return a.transform.position.y - (colliderA.bounds.size.y * 0.5) >= b.transform.position.y + (colliderB.bounds.size.y * 0.5);
}
When I originally wrote this, I had no intention to use bounding shapes other than boxes, so the algorithm worked. Now, I am using a circle collider on one of the objects and a box collider on the other one, which makes the algorithm inconsistent, as it's still treating the circle as a box. I really want this working for any shape colliders in any combination.
I thought that I could specify a point directly below the first object and then find the max point at that x position, but I can't find this option anywhere, and I'm still not sure if it's the solution I should be looking for.
As I mentioned in my comment, Collision2D has a lot of useful information regarding the collision of two objects. Specifically for your use case, I would also look into the Vector3.Dot. To be brief, using Vector3(up, otherObject will give you a result to determine if two objects are over one another.
For a little more clarification, there are three possible cases that can be returned from Vector3.Dot and they are
Dot is 0 - The two objects are perfectly perpendicular to one another
Dot is <0 - The angle between the two objects is greater than 90º
Dot is >0 - The angle between the two objects is less than 90º
For your specific use case, I would calculate the dot product between the up vector, and the vector to the other target. If the result is greater than 0 then the other target is above your current object. You can either only check this data when the objects are actively colliding or check it every frame. It would depend on how many objects you're checking against.
This already exists anyway, you are looking for Collider2D.isTouching so you simply do
return colliderA.isTouching(colliderB);
and done ;)
However, instead of passing in GameObject and everytime use GetComponent you should rather somewhere pre-cache the Collider2D references and directly use either
if(colliderA.isTouching(colliderB))
or also
if(Physics2D.isTouching(colliderA, colliderB))
They are more or less equivalent.
So, this seems to be a strange one (to me). I'm relatively new to Unity, so I'm sure this is me misunderstanding something.
I'm working on a VSEPR tutorial module in unity. VSEPR is the model by which electrons repel each other and form the shape (geometry) of atoms.
I'm simulating this by making (in this case) 4 rods (cylinder primitives) and using rigidbody.AddForce to apply an equal force against all of them. This works beautifully, as long as the force is equal. In the following image you'll see the rods are beautifully equi-distant at 109.47 degrees (actually you can "see" their attached objects, two lone pairs and two electron bonds...the rods are obscured in the atom shell.)
(BTW the atom's shell is just a sphere primitive - painted all pretty.)
HOWEVER, in the real world, the lone pairs actually exert SLIGHTLY more force...so when I add this additional force to the model...instead of just pushing the other electron rods a little farther away, it pushes the ENTIRE 4-bond structure outside the atom's shell.
THE REASON THIS SEEMS ODD IS...two things.
All the rods are children of the shell...so I thought that made them somewhat immobile compared to the shell (I.e. if they moved, the shell would move with them...which would be fine).
I have a ConfiguableJoint holding the rods to the center of the atoms shell ( 0,0,0). The x/y/z-motion is set to fixed. I thought this should keep the rods fairly immutably attached to the 0,0,0 center of the shell...but I guess not.
POINTS OF INTEREST:
The rods only push on each other, by a script attached to one rod adding force to an adjacent rod. they do not AddForce to the atom shell or anything else.
CODE:
void RepulseLike() {
if (control.disableRepulsionForce) { return; } // No force when dragging
//////////////////// DETERMINE IF FORCE APPLIED //////////////////////////////
// Scroll through each Collider that this.BondStick bumps
foreach (Collider found in Physics.OverlapSphere(transform.position, (1f))) {
// Don't repel self
if (found == this.collider) { continue; }
// Check for charged particle
if (found.gameObject.tag.IndexOf("Charge") < 0) { continue; }// No match "charge", not a charged particle
/////////////// APPLY FORCE ///////////////
// F = k(q1*q2/r^2)
// where
// k = Culombs constant which in this instance is represented by repulseChargeFactor
// r = distance
// q1 and q2 are the signed magnitudes of the charges, in this case -1 for electrons and +1 for protons
// Swap from local to global variable for other methods
other = found;
/////////////////////////////////
// Calculate pushPoints for SingleBonds
forceDirection = (other.transform.position - transform.position) * magnetism; //magnetism = 1
// F = k(q1*q2/distance^2), q1*q2 ia always one in this scenario. k is arbitrary in this scenario
force = control.repulseChargeFactor * (1 / (Mathf.Pow(distance, 2)));
found.rigidbody.AddForce(forceDirection.normalized * force);// * Time.fixedDeltaTime);
}//Foreach Collider
}//RepulseLike Method
You may want to use spheres to represent electrons, so apply forces onto them and re-orient(rotate) rods according to this sphere.
I think I found my own answer...unless someone has suggestions for a better way to handle this.
I simply reduced the mass of the electron rods, and increased the mass of the sphere. I'm not sure if this is the "best practices" solution or a kluge...so input still welcomed :-)
public void checkForCollision () {
int headX = cells[0].x;
int headY = cells[0].y;
int noOfParts = nPoints;
for(int i = 1; i <noOfParts;i++)
{
int tempX = cells[i].x;
int tempY = cells[i].y;
if(tempX == headX && tempY == headY){
JOptionPane.showMessageDialog(null,"Head hit body");
//EndGameCollectScore etc.
}
}
}
EDIT: 'Cells[]' is an array of type Point AND noOfParts is just how many segments the snake has
main Question
With the above code I'm trying to compare tempX to headX but i would like to have a sort of margin for error e.g. +-5 but am unsure how to accomplish this, my reasoning behind this is i'm thinking maybe the x and Y variables might be a few digits apart so if i have the radius of one of the segment of the snake (explanation of 'snake' in Alternate below) then if i'm right and the values are a tiny bit off it should still come back positive.
OR
Alternative
if anyone can suggest a better way for doing this? Basically it's for a Snake game and headX and headY is the head of the snake and the remaining X and Y variables in Cells is the body, and I'm attempting to compare if the head hits the body.
I tested it and it seemed to work but after i tested it again it seems it will only pick up the collision if i make the snake double back on itself for a few squares. e.g. IF i cross the body perpendicular it will not detect the collision.
Also i am fairly certain that this method is called after each block the snake moves.
Cheers,
Shane.
P.S Running of very little sleep and way too much sugar in my blood, If you need further clarification because the above doesn't make alot of sense let me know.
int eps = 5;
if (Math.abs(tempX - headX) <= eps && Math.abs(tempY - headY) <= eps) {
// ...
}
To check if two points are within a delta from each other, compute the distance between them. You can avoid going into the square root territory by using squares, like this:
int distSq = (tempX-headX)*(tempX-headX) + (tempY-headY)*(tempY-headY);
int minDist = 5;
if (distSq < minDist*minDist) {
// too close
}
I don't know how your snake looks, but if it has a complex shape, looking for a hit can be expensive in terms of speed. You can speed up collision detection if you can do a quick test, to see if a collision is possible at all. You can do this by using a bounding box. You would have to keep track of minimum and maximum x and y positions of the snake body. Only if a coordinate lies within these boundaries you would take account of the exact shape of the snake. How this has to be done depends on how the snake is represented. Check for each tile or each pixel the snake is made of or possibly check if the coordinate is within a polygon, if the snake outline is defined by a polygon. (I'm not going to explain how this works here, but you will find algorithms if you google a bit.)
If you need to calculate the distance to another point (the snake head), you can use different metrics for this. If only horizontal and vertical movements are possible within the game, the so called Manhattan or taxi distance can be used: d = |x1-x0| + |y1-y0|. It consists of adding the x and y distances, or you can use the maximum of both distances: d = Max(|x1-x0|, |y1-y0|) (correponds to 2kay's approach).
If you need the exact distance, apply the Pythagorean formula. In order to compare the distance with the error margin, you don't need to calculate the square root. Instead compare the square of the distance with the square of the error margin. This saves time. (x1-x0)^2 + (y1-y0)^2 < error_margin^2.
I am using .NET 2.0 so I don't have access to the nifty Linq; however, I have worked up some code that sorts a list of points clockwise/counterclockwise.
My problem is that the sort works perfectly fine if the list is not already sorted, but if for some reason the list is already sorted the sort function fails miserably.
I was wandering if someone could help point me in the right direction as to why this may be the cause.
Here is my call to the sort:
positions.Sort(new Comparison<Position>(MapUtils.SortCornersClockwise));
And here is the SortCornersClockwise function:
public static int SortCornersClockwise( Position A, Position B)
{
// Variables to Store the atans
double aTanA, aTanB;
// Reference Point
Pixels reference = Program.main.reference;
// Fetch the atans
aTanA = Math.Atan2(A.Pixel.Y - reference.Y, A.Pixel.X - reference.X);
aTanB = Math.Atan2(B.Pixel.Y - reference.Y, B.Pixel.X - reference.X);
// Determine next point in Clockwise rotation
if (aTanA < aTanB) return -1;
else if (aTanB > aTanA) return 1;
return 0;
}
Where my reference is the point from which I determine the respective angle to each point in my list of points.
Now say I have a list of points:
15778066, 27738237
15778169, 27738296
15778185, 27738269
15778082, 27738210
These are already sorted in the correct order but calling the sort function yields:
15778082, 27738210
15778066, 27738237
15778185, 27738269
15778169, 27738296
Now take another set of sample points:
15778180, 27738255
15778081, 27738192
15778064, 27738219
15778163, 27738282
This list is not already in the correct order and calling the sort function yields:
15778064, 27738219
15778081, 27738192
15778180, 27738255
15778163, 27738282
Which is sorted correctly. This pattern repeats it self for each set of coordinates that are already sorted and for those that aren't. Any ideas?
if (aTanA < aTanB) return -1;
else if (aTanB > aTanA) return 1;
These two conditions are the same! Either swap the variables or turn around the inequality sign, but not both.
In addition to the excellent observation that Henning made, you may also be surprised by the output once you fix that problem.
The circular symmetry makes this a somewhat unusual sorting algorithm when compared with normal sorts on totally ordered domains. Since there is circular symmetry, there are n valid clockwise orderings, given n points and it may matter to you which of those orderings you prefer.
As it stands, use of atan2 unmodified will result in a cut-line with coordinates (x,0) where x<0.
Consider coordinates A = (-10, 1) and B = (-10, -1), measured relative to the reference. You probably imagine that A should appear before B in the result, but in fact it will be the other way around since atan2 returns just less than π for A, but just more than -π for B.
If you want a cut-line with coordinates (x,0) where x>0 simply add 2π to any negative values returned from atan2.
Shouldn't that be:
else if(aTanA > aTanB) return 1;
Not sure if that would cause your issue though.