How to find the distance between a point and a line programatically? - swift

I found something similar that gave me the general form of a line but after writing it out I came across some limitations with horizontal/vertical lines. Source here: https://math.stackexchange.com/questions/637922/how-can-i-find-coefficients-a-b-c-given-two-points
I'm not that good with math but I assume there are restrictions of the general equation of a line formula and attempting to calculate the distance with the distance formula using general coefficients of A,B,C did not work out.
Here's my original code that got the coefficients.
func computeEquationOfALineCoefficients(point1: CGPoint, point2: CGPoint) -> [CGFloat] {
// Computes the equation of a line in the form Ax + By + C = 0 thorugh
// y - y1 = (y2 - y1)/(x2 - x1) * (x - x1) or alternatively
// (y1-y2) * x + (x2-x1) * y + (x1-x2)*y1 + (y2-y1)*x1 = 0
// where a = y1 - y2, b = x2 - x1, c = (x1-x2)*y1 + (y2-y1)*x1
let x1: CGFloat = point1.x
let x2: CGFloat = point2.x
let y1: CGFloat = point1.y
let y2: CGFloat = point2.y
let A = y1 - y2
let B = x2 - x1
let C = (x1 - x2) * y1 + (y2 - y1) * x1
return [A, B, C]
}
and then the distance formula I was using
func computeDistanceBetweenPointAndLine(point: CGPoint, A: Float, B: Float, C: Float) -> Float {
let x: Float = Float(point.x)
let y: Float = Float(point.y)
let distance = abs(A * x + B * y + C) / (pow(A, 2) + pow(B, 2)).squareRoot()
return distance
}
it works fine until I introduce a horizontal line like the coordinates 0,0 and 150,0.
So I then decided to try the route with the perpendicular intersection between a point and a line but I'm stumped at solving for x by setting two equations equal to each other. I found a resource for that here: https://sciencing.com/how-to-find-the-distance-from-a-point-to-a-line-13712219.html but I am not sure how this is supposed to be represented in code.
Any tips or resources I may have yet to find are appreciated. Thank you!

Related

Swift: Get n numbers of points around a rounded rect / squircle with angle

I’m searching for a method that returns a CGPoint and Angle for each of n items around a rounded rect / squircle (I’m aware those shapes are different but suspect they don’t make a relevant visual difference in my case. Therefore I’m searching for the easiest solution).
Something like this:
func getCoordinates(of numberOfPoints: Int, in roundedRect: CGRect, with cornerRadius: CGFloat) -> [(CGPoint, Angle)] {
// ... NO IDEA HOW TO COMPUTE THIS
}
My ultimate goal is to draw something like this (points distributed with equal angles):
Unfortunately my math skills are not sufficient.
Pseudocode. Used center as cx, cy, w and h as half-width and half-height, r as corner radius.
Calculate angle in side for-loop, add phase to start from needed direction (0 from OX axis, Pi/2 from OY axis)
for (i = 0..n-1):
angle = i * 2 * math.pi / n + phase
Get unit vector components for this direction and absolute values
dx = cos(angle)
dy = sin(angle)
ax = abs(dx)
ay = abs(dy)
Find vertical or horizontal for this direction and calculate point relative to center (we work in the first quadrant at this moment):
if ax * h > ay * w:
x = w
y = w * ay / ax
else:
y = h
x = ax * h / ay
Now we have to correct result if point is in rounded corner:
if (x > w - r) and (y > h - r):
recalculate x and y as below
Here we have to find intersection of the ray with circle arc.
Circle equation
(x - (w-r))^2 + (y - (h-r))^2 = r^2
(x - wr)^2 + (y - hr)^2 = r^2 //wr = w - r, hr = h - r
Ray equation (t is parameter)
x = ax * t
y = ay * t
Substitute in circle eq:
(ax*t - wr)^2 + (ay*t - hr)^2 = r^2
ax^2*t^2 - 2*ax*t*wr + wr^2 + ay^2*t^2 -2*ay*t*hr + hr^2 -r^2 = 0
t^2*(ax^2+ay^2) + t*(-2*ax*wr - 2*ay*hr) + (wr^2 +hr^2 - r^2) = 0
t^2* a + t* b + c = 0
Solve this quadratic equation for unknown t, get larger root, and find intersection point substituting t into ray equation.
Now we want to put result into correct quadrant:
if dx < 0:
x = -x
if dy < 0:
y = -y
and shift them by center coordinates
dx += cx
dy += cy
That's all.

How to get the coordinates of the point on a line that has the smallest distance from another point

