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,988
    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:
    deflection.png

    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
    60. radius = 1.05#1.05
    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.  
    68. leading_edge_X = center_x
    69. leading_edge_Y = center_y - radius
    70. leading_edge_Z = Z1_fwd
    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
    83. radius = flywheel_offset
    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.  
    91. leading_edge_X = center_x
    92. leading_edge_Y = center_y - radius
    93. leading_edge_Z = Z1_fwd
    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.  
    104. #correction for sheave/pulley radius
    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.

    wireloose.png wiretight.png
     
  2. WBahn

    Moderator

    Mar 31, 2012
    17,716
    4,788
    The problem you are trying to solve if pretty straightforward.
    tangents.png
     
    strantor likes this.
  3. strantor

    Thread Starter AAC Fanatic!

    Oct 3, 2010
    4,302
    1,988
    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,039
    287
    I believe you could also use the parametric equations. I'll have to think on this. Great problem!
     
  5. WBahn

    Moderator

    Mar 31, 2012
    17,716
    4,788
    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,988
    This works beautifully WBahn. You've saved me at least 100 mentamotional stress units. I love you.
     
Loading...