How to rotate a triangle to a certain angle in pygame? - python

I need to rotate a triangle (Not an image) around at the center of the screen. I have seen other people answer this question, but the triangle could not point upwards.
I have tried to use other peoples functions, but they see to only work partly, like the function I mentioned above.
import pygame
disp=pygame.display.set_mode((200,200))
import math
def rotate_triange(mouse_pos,triangle_pos):
#The code here
import time
while True:
time.sleep(1)
pygame.Surface.fill(disp,(255,255,255))
center = (100,100)
radius = 10
mouse_position = pygame.mouse.get_pos()
for event in pygame.event.get():
pass
points = rotate_triangle((100,100),mouse_position)
pygame.draw.polygon(disp,(0,0,0),points)
pygame.display.update()

In pygame 2 dimensional vector arithmetic is implemented in pygame.math.Vector2.
Define a Vector2 object for the mouse position and the center of the triangle. Calculate the angle of vector form the center point to the mouse position (.angle_to()):
vMouse = pygame.math.Vector2(mouse_pos)
vCenter = pygame.math.Vector2(center)
angle = pygame.math.Vector2().angle_to(vMouse - vCenter)
Define the 3 points of the triangle around the (0, 0) and rotate them by the angle (.rotate())
points = [(-0.5, -0.866), (-0.5, 0.866), (2.0, 0.0)]
rotated_point = [pygame.math.Vector2(p).rotate(angle) for p in points]
To calculate the final points, the points have to b scaled and translated by the center of the triangle:
triangle_points = [(vCenter + p*scale) for p in rotated_point]
See the example:
import pygame
import math
def rotate_triangle(center, scale, mouse_pos):
vMouse = pygame.math.Vector2(mouse_pos)
vCenter = pygame.math.Vector2(center)
angle = pygame.math.Vector2().angle_to(vMouse - vCenter)
points = [(-0.5, -0.866), (-0.5, 0.866), (2.0, 0.0)]
rotated_point = [pygame.math.Vector2(p).rotate(angle) for p in points]
triangle_points = [(vCenter + p*scale) for p in rotated_point]
return triangle_points
disp=pygame.display.set_mode((200,200))
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
mouse_position = pygame.mouse.get_pos()
points = rotate_triangle((100, 100), 10, mouse_position)
pygame.Surface.fill(disp, (255,255,255))
pygame.draw.polygon(disp, (0,0,0), points)
pygame.display.update()
A version of the algorithm, without the use of pygame.math.Vector2, looks as follows:
def rotate_triangle(center, scale, mouse_pos):
dx = mouse_pos[0] - center[0]
dy = mouse_pos[1] - center[1]
len = math.sqrt(dx*dx + dy*dy)
dx, dy = (dx*scale/len, dy*scale/len) if len > 0 else (1, 0)
pts = [(-0.5, -0.866), (-0.5, 0.866), (2.0, 0.0)]
pts = [(center[0] + p[0]*dx + p[1]*dy, center[1] + p[0]*dy - p[1]*dx) for p in pts]
return pts
Note this version is probably faster. It needs a math.sqrt operation, in compare to math.atan2 which is probably used by .angle_to() and math.sin respectively math.cos which is probably used by .rotate(), of the former algorithm.
The result coordinates are the same.

Related

How Can I Make a Thicker Bezier in Pygame?

