Edge intersection control - c#

Im trying to implement voronoi diagram generation.
Currently its working like this:
When site event happens we find a intersection point between x = const and some parabol. From this point we will draw through a bisecting line between parabols focus and current site. The line consists of 2 rays initally. Both start at the intersection point, one goes to left and other to right. Additionally for bisecting lien i have the i know the f and g from y = f*x +g.
During a circle event(getting a vertex that joins 2 edges) i need to find the intersection point of 2 edges. The following code works fine if the midpoint(start of 2 rays) lies between the 2 edge intersections. But it seems to me that quite often it happens that the mid point is beyond one of the intersection points, and then the intersection does not happen.
Can someone find the issue with the code? I followed an instruction(read the C++ code in: http://blog.ivank.net/fortunes-algorithm-and-implementation.html) where sweepline moved from top to bottom and (0,0) is top left. My sweepline moves from top to bottom and (0,0) is bottom left. So some math somewhere is off.
Full code: https://codeshare.io/GLWRQb
public Vector3 RayIntersection(VoronoiEdge a, VoronoiEdge b)
{
float x = (b.g - a.g) / (a.f - b.f);
float y = a.f * x + a.g;
if ((x - a.start.x) * a.dx < 0) return Vector3.zero;
if ((y - a.start.y) * a.dy < 0) return Vector3.zero;
if ((x - b.start.x) * b.dx < 0) return Vector3.zero;
if ((y - b.start.y) * b.dy < 0) return Vector3.zero;
return new Vector3(x,y);
}
Edit:
After some trial and error i have found that the problem is not the intersection check. But in some cases it happens that wrong edges are compared.

Related

Get points x distance inside a polygon

I can't figure out a way to do this. I have a list of vector2 points and I need all the points which are inside that polygon with a x distance.
So I have a List of Green points and looking for a List of Red points that have a x distance from respective green points.
I am thinking of getting 2 imaginary points, 1 unity towards the previous and next point.
Then moving towards the center of that 2 points by x distance. But then if the inter angle is not 90 then it will move outside of the polygon.
Vector2 me = point; // point
Vector2 next = n_point; // on left
Vector2 previous = p_point; // on right
//Debug.DrawLine(me, (me - next), Color.green);
// 2 points ep1 & ep2
Vector2 center = Vector2.Lerp(ep1,ep2, 0.5f);
Vector2 finalpoint = Vector2.Lerp(me,center,0.1f); //move towards center
I think I am overthinking this. Is there a super-easy way to do this?
Assuming that all the edges are either horizontal or vertical I would simply consider each possible case separately.
Get the direction vectors.
Vector2 from = me - previous;
Vector2 to = next - me;
I also assume that there is always a turn. I.e., if from is horizontal, then to is vertical and vice versa. Either x or y is 0.0f and the other coordinate is not zero.
I also assume that the x-axis points to the right and the y-axis upwards.
Assuming points are listed clock-wise.
float x, y;
if (from.x > 0.0f) { // from points to the right
y = me.y - distance;
if (to.y > 0.0f) x = me.x + distance else x = me.x - distance;
} else if (from.x < 0.0f) { // from points to the left
y = me.y + distance;
if (to.y > 0.0f) x = me.x + distance else x = me.x - distance;
} else if (from.y > 0.0f) { // from points upwards
x = me.x + distance;
if (to.x > 0.0f) y = me.y - distance else y = me.y + distance;
} else { // from.y < 0.0f, points downwards
x = me.x - distance;
if (to.x > 0.0f) y = me.y - distance else y = me.y + distance;
}
Vector2 me_inner = new Vector2(x, y);
I hope I got all the signs right.
There are two methods that spring to mind
Option1:
For each line define a normal, i.e. a perpendicular line pointing outward
Define a normal for each vertex as the average of the normals of the lines the vertex is part of.
Move the vertex X units along the normal.
This is fairly easy to implement, but may have problems with self-intersection for some kinds of geometry.
Option2:
For each line define a normal, i.e. a perpendicular line pointing outward
Move each line-segment X Units along the normal.
for each sequential pair of line segments determine if:
the two line segments intersect, if so, use the intersection point as the vertex. i.e. add the intersection point into your point-list.
If they do not intersect, insert a new line segment between the start and end point of the lines. i.e. Insert both start and end vertex to your point-list.
This should handle self-intersection better, but there might still be problem-cases. And it a bit more cumbersome to implement. It somewhat depend on how exact you need the new line positioned, and well it should handle different kinds of geometry.

Making a Pen's LineCap go more into the drawn Line/Curve

Currently I have the following code:
AdjustableArrowCap arrow = new AdjustableArrowCap(10, 15, false);
penStateOutline.CustomEndCap = arrow;
And it draws this:
I have tried all day to make the arrow point to the ellipse itself rather than the center of it..
Update (I was wrong about the cap extending the line; it doesn't!)
To let the line and its cap end at the cirlce's outside you need to make the line shorter by the radius of the circle.
There are two approaches:
You can find a new endpoint that sits on the circle by calculating it, either with Pythagoras or by trigonometry. Then replace the endpoint, i.e. the circle's center, when drawing the line or curve by that new point.
Or put the other way round: You need to calculate a point on the circle as the new endpoint of the line.
It requires a little math, unless the line is horizontal or vertical...
This will work well for straight lines but for curves it may cause considerable changes in the shape, depending on how close the points get and how curved the shape is.
This may or may not be a problem.
To avoid it you can replace the curve points by a series of line points that are close enough to look like a curve when drawn. From the list of points we subtract all those that do not lie inside the circle.
This sounds more complicated than it is as there is a nice class called GraphicsPath that will allow you to add a curve and then flatten it. The result is a more or less large number of points. The same class also allows you to determine whether a point lies inside a shape, i.e. our case inside the circle.
To implement the latter approach, here is a routine that transforms a list of curve points to a list of line points that will end close to the circle..:
void FlattenCurveOutside(List<Point> points, float radius)//, int count)
{
using (GraphicsPath gp = new GraphicsPath())
using (GraphicsPath gpc = new GraphicsPath())
{
// firt create a path that looks like our circle:
PointF l = points.Last();
gpc.AddEllipse(l.X - radius, l.Y - radius, radius * 2, radius* 2);
// next one that holds the curve:
gp.AddCurve(points.ToArray());
// now we flatten it to a not too large number of line segments:
Matrix m = new Matrix(); // dummy matrix
gp.Flatten(m, 0.75f); // <== play with this param!!
// now we test the pathpoints from bach to front until we have left the circle:
int k = -1;
for (int i = gp.PathPoints.Length - 1; i >= 0; i--)
{
if ( !gpc.IsVisible(gp.PathPoints[i])) k = i;
if (k > 0) break;
}
// now we know how many pathpoints we want to retain:
points.Clear();
for (int i = 1; i <= k; i++)
points.Add(Point.Round(gp.PathPoints[i]));
}
}
Note that when the last part of the curve is too straight the result may look a little jagged..
Update 2
To implement the former approach here is a function that returns a PointF on a circle of radius r and a line connecting a Point b with the circle center c:
PointF Intersect(Point c, Point a, int rad)
{
float dx = c.X - a.X;
float dy = c.Y - a.Y;
var radians = Math.Atan2(dy, dx);
var angle = 180 - radians * (180 / Math.PI);
float alpha = (float)(angle * Math.PI / 180f);
float ry = (float)(Math.Sin(alpha) * rad );
float rx = (float)(Math.Cos(alpha) * rad );
return new PointF(c.X + rx, c.Y - ry);
}
The result thankfully looks rather similar:
The math can be simplified a little..
To apply it you can get the new endpoint and replace the old one:
PointF I = Intersect(c, b, r);
points2[points2.Count - 1] = Point.Round(I);
Thank you so much to #TaW for helping.
Unfortunately the answer he provided did not match my stupid OCD..
You made me think about the problem from a different perspective and I now have found a solution that I'll share if anyone else needs, note that the solution is not perfect.
public static Point ClosestPointOnCircle(int rad, PointF circle, PointF other)
{
if (other.X == circle.X && other.Y == circle.Y) // dealing with division by 0
other.Y--;
return new Point((int)Math.Floor(circle.X + rad * ((other.X - circle.X) / (Math.Sqrt(Math.Pow(other.X - circle.X, 2) + Math.Pow(other.Y - circle.Y, 2))))), (int)Math.Floor(circle.Y + rad * ((other.Y - circle.Y) / (Math.Sqrt(Math.Pow(other.X - circle.X, 2) + Math.Pow(other.Y - circle.Y, 2))))));
}
What the code does is return a point on the circle that is the closest to the another point, then, I used the curve middle point as the other point to change the end point of the curve.
Then I used the arrow cap as normal and got this:image
Which is good enough for my project.

C# XNA Calculate distance between rectangles (rotation-friendly)

Here's 2 methods available;
if(rectangle.Intersects(otherRectangle))
{
//collision stuff
}
Catch: Only works with non-rotating rectangles.
if(Vector2.Distance(player.pos, enemy.pos) < 50)
{
//collision stuff
}
Catch: Only works with circles.
What I want is to calculate x and y in this image:
Facts
The width and length of both rectangles is defined, along with their rotations.
I can calculate D using the Pythagorean theorem.
But the TRUE distance is D - (X + Y).
General approach
Evidently x and y can be calculated using the Cosine rule.
But I only have the width or length and the angle between the two shapes.
Complication
Plus, this needs to work for any rotation.
The rectangle on the left could be rotated in any direction, and x would be different depending on said rotation.
Question
How would I calculate x and y?
I just want an effective collision detection method more complex than bounding boxes and Pythagoras' theorem.
One approach is to rotate the line with the inverse angle and check with the axis-aligned box:
class RotatedBox
{
...
float CalcIntersectionLength(Vector2 lineTo) //assume that the line starts at the box' origin
{
Matrix myTransform = Matrix.CreateRotationZ(-this.RotationAngle);
var lineDirection = Vector2.Transform(lineTo -this.Center, myTransform);
lineDirection.Normalize();
var distanceToHitLeftOrRight = this.Width / 2 / Math.Abs(lineDirection.X);
var distanceToHitTopOrBottom = this.Height / 2 / Math.Abbs(lineDirection.Y);
return Math.Min(distanceToHitLeftOrRight, distanceToHitTopOrBottom);
}
}
Now you can calculate the actual distance with
var distance = (box1.Center - box2.Center).Length
- box1.CalcIntersectionLength(box2.Center)
- box2.CalcIntersectionLength(box1.Center);
Be sure that the rotation direction matches your visualization.

Algorithm for points inside a polyline inexplicably fails (AutoCAD)

I'm using the .NET API for Autocad, I have an algorithm (which I did not write) for determining if a point lies within a polygon (straight lines only).
I have been testing my command on the same 51 polygons repeatedly. 99% it will work perfectly. Every once in a while it will fail on 1 or more of the polygons, returning false for over 2000 points I am creating inside the bounding box of the polyline. I have seen it fail when the polyline isa simple rectangle and all of the points lie distributed in a grid within the polyline. It should have returned true over 2000 times in that case. It will never fail for just 1 of the points, it will fail all of them. I have confirmed that the points are being correctly created where I expect them to be and that the vertices of the polygon are where I expect them to be. When it fails, the last angle variable for the last point is at exactly double PI.
I am not doing any multi-threading. The only possibly 'funny' thing I am doing is COM Interop with Excel. This is happening after the transaction has been committed for the part with this algorithm, and I am sure I am cleaning up all my COM objects. I have not been able to reproduce the failure without the COM Interop part but I don't think I've tested it enough yet to have enough absence of evidence.
Any ideas what could be wrong?
bool IsInsidePolygon(Polyline polygon, Point3d pt)
{
int n = polygon.NumberOfVertices;
double angle = 0;
Point pt1, pt2;
for (int i = 0; i < n; i++)
{
pt1.X = polygon.GetPoint2dAt(i).X - pt.X;
pt1.Y = polygon.GetPoint2dAt(i).Y - pt.Y;
pt2.X = polygon.GetPoint2dAt((i + 1) % n).X - pt.X;
pt2.Y = polygon.GetPoint2dAt((i + 1) % n).Y - pt.Y;
angle += Angle2D(pt1.X, pt1.Y, pt2.X, pt2.Y);
}
if (Math.Abs(angle) < Math.PI)
return false;
else
return true;
}
public struct Point
{
public double X, Y;
};
public static double Angle2D(double x1, double y1, double x2, double y2)
{
double dtheta, theta1, theta2;
theta1 = Math.Atan2(y1, x1);
theta2 = Math.Atan2(y2, x2);
dtheta = theta2 - theta1;
while (dtheta > Math.PI)
dtheta -= (Math.PI * 2);
while (dtheta < -Math.PI)
dtheta += (Math.PI * 2);
return (dtheta);
}
Some ideas:
floating point comparison have to be done using a tolerence, this might cause kind of arbitrary results especially in case where the point lies on the polyline (same remark for point3d, they must be compared using a tolerence)
maybe the last point of your polyline is at the same location as the first one, in that case, the angle cannot be computed (perhaps this is why you get a double pi value for the last point). You should then test is first and last points are equals.
I'm not sure your algorithm works regardless if the polyline is clockwise or counterclockwise (I think yes)
you may convert your polyline into a region and rely on region point containment method
another method.
Make one "temporary" point outside the polygon (find the min X and Y and make a point with X-1 and Y-1).
Then make a line between your point and the new "temporary" point.
Check if this line crosses the polygon - use polyline.IntersectWith.
If number of cross points is odd - then your point is inside, if the number of crosses is even - the your point is not inside.
This works for me, hope it will helps you also.
If you find trouble with implementing this, i can send you an example code.
Regards,
Dobriyan Benov
I used some code from Kean Walmsley to convert 3d lines into 2d lines. But be aware that the following is not (always) true:
Point2d pt = lwp.GetPoint2dAt(i);
Point2d npt = new Point2d(lwp.GetPoint3dAt(i).X, lwp.GetPoint3dAt(i).Y);
pt == npt;
I encountered it using it on a polylines, with 3d vertices. I ended up using the npt.
http://through-the-interface.typepad.com/through_the_interface/2007/04/iterating_throu.html

C# / XNA - 2D collision engine glitch?

I've got quite a lot of code here, but it's relatively straightforward.
This is all snippets from different classes, all references are right, but I think I've done a math-based error somewhere and I can't find it. It always finds a collision on the y axis a pixel before it should. I haven't tried it with different X axis positions but it seems to fall past blocks next to it fine.
The struct "mapSection" just contains two Vector2s- A top-left block and bottom-left block coordinate.
tileManager.def_ts is the default tile width and height (32). The player's size is 32x64.
The toWorldSpace function does nothing right now other than return so that's not the problem.
When I say block coordinate I mean which index the block is in the tile array (Ex 0, 0 is the first block, 0, 1 is the second block on the Y axis, 1, 3 is 1 block in on the X axis and 3 on the Y axis, I do not mean actual pixels.)
From tile engine class:
public mapSection toMapMinMax(Vector2 position, Vector2 size)
{
position = toWorldSpace(position);
position.X = (float)Math.Floor(position.X / tileManager.def_ts);
position.Y = (float)Math.Floor(position.Y / tileManager.def_ts);
size.X = (float)Math.Floor(size.X / tileManager.def_ts);
size.Y = (float)Math.Floor(size.Y / tileManager.def_ts);
return new mapSection(position, position + size);
}
public bool collision(Vector2 screenPosition, Vector2 size)
{
mapSection mapCollisionPossibilities = toMapMinMax(screenPosition, size);
for (int y = (int)mapCollisionPossibilities.topLeft.Y; y <= mapCollisionPossibilities.bottomRight.Y; y++)
{
for (int x = (int)mapCollisionPossibilities.topLeft.X; x <= mapCollisionPossibilities.bottomRight.X; x++)
{
if (x >= 0 && y >= 0 && y < tiles.Count && x < tiles[y].Count)
{
if (tileManager.tileTypes[tiles[y][x]].collideable == true)
{
return true;
}
}
}
}
return false;
}
And this is the code from the player class:
if (!tEngine.collision(position + new Vector2(0, 1), new Vector2(32, 64)))
{
position.Y += 1;
}
I add "Vector2(0, 1)" because I want to see if there's a collision a pixel further down; so that he falls until he hits something. It's very basic right now but it's only to test the collision engine, which isn't working.
There's a picture of the error. You can see the player is a pixel too high.
In the picture, "X:" is the top-left block coordinate on X axis, "X2:" is the bottom-right block coordinate on the X axis, and same with "Y:" and "Y2: except Y axis. They're read from the mapSection directly.
If anyone can notice why this is happening, it would be massively appreciated.
Thanks.
If you cannot understand any section of the code just post in the comments and I'll be happy to explain, or if you think I've been a bit too unspecific in some area of this post.
EDIT: For the tile coordinates issue, your toMapMinMax code should be more like this:
EDIT2: have subtracted (1, 1) from bottomRight, since it is a size we are adding.
public mapSection toMapMinMax(Vector2 position, Vector2 size)
{
Vector2 topLeft = position;
Vector2 bottomRight = position + size - new Vector2(1, 1);
topLeft.X = (float)Math.Floor(topLeft.X / tileManager.def_ts);
topLeft.Y = (float)Math.Floor(topLeft.Y / tileManager.def_ts);
bottomRight.X = (float)Math.Floor(bottomRight.X / tileManager.def_ts);
bottomRight.Y = (float)Math.Floor(bottomRight.Y / tileManager.def_ts);
return new mapSection(topLeft, bottomRight);
}
Also, I was wrong in my above comment; you do want <= signs in your two for loops, because most of the time you will be checking 6 tiles.
for the off-by-one-pixel issue:
In order for you to see the character off by some amount of pixels, the draw code and the collision code must be different. If they were identical, for example if they were both off by 15 pixels (you collide 15 pixels too early, but you are also drawing 15 pixels ahead), you wouldn't see any change.
The 1 pixel gap indicates a 1 pixel difference between the draw coordinate calculation and the collision coordinate calculation. This 1 pixel difference is most likely caused by differences in rounding, probably that you are calling Math.Floor in the collision code, but are not rounding the coordinates in the draw code. (I would guess you are probably just passing the position Vector2 straight to the SpriteBatch.Draw method).

Categories

Resources