I am trying to create a smooth curve between two straight lines with known gradients, I have found an equation to find theses two point but when i try to find the angle for each of the points the arc doesn't line up.I have done the math and it (should) be right.
def angle(A,B):
o = abs(B[1]-A[1])
a = abs(B[0] - A[0])
angle = math.atan(o/a)
return angle
a1 = angle((250, 175),(75, 300))
a2 = angle((250, 175),(400, 315))
pygame.draw.arc(gameDisplay, WHITE, (0 ,0 ,500,350), (math.pi) + a2,(2*math.pi) - a1)
pygame.draw.line(gameDisplay, BLUE, [400,0], [400, 500], 1)
pygame.draw.line(gameDisplay, BLUE, [0,315], [1000, 315], 1)
pygame.draw.line(gameDisplay, RED, [75,0], [75, 500], 1)
pygame.draw.line(gameDisplay, RED, [0,300], [1000, 300], 1)
This is the basic code for finding the start and stop angle, with (250,175) being the center of the ellipse and (75, 300) and (400, 315) being the two points. The lines are where the arc should start and stop.
You do not draw a perfect circular arc, actually you are drawing an elliptical arc. You've to take into account the rectangular area, which is passed to pygame.draw.arc() ((0, 0, 500, 350)), when the start and end angle for the arc is calculated.
As mention in the comments you've to use math.atan2 rather than math.atan to get angles in the range [-pi, pi]. In pygame, the y axis points downwards, so the y component of the direction vector has to be inverted
def angle(A, B, aspectRatio):
x = B[0] - A[0]
y = B[1] - A[1]
angle = math.atan2(-y, x / aspectRatio)
return angle
a1 = angle((250, 175), (75, 300), 500/350)
a2 = angle((250, 175),(400, 315), 500/350)
pygame.draw.arc(gameDisplay, WHITE, (0, 0, 500, 350), a1, a2)
Related
It's for a handwriting recognition algorithm I'm working on. I'm trying to scale the set of points so that no matter how small or big you write the digit, it all gets scaled to the same size in the end.
I'm having trouble figuring out how to do this. My approach was this. I want the bounding box to be scaled within to match within the bounding bo defined by x = (-256,256) and y = (-256,256) perfectly. It should stretch and squish all points so that the 4 corners match that size. So what I did was calculate the max x distance between the points, then divide that into 512 which should give me how much I need to scale each point on the x-axis to match the -256, 256 borders. Repeat that for the y-axis. Intuitively I thought it should work but it doesn't.
How do I do this? And what is this called in Mathematics what I'm trying to accomplish here?
Here's my Python code and my attempt at it. pointsmod should have the scaled points.
import matplotlib.pyplot as plt
import math
fig = plt.figure()
ax = fig.add_subplot()
points = [(73, 172), (3, 171), (-82, 170), (-130, 164), (-130, 103), (-130, 51), (-89, 80), (-35, 91), (16, 81), (57, 55), (71, 9), (65, -39), (36, -77), (-18, -85), (-65, -68), (-104, -32)]
def boundingbox(points):
x_min = min(point[0] for point in points)
x_max = max(point[0] for point in points)
y_min = min(point[1] for point in points)
y_max = max(point[1] for point in points)
return [(x_min, y_min), (x_min, y_max), (x_max, y_max), (x_max, y_min)]
bb = boundingbox(points)
def distance(point1, point2):
return math.sqrt((point1[0] - point2[0])**2+(point1[1] - point2[1])**2)
xlen = distance(bb[0], bb[3])
ylen = distance(bb[0],bb[1])
pointsmod = []
xfac = 512/xlen
yfac = 512/ylen
for point in points: # My attempt at scaling the points
x = point[0]*xfac
y = point[1]*yfac
pointsmod.append((x,y))
plt.xlim(-256, 256)
plt.ylim(-256, 256)
plt.scatter(*zip(*points))
plt.scatter(*zip(*pointsmod))
ax.set_aspect('equal')
plt.show()
Well, you're not taking the different origins into account. Your bounding box is not centered on (0,0). Say we have
xmin = min(point[0] for point in points)
xmax = max(point[0] for point in points)
ymin = min(point[1] for point in points)
ymax = max(point[1] for point in points)
xlen = xmax - xmin
ylen = ymax - ymin
Then the translation from the points box to (-256,+256) is this:
def scale( oldx, oldy ):
newx = (oldx - xmin) * 512 / xlen - 256
newy = (oldy - ymin) * 512 / ylen - 256
return newx, newy
That moves the (0,0) to upper left, then does the scaling, then moves the origin back to the middle.
I have simple rectangle I just want to rotate this rectangle in any input angle
my code is:
import cv2
import numpy as np
imgc = np.zeros((500, 500, 3), np.uint8)
p0 = (100, 100)
p1 = (100 , 150)
p2 = (150, 150)
p3 = (150, 100)
pp = np.array([p0, p1, p2, p3])
cv2.drawContours(imgc, [pp], 0, (155, 155, 155), -1, cv2.LINE_AA)
cv2.imshow("image",imgc)
cv2.waitKey()
What you need is Rotation Matrices. But you need to remember this rotating a point with a given angle (in radians) around the ORIGIN.
You need to move your points to the origin rotate them and move them back same amount.
Here what is would look line when you break down all dot production steps into one equation:
def rotate(points, angle):
ANGLE = np.deg2rad(angle)
c_x, c_y = np.mean(points, axis=0)
return np.array(
[
[
c_x + np.cos(ANGLE) * (px - c_x) - np.sin(ANGLE) * (py - c_x),
c_y + np.sin(ANGLE) * (px - c_y) + np.cos(ANGLE) * (py - c_y)
]
for px, py in points
]
).astype(int)
Please notice: drawContours expects points as integers not floats so you need to convert your arrays to int.
Here the blue rectangle was the original one and the magenta rectangle is the 45 degrees rotated one:
I'm trying to draw a polygon with DDA line algorithm using pygame.
def Round(a):
return int(a + 0.5)
def mainloop():
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
def Draw():
DrawPoly((100, 100), 6, 100)
pygame.display.flip()
def drawDDA(p1, p2, color=[0, 0, 0]):
x0, y0, x1, y1 = p1[0], p1[1], p2[0], p2[1]
steps = abs(x0-x1) if abs(x0-x1) > abs(y0-y1) else abs(y0-y1)
dx = (x1-x0)/steps
dy = (y1-y0)/steps
x, y = x0, y0
gfxdraw.pixel(screen,Round(x),Round(y),color)
for i in range(int(steps)):
x += dx
y += dy
gfxdraw.pixel(screen,Round(x), Round(y),color)
def DrawPoly(center, n, s, color=[0, 0, 0]):
cx, cy = center[0], center[1]
sideAngle = 360/n
bv1x = cx-s/2
bv1y = cy - (s/2)*(1/math.tan(math.radians(sideAngle/2)))
bv2x = cx+s/2
bv2y = bv1y
drawDDA((bv1x, bv1y), (bv2x, bv2y), color)
for i in range(n-1):
# i want to rotate the coordinate plane about an angle "sideAngle" with (cx,cy) as the center
# code here
drawDDA((bv1x, bv1y), (bv2x, bv2y), color)
size = [640, 720]
os.environ['SDL_VIDEO_CENTERED'] = '0'
pygame.init()
screen = pygame.display.set_mode(size)
screen.fill((255, 255, 255))
Draw()
mainloop()
The idea I tried to execute to draw a polygon is
I'm taking a point as center of the polygon
calculating the coordinates of two consecutive points
draw a line between those two points
rotate the whole plane about the center of the polygon by side angle which is (360 degrees/number of sides of the polygon)
now, draw a line with the same coordinates to get another side of the polygon
repeat 4 and 5 for n-2 times to get all the remaining side. (n is the number of sides)
I need a way to rotate the coordinate axis about the center of my polygon.
If you can transform Polar coordinates (d, a) to Cartesian coordinate (x, y) with math.sin and math.cos
x = d * cos(a)
y = d * sin(a)
Change the function DrawPoly:
def DrawPoly(center, n, s, color=[0, 0, 0]):
x0, y0 = center[0], center[1]
a = math.radians(360 / n)
d = s / 2 / math.sin(a / 2)
pts = []
for i in range(n+1):
sideAngle = math.radians(360 * i / n)
x = x0 + d * math.cos(sideAngle)
y = y0 + d * math.sin(sideAngle)
pts.append([x, y])
for i in range(n):
drawDDA(pts[i], pts[i+1], color)
Different polygons with the same side length:
def Draw():
DrawPoly((100, 100), 3, 100, (192, 0, 0))
DrawPoly((100, 100), 4, 100, (0, 192, 0))
DrawPoly((100, 100), 5, 100, (0, 0, 192))
DrawPoly((100, 100), 6, 100, (0, 0, 0))
pygame.display.flip()
I'm using Python 3.7.4 and Pygame 1.9.6. I know about the pygame.draw.arc and pygame.draw.circle. I want to draw a quarter circle like here:
It's the quarter circle of the soccer field.
My attempts:
pygame.draw.arc(screen, (255, 255, 255), (75, 25, 50, 50), 90, 120, 5)
pygame.draw.arc(screen, (255, 255, 255), (75, 25, 50, 50), 80, 100, 5)
Changing the starting angle and ending angle doesn't change a thing it seems.
So it must be the coordinates of drawing the boundaries.
When I change the 75 in this(75, 25, 50, 50) it just makes the circle go to the right more. If the change the 25 in (75, 25, 50, 50) it makes it go up or down more. If I change the first 50 in (75, 25, 50, 50) it makes it wider width bigger or smaller depending on changing the numbers. If I change the second 50 in (75, 25, 50, 50) it makes the height bigger or smaller.
I've just tried decimals in the angles, because the coordinates have to be integers. When I do pygame.draw.arc(screen, (255, 255, 255), (75, 25, 100, 50), 95.5, 100.5, 5) look at the decimals.
I get:
So maybe the key it changing the starting and ending angles I'll keep updating what I find.
I found the right one with help from #bashBedlam in the comments:
pygame.draw.arc(screen, (255, 255, 255), (60, 13, 50, 50), 17, 13, 5)
I changed the coordinates but keep the same starting and ending angle.
Which is pretty much perfect though I do hate the little black dots though.
The screen is 600 x 600
I can't get to right angle or the circle itself. I'm not aloud to use decimals. Only allows integers. So I don't know how to properly make a quarter circle. I appreciate the advice and thanks.
I just wanted to say for people helping me I've created my own soccer field. I drew all the lines out, but then I just turned it into a png so it wouldn't lag me out of loading all 27 lines with the background constantly. Here is the finished result with everyone's help:
The issue with your code is that pygame.draw.arc takes angles in radians as stated in the documentation. Also angles between 90 and 120 would not draw and arc like you need, 90 to -90 will.
import pygame
import math
pygame.init()
d = pygame.display.set_mode((1200, 600))
while True:
pygame.event.get()
d.fill((255, 255, 255))
pygame.draw.arc(d, (0, 0, 0), [900, 300, 100, 100], math.radians(90), math.radians(-90), 5)
pygame.draw.arc(d, (0, 0, 0), [300, 300, 100, 100], math.radians(-90), math.radians(90), 5)
pygame.display.update()
Edit:
So i was looking online for ways for pygame.draw.arc to draw arc without dots without using floating point numbers, but couldn't any, so i wrote this function for you. If you consider this function as not being a part of the code you wrote (like pygame.draw.arc) then technically, you are not using decimal because the decimal is only used inside of the function(finessed the system :)). Here's the function:
def drawArc(display, startAngle, endAngle, distance, pos, color, thickness=1):
if startAngle > endAngle:
theta = endAngle
bigger = startAngle
else:
theta = startAngle
bigger = endAngle
while theta < bigger:
for t in range(thickness):
x = round((cos(radians(theta)) * (distance-t)) + pos[0])
y = round((-sin(radians(theta)) * (distance-t)) + pos[1])
display.set_at((x, y), color)
theta += 0.01
Think of this function like a compass, it draws an arc in-between the angle you specify (in degrees). Argument pos being the centre of the compass and distance being the distance of the arc from the centre. So now just draw the quarter circles in 4 different corners.
import pygame
from math import radians, sin, cos
pygame.init()
d = pygame.display.set_mode((1200, 600))
def drawArc(display, startAngle, endAngle, distance, pos, color, thickness=1):
if startAngle > endAngle:
theta = endAngle
bigger = startAngle
else:
theta = startAngle
bigger = endAngle
while theta < bigger:
for t in range(thickness):
x = round((cos(radians(theta)) * (distance-t)) + pos[0])
y = round((-sin(radians(theta)) * (distance-t)) + pos[1])
display.set_at((x, y), color)
theta += 0.01
while True:
pygame.event.get()
d.fill((255, 255, 255))
drawArc(d, -90, 0, 100, [0, 0], (0, 0, 0), thickness=5)
drawArc(d, 180, 270, 100, [1200, 0], (0, 0, 0), thickness=5)
drawArc(d, 0, 90, 100, [0, 600], (0, 0, 0), thickness=5)
drawArc(d, 180, 90, 100, [1200, 600], (0, 0, 0), thickness=5)
pygame.display.update()
Btw, using this function hinders performance, so i strongly recommend using pygame.draw.arc with floating point numbers if possible.
If i have a list of coordinates coords = [(7, 354), (307, 339), (304, 296), (4, 311)] for an angled rectangle. I would like to be able to convert these four points into x,y,w,h,o format.
How can I convert these for x,y pairs into a centroid, width, height and orientation? The centroid is easy to calculate as is the width and height. How about the orientation?
I'm ideally looking for an easy way to switch between the two. ex. convert_to_xywho() and convert_to_xy_list()
p1, p2 = end[:2]
p3, p4 = end[2:]
w = math.hypot(p2[0] - p1[0], p2[1] - p1[1])
h = math.hypot(p3[0] - p2[0], p3[1] - p2[1])
c = [[p1[0], p2[0], p3[0], p4[0]], [p1[1], p2[1], p3[1], p4[1]]]
centroide = (sum(c[0])/len(c[0]),sum(c[1])/len(c[1]))
Any suggestions on how to find the orientation. And apply such orientation to x,y,w,h,o to get a list of x,y pairs
Note: i'm using the PyGame coordinate system where the origin is in the top left.
The orientation comes from the tilt of one line segment.
tan(theta) = (y2-y1) / (x2-x1)
I suspect you can finish from there.