I'm building a specialized node editor in Pygame. Each node will be connected with a bezier curve. This curve is built by clicking on a node first. A bezier is drawn between the mouse cursor and the node and once you click on a second node, the bezier line is fixed. My code can already draw the curve and follow the mouse cursor. My problem is that the curve is too thin. Does anyone know of a way to easily specify width in pygame.gfxdraw.bezier? Also, I have no idea what the argument "6" corresponds to; I only know the code won't function without it.
# This draws the bezier curve for the node editor
x, y = pygame.mouse.get_pos()
b_points = [(380,390),(410,385),(425,405), (425, y), (x, y)]
pygame.gfxdraw.bezier(screen, b_points, 6, blue)
Simple answer: You cant't, at least not with pygame.gfxdraw
or pygame.draw. You have to do it yourself.
Compute points along the curve and connect them with pygame.draw.lines.
See Finding a Point on a Bézier Curve: De Casteljau's Algorithm and create a function that draw a bezier curve piont, by point:
import pygame
def ptOnCurve(b, t):
q = b.copy()
for k in range(1, len(b)):
for i in range(len(b) - k):
q[i] = (1-t) * q[i][0] + t * q[i+1][0], (1-t) * q[i][1] + t * q[i+1][1]
return round(q[0][0]), round(q[0][1])
def bezier(surf, b, samples, color, thickness):
pts = [ptOnCurve(b, i/samples) for i in range(samples+1)]
pygame.draw.lines(surf, color, False, pts, thickness)
Minimal example:
import pygame, pygame.gfxdraw
def ptOnCurve(b, t):
q = b.copy()
for k in range(1, len(b)):
for i in range(len(b) - k):
q[i] = (1-t) * q[i][0] + t * q[i+1][0], (1-t) * q[i][1] + t * q[i+1][1]
return round(q[0][0]), round(q[0][1])
def bezier(surf, b, samples, color, thickness):
pts = [ptOnCurve(b, i/samples) for i in range(samples+1)]
pygame.draw.lines(surf, color, False, pts, thickness)
pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
x, y = pygame.mouse.get_pos()
b_points = [(380,390), (410,385), (425,405), (425, y), (x, y)]
screen.fill(0)
bezier(screen, b_points, 20, (255, 255, 0), 6)
pygame.draw.lines(screen, (255, 255, 255), False, b_points, 1)
pygame.gfxdraw.bezier(screen, b_points, 6, (255, 0, 0))
pygame.display.flip()
clock.tick(60)
pygame.quit()

Angle between two points isn't working in Pygame

Everywhere online says to use atan2(pointA.y-pointB.y, pointA.x, pointB.x) but it just doesn't work.
import pygame
import sys
from math import *
WIDTH, HEIGHT = 1024, 576
screen = pygame.display.set_mode((WIDTH, HEIGHT))
def rot(surface, angle):
rotated_surface = pygame.transform.rotozoom(surface, angle, 1)
rotated_rect = rotated_surface.get_rect()
return rotated_surface, rotated_rect
surf = pygame.Surface((64, 32), flags=pygame.SRCALPHA)
surf.fill((255, 0, 0))
def map_range(value, leftMin, leftMax, rightMin, rightMax):
# Figure out how 'wide' each range is
leftSpan = leftMax - leftMin
rightSpan = rightMax - rightMin
# Convert the left range into a 0-1 range (float)
valueScaled = float(value - leftMin) / float(leftSpan)
# Convert the 0-1 range into a value in the right range.
return rightMin + (valueScaled * rightSpan)
while 1:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
mouse_pos = pygame.Vector2(pygame.mouse.get_pos())
center = pygame.Vector2(surf.get_rect().center)
angle = degrees(atan2(mouse_pos.y - center.y, mouse_pos.x - center.x))
rotated_surface = rot(surf, angle)
rotated_surface[1].center = (WIDTH/3, HEIGHT/3)
screen.fill((0,0,0))
screen.blit(rotated_surface[0], rotated_surface[1])
pygame.display.update()
I converted it in degrees and all, but it just doesn't rotate properly towards the cursor. Am I missing something?
You're using the center of surf in your calculation. The center of surf is in the top-left of your screen. Use the center of your rotated_surface.
Negate the angle.
2 Mistakes:
The center of the object is (WIDTH/3, HEIGHT/3)
The y-axis of the pygame coordinate system points down. So you need to invert the y-axis before calculating the angle.
center = pygame.Vector2(WIDTH/3, HEIGHT/3)
angle = degrees(atan2(-(mouse_pos.y - center.y), mouse_pos.x - center.x))
rotated_surface = rot(surf, angle)
rotated_surface[1].center = center.x, center.y
See also How to know the angle between two vectors? and How to rotate an image(player) to the mouse direction?.

How to solve ValueError raised when drawing a circle with a width value greater than its radius is pygame module?

