Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can you rotate an image around an off center pivot in Pygame

Tags:

python

pygame

I want to rotate an image around a pivot, which is not in the center of the Surface in Pygame.
The pivot is the green cross in the image:

enter image description here

I know the position of the pivot in the game window. How do I track the image at this point and I rotate it around this point simultaneously.

image = pygame.image.load("boomerang64.png")
pos = (200, 200)
angle = 0

while True:
    # [...]

    rotate_rect, rotate_image = ???? # rotate around green cross by angle 
    surf.blit(rotated_image, rotate_rect)
    angle += 1

    # [...]
like image 228
Rabbid76 Avatar asked Jan 01 '23 08:01

Rabbid76


1 Answers

First the position of the pivot on the Surface has to be defined:

image = pygame.image.load("boomerang64.png") # 64x64 surface
pivot = (48, 21)                             # position of the pivot on the image

When an image is rotated, then its size increase. We have to compare the axis aligned bounding box of the image before the rotation and after the rotation.
For the following math pygame.math.Vector2 is used. Note in screen coordinates the y points down the screen, but the mathematical y axis points form the bottom to the top. This causes that the y axis has to be "flipped" during calculations. Notice that pygame.math.Vector2.rotate rotates in the opposite direction than pygame.transform.rotate. Therefore the angle has to be inverted.

Set up a list with the 4 corner points of the bounding box and rotate the vectors to the corner points by pygame.math.Vector2.rotate. Finally find the minimum of the rotated box. Since the y axis in pygame points downwards, this has to be compensated by finding the maximum of the rotated inverted height ("max(rotate(-h))"):

Compute the vector from the center of the image to the pivot:

image_rect = image.get_rect(topleft = (pos[0] - originPos[0], pos[1]-originPos[1]))
offset_center_to_pivot = pygame.math.Vector2(pos) - image_rect.center

Rotate the offset vector:

rotated_offset = offset_center_to_pivot.rotate(-angle)

Calculate the center of the rotated image:

rotated_image_center = (pos[0] - rotated_offset.x, pos[1] - rotated_offset.y)

Rotate and blit the image:

rotated_image = pygame.transform.rotate(image, angle)
rotated_image_rect = rotated_image.get_rect(center = rotated_image_center)

screen.blit(rotated_image, rotated_image_rect)

In the following example program, the function blitRotate(surf, image, pos, originPos, angle) does all the above steps and blit a rotated image to the Surface which is associated to the display:

  • surf is the target Surface
  • image is the Surface which has to be rotated and blit
  • pos is the position of the pivot on the target Surface surf (relative to the top left of surf)
  • originPos is position of the pivot on the image Surface (relative to the top left of image)
  • angle is the angle of rotation in degrees

import math
import pygame

def blitRotate(surf, image, pos, originPos, angle):
    image_rect = image.get_rect(topleft = (pos[0] - originPos[0], pos[1]-originPos[1]))
    offset_center_to_pivot = pygame.math.Vector2(pos) - image_rect.center
    rotated_offset = offset_center_to_pivot.rotate(-angle)
    rotated_image_center = (pos[0] - rotated_offset.x, pos[1] - rotated_offset.y)
    rotated_image = pygame.transform.rotate(image, angle)
    rotated_image_rect = rotated_image.get_rect(center = rotated_image_center)
    surf.blit(rotated_image, rotated_image_rect)
  
pygame.init()
size = (400,400)
screen = pygame.display.set_mode(size)
clock = pygame.time.Clock()

image = pygame.image.load('Boomerang64.png')
pivot = (48, 21)

angle, frame = 0, 0
done = False
while not done:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            done = True
    screen.fill(0)

    pos = (200 + math.cos(frame * 0.05)*100, 200 + math.sin(frame * 0.05)*50)
    blitRotate(screen, image, pos, pivot, angle)

    pygame.draw.line(screen, (0, 255, 0), (pos[0]-20, pos[1]), (pos[0]+20, pos[1]), 3)
    pygame.draw.line(screen, (0, 255, 0), (pos[0], pos[1]-20), (pos[0], pos[1]+20), 3)
    pygame.draw.circle(screen, (0, 255, 0), pos, 7, 0)

    pygame.display.flip()
    frame += 1
    angle += 10
    
pygame.quit()
exit()

The same algorithm can be used for a Sprite, too.
In that case the position (self.pos), pivot (self.pivot) and angle (self.angle) are instance attributes of the class. In the update method the self.rect and self.image attributes are computed.

Minimal example: repl.it/@Rabbid76/PyGame-RotateSpriteAroundOffCenterPivot

import math
import pygame

class SpriteRotate(pygame.sprite.Sprite):

    def __init__(self, imageName, origin, pivot):
        super().__init__() 
        self.image = pygame.image.load(imageName)
        self.original_image = self.image
        self.rect = self.image.get_rect(topleft = (origin[0]-pivot[0], origin[1]-pivot[1]))
        self.origin = origin
        self.pivot = pivot
        self.angle = 0

    def update(self):
        
        # offset from pivot to center
        image_rect = self.original_image.get_rect(topleft = (self.origin[0] - self.pivot[0], self.origin[1]-self.pivot[1]))
        offset_center_to_pivot = pygame.math.Vector2(self.origin) - image_rect.center
        
        # roatated offset from pivot to center
        rotated_offset = offset_center_to_pivot.rotate(-self.angle)

        # roatetd image center
        rotated_image_center = (self.pos[0] - rotated_offset.x, self.pos[1] - rotated_offset.y)

        # get a rotated image
        self.image  = pygame.transform.rotate(self.original_image, self.angle)
        self.rect   = self.image.get_rect(center = rotated_image_center)
        self.angle += 10
  
pygame.init()
size = (400,400)
screen = pygame.display.set_mode(size)
clock = pygame.time.Clock()

boomerang = SpriteRotate('Boomerang64.png', (200, 200), (48, 21))
all_sprites = pygame.sprite.Group(boomerang)

frame = 0
done = False
while not done:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            done = True
    
    pos = (200 + math.cos(frame * 0.05)*100, 200 + math.sin(frame * 0.05)*50)
    boomerang.pos = pos
    all_sprites.update()

    screen.fill(0)
    
    all_sprites.draw(screen)
    #pygame.draw.line(screen, (0, 255, 0), (pos[0]-20, pos[1]), (pos[0]+20, pos[1]), 3)
    #pygame.draw.line(screen, (0, 255, 0), (pos[0], pos[1]-20), (pos[0], pos[1]+20), 3)
    #pygame.draw.circle(screen, (0, 255, 0), pos, 7, 0)

    pygame.display.flip()
    frame += 1
    
pygame.quit()
exit()

How do I rotate an image around its center using Pygame?
How to rotate an image around its center while its scale is getting larger(in Pygame)

like image 146
Rabbid76 Avatar answered Jan 03 '23 06:01

Rabbid76