i'm struggling with this geometry problem right now.
Let's say we have a line defined by point A(x1,y1) and point B(x2,y2)
We also have a point C(x3,y3).
What function written in SWIFT could give me the coordinates (X,Y) of the point that has the smallest distance from the line ? In other words, the point on the line which is the intersection between a perpendicular segment and the other point.
func getCoordsOfPointsWithSmallestDistanceBetweenLineAndPoint(lineX1: Double, lineY1: Double, lineX2: Double, lineY2: Double, pointX3: Double, pointY3: Double) -> [Double] {
// ???
return [x,y]
}
In a mathematical point of view you can :
first find the equation of the line :
y1 = a1x1+b1
a1 = (y2-y1) / (x2-x1)
b1 = y1-a1*x1
Then calculate the gradient of the second line knowing :
a1 * a2 = -1 <->
a2 = -1/a1
with a2 you can find the value of b for the second equation :
y3 = a2*x3 + b2 <->
b2 = y3 - a2*x3
Finally calculate the intersection of the 2 lines :
xi = (b2-b1) / (a1-a2)
y = a1*xi + b1
Then it's quite straightforward to bring that to swift :
typealias Line = (gradient:CGFloat, intercept:CGFloat)
func getLineEquation(point1:CGPoint, point2:CGPoint) -> Line {
guard point1.x != point2.x else {
if(point1.y != point2.y)
{
print("Vertical line : x = \(point1.x)")
}
return (gradient: .nan, intercept: .nan)
}
let gradient = (point2.y - point1.y)/(point2.x-point1.x)
let intercept = point1.y - gradient*point1.x
return (gradient: gradient, intercept: intercept)
}
func getPerpendicularGradient(gradient:CGFloat) -> CGFloat
{
guard gradient != 0 else {
print("horizontal line, the perpendicilar line is vertical")
return .nan
}
return -1/gradient
}
func getIntercept(forPoint point:CGPoint, withGradient gradient:CGFloat) -> CGFloat
{
return point.y - gradient * point.x
}
func getIntersectionPoint(line1:Line, line2:Line)-> CGPoint
{
guard line1.gradient != line2.gradient else {return CGPoint(x: CGFloat.nan, y: CGFloat.nan)}
let x = (line2.intercept - line1.intercept)/(line1.gradient-line2.gradient)
return CGPoint(x:x, y: line1.gradient*x + line1.intercept)
}
func getClosestIntersectionPoint(forLine line:Line, point:CGPoint) -> CGPoint
{
let line2Gradient = getPerpendicularGradient(gradient:line.gradient)
let line2 = (
gradient: line2Gradient,
intercept: getIntercept(forPoint: point, withGradient: line2Gradient))
return getIntersectionPoint(line1:line, line2:line2)
}
func getClosestIntersectionPoint(forLinePoint1 linePoint1:CGPoint, linePoint2:CGPoint, point:CGPoint) -> CGPoint
{
return getClosestIntersectionPoint(
forLine:getLineEquation(point1: linePoint1, point2: linePoint2),
point:point)
}
You can minimize the squared distance of C to a point on the straight line AB:
(CA + t.AB)² = t²AB² + 2t AB.CA + CA²
The minimum is achieved by
t = - AB.CA / AB²
and
CP = CA + t.AB
To elaborate on Yves Daoust answer which if converted to a function has the form
func closestPnt(x: Double, y: Double, x1: Double, y1: Double, px: Double, py: Double)->[Double]{
let vx = x1 - x // vector of line
let vy = y1 - y
let ax = px - x // vector from line start to point
let ay = py - y
let u = (ax * vx + ay * vy) / (vx * vx + vy * vy) // unit distance on line
if u >= 0 && u <= 1 { // is on line segment
return [x + vx * u, y + vy * u] // return closest point on line
}
if u < 0 {
return [x, y] // point is before start of line segment so return start point
}
return [x1, y1] // point is past end of line so return end
}
Note that the function is for line segments, if the closest points unit distance is behind the start or past the end then an end point is the closest.
If you want the point on a line (finitely long) then the following will do that.
func closestPnt(x: Double, y: Double, x1: Double, y1: Double, px: Double, py: Double)->[Double]{
let vx = x1 - x // vector of line
let vy = y1 - y
let ax = px - x // vector from line start to point
let ay = py - y
let u = (ax * vx + ay * vy) / (vx * vx + vy * vy) // unit distance on line
return [x + vx * u, y + vy * u] // return closest point on line
}
Note That both functions assume that !(x1 == x && y1 == y) is be true. IE the line segment MUST have a length > 0.

