Get points of any regular polygon that can be also rotated - python

I'm trying to draw any regular polygon, so from triangles to polygons with so many corners, it looks like a circle. to make it easier, they must be regular, so a normal pentagon/hexagon/octagon etc. I want to be able to rotate them. What ive tried is to draw a circle and divide 360 by the amount of points i want then, create a point every nth degrees around the circle, putting these points in pygame.draw.polygon() then creates the shape i want, the problem is it isn't the right size, i also want to be able to have stretch the shapes, so have a different width and height.
def regular_polygon(hapi, x, y, w, h, n, rotation, angle_offset = 0):
#angle_offset is the starting angle in the circle where rotation is rotating the circle
#so when its an oval, rotation rotates the oval and angle_offset is where on the oval to start from
if n < 3:
n = 3
midpoint = pygame.Vector2(x + w//2, y + h//2)
r = sqrt(w**2 + h**2)
#if angle_offset != 0:
#w = (w//2)//cos(angle_offset)
#if angle_offset != 90:
#h = (h//2)//sin(angle_offset)
w,h = r,r
points = []
for angle in range(0, 360, 360//n):
angle = radians(angle + angle_offset)
d = pygame.Vector2(-sin(angle)*w//2, -cos(angle)*h//2).rotate(rotation) #the negative sign is because it was drawing upside down
points.append(midpoint + d)
#draws the circle for debugging
for angle in range(0, 360, 1):
angle = radians(angle + angle_offset)
d = pygame.Vector2(-sin(angle)*w//2, -cos(angle)*h//2).rotate(rotation)
pygame.draw.rect(screen, (0,255,0), (midpoint[0] + d[0], midpoint[1] + d[1], 5, 5))
pygame.draw.polygon(screen,(255,0,0),points)
the red square is the the function above is making, the blue one behind is what it should be
as you can see the circle does line up with the edges of the rect, but because the angles of the circle are not even, the rectangle the func makes is not right.
i think i need to change the circle to an oval but cannot find out how to find the width radius and height radius of it. currently ive found the radius by using pythag.
this is what happens when i dont change the width or height

I found the solution
doing w *= math.sqrt(2) and h *= math.sqrt(2) works perfectly. dont quite understand the math, but after trial and error, this works. You can probable find the maths here but i just multiplied the width and height by a number and printed that number when it lined up which was very close to the sqrt(2)

Related

Pygame: How do I blit and rotate an image to connect two points on the screen?

Here is a test program. I started with two random dots and the line connecting them. Now, I want to take a given image (with x,y dimensions of 79 x 1080) and blit it on top of the guide line. I understand that arctan will give me the angle between the points on a cartesian grid, but because y is backwards the screen (x,y), I have to invert some values. I'm confused about the negating step.
If you run this repeatedly, you'll see the image is always parallel to the line, and sometimes on top, but not consistently.
import math
import pygame
import random
pygame.init()
screen = pygame.display.set_mode((600,600))
#target = (126, 270)
#start = (234, 54)
target = (random.randrange(600), random.randrange(600))
start = (random.randrange(600), random.randrange(600))
BLACK = (0,0,0)
BLUE = (0,0,128)
GREEN = (0,128,0)
pygame.draw.circle(screen, GREEN, start, 15)
pygame.draw.circle(screen, BLUE, target, 15)
pygame.draw.line(screen, BLUE, start, target, 5)
route = pygame.Surface((79,1080))
route.set_colorkey(BLACK)
BMP = pygame.image.load('art/trade_route00.png').convert()
(bx, by, bwidth, bheight) = route.get_rect()
route.blit(BMP, (0,0), area=route.get_rect())
# get distance within screen in pixels
dist = math.sqrt((start[0] - target[0])**2 + (start[1] - target[1])**2)
# scale to fit: use distance between points, and make width extra skinny.
route = pygame.transform.scale(route, (int(bwidth * dist/bwidth * 0.05), int( bheight * dist/bheight)))
# and rotate... (invert, as negative is for clockwise)
angle = math.degrees(math.atan2(-1*(target[1]-start[1]), target[0]-start[0]))
route = pygame.transform.rotate(route, angle + 90 )
position = route.get_rect()
HERE = (abs(target[0] - position[2]), target[1]) # - position[3]/2)
print(HERE)
screen.blit(route, HERE)
pygame.display.update()
print(start, target, dist, angle, position)
The main problem
The error is not due to the inverse y coordinates (0 at top, max at bottom) while rotating as you seems to think. That part is correct. The error is here:
HERE = (abs(target[0] - position[2]), target[1]) # - position[3]/2)
HERE must be the coordinates of the top-left corner of the rectangle inscribing your green and blue dots connected by the blue line. At those coordinates, you need to place the Surface route after rescaling.
You can get this vertex by doing:
HERE = (min(start[0], target[0]), min(start[1], target[1]))
This should solve the problem, and your colored dots should lay on the blue line.
A side note
Another thing you might wish to fix is the scaling parameter of route:
route = pygame.transform.scale(route, (int(bwidth * dist/bwidth * 0.05), int( bheight * dist/bheight)))
If my guess is correct and you want to preserve the original widht/height ratio in the rescaled route (since your original image is not a square) this should be:
route = pygame.transform.scale(route, (int(dist* bwidth/bheight), int(dist)))
assuming that you want height (the greater size in the original) be scaled to dist. So you may not need the 0.05, or maybe you can use a different shrinking parameter (probably 0.05 will shrink it too much).

How can I know if a circle and a rect is touched in Pygame?

There is a circle and a rect moving on the surface in my program. I want to know if a circle and a rect is touched to each other. It had to be very accurate. I'm sorry for not explaining its detail, but I hope you understood.
Consider a axis aligned rectangle is given by a top left origin and a width and a height:
rect_tl = (x, y)
rect_size = (width, height)
And a circle is given by a center point and a radius:
circle_cpt = (x, y)
circle_rad = r
If you want to test whether these two shapes overlap, you need to run 2 tests to capture all possible cases.
First it has to be tested if the center point of the circle is inside the rectangle. This can be done by pygame.Rect.collidepoint with ease:
rect = pygame.Rect(*rect_tl, *rect_size)
isIsect = rect.collidepoint(*circle_cpt)
Furthermore it has to be tested if any corner point of the rectangle is inside the circle. This is the case if the distance between a corner point and the center point of the circle is less than is or equal the radius of the circle. A point can be represented by pygame.math.Vector2 and the distance between 2 points can be get by pygame.math.Vector2.distance_to():
centerPt = pygame.math.Vector2(*circle_cpt)
cornerPts = [rect.bottomleft, rect.bottomright, rect.topleft, rect.topright]
isIsect = any([p for p in cornerPts if pygame.math.Vector2(*p).distance_to(centerPt) <= circle_rad])
A function which combines both tests may look like this:
def isectRectCircle(rect_tl, rect_size, circle_cpt, circle_rad):
rect = pygame.Rect(*rect_tl, *rect_size)
if rect.collidepoint(*circle_cpt):
return True
centerPt = pygame.math.Vector2(*circle_cpt)
cornerPts = [rect.bottomleft, rect.bottomright, rect.topleft, rect.topright]
if [p for p in cornerPts if pygame.math.Vector2(*p).distance_to(centerPt) <= circle_rad]:
return True
return False

How to increase elliptical arc resolution for high radius values in opencv?

I've been trying to draw an elliptical arc in openCV using the ellipse function (https://docs.opencv.org/3.0-beta/modules/imgproc/doc/drawing_functions.html), however, for high radius values the arcs seem segmented.
Do you know how can I sort of increase the arc's resolution to appear better for high radius values?
I tried to draw an arc with a small radius and it looked smooth and I also tried increasing image resolution but no difference was noticed.
My code is as follows:
A[0] = round(A[0]*dpm - Xmin + margin) #Normalize CenterX
A[1] = round(A[1]*dpm - Ymin + margin) #Normalize CenterY
A[2] = round(A[2]*dpm) #Normalize Radius
startAng = A[3]
endAng = A[4]
A=A.astype(int)
cv2.ellipse(Blank,(A[0],A[1]),(A[2],A[2]), 0, startAng, endAng, 0 ,1)
while:
Blank is the image I want to draw the arc on (np array, size= (398, 847)
(A[0],A[1]) is the center point
(A[2],A[2]) ellipse axes
0 is the angle
startAng is the starting angle of the arc
endAng is the ending angle of the arc
0 is the line color (Black)
1 is the line thickess
The code should produce a smooth arc but it looks segmented as if it is made of 4 lines.
I ended up writing a function to plot an arc on an input image:
import numpy as np
import cv2
blank = np.ones((500,500))
def DrawArc(image, center, radius, startAng, endAng, color,resolution):
'''Draws an arc with specific reslution and color on an input image
Args:
image - The input image to draw the arc on
center - Arc's center
radius - Arc's radius
startAng - the starting angle of the arc
engAng - the ending angle of the arc
color - Arc's color on the input image
resolution - Number of points for calculation
output:
image - updated image with plotted arc'''
startAng += 90
endAng += 90
theta = np.linspace(startAng,endAng,resolution)
x = np.round(radius*np.cos(np.deg2rad(theta))) + center[0]
y = np.round(radius*np.sin(np.deg2rad(theta))) + center[1]
x=x.astype(int)
y=y.astype(int)
for k in range(np.size(theta)):
image[x[k]][y[k]] = color
return image
image = DrawArc(blank,(250,250),200,0,90,0,1000)
cv2.imshow("Arc",image)
cv2.waitKey()
The output image is
Output

Efficient way to find the pixel coordinates in image for circle circumference intersection

I would like to efficiently find the coordinates of the line described by the intersection between the circumference of a circle and an image (origin of circle is outside the image). Right now I'm using a loop in python to start at one edge of the image and move through the image a step at a time. Each step moves a certain distance (say 0.01 inches). I calculate the angle needed to move that distance and then use polar geometry formulas to define the next pixel coordinate. This all works just fine, however, it takes a long time. I'm creating many of these lines through the image as the radius of the circle increases.
Is there a way to use a built in function or an array based formula so that I don't have to have so many steps in my algorithm? Basically, what is the most efficient way to accomplish this in python 2?
Thanks,
rb3
# circle parameters
x0 = -5
y0 = -5
R = 25
# image size
max_x = 100
max_y = 100
# sample points
theta = np.linspace(0, 2*np.pi, 2048) # make bigger if you have huge images
# the pixels that get hit
xy = list(set([xy for xy in zip( (R * cos(theta) - x0).astype(int), (R * sin(theta) - y0).astype(int)) if
xy[0] >= 0 and xy[0] < max_x and xy[1] >= 0 and xy[1] < max_y]))

python arrange images on canvas in a circle

I have bunch of images (say 10) I have generated both as array or PIL object.
I need to integrate them into a circular fashion to display them and it should adjust itself to the resolution of the screen, is there anything in python that can do this?
I have tried using paste, but figuring out the resolution canvas and positions to paste is painful, wondering if there is an easier solution?
We can say that points are arranged evenly in a circle when there is a constant angle theta between neighboring points. theta can be calculated as 2*pi radians divided by the number of points. The first point is at angle 0 with respect to the x axis, the second point at angle theta*1, the third point at angle theta*2, etc.
Using simple trigonometry, you can find the X and Y coordinates of any point that lies on the edge of a circle. For a point at angle ohm lying on a circle with radius r:
xFromCenter = r*cos(ohm)
yFromCenter = r*sin(ohm)
Using this math, it is possible to arrange your images evenly on a circle:
import math
from PIL import Image
def arrangeImagesInCircle(masterImage, imagesToArrange):
imgWidth, imgHeight = masterImage.size
#we want the circle to be as large as possible.
#but the circle shouldn't extend all the way to the edge of the image.
#If we do that, then when we paste images onto the circle, those images will partially fall over the edge.
#so we reduce the diameter of the circle by the width/height of the widest/tallest image.
diameter = min(
imgWidth - max(img.size[0] for img in imagesToArrange),
imgHeight - max(img.size[1] for img in imagesToArrange)
)
radius = diameter / 2
circleCenterX = imgWidth / 2
circleCenterY = imgHeight / 2
theta = 2*math.pi / len(imagesToArrange)
for i, curImg in enumerate(imagesToArrange):
angle = i * theta
dx = int(radius * math.cos(angle))
dy = int(radius * math.sin(angle))
#dx and dy give the coordinates of where the center of our images would go.
#so we must subtract half the height/width of the image to find where their top-left corners should be.
pos = (
circleCenterX + dx - curImg.size[0]/2,
circleCenterY + dy - curImg.size[1]/2
)
masterImage.paste(curImg, pos)
img = Image.new("RGB", (500,500), (255,255,255))
#red.png, blue.png, green.png are simple 50x50 pngs of solid color
imageFilenames = ["red.png", "blue.png", "green.png"] * 5
images = [Image.open(filename) for filename in imageFilenames]
arrangeImagesInCircle(img, images)
img.save("output.png")
Result:

Categories

Resources