How can I detect a tangent common to two circles?

Discussion in 'Math' started by strantor, May 1, 2015.

1. strantor Thread Starter AAC Fanatic!

Oct 3, 2010
4,302
1,989
This question is heavily math and heavily Python programming. If you have some input on the math but not the Python, that's still very welcome - I posted this in the math section because I don't want to limit it to a discussion of Python.

This is a real-world problem, not homework help. I'm using Blender to make a video game, and blender games are scripted in Python. In my video game I want to render a wire (just a line) wrapping around some sheave/pulleys and a large circle. The lines (including circles) must be drawn using X,Y coordinates. Sorry, no vectors or rays or curves or anything like that.

I can draw circles (24 sided polygons), arcs/hemicircles/quadrants (fractional 24-sided polygons), and straight lines with the X,Y method, but I cannot figure out how to detect the point on a circle where the wire should land, as if the wire were rested on the circumference of it.

Here's a visual:

I want the red line to come up from left (just as it does), wrap around the left side wheel (just as it does), and then instead of continuing over to the right wheel (just as it does), it should go down around the circumference of the center circle and back up to the right wheel (just as the green dashed line does).

In order to do this, I need to know the line which is tangent to the left wheel and the center circle (and same for the right side). This will allow me to continue the arc around the left wheel >90 degrees (as shown, fixed 90deg) to the tangent point, draw the tangent, and begin another arc at the tangent point of the center circle, and so on, completing the wire loop.

I cannot use fixed coordinates, as the system is dynamic
Radius of left & right wheel is constant
Radius of the center circle is variable, and will get smaller during gameplay.
Y-value of the left & right wheels is variable.
Y-value of the center circle is variable.

If you have any input on the math, please chime in now.
If you have any Python experience, please check out the code below (the code used to generate the lines in the above image) and chime in if you have any input about that.

Thanks!

Code (Text):
1. import bge
2. import math
3. cont = bge.logic.getCurrentController()
4. obj = cont.owner
5. sce = obj.scene
6. objects = sce.objects
7.
8. RGB = [0.0,1.0,0.0]
9. GBR = [1.0,0.0,0.0]
10.
11. #         Y is FWD/BACK (up & down the track)
12. #         X is left & right (port = neg, stbd = pos)
13. # circles start from STBD side and draw CCW; I use negative numbers & subtraction to make them draw CW
14.
15.
16. #3-Dimensional cartesian distance formula
17. def getdist(Xa,Ya,Za,Xb,Yb,Zb):
18.     Xs = (Xa-Xb)**2
19.     Ys = (Ya-Yb)**2
20.     Zs = (Za-Zb)**2
21.     return (math.sqrt(Xs+Ys+Zs))
22.
23. #generates the coordinates for a catenary/parabolic droop given 2 end points
24. #Only in effect when the wire is slack; when tensioned, wire is straight
25. def create_catenary(amplitude,X1,Y1,Z1,X2,Y2,Z2,distance=0):
26.     #DIVIDE THE SPAN OF THE LENGTH OF PORT SIDE INTO 100 SEGMENTS
27.     #AND GENERATE A 100 SEGMENT LINE BETWEEN THE TWO POINTS
28.     if distance == 0:
29.         dist = getdist(X1,Y1,Z1,X2,Y2,Z2)
30.     else:
31.         dist = distance
32.     y_change = (Y2-Y1)/100
33.     x_change = (X2-X1)/100
34.     for i in range(1,101):
35.         deg1 = i * 1.78218 #180 degrees, divided by 101 segments
36.         deg2 = (i + 1) * 1.78218
37.         Y = Y1 + (y_change * i)
38.         X = X1 + (x_change * i)
39.         Z = Z1 + (math.sin(math.radians(deg2)))*amplitude
40.         point_1 = [X,Y,Z]
41.         Y = Y1 + (y_change * i) - y_change
42.         X = X1 + (x_change * i) - x_change
43.         Z = Z1 + (math.sin(math.radians(deg1)))*amplitude
44.         point_2 = [X,Y,Z]
45.         bge.render.drawLine(point_1, point_2, GBR)
46.
47. flywheel_offset = .18
48.
49. X1_port, Y1_port, Z1_port = objects["PORT AFT FLY"].worldPosition
50. X2_port, Y2_port, Z2_port = objects["PORT FWD FLY"].worldPosition
51. X1_fwd, Y1_fwd, Z1_fwd = objects["PORT FWD FLY"].worldPosition
52. X2_fwd, Y2_fwd, Z2_fwd = objects["STBD FWD FLY"].worldPosition
53. X1_stbd, Y1_stbd, Z1_stbd = objects["STBD FWD FLY"].worldPosition
54. X2_stbd, Y2_stbd, Z2_stbd = objects["STBD AFT FLY"].worldPosition
55. X1_aft, Y1_aft, Z1_aft = objects["STBD AFT FLY"].worldPosition
56. X2_aft, Y2_aft, Z2_aft = objects["PORT AFT FLY"].worldPosition
57.
58. #center circle/hemicircle
59. start_angle = 2 * math.pi
61. sides = 24
62. angle_increment = 2 * math.pi / sides
63.
64. center_x = objects["PYLON"].worldPosition.x
65. center_y = objects["PYLON"].worldPosition.y
66. center_z = Z1_fwd
67.
71.
72. end_angle = 1 * math.pi
73. while start_angle > end_angle:
74.     circ_x1 = round(radius*math.cos(start_angle) + center_x,2)
75.     circ_y1 = round(radius*math.sin(start_angle) + center_y,2)
76.     circ_x2 = round(radius*math.cos(start_angle-angle_increment) + center_x,2)
77.     circ_y2 = round(radius*math.sin(start_angle-angle_increment) + center_y,2)
78.     bge.render.drawLine([circ_x1,circ_y1,center_z],[circ_x2,circ_y2,center_z],GBR)
79.     start_angle -= angle_increment
80.
81. #port forward radius around sheave/pulley
82. start_angle = 1 * math.pi
84. sides = 24
85. angle_increment = 2 * math.pi / sides
86.
87. center_x = X1_fwd
88. center_y = Y1_fwd
89. center_z = Z1_fwd
90.
94.
95. end_angle = .5 * math.pi
96. while start_angle > end_angle:
97.     circ_x1 = round(radius*math.cos(start_angle) + center_x,2)
98.     circ_y1 = round(radius*math.sin(start_angle) + center_y,2)
99.     circ_x2 = round(radius*math.cos(start_angle-angle_increment) + center_x,2)
100.     circ_y2 = round(radius*math.sin(start_angle-angle_increment) + center_y,2)
101.     bge.render.drawLine([circ_x1,circ_y1,center_z],[circ_x2,circ_y2,center_z],GBR)
102.     start_angle -= angle_increment
103.
105. X1_port -=flywheel_offset
106. X2_port -=flywheel_offset
107. port_dist = getdist(X1_port,Y1_port,Z1_port,X2_port,Y2_port,Z2_port)
108. Y1_fwd +=flywheel_offset
109. Y2_fwd +=flywheel_offset
110. fwd_dist = getdist(X1_fwd,Y1_fwd,Z1_fwd,X2_fwd,Y2_fwd,Z2_fwd)
111. X1_stbd +=flywheel_offset
112. X2_stbd +=flywheel_offset
113. stbd_dist = getdist(X1_stbd,Y1_stbd,Z1_stbd,X2_stbd,Y2_stbd,Z2_stbd)
114. Y1_aft -=flywheel_offset
115. Y2_aft -=flywheel_offset
116. aft_dist = getdist(X1_aft,Y1_aft,Z1_aft,X2_aft,Y2_aft,Z2_aft)
117.
118. #determine whether to slack wire or not
119. # (if distance between pulleys is less than wire loop length)
120. total_dist = port_dist +\
121.     stbd_dist+\
122.     fwd_dist+\
123.     aft_dist+\
124.     (flywheel_offset*4)
125. print(total_dist)
126. loop_length = 20.0790
127.
128. if total_dist < loop_length:
129.     amplitude = (total_dist - loop_length) * .5
130. else:
131.     amplitude = 0
132. print(amplitude)
133.
134. create_catenary(amplitude, X1_port, Y1_port, Z1_port, X2_port, Y2_port, Z2_port, port_dist)
135. create_catenary(amplitude, X1_fwd, Y1_fwd, Z1_fwd, X2_fwd, Y2_fwd, Z2_fwd, fwd_dist)
136. create_catenary(amplitude, X1_stbd, Y1_stbd, Z1_stbd, X2_stbd, Y2_stbd, Z2_stbd, stbd_dist)
137. create_catenary(amplitude, X1_aft, Y1_aft, Z1_aft, X2_aft, Y2_aft, Z2_aft, aft_dist)
The screenshot above will probably leave you wondering about all the "catenary" business in my code. For a visual explanation, see the pics below. The "wire" is a continuous loop which can be tightened/loosened by driving the wheels along the track in opposite directions.