Where a vector would intersect the screen if extended towards it's direction (swift)

I'm trying to write a function in swift, which returns a CGPoint where the extension of a vector (which is within a screen) will intersect the screen. Let's assume that the screen is 800 x 600. It's like the scheme:
The function should have the following parameters:
func calcPoint(start: CGPoint, end: CGPoint) -> CGPoint
start: CGPoint(x: x1, y: y1) - this is the beginning of the vector.
end: CGPoint(x: x1, y: y1) - this is the end point of the vector.
the return point is the one at which the vector intersects the screen (CGPoint(x: x3, y: y3) as shown at the scheme).
The values for the vector start and end are aways points within the screen (the rectangle 0, 0, 800, 600).
EDIT (for Alexander):
Is there a formula, which in the given situation will make it easy to write the function, in not the obvious way using if ... else ... and triangle vertices ratio?
To compute point E you can look at the triangles given by your setting. You have the Triangle ABC and DBE. Note that they are similar, such that we can set up following relation AB : AC = DB : DE using the intercept theorem (AB etc. stands for the line segment between A and B). In the given setting you know all points but E.
Using start and end Points from given setting:
In case start and end have the same x or y-coordinate it is only the top bottom or left right border with the same coordinate.
Using the absolute values it should work for all four corners of your rectangle. Then of course you have to consider E being out of your rectangle, again the same relation can be used AB : AC = D'B : D'E'
A pure swift solution for everyone interested in such (thanks to Ivo Ivanoff):
// Example for iOS
/// The height of the screen
let screenHeight = UIScreen.main.bounds.height
/// The width of the screen
let screenWidth = UIScreen.main.bounds.width
func calculateExitPoint(from anchor : CGPoint, to point: CGPoint) -> CGPoint {
var exitPoint : CGPoint = CGPoint()
let directionV: CGFloat = anchor.y < point.y ? 1 : -1
let directionH: CGFloat = anchor.x < point.x ? 1 : -1
let a = directionV > 0 ? screenHeight - anchor.y : anchor.y
let a1 = directionV > 0 ? point.y - anchor.y : anchor.y - point.y
let b1 = directionH > 0 ? point.x - anchor.x : anchor.x - point.x
let b = a / (a1 / b1)
let tgAlpha = b / a
let b2 = directionH > 0 ? screenWidth - point.x : point.x
let a2 = b2 / tgAlpha
exitPoint.x = anchor.x + b * directionH
exitPoint.y = point.y + a2 * directionV
if (exitPoint.x > screenWidth) {
exitPoint.x = screenWidth
} else if (exitPoint.x < 0) {
exitPoint.x = 0;
} else {
exitPoint.y = directionV > 0 ? screenHeight : 0
}
return exitPoint
}
Any kind of optimizations are welcomed ;-)
There is no single formula, because intersection depends on starting point position, line slope and rectangle size, and it may occur at any rectangle edge.
Here is approach based on parametric representation of line. Works for any slope (including horizontal and vertical). Finds what border is intersected first, calculates intersection point.
dx = end.x - start.x
dy = end.y - start.y
//parametric equations for reference:
//x = start.x + dx * t
//y = start.y + dy * t
//prerequisites: potential border positions
if dx > 0 then
bx = width
else
bx = 0
if dy > 0 then
by = height
else
by = 0
//first check for horizontal/vertical lines
if dx = 0 then
return ix = start.x, iy = by
if dy = 0 then
return iy = start.y, ix = bx
//in general case find parameters of intersection with horizontal and vertical edge
tx = (bx - start.x) / dx
ty = (by - start.y) / dy
//and get intersection for smaller parameter value
if tx <= ty then
ix = bx
iy = start.y + tx * dy
else
iy = by
ix = start.x + ty * dx
return ix, iy

Drag on a line segment control - calculate point on line

