Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Rotating a rectangle
#1
Hello, i'm having trouble rotating a rectangle on screen. I'm not a computer science student and I need help because I have been at it for a couple of days.

The goal is to rotate a rectangle around it's top left corner like a sail around a post, but now the white rectangle rotates around it's center in a black square...

Here's my code:

import random, pygame, sys
from pygame.locals import *

Blue = (0,0,255)
Green = (0,255,0)
White = (255,255,255)

pygame.init()
DISPLAYSURF = pygame.display.set_mode((400, 300))
DISPLAYSURF.fill(Blue)
pygame.display.set_caption('Sailing!')

FPS = 30
fpsClock = pygame.time.Clock()

Sail = pygame.Surface([100,10])
Sail.fill(White)

degrees = 0

while True:
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()

    rotatedSail = pygame.transform.rotozoom(Sail, degrees, 1)
    Sail_rect = Sail.get_rect(topleft =(200,150))
    DISPLAYSURF.blit(rotatedSail, Sail_rect)
    pygame.display.flip()

    fpsClock.tick(FPS)

    degrees += 1
I played around with the code using transform.rotate, draw.rect and I watched youtube videos, but i can't use rotate with draw.rect, i make the image fly off the screen or I just have different version of this problem and I don't want to load an image I want to create a rectangle.

Any help is welcomed
thank you
Reply
#2
I think the pygame rotations are rotations about the Z axis (axis pointing out of the screen). Sounds like you want to rotate about the Y axis instead. This would make appear like the rectangle is coming out of the screen. Like this?
import math
import tkinter as tk

class Point():
    '''A point in space'''
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

    def rotate_y(self, angle, center=(0, 0, 0)):
        '''Rotate self around center.y.  Angle is in radians'''
        cx, cy, cz = center
        x = self.x - cx
        z = self.z - cz
        d = math.hypot(x, z)
        theta  = math.atan2(x, z) + angle
        self.z = cz + d * math.cos(theta)
        self.x = cx + d * math.sin(theta)

    def __str__(self):
        return f'({self.x}, {self.y}, {self.z})'

class Rectangle():
    def __init__(self, width, height):
        self.points = [
            Point(-width/2, height/2, 0),
            Point(width/2, height/2, 0),
            Point(width/2, -height/2, 0),
            Point(-width/2, -height/2, 0),
        ]

    def rotate_y(self, angle, center=(0, 0, 0)):
        '''Rotate self around center.y.  Angle is in radians'''
        for point in self.points:
            point.rotate_y(angle, center)

    def draw(self, canvas, center=(0, 0)):
        '''Draw self on canvas'''
        cx, cy = center
        for i in range(4):
            a = rectangle.points[i]
            b = rectangle.points[(i+1)%4]
            canvas.create_line(a.x+cx, a.y+cy, b.x+cx, b.y+cy)

    def __str__(self):
        return f'{self.points[0]}, {self.points[1]}, {self.points[2]}, {self.points[3]}'

rectangle = []

def rotate_y(rectangle, angle, center=(0, 0, 0)):
    canvas.delete("all")
    rectangle.rotate_y(angle, center)
    rectangle.draw(canvas, (140, 100))
    canvas.after(10, lambda: rotate_y(rectangle, angle, center))

root = tk.Tk()
canvas = tk.Canvas(root, width=200, height=200)
canvas.pack()

rectangle = Rectangle(80, 80)
rotate_y(rectangle, 0.02, (-40, 0, 0))
root.mainloop()
For a more realistic view the rectangle should be drawn in perspective instead of drawing the projection.
Reply
#3
Thank you for the answer, but I am trying to make it rotate on the z axis. A point in the center of the screen, like a ship seen from above. I think the white surface is rotating around it from it's top left corner, but the get_rect() function is all black and doesn't allow it to continue it's rotation around the screen so the white surface is stuck rotating in a black square in the lower portion of the screen.

All i want is to make a white rectangle acting like the radii of circle which has it's origin in the middle of a blue screen.
Reply
#4
Like this?
import math
import tkinter as tk

