Search code examples
pythonmathtrigonometry

Plot arc path between two points


I am trying to plot a curved path for a robot to follow using the following as a guide: http://rossum.sourceforge.net/papers/CalculationsForRobotics/CirclePath.htm

The code i have does not create a path that ends at the destination. I am expecting the path to curve left or right depending on the quadrant the destination is in (+x+y,+x-y,-x+y,-x-y)

import math
start = [400,500]
dest = [200,300]
speed = 10
startangle = 0
rc =0
rotv =0
rads =0

def getPos(t):
    ang = (rotv*t)+rads
    x = start[0] - rc * math.sin(rads) + rc * math.sin(rotv*(t)+rads)
    y = start[1] + rc * math.cos(rads) - rc * math.cos(rotv*(t)+rads)
    return (int(x),int(y), ang)

dx = dest[0] - start[0]
dy = dest[1] - start[1]
rads = math.atan2(-dy,dx)
rads %= 2*math.pi
distance = (dx**2 + dy**2)**.5  #rg
bangle = 2*rads
rc = distance /(2 * math.sin(rads))
if rads > (math.pi/2):
    bangle = 2*(rads-math.pi)
    rc= -rc
if rads < -(math.pi/2):
    bangle = 2*(rads+math.pi)
    rc= -rc
pathlength = rc * bangle
xc = start[0] - rc * math.sin(rads)
yc = start[1] + rc * math.cos(rads)
rotcenter = [xc,yc]
traveltime = pathlength/speed
rotv = bangle/traveltime
for p in range(int(traveltime)):
    pos = getPos(p)

Start: Blue, End: Red, Rotation Point: Purple enter image description here

UPDATE: I have added code to allow positive and negative x/y values. I have updated the image.


Solution

  • To answer your question I first read through the article you linked. I think it is very interesting and explains the ideas behind the formulas pretty well, althought it lacks the formulas for when the starting position is not at the origin and when the starting angle is not 0.

    It took a little while to come up with these formulas, but now it works for every case I could think of. To be able to use the formulas given in the linked article, I used the names of the variables given there. Notice that I also used the notation with t_0 as the starting time, which you just ignored. You can easily remove any instance of t_0 or set t_0 = 0.

    The last part of the following code is used for testing and creates a little red turtle that traces the path of the computed arc in the specified direction. The black turtle indicates the goal position. Both turtles are close to each other at the end of the animation, but they are not directly above each other, because I am only iterating over integers and it is possible that t_1 is not an integer.

    from math import pi, hypot, sin, cos, atan2, degrees
    
    def norm_angle(a):
        # Normalize the angle to be between -pi and pi
        return (a+pi)%(2*pi) - pi
    
    # Given values
    # named just like in http://rossum.sourceforge.net/papers/CalculationsForRobotics/CirclePath.htm
    x_0, y_0 = [400,500] # initial position of robot
    theta_0 = -pi/2      # initial orientation of robot
    s = 10               # speed of robot
    x_1, y_1 = [200,300] # goal position of robot
    t_0 = 0              # starting time
    
    # To be computed:
    r_G = hypot(x_1 - x_0, y_1 - y_0)        # relative polar coordinates of the goal
    phi_G = atan2(y_1 - y_0, x_1 - x_0)
    phi = 2*norm_angle(phi_G - theta_0)      # angle and 
    r_C = r_G/(2*sin(phi_G - theta_0))       # radius (sometimes negative) of the arc
    L = r_C*phi                              # length of the arc
    if phi > pi:
        phi -= 2*pi
        L = -r_C*phi
    elif phi < -pi:
        phi += 2*pi
        L = -r_C*phi
    t_1 = L/s + t_0                        # time at which the robot finishes the arc
    omega = phi/(t_1 - t_0)                # angular velocity           
    x_C = x_0 - r_C*sin(theta_0)           # center of rotation
    y_C = y_0 + r_C*cos(theta_0)
    
    def position(t):
        x = x_C + r_C*sin(omega*(t - t_0) + theta_0)
        y = y_C - r_C*cos(omega*(t - t_0) + theta_0)
        return x, y
    
    def orientation(t):
        return omega*(t - t_0) + theta_0
    
    #--------------------------------------------
    # Just used for debugging
    #--------------------------------------------
    import turtle
    
    screen = turtle.Screen()
    screen.setup(600, 600)
    screen.setworldcoordinates(0, 0, 600, 600)
    
    turtle.hideturtle()
    turtle.shape("turtle")
    turtle.penup()
    turtle.goto(x_1, y_1)
    turtle.setheading(degrees(orientation(t_1)))
    turtle.stamp()
    turtle.goto(x_0, y_0)
    turtle.color("red")
    turtle.showturtle()
    turtle.pendown()
    for t in range(t_0, int(t_1)+1):
        turtle.goto(*position(t))
        turtle.setheading(degrees(orientation(t)))
    

    I am not sure at which point your code failed, but I hope this works for you. If you intend to use this snippet multiple times in you code consider encapsulating it in a function that takes in the given values as parameters and returns the position function (and if you like rotation function as well).