I have a line segment defined by P1 und P2 and a knob on that line which can be dragged. The drag should also work if the drag location L is away from the line segment.
One idea was to find the perpendicular line and then find the intersection. But that won`t work if the drag location L is extacly on the line segment or on the (infinite) line going through P1 and P2.
Infact at the end of the day this should be a simple slider-control with the only difference that the control is not always a horizontal or vertical line segment (where you can drag on) but can be of any angle.
Let's define V as the vector P2 - P1. So there is a line defined by P1 + t V (for all real t), and your line segment (call it S) is the subset of that line where 0 ≤ t ≤ 1.
You want to find the point in S that is closest to your drag point L. For any t, the distance from L to the t point on the line is
sqrt((P1x + t Vx - Lx)^2 + (P1y + t Vy - Ly)^2)).
To find the closest point to L, we want to find the t that minimizes this distance. In fact it suffices to minimize the square of the distance
(P1x + t Vx - Lx)^2 + (P1y + t Vy - Ly)^2
which is sometimes called the quadrance. To find the t that minimizes the quadrance, we take the derivative of the quadrance with respect to t, set it equal to zero, and solve for t:
Solve[D[(P1x + t Vx - Lx)^2 + (P1y + t Vy - Ly)^2, t] == 0, t]
If you type that into Mathematica, you'll get the answer
{{t->(Lx Vx - P1x Vx + Ly Vy - P1y Vy) / (Vx^2 + Vy^2)}}
But that t could be any real number. You'll need to clamp it to the range 0 ... 1 to guarantee that you get a point in your line segment.
In Swift:
extension CGPoint {
func closestPointOnLineSegment(start p1: CGPoint, end p2: CGPoint) -> CGPoint {
let v = CGPointMake(p2.x - p1.x, p2.y - p1.y)
var t: CGFloat = (self.x * v.x - p1.x * v.x + self.y * v.y - p1.y * v.y) / (v.x * v.x + v.y * v.y)
if t < 0 { t = 0 }
else if t > 1 { t = 1 }
return CGPointMake(p1.x + t * v.x, p1.y + t * v.y)
}
}
Example use:
let knobPoint = dragPoint.closestPointOnLineSegment(start: p1, end: p2)
You need to find projection of point L onto line P1P2.
If denote vector P=P1P2, and vector M = P1L, then needed projection
L' = P * DotProduct(M, P) / DotProduct(P, P)
Probably you really need to find a ratio of P1L' and P1P2, then
t = DotProduct(M, P) / DotProduct(P, P)

Finding min/max of quadratic bezier with CoreGraphics

I am using CoreGraphics to draw a quadratic bezier but want to computer the min/max value of the curve. I am not from a mathematical background so this has become a bit troublesome. Does anyone have any articles or ideas about how to solve this?
For a quadratic Bezier, this is actually quite simple.
Define your three control points as P0 = (x0,y0), P1 = (x1,y1) and P2 = (x2,y2). To find the extrema in x, solve this equation:
t = (x0 - x1) / (x0 - 2*x1 + x2)
If 0 <= t <= 1, then evaluate your curve at t and store the location as Px. Do the same thing for y:
t = (y0 - y1) / (y0 - 2*y1 + y2)
Again, if 0 <= t <= 1, evaluate your curve at t and store the location as Py. Finally, find the axis-aligned bounding box containing P0, P2, Px (if found) and Py (if found). This bounding box will also tightly bound your 2D quadratic Bezier curve.
Calculus gives the standard box of tricks for finding the min/max of continuous, differentiable curves.
Here is a sample discussion.
I have made a representation of this in javascript:
Jsfiddle link
function P(x,y){this.x = x;this.y = y; }
function pointOnCurve(P1,P2,P3,t){
if(t<=0 || 1<=t || isNaN(t))return false;
var c1 = new P(P1.x+(P2.x-P1.x)*t,P1.y+(P2.y-P1.y)*t);
var c2 = new P(P2.x+(P3.x-P2.x)*t,P2.y+(P3.y-P2.y)*t);
return new P(c1.x+(c2.x-c1.x)*t,c1.y+(c2.y-c1.y)*t);
}
function getQCurveBounds(ax, ay, bx, by, cx, cy){
var P1 = new P(ax,ay);
var P2 = new P(bx,by);
var P3 = new P(cx,cy);
var tx = (P1.x - P2.x) / (P1.x - 2*P2.x + P3.x);
var ty = (P1.y - P2.y) / (P1.y - 2*P2.y + P3.y);
var Ex = pointOnCurve(P1,P2,P3,tx);
var xMin = Ex?Math.min(P1.x,P3.x,Ex.x):Math.min(P1.x,P3.x);
var xMax = Ex?Math.max(P1.x,P3.x,Ex.x):Math.max(P1.x,P3.x);
var Ey = pointOnCurve(P1,P2,P3,ty);
var yMin = Ey?Math.min(P1.y,P3.y,Ey.y):Math.min(P1.y,P3.y);
var yMax = Ey?Math.max(P1.y,P3.y,Ey.y):Math.max(P1.y,P3.y);
return {x:xMin, y:yMin, width:xMax-xMin, height:yMax-yMin};
}