class Point():
    '''A point in space'''
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

    def move(self, x=0, y=0, z=0):
        self.x += x
        self.y += y
        self.z += z

    def rotate_z(self, angle, center=(0, 0, 0)):
        cx, cy, cz = center
        x = self.x - cx
        y = self.y - cy
        d = math.hypot(y, x)
        theta  = math.atan2(y, x) + angle
        self.x = cx + d * math.cos(theta)
        self.y = cy + d * math.sin(theta)

        def __str__(self):
            return f'({self.x}, {self.y}, {self.z})'

class Rectangle():
    def __init__(self, width, height):
        self.points = [
            Point(-width/2, height/2, 0),
            Point(width/2, height/2, 0),
            Point(width/2, -height/2, 0),
            Point(-width/2, -height/2, 0),
        ]

    def move(self, x=0, y=0, z=0):
        for point in self.points:
            point.move(x, y, z)

    def rotate_z(self, angle, center=(0, 0, 0)):
       for point in self.points:
            point.rotate_z(angle, center)

    def draw(self, canvas, center=(0, 0)):
        '''Draw self on canvas'''
        cx, cy = center
        for i in range(4):
            a = rectangle.points[i]
            b = rectangle.points[(i+1)%4]
            canvas.create_line(a.x+cx, a.y+cy, b.x+cx, b.y+cy)

    def __str__(self):
        return f'{self.points[0]}, {self.points[1]}, {self.points[2]}, {self.points[3]}'

rectangle = []

def rotate_z(rectangle, angle, center=(0, 0, 0)):
    canvas.delete("all")
    rectangle.rotate_z(angle, center)
    rectangle.draw(canvas, (100, 100))
    canvas.after(10, lambda: rotate_z(rectangle, angle, center))

root = tk.Tk()
canvas = tk.Canvas(root, width=200, height=200)
canvas.pack()

rectangle = Rectangle(80, 10)
rectangle.move(40, 0)
rotate_z(rectangle, 0.02)
root.mainloop()
Using the pygame transorm functions you need to rotate and translate (move).
Reply
#5
yes exactly, but... there has to be a simpler way, like 4 lines of code using only pygame
Reply
#6
You need to do a rotate and a move. The rotate you probably already have (I am not going to install pygame to verify). The move is not that bad. You could steal the code from Point.rotate_z.
    def rotate_z(self, angle, center=(0, 0, 0)):
        cx, cy, cz = center
        x = self.x - cx
        y = self.y - cy
        d = math.hypot(y, x)
        theta  = math.atan2(y, x) + angle
        self.x = cx + d * math.cos(theta)
        self.y = cy + d * math.sin(theta)
The self.x and self.y would be the center of the rectangle/image/surface/sail. The cx and cy the point about which you want the sail to rotate. It shouldn't be difficult to strip this code out of the class and make it a function.

As for the "black square", that is the background of the image/surface/sail. You need to make the background transparent
Reply
#7
Is something like this going to work for you?

import random, pygame, sys
from pygame.locals import *

Blue = (0,0,255)
Black = (0, 0, 0) 
Green = (0,255,0)
White = (255,255,255)
 
pygame.init()
DISPLAYSURF = pygame.display.set_mode((400, 300))
pygame.display.set_caption('Sailing!')
 
FPS = 30
fpsClock = pygame.time.Clock()
 
Sail = pygame.Surface([100,10])
Sail.set_colorkey (Black)
Sail.fill(White)
 
degrees = 0
 
while True:
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()
 
    rotatedSail = pygame.transform.rotate(Sail, degrees)
    Sail_rect = Sail.get_rect(topleft =(200,150))
    DISPLAYSURF.fill(Blue)
    DISPLAYSURF.blit(rotatedSail, Sail_rect)
    pygame.display.flip()
 
    fpsClock.tick(FPS)
 
    degrees += 1
Reply
#8
okay, okay, okay,
first of all thank you for your time.
The blue fill is great and the post about making it move got me thinking and it kind of blew my mind so i tried writing it myself, but it didn't work...

So here's what i have now. In my head it should be working. You augment the degrees and the math function should do the trick???

import random, pygame, math, sys
from pygame.locals import *
 
Blue = (0,0,255)
Black = (0, 0, 0) 
Green = (0,255,0)
White = (255,255,255)
  
pygame.init()
DISPLAYSURF = pygame.display.set_mode((400, 300))
pygame.display.set_caption('Sailing!')
  
FPS = 30
fpsClock = pygame.time.Clock()
  
Sail = pygame.Surface([100,10])
Sail.set_colorkey (Black)
Sail.fill(White)
  
