Reputation: 312
I am bulding a game and I need to creat a map for it. in order to do it i have to use two kinds of elipse: one that is found using 2 points and one that found using 3 points (and go through them). I have found a way to find the equation with 2 points but now I have not found a way to do with 3 point. So I am looking to find the equation for an ellipse given 3 point. that is how i have done 2 points (it is not elegant but it does the job great):
import turtle
import sympy as sp
import random
SCREEN = turtle.Screen()
t = turtle.Turtle()
t.up()
t.ht()
t.speed(0)
tt = turtle.Turtle()
tt.up()
tt.ht()
tt.speed(0)
press = 1
def get_mouse_click_coor(x, y):
turtle.onscreenclick(None)
print(x, y)
draw(x,y)
def draw(mou_x,mou_y):
SCREEN.tracer(0)
global press
global x_press1, y_press1
if press%2 != 0:
t.goto(mou_x,mou_y)
x_press1, y_press1 = mou_x, mou_y
tt.color('blue')
tt.goto(mou_x,mou_y)
tt.dot(7)
if press%2 == 0:
if mou_x == x_press1 and mou_y == y_press1:
press = press - 1
else:
tt.color('red')
tt.goto(mou_x,mou_y)
tt.dot(7)
if mou_x > x_press1 and mou_y > y_press1:
turn = 1
if mou_x > x_press1 and mou_y < y_press1:
turn = 2
if mou_x < x_press1 and mou_y < y_press1:
turn = 3
if mou_x < x_press1 and mou_y > y_press1:
turn = 4
# if turn <= 2:
x0 = -mou_x+x_press1
if turn >= 3:
x0 = -x_press1+mou_x
y0 = mou_y - y_press1
for x in range(int(x0), 1, 1):
y = y0 - y0*sp.root(1.0-(float((x-x0)**2)/(x0**2)),2)
if turn >= 3:
x = -x
t.goto(x+mou_x, y+y_press1)
t.down()
t.up()
press = press + 1
SCREEN.tracer(1)
while True:
t.color('black')
turtle.onscreenclick(get_mouse_click_coor)
turtle.mainloop()
Upvotes: 0
Views: 3879
Reputation: 19029
Sounds like a fun problem! If your 3 click points are in the same quadrant then one of the angles of the triangle defined by those points must be obtuse. Call that B and the other two vertices A and C. The general equation for an x-y oriented ellipse has 4 parameters in it. Substituting the x and y coordinates of A, B and C into the ellipse equation will give you three equations. You need to come up with a 4th. You have the freedom to pick that 4th point in some way that makes sense in the context of your game. Here's a SymPy code snippet that assigns an x value to the center of the ellipse based on A, B and C and a parameter 'f'.
def e3(p1, p2, p3, f):
t = Triangle(p1, p2, p3)
a = t.angles
for p in a:
if a[p] > pi/2:
break
else:
return
while p != p2:
p1, p2, p3 = p2, p3, p1
pts = A, B, C = p1, p2, p3
# perpendicular line to AB passing through A
perp = Segment(A, B).perpendicular_line(A)
# intersection between that and the perpendicular to that
# which passes through C
m = perp.perpendicular_line(C).intersection(perp)[0]
delta = m - A
c = A + delta*f
x, y, h, r, y0 = symbols('x y h r y0', cls=Dummy)
eq = Ellipse((c.x, y0), h, r).equation(x, y)
s = solve([
eq.xreplace(dict(zip((x, y), p))) for p in pts], dict=True)[0]
return eq.subs(s) # for desmos graphing: str(eq.subs(s)).replace('**','^').replace('_','')+'=0'
Some of the results for f = .125, .25, .5, 1, 2 are shown at desmos.
You can see that they all go through the points that you gave as an example. The red circle provides an interesting point of reference. You might want to pick an f so the resulting width of the ellipse is some fraction of the diameter of the circle that would have gone through the points.
The points that the ellipses are going through do not lay in the same quadrant of each ellipse, however. To meet that criteria, you have to consider an "off axis" ellipse. It may also be helpful to use the equation of the arbitrary ellipse that I posted here to guide you in making a selection for variables to control when defining your ellipse.
To make the equation as simple as possible to solve and deterministic, you can make make the axis go through the 2nd click point if that angle creates an acute angle else go through the 1st and 3rd point. Here is code demonstrating that:
def e3(A, B, C, n=7):
'''Return centered at the midpoint of A and C with the axis going through B if the angle at B is acute, else going through A and C.
'''
from sympy import Line
xc, yc = ctr = (A + C)/2
AC = A.distance(C)
smalls = True
if AC >= B.distance(A) and AC >= B.distance(C):
s = Line(A, C).slope
M2 = ctr.distance(A)**2
b = B
if abs(s) <= 1:
m2 = -M2*(s*(b.x - xc) - b.y + yc)**2/(
-M2*(s**2 + 1) + (s*(b.y - yc) + b.x - xc)**2)
else:
s = 1/s
m2 = M2*(s*(b.y - yc) - b.x + xc)**2/(
M2*(s**2 + 1) - (s*(b.x - xc) + b.y - yc)**2)
smalls = False
else:
s = Line(B, ctr).slope
M2 = ctr.distance(B)**2
p = A # or C
if abs(s) <= 1:
m2 = -M2*(s*(p.x - xc) - p.y + yc)**2/(
-M2*(s**2 + 1) + (s*(p.y - yc) + p.x - xc)**2)
else:
s = 1/s
m2 = M2*(s*(p.y - yc) - p.x + xc)**2/(
M2*(s**2 + 1) - (s*(p.x - xc) + p.y - yc)**2)
smalls = False
if smalls:
el = -1 + (-s*(x - xc) + y - yc)**2/(m2*(s**2 + 1)) + (
s*(y - yc) + x - xc)**2/(M2*(s**2 + 1))
else:
el = (M2*(s**2 + 1)*(-m2*(s**2 + 1) + (s*(y - yc) - x + xc)**2
) + m2*(s**2 + 1)*(s*(x - xc) + y - yc)**2)/(
M2*m2*(s**2 + 1)**2)
return el.expand().n(n=n)
from sympy import Point
a,b,c = Point(7/5, 12/5), Point(3/2, 5/2), Point(19/10, 3/2)
e3(a,b,c)
e3(b,c,a)
e3(c,a,b)
Depending on the 2nd click point, then, you will get 1 or 3 ellipses:
Upvotes: 4
Reputation: 94
Not sure if I get your point as it is actually a math question. For me it seems like your looking for something like a function that requires 3 input parameters to draw an ellipse. This would be then following code, where width is the major axis and height the minor axis. xy is the center point of the ellipse.
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Ellipse
# These are 3 xy points
xpoints=(1.5,1.4,1.9)
ypoints=(2.5,2.4,1.5)
# calculate the center
mean_x=np.mean(xpoints)
mean_y=np.mean(ypoints)
# set the size of the ellipse
w=np.max(xpoints)+0.1
h=np.max(ypoints)+0.1
# buid the ellipse around it
ells = Ellipse((mean_x,mean_y), width=w, height=h,facecolor='None')
# plot it
fig = plt.figure(0)
ax = fig.add_subplot(111, aspect='equal')
ax.add_artist(ells)
ax.scatter(xpoints,ypoints, color='r')
ax.set_xlim(-4, 4)
ax.set_ylim(-4, 4)
plt.show()
Upvotes: 1