Formula for rotating a 4 point (rectangle) polygon? - python

I am using Zelle graphics, but I suppose it is the same for any graphics program. From what I can tell, Zelle graphics does not have a "setheading" or "rotate" command for the rectangle (this is probably because the bounding box is created with two points). In turtle graphics it is easy with setheading(). So presently, I have a class that creates a polygon. I have a draw method and a rotate method. In the rotate method I am changing the Point(x1,y1) for each corner in a FOR LOOP. I broke out some graph paper to get the initial first few points to rotate, there is complex math I believe that would make this more palatable.
Presently this is my init method with these attributes, but I am sure there is a better way:
class create():
def __init__(self, p1x,p1y,p2x,p2y,p3x,p3y,p4x,p4y):
self.p1x = p1x
self.p2x = p2x
self.p3x = p3x
self.p4x = p4x
self.p1y = p1y
self.p2y = p2y
self.p3y = p3y
self.p4y = p4y
NOTE : this is not the entirety of my code. This is just the INIT method. I don't want anyone to think I have not tried this. I have with working code to rotate a polygon to the left about 5 points, but this is just using self.p1x=self.p1x+1 - I know this is not the correct way to rotate a rectangle. But at present I have a working model for rotating it slightly.
EDIT :
Here is the new work I have tried. I am still trying to understand this :
I need the midpoint, because the equation is derived from it. That is after I get the rotation correct. I am going to assume a midpoint in a 10x10 starting from the origin is going to be 5,5 for this example.
Does this code look ok so far? I am going to assume a rotation angle of 10 degrees for this example.
import math
#assume a 10 x 10 rectangle top left corner 0,0
#top left coords
x=0
y=0
#midpoints
midx = 5
midy = 5
#degrees to radians???????
angle=math.radians(10)
#new top left x
newx1 = x - midx * math.cos(angle) - (y-midy)*math.sin(angle)+ midx
#new top left y
newy1 = y - midy * math.sin(angle) + (x-midx)*math.cos(angle)+ midy
print(newx1, newy1)
The output is : 0.9442021232736115 -0.7922796533956911