I am coding for a fourier transform simulation. Here I have to draw many epicycles. I have a some values of radius which are less than 1 like: 7x10^-14, 0 etc. So, when I draw the circles and assign border width to 1 , I get a value error: width greater than radius. If I put the border width to zero then the circle becomes filled up in color and seems very ugly. So, please show me a way to how I can draw a cricle with a border and radius values less than 1. Here is the code:
radius_list = [0.0, 8.539660890638339e-15, 66.66666666666669, 3.3275832379191784e-14, `
1.1234667099445444e-14, 2.534379764899661e-14, 33.333333333333336, 1.018719954857117e-14,
2.0236265985141534e-14, 2.4825216024150285e-14, 66.66666666666674, 1.5953403096630258e-13`]
run = False
while not run:
clock.tick(fps)
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
x = x_pos
y = y_pos
for i in range(iteration):
prevx = x
prevy = y
frequency = freq_list[i]
radius = radius_list[i]
phase = phase_list[i]
print(radius)
x+= int(radius*math.cos((frequency*time) + phase + math.pi/2))
y+= int(radius*math.sin((frequency*time) + phase + math.pi/2))
**pygame.draw.circle(screen, white, (prevx, prevy), int(radius),1)**
[...] please show me a way to how I can draw a circle with a border and radius values less than 1
You can't. The unit of drawing in PyGame is pixels. PyGame cannot draw half a pixel. It makes no sens to draw something with a size less than 1, because PyGame can't do it:
Ensure that the minimum radius is 1:
pygame.draw.circle(screen, white, (prevx, prevy), min(1, round(radius)), 1)
All you can do is to skip the circles with a radius less than 0.5:
if radius >= 0.5;
pygame.draw.circle(screen, white, (prevx, prevy), min(1, round(radius)), 1)

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.

Python - Pygame: "Co-ordinates of square on sinus"

I have problem with this code, cause I want 4 points, (which are circles) to make vertex of square, but I don't know what the difference should be between those vertexes (variable "change"). I left this variable empty, please, can you give me value I should insert there and explain why?
Here's the code:
import pygame
from math import sin, cos
pygame.init()
screen = pygame.display.set_mode((800,600))
BLACK = (0,0,0)
WHITE = (255,255,255)
BLUE = (0,0,255)
GRAY = (175,175,175)
clock = pygame.time.Clock()
Font = pygame.font.SysFont(None, 50)
angle = 0
angle_c = 0
ex = False
a = (0,0)
b = (0,0)
c = (0,0)
d = (0,0)
change =
size = 95
x_c = 400
y_c = 200
while not ex:
for event in pygame.event.get():
if event.type == pygame.QUIT:
ex = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
angle_c = 0.05
if event.key == pygame.K_RIGHT:
angle_c = -0.05
if event.type == pygame.KEYUP:
if event.key == pygame.K_RIGHT or event.key == pygame.K_LEFT:
angle_c = 0
angle += angle_c
a = (round(sin(angle)*size+x_c), round(cos(angle)*size+y_c))
b = (round(sin(angle+change)*size+x_c), round(cos(angle+change)*size+y_c))
c = (round(sin(angle+change*2)*size+x_c), round(cos(angle+change*2)*size+y_c))
d = (round(sin(angle+change*3)*size+x_c), round(cos(angle+change*3)*size+y_c))
screen.fill(WHITE)
pygame.draw.circle(screen, BLUE, (400,200), round(sin(360)*100), 3)
pygame.draw.circle(screen, BLUE, a, 10)
pygame.draw.circle(screen, WHITE, a, 8)
pygame.draw.circle(screen, BLUE, b, 10)
pygame.draw.circle(screen, WHITE, b, 8)
pygame.draw.circle(screen, BLUE, c, 10)
pygame.draw.circle(screen, WHITE, c, 8)
pygame.draw.circle(screen, BLUE, d, 10)
pygame.draw.circle(screen, WHITE, d, 8)
pygame.display.update()
clock.tick(50)
pygame.quit()
The angles from the center of a square to the square's vertices differ by 90 degrees, or pi / 2 radians (which is the unit expected by Python's sin and cos functions).
So you could set your change variable to could be pi / 2 (after adding pi to the list of names to import from the math module), and your code would probably work.
But it's even easier to calculate the coordinates than that, as rotations by 90 degrees change sine and cosine values in a predictable way:
sin(a + pi/2) is cos(a)
cos(a + pi/2) is -sin(a).
Applying this transformation repeatedly lets you figure out what the sines and cosines should be after further rotations.
You only need to call sin and cos once each and then you can use the values to find all the coordinates.
y = round(sin(angle) * size)
x = round(cos(angle) * size)
a = ( x + x_c, y + y_c)
b = (-y + x_c, x + y_c)
c = (-x + x_c, -y + y_c)
d = ( y + x_c, -x + y_c)
Note that in the code above I'm following the mathematical convention that angles start at zero along the positive x-axis and increase as you initially start rotating towards the positive y-axis (which will be clockwise in pygame's coordinate system). Your previous code seemed to measure angles from the positive y-axis and increase in the opposite direction. If that's what you really want, you can simply reverse x and y in the initial assignments (assign the cosine to y and the sine to x).

Categories