I'm drawing a custom diagram of business objects using .NET GDI+. Among other things, the diagram consists of several lines that are connecting the objects.
In a particular scenario, I need to shorten a line by a specific number of pixels, let's say 10 pixels, i.e. find the point on the line that lies 10 pixels before the end point of the line.
Imagine a circle with radius r = 10 pixels, and a line with start point (x1, y1) and end point (x2, y2). The circle is centered at the end point of the line, as in the following illustration.
How do I calculate the point marked with a red circle, i.e. the intersection between circle and line? This would give me the new end point of the line, shortening it by 10 pixels.
Solution
Thank you for your answers from which I was able to put together the following procedure. I named it LengthenLine, since I find it more natural to pass a negative number of pixels if I want the line shortened.
Specifically, I was trying to put together a function that could draw a line with rounded corners, which can be found here.
public void LengthenLine(PointF startPoint, ref PointF endPoint, float pixelCount)
{
if (startPoint.Equals(endPoint))
return; // not a line
double dx = endPoint.X - startPoint.X;
double dy = endPoint.Y - startPoint.Y;
if (dx == 0)
{
// vertical line:
if (endPoint.Y < startPoint.Y)
endPoint.Y -= pixelCount;
else
endPoint.Y += pixelCount;
}
else if (dy == 0)
{
// horizontal line:
if (endPoint.X < startPoint.X)
endPoint.X -= pixelCount;
else
endPoint.X += pixelCount;
}
else
{
// non-horizontal, non-vertical line:
double length = Math.Sqrt(dx * dx + dy * dy);
double scale = (length + pixelCount) / length;
dx *= scale;
dy *= scale;
endPoint.X = startPoint.X + Convert.ToSingle(dx);
endPoint.Y = startPoint.Y + Convert.ToSingle(dy);
}
}
Find the direction vector, i.e. let the position vectors be (using floats) B = (x2, y2) and A = (x1, y1), then AB = B - A. Normalize that vector by dividing by its length ( Math.Sqrt(xx + yy) ). Then multiply the direction vector AB by the original length minus the circle's radius, and add back to the lines starting position:
double dx = x2 - x1;
double dy = y2 - y1;
double length = Math.Sqrt(dx * dx + dy * dy);
if (length > 0)
{
dx /= length;
dy /= length;
}
dx *= length - radius;
dy *= length - radius;
int x3 = (int)(x1 + dx);
int y3 = (int)(y1 + dy);
Edit: Fixed the code, aaand fixed the initial explanation (thought you wanted the line to go out from the circle's center to its perimeter :P)
I'm not sure why you even had to introduce the circle. For a line stretching from (x2,y2) to (x1,y1), you can calculate any point on that line as:
(x2+p*(x1-x2),y2+p*(y1-y2))
where p is the percentage along the line you wish to go.
To calculate the percentage, you just need:
p = r/L
So in your case, (x3,y3) can be calculated as:
(x2+(10/L)*(x1-x2),y2+(10/L)*(y1-y2))
For example, if you have the two points (x2=1,y2=5) and (x1=-6,y1=22), they have a length of sqrt(72 + 172 or 18.38477631 and 10 divided by that is 0.543928293. Putting all those figures into the equation above:
(x2 + (10/l) * (x1-x2) , y2 + (10/l) * (y1-y2))
= (1 + 0.543928293 * (-6- 1) , 5 + 0.543928293 * (22- 5))
= (1 + 0.543928293 * -7 , 5 + 0.543928293 * 17 )
= (x3=-2.807498053,y3=14.24678098)
The distance between (x3,y3) and (x1,y1) is sqrt(3.1925019472 + 7.7532190152) or 8.384776311, a difference of 10 to within one part in a thousand million, and that's only because of rounding errors on my calculator.
You can use similar triangles. For the main triangle, d is the hypotenuses and the extension of r is the vertical line that meets the right angle. Inside the circle you will have a smaller triangle with a hypotenuses of length r.
r/d = (x2-a0)/(x2-x1) = (y2-b0)/(y2-y1)
a0 = x2 + (x2-x1)r/d
b0 = y2 + (y2-y1)r/d
Related
I have a line segment whose end points I know
Line1 (X1,Y1) (X2,Y2)
I have a second line
Line2 (X3,Y3) (X4,Y4)
I want to calculate new end points for line 1 such that the resulting line is parallel to Line2, and Line1's centre point remains at the same coordinates.
i.e. such that Line1 simply rotates so it is parallel to Line2
I know I can calculate each line's angle
var line1Angle = (Mathf.Atan2(x2 - x1, y2 - y1));
var line2Angle = (Mathf.Atan2(x4 - x3, y4 - y3));
I can also calculate the lengths
var len1 = Math.Sqrt((x2-x1)*(x2-x1)+ (y2-y1) * (y2-y1));
var len2 = Math.Sqrt((x4-x3)*(x4-x3)+ (y4-y3) * (y4-y3));
but everything I have tried seems to fail - either not rotating correctly, or rotating but with the incorrect length.
The closest code I have (below) rotates correctly, but the length of Line1 is not retained.
The code uses an 'offset' which was used by the code this version is based on as it simply drew a parallel line 'offset' pixels from the destination line - I have set it to an arbitrary value but I believe should be the distance of Line1's centre point from the closest point on Line2.
I'd love it if someone could supply a code version, rather than an explanation (or as well as!) as I've read and tried so many non-code solutions, and evidently my understanding / translation to code is flawed!
float len1 = (float)Math.Sqrt((x2-x1)*(x2-x1)+ (y2-y1) * (y2-y1));
float len2 = (float)Math.Sqrt((x4-x3)*(x4-x3)+ (y4-y3) * (y4-y3));
float offset = 3.0f; // This should be the dist from our center to the closest wall but I"m compromising for now!
float newX1 = x3 + offset * (y4 - y3) * (len1 / len2);
float newX2 = x4 + offset * (y4 - y3) * (len1 / len2);
float newY1 = y3 + offset * (x3 - x4) * (len1 / len2);
float newY2 = y4 + offset * (x3 - x4) * (len1 / len2);
Approach without angles: you know segment lengths, and can just form new segment ends with the same direction as line2 defines
dx1 = x2 - x1
dy1 = y2 - y1
dx2 = x4 - x3
dy2 = y4 - y3
len1 = hypot(dx1, dy1)
len2 = hypot(dx2, dy2)
midx = (x1 + x2) / 2
midy = (y1 + y2) / 2
coeff = 0.5 * len1 / len2
//now we make a vector with direction of line2
//and length of half of line1
newx1 = midx - dx2 * coeff
newy1 = midy - dy2 * coeff
newx2 = midx + dx2 * coeff
newy2 = midy + dy2 * coeff
I would highly recommend that you define actual types for your line segment and points. You can use Math.Net or System.Numerics.Vectors if you want something to start with.
Assuming your line segment have a StartPoint and EndPoint we can define a MidPoint and Direction extension methods. I'm going to use the Math.Net types for the example, but it is not difficult to make your own types.
static Vector2D Midpoint(this Line2D l) => (l.StartPoint + l.EndPoint) / 2;
static Vector2D Direction(this Line2D l) => (l.EndPoint - l.StartPoint ).Normalize() ;
We can also define a static method to create a new line from these methods/Properties:
static Line2D FromMidpointDirection(Vector2D midpoint, Vector2D direction, float length){
var halfDir = direction * length/ 2;
return new Line2D(midpoint - halfDir , midpint + halfDir );
Note that you might want to add comments or pick another name for Direction, since it is not obvious if this is normalized or not.
Then you can recreate your line:
var mid = sourceLine.Midpoint();
var dir = targetLine.Direction();
var newLine = FromMidpointDirection(mid, dir,sourceLine.Length);
Using higher level types like this tend to make your code more reusable and easier to read and understand.
Given an Point array and an arbitrary x,y coordinate, find the index for _points that is closest to the given coordinate.
PointD[] _points
//create a list of x,y coordinates:
for (int i = 0; i < _numberOfArcSegments + 1; i++)
{
double x1 = _orbitEllipseSemiMaj * Math.Sin(angle) - _focalDistance; //we add the focal distance so the focal point is "center"
double y1 = _orbitEllipseSemiMinor * Math.Cos(angle);
//rotates the points to allow for the LongditudeOfPeriapsis.
double x2 = (x1 * Math.Cos(_orbitAngleRadians)) - (y1 * Math.Sin(_orbitAngleRadians));
double y2 = (x1 * Math.Sin(_orbitAngleRadians)) + (y1 * Math.Cos(_orbitAngleRadians));
angle += _segmentArcSweepRadians;
_points[i] = new PointD() { x = x2, y = y2 };
}
I'm drawing an ellipse which represents an orbit. I'm first creating the point array above, then when I draw it, I (attempt) to find the point closest to where the orbiting body is.
To do this I've been attempting to calculate the angle from the center of the ellipse to the body:
public void Update()
{
//adjust so moons get the right positions (body position - focal point position)
Vector4 pos = _bodyPositionDB.AbsolutePosition - _positionDB.AbsolutePosition;
//adjust for focal point
pos.X += _focalDistance;
//rotate to the LonditudeOfPeriapsis.
double x2 = (pos.X * Math.Cos(-_orbitAngleRadians)) - (pos.Y * Math.Sin(-_orbitAngleRadians));
double y2 = (pos.X * Math.Sin(-_orbitAngleRadians)) + (pos.Y * Math.Cos(-_orbitAngleRadians));
_ellipseStartArcAngleRadians = (float)(Math.Atan2(y2, x2)); //Atan2 returns a value between -180 and 180;
}
then:
double unAdjustedIndex = (_ellipseStartArcAngleRadians / _segmentArcSweepRadians);
while (unAdjustedIndex < 0)
{
unAdjustedIndex += (2 * Math.PI);
}
int index = (int)unAdjustedIndex;
The ellipse draws fine, (the point array is correct and all is good once adjusted for viewscreen and camera offsets and zoom)
But does not start at the correct point (I'm decreasing the alpha in the color so the resulting ellipse fades away the further it gets from the body)
I've spend days trying to figure out what I'm doing wrong here and tried a dozen different things trying to figure out where my math is wrong, but I'm not seeing it.
I assume that _points should be an array of PointD;
This is the shortest way to get the closest point to your array (calcdistance should be a simple function that calculate the euclidean distance):
PointD p = _points.OrderBy(p => CalcDistance(p, gievnPoint)).First();
I have a line going bettween two Vector3 points and I want to find when the line is intersected at a height along the Z axis.
I am trying to write a function to calculate the intersection point.
void Main()
{
Vector3 A = new Vector3(2.0f, 2.0f, 2.0f);
Vector3 B = new Vector3(7.0f, 10.0f, 6.0f);
float Z = 3.0f;
Vector3 C = getIntersectingPoint(A, B, Z);
//C should be X=3.25, Y=4.0, Z=3.0
}
But trying to figure out how to do the math to handle possible negative numbers correctly is really starting to confuse me.
This is what I have and the moment, but this isn't correct.
public static Vector3 getIntersectingPoint(Vector3 A, Vector3 B, float Z)
{
// Assume z is bettween A and B and we don't need to validate
// Get ratio of triangle hight, height Z divided by (Za to Zb)
("absolute value: " + Math.Abs(A.Z-B.Z)).Dump();
("z offset: " + (Math.Abs(Z-B.Z)<Math.Abs(A.Z-Z)?Math.Abs(Z-B.Z):Math.Abs(A.Z-Z))).Dump();
float ratio = (Math.Abs(Z-B.Z)<Math.Abs(A.Z-Z)?Math.Abs(Z-B.Z):Math.Abs(A.Z-Z))/Math.Abs(A.Z-B.Z);
("ratio: " + ratio.ToString()).Dump();
float difX = ratio*Math.Abs(A.X-B.X);//this still needs to be added to or taken from the zero point offset
("difX: " + difX.ToString()).Dump();
float difY = ratio*Math.Abs(A.Y-B.Y);//this still needs to be added to or taken from the zero point offset
("difY: " + difY.ToString()).Dump();
float X = difX + (A.X<B.X?A.X:B.X);
("X: " + X).Dump();
float Y = difY + (A.Y<B.Y?A.Y:B.Y);
("Y: " + Y).Dump();
return new Vector3(X,Y,Z);
}
Does anyone know if there are any Math libraries that will already do this or examples that show how to do this that I can follow?
You have the starting (2.0f) and ending (6.0f) Z coordinates. The Z distance between the two points is 4.0f. You want to know the X and Y coordinates at the point where Z is 3.0f.
Remember that Z changes linearly along the segment. The segment is 4 units long, The point you're interested in is 1 unit from the start, or 1/4 of the length of the segment.
The X distance of the entire segment is 7.0 - 2.0, or 5 units. 1/4 of 5 is 1.25, so the X coordinate at the intersection is 3.25.
The Y distance of the entire segment is 8. 1/4 of 8 is 2. So the Y coordinate of the intersection point is 6.0.
The intersection point is (3.25f, 6.0f, 3.0f).
How to compute:
// start is the starting point
// end is the ending point
// target is the point you're looking for.
// It's pre-filled with the Z coordinate.
zDist = Math.Abs(end.z - start.z);
zDiff = Math.Abs(target.z - start.z);
ratio = zDiff / zDist;
xDist = Math.Abs(end.x - start.x);
xDiff = xDist * ratio;
xSign = (start.x < end.x) ? 1 : -1;
target.x = start.x + (xDiff * xSign);
yDist = Math.Abs(end.y - start.y);
yDiff = yDist * ratio;
ySign = (start.y < end.y) ? 1 : -1;
target.y = start.y + (yDiff * ySign);
Come to think of it, the whole sign thing shouldn't be necessary. Consider this, when end.x = 10 and start.x = 18:
xDist = end.x - start.x; // xDist = -8
xDiff = xDist * ratio; // xDiff = -2
target.x = start.x + xDiff; // target.x = 18 + (-2) = 16
Yeah, no need for sign silliness.
Also no need for the calls to Math.Abs when computing the ratio. We know that zDist and zDiff will both have the same sign, so ratio will always be positive.
I have 1 line with 2 known points:
PointF p2_1 = new PointF();
p2_1.X = 100; // x1
p2_1.Y = 150; // y1
PointF p2_2 = new PointF();
p2_2.X = 800; // x2
p2_2.Y = 500; // y2
float dx = p2_2.X - p2_1.X;
float dy = p2_2.Y- p2_1.Y;
float slope = dy / dx; // slope m
float intercept = p2_1.Y - slope * p2_1.X; // intercept c
// y = mx + c
I'd like to iterate through 10 pixels to the left (or right) to 1 line (at x1, y1).
The red dots are the ones that I'd like process. Example:
for (int i = 10; i > 0; i--)
{
// start with distant coordinates
PointF new_point = new Point(); // (grab x,y, coords accordingly)
// repeat until I'm at (x1, y1)
}
How do I iterate through these coords?
A perpendicular vector will be of the form:
[-dy dx] where [dx dy] is your current vector. Once you have the perpendicular vector, you can normalize it (unit length), then iterate by a set amount:
float perp_dx = -dy / Math.sqrt(dy*dy+dx*dx); //normalized
float perp_dy = dx /Math.sqrt(dy*dy+dx*dx); //normalized
for(int i =0; /*logic here*/){
float new_x = perp_dx * i + start_x;
float new_y = perp_dy * i + start_y;
}
The line perpendicular to a given line has slope equal to the negative inverse of the slope of the given line.
The slope of the given line is (y2-y1) / (x2-x1)
So the red line has slope = - 1 / [(y2-y1) / (x2-x1)]
So each ith point on this line has coordinates (xi, yi) where
(yi - y1) / (xi - x1) = - 1 / (y2-y1) / x2-x1)
and is a multiple of one pixel fixed distance away from (x1, y1), i.e., where
(yi-y1) * (yi-y1) + (xi-x1) * (xi-x1) = i * i
what I would do is calculate what this increment vector (dx, dy) is for or between each point on the red line, and then just keep adding that increment in a loop that iterates 10 times.
I want to add additional feature of my project in C#, I can already draw lines in my program but I want to detect INTERSECTING LINES of a one line drawn and display the point they've intersect. Is it possible? Thank you
My program also includes computing for Perpendicular Distance, here is the sample code:
public static Double PerpendicularDistance(Point Point1, Point Point2, Point Point)
{
Double area = Math.Abs(.5 * (Point1.X * Point2.Y + Point2.X * Point.Y + Point.X * Point1.Y - Point2.X * Point1.Y - Point.X * Point2.Y - Point1.X * Point.Y));
Double bottom = Math.Sqrt(Math.Pow(Point1.X - Point2.X, 2) + Math.Pow(Point1.Y - Point2.Y, 2));
Double height = area / bottom * 2;
return height;
}
}
The POINT here is a class for my X and Y coordinates.
If you are trying to find the intersection of two line, then the solution is fairly trivial.
If the two line are in the form Ax + By = C:
float delta = a1*b2 - a2*b1;
if(delta == 0)
throw new ArgumentException("Lines are parallel");
float x = (b2*c1 - b1*c2)/delta;
float y = (a1*c2 - a2*c1)/delta;
My concern is comment above that says there is only one drawn line. I'm not sure what you mean. Does it mean that the app provides one line and the user the other, or are we dealing in curved lines where the line intersects itself?