degrees = 0
hyp = 100
x = 200
y = 150


while True:
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()
    adj = 100 * math.cos(degrees)
    opp = 100 * math.sin(degrees)
    dx = adj + 200
    dy = opp + 150
    sadj = math.pow(adj, 2)
    sopp = math.pow(opp, 2)
    shyp = math.pow(hyp,2)
    rotatedSail = pygame.transform.rotate(Sail, degrees)
    Sail_rect = Sail.get_rect(topleft =(dx,dy))
    DISPLAYSURF.fill(Blue)
    DISPLAYSURF.blit(rotatedSail, Sail_rect)
    pygame.display.flip()
  
    fpsClock.tick(FPS)
  
    degrees += 1
 
Reply
#9
Example
import pygame

def main():
    # Basic Pygame Setup
    pygame.display.set_caption("Rotating Sailing Ship")
    surface = pygame.display.set_mode((400, 300))
    clock = pygame.time.Clock()
    rect = surface.get_rect()
    running = True
    delta = 0
    fps = 30

    # Ship
    ship = pygame.sprite.Sprite()
    # Keep orignal image. For it doesn't get distorted.
    # pygame.SRCALPHA for transparency
    ship.orignal_image = pygame.Surface((100, 10), pygame.SRCALPHA)
    ship.image = ship.orignal_image
    ship.image.fill("white")
    ship.rect = ship.image.get_rect(center=rect.center)
    # Hold are floats
    ship.position = pygame.Vector2(ship.rect.center)
    # Hold are heading
    ship.vector = pygame.Vector2(1, 0)
    ship.rotate_velocity = 60
    ship.velocity = 80

    # Main Loop
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

        # Move and Rotate
        rotate = 0
        key_state = pygame.key.get_pressed()
        if key_state[pygame.K_UP] or key_state[pygame.K_w]:
            ship.position += ship.vector * ship.velocity * delta
            ship.rect.center = ship.position

        if key_state[pygame.K_LEFT] or key_state[pygame.K_a]:
            rotate = -ship.rotate_velocity * delta

        if key_state[pygame.K_RIGHT] or key_state[pygame.K_d]:
            rotate = ship.rotate_velocity * delta

        if rotate != 0:
            ship.vector.rotate_ip(rotate)
            angle = ship.vector.as_polar()[1]
            ship.image = pygame.transform.rotate(ship.orignal_image, -angle)
            # Keep ship in center
            ship.rect = ship.image.get_rect(center=ship.rect.center)

        # Draw
        surface.fill("blue")
        surface.blit(ship.image, ship.rect)

        # Render To Screen
        pygame.display.flip()

        # Delta for smooth movement
        delta = clock.tick(fps) * 0.001

if __name__ == "__main__":
    pygame.init()
    main()
    pygame.quit()
99 percent of computer problems exists between chair and keyboard.
Reply
#10
I spent too much effort solving the wrong problem. The problem is not rotating a rectangle, the problem is rotating an image of a sail. When you first tried this you had a white rectangle rotating about it's center inside a black rectangle.

To solve the problem of the white rectangle rotating about it's center, the easy solution is make the image 4 times larger and place the sail (rectangle) so the mast is at the center of the image. That way when you rotate the image the sail will rotate about the mast. There is no need to calculate how a rectangle should move as it rotates about a point if there is no rectangle.

The second problem is the black rectangle. The solution here is to make the background of the image transparent so the only thing blitted to the screen is the sail part. Here's a tutorial about that:

https://riptutorial.com/pygame/example/2...ansparency

Frustratingly pygame measures angles in degrees and python.math measures them in radians. That would be one reason why your results were off. 90 degrees is PI/2 radians (1.57)
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  [PyGame] Surface and rectangle in pygame Fabrizio_fg 6 2,211 May-27-2023, 09:15 AM
Last Post: Fabrizio_fg
  Make rectangle snap to other rectangle corners Pedroski55 2 4,319 Aug-13-2021, 09:59 PM
Last Post: Pedroski55
  [PyGame] Rotating image issue Evoluxman 8 6,062 Oct-12-2019, 12:54 PM
Last Post: Evoluxman
  [PyGame] Rectangle keeps teleporting? keyfive 1 3,177 Jun-27-2018, 11:49 PM
Last Post: sonnekrieger7

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020