This is version which works for me - it rotates Point around midx, midy with angle.
First it has to move middle to (0,0) and get current angle.
def rotate_point(point, angle, midx, midy):
old_x = point.x
old_y = point.y
old_cx = old_x - midx # move middle to (0,0)
old_cy = old_y - midy # move middle to (0,0)
angle_radians = math.radians(angle)
angle_sin = math.sin(angle_radians)
angle_cos = math.cos(angle_radians)
new_cx = old_cx * angle_cos - old_cy * angle_sin
new_cy = old_cx * angle_sin + old_cy * angle_cos
new_x = new_cx + midx # move back
new_y = new_cy + midy # move back
point = Point(new_x, new_y)
return point
And next I can use it to rotate Polygon
def rotate_polygon(polygon, angle, midx, midy):
new_points = []
for p in polygon.getPoints():
new_p = rotate_point(p, angle, midx, midy)
new_points.append(new_p)
return Polygon(*new_points)
Here full code which rotates
rectangle around middle point (angles: 30, 60),
triangle around one of corners (angles: 90, 180, 270).
import math
from graphics import *
def rotate_point(point, angle, midx, midy):
old_x = point.x
old_y = point.y
old_cx = old_x - midx # move middle to (0,0)
old_cy = old_y - midy # move middle to (0,0)
angle_radians = math.radians(angle)
angle_sin = math.sin(angle_radians)
angle_cos = math.cos(angle_radians)
new_cx = old_cx * angle_cos - old_cy * angle_sin
new_cy = old_cx * angle_sin + old_cy * angle_cos
new_x = new_cx + midx # move back
new_y = new_cy + midy # move back
point = Point(new_x, new_y)
return point
def rotate_polygon(polygon, angle, midx, midy):
new_points = []
for p in polygon.getPoints():
new_p = rotate_point(p, angle, midx, midy)
new_points.append(new_p)
return Polygon(*new_points)
# --- main ---
win = GraphWin()
# --- rectangle ---
x = 100
y = 100
w = 50
h = 50
midx = x + w//2 # rectangle middle
midy = y + h//2 # rectangle middle
p1 = Polygon(Point(x, y), Point(x+w, y), Point(x+w, y+h), Point(x, y+h), Point(x, y))
p1.draw(win)
p2 = rotate_polygon(p1, 30, midx, midy)
p2.draw(win)
p3 = rotate_polygon(p1, 60, midx, midy)
p3.draw(win)
# --- triangle ---
x = 60
y = 60
w = 50
h = 50
midx = x #+5 # triangle corner
midy = y #+5 # triangle corner
t1 = Polygon(Point(x, y), Point(x+w, y), Point(x+w//2, y+h), Point(x, y))
t1.draw(win)
t2 = rotate_polygon(t1, 90, midx, midy)
t2.draw(win)
t3 = rotate_polygon(t1, 180, midx, midy)
t3.draw(win)
t4 = rotate_polygon(t1, 270, midx, midy)
t4.draw(win)
# ---
win.getMouse()
win.close()

Related

OpenGL GL_POLYGON only works when drawing clockwise?

I'm new to OpenGL and I try to use the following code (GL_POLYGON) to draw a circle using python. But it seems like it only draws it when the points are added in a clockwise manner, otherwise, it just draws nothing
This successfully draws a circle
p = self.pos + Y_VEC * self.height # center of the circle
dv = self.dir_vec * self.radius # vector point forward
rv = self.right_vec * self.radius # vector point to right
sides = 20 # sides of the circle (circle is in fact polygon)
angle = 0
inc = 2 * math.pi / sides
glColor3f(0, 1, 0)
glPointSize(10.0)
glBegin(GL_POLYGON) # GL_POLYGON drawn but not shown in top view? GL_LINE_LOOP works
# glVertex3f(*p) # used for TRIANGLE_FAN
for i in range(sides+1):
pc = p + dv * math.cos(angle) + rv * math.sin(angle)
glVertex3f(*pc)
angle -= inc
glEnd()
Nothing rendered (only change is "-=" to "+=")
angle = 0
inc = 2 * math.pi / sides
glColor3f(0, 1, 0)
glPointSize(10.0)
glBegin(GL_POLYGON) # GL_POLYGON drawn but not shown in top view? GL_LINE_LOOP works
# glVertex3f(*p) # used for TRIANGLE_FAN
for i in range(sides+1):
pc = p + dv * math.cos(angle) + rv * math.sin(angle)
glVertex3f(*pc)
angle += inc # change here
glEnd()
Is this normal? What am I doing wrong?
Make sure face culling is disabled with glDisable(GL_CULL_FACE).

Difficulty calculating slope for a set of rotated and shifted ellipses, sometimes inverted, sometimes completely wrong

I am using OpenCV-Python to fit an ellipse to the shape of a droplet.
Then I choose a line, which represents the surface the droplet is resting on.
I calculate the tangents at the intersection of the surface and the ellipse to get the contact angle of the droplet.
It works most of the time, but in some cases, the tangents are flipped upside down or just wrong.
It seems that the calculation for the slope of the tangent fails.
Can someone tell me why this happens?
Here you can see how it should look like (surface at y=250):
And this is the result when I choose a surface level of y=47:
I did some research and I need to detect which of the two maj_ax, min_ax was parallel to the x-Axis before the ellipse gets rotated by phi or else the slope calculation algorithm fails.
What am I doing wrong?
Here is a minimal reproducible example:
from math import cos, sin, pi, sqrt, tan, atan2, radians
import cv2
class Droplet():
def __init__(self):
self.is_valid = False
self.angle_l = 0
self.angle_r = 0
self.center = (0,0)
self.maj = 0
self.min = 0
self.phi = 0.0
self.tilt_deg = 0
self.foc_pt1 = (0,0)
self.foc_pt2 = (0,0)
self.tan_l_m = 0
self.int_l = (0,0)
self.line_l = (0,0,0,0)
self.tan_r_m = 0
self.int_r = (0,0)
self.line_r = (0,0,0,0)
self.base_diam = 0
def evaluate_droplet(img, y_base) -> Droplet:
drplt = Droplet()
crop_img = img[:y_base,:]
shape = img.shape
height = shape[0]
width = shape[1]
# values only for 8bit images!
bw_edges = cv2.Canny(crop_img, 76, 179)
contours, hierarchy = cv2.findContours(bw_edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
if len(contours) == 0:
raise ValueError('No contours found!')
edge = max(contours, key=cv2.contourArea)
(x0,y0),(maj_ax,min_ax),phi_deg = cv2.fitEllipse(edge)
phi = radians(phi_deg) # to radians
a = maj_ax/2
b = min_ax/2
intersection = calc_intersection_line_ellipse((x0,y0,a,b,phi),(0,y_base))
if intersection is None:
raise ValueError('No intersections found')
# select left and right intersection points
x_int_l = min(intersection)
x_int_r = max(intersection)
foc_len = sqrt(abs(a**2 - b**2))
# calc slope and angle of tangent
m_t_l = calc_slope_of_ellipse((x0,y0,a,b,phi), x_int_l, y_base)
angle_l = pi - atan2(m_t_l,1)
m_t_r = calc_slope_of_ellipse((x0,y0,a,b,phi), x_int_r, y_base)
angle_r = atan2(m_t_r,1) + pi
drplt.angle_l = angle_l
drplt.angle_r = angle_r
drplt.maj = maj_ax
drplt.min = min_ax
drplt.center = (x0, y0)
drplt.phi = phi
drplt.tilt_deg = phi_deg
drplt.tan_l_m = m_t_l
drplt.tan_r_m = m_t_r
drplt.line_l = (int(round(x_int_l - (int(round(y_base))/m_t_l))), 0, int(round(x_int_l + ((height - int(round(y_base)))/m_t_l))), int(round(height)))
drplt.line_r = (int(round(x_int_r - (int(round(y_base))/m_t_r))), 0, int(round(x_int_r + ((height - int(round(y_base)))/m_t_r))), int(round(height)))
drplt.int_l = (x_int_l, y_base)
drplt.int_r = (x_int_r, y_base)
drplt.foc_pt1 = (x0 + foc_len*cos(phi), y0 + foc_len*sin(phi))
drplt.foc_pt2 = (x0 - foc_len*cos(phi), y0 - foc_len*sin(phi))
drplt.base_diam = x_int_r - x_int_l
drplt.is_valid = True
# draw ellipse and lines
img = cv2.drawContours(img,contours,-1,(100,100,255),2)
img = cv2.drawContours(img,edge,-1,(255,0,0),2)
img = cv2.ellipse(img, (int(round(x0)),int(round(y0))), (int(round(a)),int(round(b))), int(round(phi*180/pi)), 0, 360, (255,0,255), thickness=1, lineType=cv2.LINE_AA)
y_int = int(round(y_base))
img = cv2.line(img, (int(round(x_int_l - (y_int/m_t_l))), 0), (int(round(x_int_l + ((height - y_int)/m_t_l))), int(round(height))), (255,0,255), thickness=1, lineType=cv2.LINE_AA)
img = cv2.line(img, (int(round(x_int_r - (y_int/m_t_r))), 0), (int(round(x_int_r + ((height - y_int)/m_t_r))), int(round(height))), (255,0,255), thickness=1, lineType=cv2.LINE_AA)
img = cv2.ellipse(img, (int(round(x_int_l)),y_int), (20,20), 0, 0, -int(round(angle_l*180/pi)), (255,0,255), thickness=1, lineType=cv2.LINE_AA)
img = cv2.ellipse(img, (int(round(x_int_r)),y_int), (20,20), 0, 180, 180 + int(round(angle_r*180/pi)), (255,0,255), thickness=1, lineType=cv2.LINE_AA)
img = cv2.line(img, (0,y_int), (width, y_int), (255,0,0), thickness=2, lineType=cv2.LINE_AA)
img = cv2.putText(img, '<' + str(round(angle_l*180/pi,1)), (5,y_int-5), cv2.FONT_HERSHEY_COMPLEX, .5, (0,0,0))
img = cv2.putText(img, '<' + str(round(angle_r*180/pi,1)), (width - 80,y_int-5), cv2.FONT_HERSHEY_COMPLEX, .5, (0,0,0))
cv2.imshow('Test',img)
cv2.waitKey(0)
return drplt
def calc_intersection_line_ellipse(ellipse_pars, line_pars):
"""
calculates intersection(s) of an ellipse with a line
:param ellipse_pars: tuple of (x0,y0,a,b,phi): x0,y0 center of ellipse; a,b sem-axis of ellipse; phi tilt rel to x axis
:param line_pars: tuple of (m,t): m is the slope and t is intercept of the intersecting line
:returns: x-coordinate(s) of intesection as list or float or none if none found
"""
## -->> http://quickcalcbasic.com/ellipse%20line%20intersection.pdf
(x0, y0, h, v, phi) = ellipse_pars
(m, t) = line_pars
y = t - y0
try:
a = v**2 * cos(phi)**2 + h**2 * sin(phi)**2
b = 2*y*cos(phi)*sin(phi) * (v**2 - h**2)
c = y**2 * (v**2 * sin(phi)**2 + h**2 * cos(phi)**2) - (h**2 * v**2)
det = b**2 - 4*a*c
if det > 0:
x1 = int(round((-b - sqrt(det))/(2*a) + x0))
x2 = int(round((-b + sqrt(det))/(2*a) + x0))
return x1,x2
elif det == 0:
x = int(round(-b / (2*a)))
return x
else:
return None
except Exception as ex:
raise ex
def calc_slope_of_ellipse(ellipse_pars, x, y):
"""
calculates the slope of the tangent at point x,y, the point needs to be on the ellipse!
:param ellipse_params: tuple of (x0,y0,a,b,phi): x0,y0 center of ellipse; a,b sem-axis of ellipse; phi tilt rel to x axis
:param x: x-coord where the slope will be calculated
:returns: the slope of the tangent
"""
(x0, y0, a, b, phi) = ellipse_pars
# transform to non-rotated ellipse
x_rot = (x - x0)*cos(phi) + (y - y0)*sin(phi)
y_rot = (x - x0)*sin(phi) + (y - y0)*cos(phi)
m_rot = -(b**2 * x_rot)/(a**2 * y_rot) # slope of tangent to unrotated ellipse
#rotate tangent line back to angle of the rotated ellipse
m_tan = tan(atan2(m_rot,1) + phi)
return m_tan
if __name__ == "__main__":
im = cv2.imread('untitled1.png')
# any value below 250 is just the droplet without the substrate
drp = evaluate_droplet(im, 250)
Original image:
I made a mistake in calc_slope_ellipse:
x_rot = (x - x0)*cos(phi) + (y - y0)*sin(phi)
should be
x_rot = (x - x0)*cos(phi) - (y - y0)*sin(phi)
this fixes the wrong sign of the slope at y=47.
I replaced the atan2:
m_rot = -(b**2 * x_rot)/(a**2 * y_rot) # slope of tangent to unrotated ellipse
#rotate tangent line back to angle of the rotated ellipse
m_tan = tan(atan2(m_rot,1) + phi)
with
tan_a = x_rot/a**2
tan_b = y_rot/b**2
#rotate tangent line back to angle of the rotated ellipse
tan_a_r = tan_a*cos(phi) + tan_b*sin(phi)
tan_b_r = tan_b*cos(phi) - tan_a*sin(phi)
m_tan = - (tan_a_r / tan_b_r)
This fixes the weird behaviour for certain cases (y=62).
Complete fcn:
def calc_slope_of_ellipse(ellipse_pars, x, y):
(x0, y0, a, b, phi) = ellipse_pars
# transform to non-rotated ellipse centered to origin
x_rot = (x - x0)*cos(phi) - (y - y0)*sin(phi)
y_rot = (x - x0)*sin(phi) + (y - y0)*cos(phi)
# Ax + By = C
tan_a = x_rot/a**2
tan_b = y_rot/b**2
#rotate tangent line back to angle of the rotated ellipse
tan_a_r = tan_a*cos(phi) + tan_b*sin(phi)
tan_b_r = tan_b*cos(phi) - tan_a*sin(phi)
m_tan = - (tan_a_r / tan_b_r)
return m_tan

Filling a circle produced by Midpoint Algorithm

In python, I have written some code that generates a circle using Bresenham's Midpoint Algorithm:
from PIL import Image, ImageDraw
radius = 100 #radius of circle
xpts = [] #array to hold x pts
ypts = [] #array to hold y pts
img = Image.new('RGB', (1000, 1000))
draw = ImageDraw.Draw(img) #to use draw.line()
pixels = img.load()
d = (5/4) - radius
x = 0
y = radius
xpts.append(x) #initial x value
ypts.append(y) #initial y value
while x < y:
if d < 0:
d += (2*x + 3)
x += 1
xpts.append(x + 500) #translate points to center by 500px
ypts.append(y - 500)
else:
d += (2 * (x - y) + 5)
x += 1
y -= 1
xpts.append(x + 500) #translate points to center by 500px
ypts.append(y - 500)
for i in range(len(xpts)): #draw initial and reflected octant points
pixels[xpts[i] ,ypts[i]] = (255,255,0) #initial octant
pixels[xpts[i],-ypts[i]] = (255,255,0)
pixels[-xpts[i],ypts[i]] = (255,255,0)
pixels[-xpts[i],-ypts[i]] = (255,255,0)
pixels[ypts[i],xpts[i]] = (255,255,0)
pixels[-ypts[i],xpts[i]] = (255,255,0)
pixels[ypts[i],-xpts[i]] = (255,255,0)
pixels[-ypts[i],-xpts[i]] = (255,255,0)
img.show()
To fill it, I had planned to use ImageDraw to draw a line horizontally within the circle from each point that is generated from the initial octant using draw.line(). I have the x and y coordinates stored in arrays. However, I am stuck interpreting each point and its reflection point to draw the horizontal line using draw.line(). Could someone clarify this?
Instead of drawing individual pixels, you would just add a line that connects the pixels corresponding to each other (either -x and +x or -y and +y). For each Bresenham step, you draw four lines (each connecting two octants).
Here is your adapted sample code. I dropped the points array and instead drew the lines directly. I also added the cx and cy variables that define the circle center. In your code, you sometimes used negative indices. This only works by coincidence because the circle is in the center:
from PIL import Image, ImageDraw
radius = 100 # radius of circle
xpts = [] # array to hold x pts
ypts = [] # array to hold y pts
img = Image.new('RGB', (1000, 1000))
draw = ImageDraw.Draw(img) # to use draw.line()
pixels = img.load()
d = (5 / 4) - radius
x = 0
y = radius
cx = 500
cy = 500
def draw_scanlines(x, y):
color = (255, 255, 0)
draw.line((cx - x, cy + y, cx + x, cy + y), fill=color)
draw.line((cx - x, cy - y, cx + x, cy - y), fill=color)
draw.line((cx - y, cy + x, cx + y, cy + x), fill=color)
draw.line((cx - y, cy - x, cx + y, cy - x), fill=color)
draw_scanlines(x, y)
while x < y:
if d < 0:
d += (2 * x + 3)
x += 1
else:
d += (2 * (x - y) + 5)
x += 1
y -= 1
draw_scanlines(x, y)
img.show()
Instead of drawing lines, you can fill all points inside a circle with radius radius in O(n^2) using:
# Your code here
for x in range(radius):
for y in range(radius):
if x**2 + y**2 < radius**2:
pixels[ x + 500 , y-500] = (255,255,0)
pixels[ x + 500 , -y-500] = (255,255,0)
pixels[ -x + 500 , y-500] = (255,255,0)
pixels[ -x + 500 , -y-500] = (255,255,0)
img.show()

Draw a rotated box in openCV in python

I would like to draw a rotated rectangle I've got the top left point and bottom right point, width and height of box. As well as the angle. But I can't seem work out how you draw the rotated rectangle using OpenCV in Python. Please note that I do not want to rotate the image.
Thanks
There are many ways to draw a rectangle in OpenCV.
From the OpenCV documentatation: Drawing Functions
rectangle
Draws a simple, thick, or filled up-right rectangle.
So this function doesn't help as you want to draw it rotated.
A rectangle is nothing but a special 4-sided polygon. So simply use the function for drawing polygons instead.
polylines
Draws several polygonal curves.
Python:
cv2.polylines(img, pts, isClosed, color[, thickness[, lineType[, shift]]]) → img
and insert the 4 vertices of your rotated rectangle
or draw the 4 sides separately using
line
Draws a line segment connecting two points.
or
drawContours
Draws contours outlines or filled contours.
The points can be obtained using simple math or for example using OpenCV's RotatedRect https://docs.opencv.org/2.4/modules/core/doc/basic_structures.html#rotatedrect
class Point:
def __init__(self, x, y):
self.x = int(x)
self.y = int(y)
class Rectangle:
def __init__(self, x, y, w, h, angle):
# Center Point
self.x = x
self.y = y
# Height and Width
self.w = w
self.h = h
self.angle = angle
def rotate_rectangle(self, theta):
pt0, pt1, pt2, pt3 = self.get_vertices_points()
# Point 0
rotated_x = math.cos(theta) * (pt0.x - self.x) - math.sin(theta) * (pt0.y - self.y) + self.x
rotated_y = math.sin(theta) * (pt0.x - self.x) + math.cos(theta) * (pt0.y - self.y) + self.y
point_0 = Point(rotated_x, rotated_y)
# Point 1
rotated_x = math.cos(theta) * (pt1.x - self.x) - math.sin(theta) * (pt1.y - self.y) + self.x
rotated_y = math.sin(theta) * (pt1.x - self.x) + math.cos(theta) * (pt1.y - self.y) + self.y
point_1 = Point(rotated_x, rotated_y)
# Point 2
rotated_x = math.cos(theta) * (pt2.x - self.x) - math.sin(theta) * (pt2.y - self.y) + self.x
rotated_y = math.sin(theta) * (pt2.x - self.x) + math.cos(theta) * (pt2.y - self.y) + self.y
point_2 = Point(rotated_x, rotated_y)
# Point 3
rotated_x = math.cos(theta) * (pt3.x - self.x) - math.sin(theta) * (pt3.y - self.y) + self.x
rotated_y = math.sin(theta) * (pt3.x - self.x) + math.cos(theta) * (pt3.y - self.y) + self.y
point_3 = Point(rotated_x, rotated_y)
return point_0, point_1, point_2, point_3
Returns four new points that have been translated by theta
https://github.com/rij12/YOPO/blob/yopo/darkflow/net/yopo/calulating_IOU.py

Collisions in Zelle graphics.py

I am trying to make my circle bounce off of my rectangle using Zelle graphics.py. Once the circle bounces off of the rectangle I wanted it to keep moving randomly. Here is my code so far, and it's working!
Also I know that each circle graphics technically can use the points of the smallest possible square that would fit around the circle to do the collision but I'm having trouble with doing that.
from graphics import *
import random
def delay(d):
for i in range(d):
for i in range(50):
pass
#-------------------------------------------------
def main():
win=GraphWin("Moving Circle",500,400)
win.setBackground('white')
pt= Point(100,200)
cir=Circle(pt,30)
#changes the color of the circle for each game
r = random.randrange(256)
b = random.randrange(256)
g = random.randrange(256)
color = color_rgb(r, g, b)
cir.setFill(color)
cir.draw(win)
#rectangle
rec = Rectangle(Point(450,450), Point(275, 425))
rec.draw(win)
rec.setFill('black')
#-------------------------------------------------
pt5 = Point(250,30)
instruct1=Text(pt5, "click multiple times to start(rectangle can take multiple clicks to move)")
instruct1.setTextColor('black')
instruct1.draw(win)
#-------------------------------------------------
p=cir.getCenter()
p2=win.getMouse()
dx=1
dy=1
keepGoing=True
while keepGoing:
d = 100
delay(d)
cir.move(dx,dy)
p=cir.getCenter()
p2=win.checkMouse()
instruct1.setText("")
#rectanlge
isClicked= win.checkMouse()
if isClicked:
rp = isClicked
rc = rec.getCenter()
rdx = rp.getX() - rc.getX()
rdy = rp.getY() - rc.getY()
rec.move(rdx,rdy)
#circle
if((p.getX()-30)<=0.0) or ((p.getX()+30)>=500):
dx= -dx
if((p.getY()-30)<=0.0) or ((p.getY()+30)>=400):
dy=-dy
p3=win.checkMouse()
main()
I know that each circle graphics technically can use the points of the
smallest possible square that would fir around the circle to do the
collision
I'm playing with an alternate idea -- we could consider a circle around the rectangle instead of a square around the circle. The issue for me is that we not only need to detect collision, but come out with a sense of which way to move away from the other object. It's not just True and False but rather a (dx, dy) type of result.
Obviously, a circle around the rectangle is too crude, but suppose it were lots of smaller circles making up the rectangle and we measure circle center to center distance to detect a hit:
A hit on just a central (green) rectangle circle means reverse the vertical direction of the big circle. A hit on just the end (red) circle means reverse the horizontal direction of the big circle. And we can detect both kinds of hits and reverse the big circle completely.
Here's my rework of your code with the above in mind -- I also fixed your multiple clicking issue and made lots of style changes:
from random import randrange
from graphics import *
WIDTH, HEIGHT = 500, 400
RADIUS = 30
def delay(d):
for _ in range(d):
for _ in range(50):
pass
def distance(p1, p2):
return ((p2.getX() - p1.getX()) ** 2 + (p2.getY() - p1.getY()) ** 2) ** 0.5
def intersects(circle, rectangle):
dx, dy = 1, 1 # no change
center = circle.getCenter()
rectangle_radius = (rectangle.p2.getY() - rectangle.p1.getY()) / 2
rectangle_width = rectangle.p2.getX() - rectangle.p1.getX()
y = rectangle.getCenter().getY()
for x in range(int(rectangle_radius * 2), int(rectangle_width - rectangle_radius * 2) + 1, int(rectangle_radius)):
if distance(center, Point(rectangle.p1.getX() + x, y)) <= rectangle_radius + RADIUS:
dy = -dy # reverse vertical
break
if distance(center, Point(rectangle.p1.getX() + rectangle_radius, y)) <= rectangle_radius + RADIUS:
dx = -dx # reverse horizontal
elif distance(center, Point(rectangle.p2.getX() - rectangle_radius, y)) <= rectangle_radius + RADIUS:
dx = -dx # reverse horizontal
return (dx, dy)
def main():
win = GraphWin("Moving Circle", WIDTH, HEIGHT)
circle = Circle(Point(WIDTH / 5, HEIGHT / 2), RADIUS)
# change the color of the circle for each game
color = color_rgb(randrange(256), randrange(256), randrange(256))
circle.setFill(color)
circle.draw(win)
# rectangle
rectangle = Rectangle(Point(275, 425), Point(450, 450)) # off screen
rectangle.setFill('black')
rectangle.draw(win)
dx, dy = 1, 1
while True:
delay(100)
circle.move(dx, dy)
# rectangle
isClicked = win.checkMouse()
if isClicked:
point = isClicked
center = rectangle.getCenter()
rectangle.move(point.getX() - center.getX(), point.getY() - center.getY())
# circle
center = circle.getCenter()
if (center.getX() - RADIUS) <= 0.0 or (center.getX() + RADIUS) >= WIDTH:
dx = -dx
if (center.getY() - RADIUS) <= 0.0 or (center.getY() + RADIUS) >= HEIGHT:
dy = -dy
# collision bounce
x, y = intersects(circle, rectangle)
dx *= x
dy *= y
main()
Not perfect, but something to play around with, possibly plugging in a better intersects() implementation.

Categories