2. WBahn Moderator

Mar 31, 2012
18,085
4,917
The problem you are trying to solve if pretty straightforward.

strantor likes this.
3. strantor Thread Starter AAC Fanatic!

Oct 3, 2010
4,302
1,989
Thank you wBahn. That looks very familiar. My pile of scratch papers have all those same lines drawn on them but with different trig functions. I think I'm just taking the scenic route to where you are, and get the same answers. But I am working a different way to get the X/Y coordinates out of it. That's the whole point; just looking for the most efficient way to get dots that I can plot. That's still got me chasing my tail. My trig is weak but I'll get it. I can get some of the trig functions to return coordinates but they're messed up, I guess because the trig functions assume a 0,0 starting point and a right triangle aligned to X & Y. Trying to correct them for orientation and position is making my brain hurt.

4. KL7AJ AAC Fanatic!

Nov 4, 2008
2,043
292
I believe you could also use the parametric equations. I'll have to think on this. Great problem!

5. WBahn Moderator

Mar 31, 2012
18,085
4,917
You need the points at which the red line L touches the circles. Let's call these <x3,y4> on the left and <x4,y4>on the right.

x3 = x1 + R1·sin('a')
y3 = y1 + R1·cos('a')

x4 = x2 - R2·sin('a')
y4 = y2 - R2·cos('a')

This assumes that x increases to the right and y increases upward. It also assumed that the tangent line goes from the top of the left circle to the bottom of the right circle (which is why it is addition in the first pair of equations and subtraction in the second). For your problem, there is also the assumption that Y2 < Y1 + (R1+R2) otherwise the line will not touch the second circuit at all, but rather just go straight across).

You can use symmetry to find the points for the right hand tangent line provided that symmetry applies. If not, it is easy enough to adjust the equations for the case of going from the bottom of the left circle to the top of the right circle.

strantor likes this.
6. strantor Thread Starter AAC Fanatic!

Oct 3, 2010
4,302
1,989
This works beautifully WBahn. You've saved me at least 100 mentamotional stress units. I love you.