circle_to_ellipse.py

#!/usr/bin/python3
# ==================================================================
# Calculate points (x,y coords) on an ellipse. The formula works
# with ints and floats, but this code limited it self to
# integers (pixels).
#
# from: www.physicsforums.com/threads/
#       calculating-angle-of-circle-to-produce-given-ellipse.625187/
# ==================================================================

import numpy as np

# ------------------------------------------------------------------
# calculate the x,y coordinates of a circle tilted towards the
# viewer (assumed to be at +z = infinity). An ellipse appears
# (to the viewer) by tilting a circle (of raidus r) around the X
# axis. Tilted (angle a) towards or away from the viewer.
# ------------------------------------------------------------------
# This function only does 1/4 of an ellipse. Input parameters are
# converted to positive values. This is not a problem because
# the ellipse is symmetrical around the x and y axes.
#
#              +y
#      (-x,y)   |   (x,y)
#               |
#        -x ----+---- +x
#               |
#     (-x,-y)   |   (x,-y)
#              -y
#
# ------------------------------------------------------------------
# circle drawn using '*' in the x,y plane with z = 0
#
#               +y    -z
#                |   /  
#               *** /
#            *** | ***
#           **   |/  **
#      -x --*----+----*---- +x
#           **  /|   **
#            **/ | ***
#             / ***
#            /   |
#           +z   -y
#
# ------------------------------------------------------------------
# input parameters
#
# x   pixel coordinates along the +x (0 to r)
# r   circle radius
# a   tilt angle          (degrees)
# ------------------------------------------------------------------

def circle_to_ellipse(x,r,a):

    # ---- no negative input values allowed

    x = int(np.abs(x))
    r = int(np.abs(r))
    a = np.abs(a)              # not necessary but consistant

    # ---- x can not exceed r

    if x > r:
        print('Error: X exceeds Radius')
        return (0,0)

    # ---- given circle radius and a circle's x pixel coordinate
    # ---- along the +X axis, calculate the circle's y pixel
    # ---- coordinate. the circle formula is x**2 + y**2 = r**2

    y = round(np.sqrt(r**2 - x**2))     # convert to integer

    # ---- calculate the cosine of the tilt angle

    rad = np.radians(a)        # convert angle to radians
    c   = np.cos(rad)          # cos of angle

    # ---- ellipse formula
    # ---- (xx/rr) + (yy/(rr*cc)) = 1
    # ---- yy = cc * (rr - xx)
    # ---- calculate ellipse x,y coordinates (given x)

    xe = x

    if y == 0:
        ye = y
    else:
        xx   = x*x             # squared
        rr   = r*r             # squared
        cc   = c*c             # cos of angle squared

        ye = round(np.sqrt(cc*(rr - xx)))

    # ---- return ellipse x,y coordinates

    return (xe,ye)

# ------------------------------------------------------------------
# ---- main
# ------------------------------------------------------------------

if __name__ == '__main__':

    import graphics as gr
    import user_interface as ui
    import coordinate_conversion as cc
    import draw_axes as ax

    def calc_and_draw(win,color,r,a):

        for x in [0,25,50,75,100,125,150,175,190,200]:

            xe,ye = circle_to_ellipse(x, r, a)
            ##print(f'xe = {xe},  ye = {ye},  a = {a}')

            # ---- draw the ellipse x,y coordinates

            wx,wy = cc.center_to_win_coords(xe,ye,wwidth,wheight)
            c = gr.Circle(gr.Point(wx,wy),4)
            c.setWidth(1)
            c.setFill(color)
            c.draw(win)

            wx,wy = cc.center_to_win_coords(xe,-ye,wwidth,wheight)
            c = gr.Circle(gr.Point(wx,wy),4)
            c.setWidth(1)
            c.setFill(color)
            c.draw(win)

            wx,wy = cc.center_to_win_coords(-xe,ye,wwidth,wheight)
            c = gr.Circle(gr.Point(wx,wy),4)
            c.setWidth(1)
            c.setFill(color)
            c.draw(win)

            wx,wy = cc.center_to_win_coords(-xe,-ye,wwidth,wheight)
            c = gr.Circle(gr.Point(wx,wy),4)
            c.setWidth(1)
            c.setFill(color)
            c.draw(win)


    raidus  = 200          # circle radius
    wwidth  = 601          # window width
    wheight = 601          # window height


    win = gr.GraphWin("Elipse Test",wwidth,wheight)
    win.setBackground("white")

    ax.draw_xy_axes(win,True)

    calc_and_draw(win,"black",raidus,60.0)

    calc_and_draw(win,"red",raidus,30.0)

    calc_and_draw(win,"green",raidus,0.0)

    ui.pause()