1

Circle center : Cx,Cy

Circle radius : a

Point from which we need to draw a tangent line : Px,Py

I need the formula to find the two tangents (t1x, t1y) and (t2x,t2y) given all the above.

Edit: Is there any simpler solution using vector algebra or something, rather than finding the equation of two lines and then solving equation of two straight lines to find the two tangents separately? Also this question is not off-topic because I need to write a code to find this optimally

cegprakash
  • 2,937
  • 33
  • 60
  • I've done some edits to the question. Kindly vote for reopen. Thanks. – cegprakash Apr 22 '18 at 18:19
  • I can see two basic computational methods. One is easier to understand (if you know trigonometry) and explain but uses trigonometric functions and thus may not give an exact answer when an exact answer is possible. Another way is more difficult to understand and calculate but uses only the 4 basic operations and square roots so is more likely to give exact results when possible. Which do you prefer? (Next time, explain the relation to programming when you first ask the question.) – Rory Daulton Apr 22 '18 at 18:44
  • Both or any. Thanks for voting to reopen :) – cegprakash Apr 22 '18 at 19:07
  • @RoryDaulton in 2D you do not need any goniometrics as rotation equation is just single swap and one negation... in 3D you also do not need any you just use cross product ... for ND there is the [perpendicular vector formula](https://stackoverflow.com/a/42339177/2521214) (still no goniometrics) – Spektre Apr 23 '18 at 10:04
  • @Spektre: I realize that the trigonometric functions are not required in this problem--I thought I made that clear in my comment. I just believe that the trig approach is simpler and easier to understand for people who know trigonometry. I believe that the code, which is complete, in my answer shows this to be true. – Rory Daulton Apr 23 '18 at 19:41

5 Answers5

10

Here is one way using trigonometry. If you understand trig, this method is easy to understand, though it may not give the exact correct answer when one is possible, due to the lack of exactness in trig functions.

enter image description here

The points C = (Cx, Cy) and P = (Px, Py) are given, as well as the radius a. The radius is shown twice in my diagram, as a1 and a2. You can easily calculate the distance b between points P and C, and you can see that segment b forms the hypotenuse of two right triangles with side a. The angle theta (also shown twice in my diagram) is between the hypotenuse and adjacent side a so it can be calculated with an arccosine. The direction angle of the vector from point C to point P is also easily found by an arctangent. The direction angles of the tangency points are the sum and difference of the original direction angle and the calculated triangle angle. Finally, we can use those direction angles and the distance a to find the coordinates of those tangency points.

Here is code in Python 3.

# Example values
(Px, Py) = (5, 2)
(Cx, Cy) = (1, 1)
a = 2

from math import sqrt, acos, atan2, sin, cos

b = sqrt((Px - Cx)**2 + (Py - Cy)**2)  # hypot() also works here
th = acos(a / b)  # angle theta
d = atan2(Py - Cy, Px - Cx)  # direction angle of point P from C
d1 = d + th  # direction angle of point T1 from C
d2 = d - th  # direction angle of point T2 from C

T1x = Cx + a * cos(d1)
T1y = Cy + a * sin(d1)
T2x = Cx + a * cos(d2)
T2y = Cy + a * sin(d2)

There are obvious ways to combine those calculations and make them a little more optimized, but I'll leave that to you. It is also possible to use the angle addition and subtraction formulas of trigonometry with a few other identities to completely remove the trig functions from the calculations. However, the result is more complicated and difficult to understand. Without testing I do not know which approach is more "optimized" but that depends on your purposes anyway. Let me know if you need this other approach, but the other answers here give you other approaches anyway.

Note that if a > b then acos(a / b) will throw an exception, but this means that point P is inside the circle and there is no tangency point. If a == b then point P is on the circle and there is only one tangency point, namely point P itself. My code is for the case a < b. I'll leave it to you to code the other cases and to decide the needed precision to decide if a and b are equal.

Rory Daulton
  • 21,934
  • 6
  • 42
  • 50
  • Very good explanation, thanks! I believe there is an error though, `d` should be `d = atan2(Py - Cy, Px - Cx)` – Alberto Malagoli Jul 17 '18 at 14:01
  • @AlbertoMalagoli: Yes, you are correct, thank you very much! I have edited my code, tested it, and changed my answer above. I should have done more thorough testing in the first place! – Rory Daulton Jul 17 '18 at 14:18
5

Here's another way using complex numbers. If a is the direction (a complex number of length 1) of the tangent point on the circle from the centre c, and d is the (real) length along the tangent to get to p, then (because the direction of the tangent is I*a)

p = c + r*a + d*I*a 

rearranging

(r+I*d)*a = p-c

But a has length 1 so taking the length we get

|r+I*d| = |p-c|

We know everything but d, so we can solve for d:

d = +- sqrt( |p-c|*|p-c| - r*r)

and then find the a's and the points on the circle, one of each for each value of d above:

a = (p-c)/(r+I*d)
q = c + r*a
dmuir
  • 4,211
  • 2
  • 14
  • 12
  • I understood all ur steps except the first step. what is I? How is the direction of tangent from the circle is I*a? – cegprakash Apr 23 '18 at 15:00
  • @cegprakash I is the square root of -1. Multiplying a complex number by another of length one is to rotate the first number, Multiplying by 1 is a rotation through 0 degrees, by -1 a rotation through 180 degrees and by I a rotation though 90 degrees. Generally multiplying by cos(a) + I*sin(a) is to rotate through a (radians). So what I said amounts to the tangent to a circle at a point is at right angles to the radial vector to the point. – dmuir Apr 23 '18 at 15:21
  • How do I get rid of the complex number to find a? – cegprakash Apr 23 '18 at 16:02
  • Simplifying I get a = ((p - c)r - (p-c)Id ) / (r^2 + d^2) So Is it sufficient for me to rotate the vector p-c to 90 degrees? – cegprakash Apr 23 '18 at 16:17
  • @cegprakash well you need to compute both (p-c)*r and (p-c)*I*d. I'm not convinced thats much of a simplification, I think it's simpler just to do the complex divide as long as your language supports it. Note that a is a complex number. If you want the angle in radians in C that would be carg(a). – dmuir Apr 23 '18 at 17:59
  • I think I can rotate the CP vector by Theta where cos theta = r/|CP| to find a. Similarly rotating -a by 90 degrees should find the equivalent vector of I*a. This should avoid complex numbers right? – cegprakash May 04 '18 at 12:30
  • added a c# implementation of this here https://stackoverflow.com/a/53252024/175592 – Bas Smit Nov 11 '18 at 18:51
1

Hmm not really an algorithm question (people tend to mistake algorithm and equation) If you want to write a code then do (you did not specify language nor what prevents you from doing this which is the reason of close votes)... Without this info your OP is just asking for math equation which is indeed off-topic here and by answering this I risk (right-full) down-votes too (but this is/was asked a lot here with much less info and 4 reopen votes against 1 close put my decision weight on reopen and answering this anyway).

You can exploit the fact that you are in 2D as in 2D perpendicular vectors to vector a(x,y) are computed like this:

c = (-y, x)
d = ( y,-x)
c = -d

so you swap x,y and negate one (which one determines if the perpendicular vector is CW or CCW). It is really a rotation formula but as we rotate by 90deg the cos,sin are just +1 and -1.

Now normal n to any circumference point on circle lies in the line going through that point and circles center. So putting all this together your tangents are:

// normal
nx = Px-Cx
ny = Py-Cy
// tangent 1
tx = -ny
ty = +nx
// tangent 2
tx = +ny
ty = -nx

If you want unit vectors than just divide by radius a (not sure why you do not call it r like the rest of the math world) so:

// normal
nx = (Px-Cx)/a
ny = (Py-Cy)/a
// tangent 1
tx = -ny
ty = +nx
// tangent 2
tx = +ny
ty = -nx
Spektre
  • 49,595
  • 11
  • 110
  • 380
  • I don't understand. Assuming (Cx,Cy) to be (1,1) a to be 1 and (Px,Py) to be (3,1) I get (0,2) and (0,-2) as tangents. Are you assuming something wrong? P is an external point outside the circle. – cegprakash Apr 23 '18 at 16:37
  • @cegprakash equation is correct. The input of yours is wrong. If you got `C(1,1)` and `a=1` then `Px` can not be `3` as that would be too far from your center ( `|Px-Cx|` is twice the radius which is wrong) so your test `P` does not lie on your circle. but the tangents are OK even for your wrong `P` they are just wrong in size ... `P(3,1)` is directly to the right of the circle center C(1,1) so the tangents are pointing only up and down hence `(0,+/-2)` or `(0,+/-1)` for correct `a=2` so no `tx` component is present – Spektre Apr 23 '18 at 17:57
0

Let's go through derivation process: enter image description here enter image description here

As you can see, if the interior of the square is < 0 it's because the point is interior to the circumferemce. When the point is outside of the circumference there are two solutions, depending on the sign of the square.

The rest is easy. Take atan(solution) and be carefull here with the signs, you may better do some checks.
Use (2) and then undo (1) transformations and that's all.

Ripi2
  • 7,031
  • 1
  • 17
  • 33
0

c# implementation of dmuir's answer:

static void FindTangents(Vector2 point, Vector2 circle, float r, out Line l1, out Line l2)
{
    var p = new Complex(point.x, point.y);
    var c = new Complex(circle.x, circle.y);
    var cp = p - c;
    var d = Math.Sqrt(cp.Real * cp.Real + cp.Imaginary * cp.Imaginary - r * r);
    var q = GetQ(r, cp, d, c);
    var q2 = GetQ(r, cp, -d, c);

    l1 = new Line(point, new Vector2((float) q.Real, (float) q.Imaginary));
    l2 = new Line(point, new Vector2((float) q2.Real, (float) q2.Imaginary));
}

static Complex GetQ(float r, Complex cp, double d, Complex c)
{
    return c + r * (cp / (r + Complex.ImaginaryOne * d));
}
Bas Smit
  • 645
  • 1
  • 7
  • 19