Get smooth diagonal movement in Pygame

Question:

I’m working on this decal creator using pygame. You basically have a bunch of sliders which controls the angle, radius, offset, etc. The sliders are incrementing in integers.

As I’m increasing the offset from the center you can notice the dots are not following a perfect straight path.

enter image description here

This is the code I’m using for drawing the dots:

def drawAADot(surf, center, radius, color):
    gfx.aacircle(surf, center[0], center[1], radius, color)
    gfx.filled_circle(surf, center[0], center[1], radius, color)

And this code for distributing the dots along a circular path:

for i in range(divisions+1):
    i *= (angle/(divisions))
    move_vec = pygame.math.Vector2(center)
    move_vec.from_polar((radius + DotOffset, i+90-counter_angle))
    pos = center[0] + round(move_vec.x), center[1] + round(move_vec.y)
    drawAADot(screen, pos, DotRadius, (255,255,255))

I’ve searched and read about problems regarding movement with float accuracy, since the coordinates of the objects needs to be in integers. What I don’t understand is if it really matters in this situation as soon as the positions are being rounded. I can see that a 45 degree angle could run smooth as long as your increasing one pixel sideways and one up. But how about something like lets say 15 degrees?

It doesn’t have to be AutoCad precision, I’m aware of the limitations, I’m just wondering of I’m not overlooking something or that this is just as good as it gets.

Asked By: Stechouse

||

Answers:

One way this can be approximated is by drawing to a larger screen and then scaling down to the correct size. This isn’t perfect, the borders somewhat wobble because of scaling artifacts, but it’s relatively hard to notice. Also, depending on your hardware, GPU, CPU and other factors, this could mean an unacceptable performance penailty.


When defining your screen, also define a TEMP_SCALE and a temp_screen:

TEMP_SCALE = 4
screen = pg.display.set_mode((W, H), FLAGS)
temp_screen = pg.Surface((W * TEMP_SCALE, H * TEMP_SCALE), 0, screen)

When drawing, don’t reset and draw to the actual screen, but the temp_screen:

temp_screen.fill((0, 0, 0))
for p in positions:
    drawAADot(temp_screen, (p[0]*TEMP_SCALE, p[1]*TEMP_SCALE), 10*TEMP_SCALE, (255, 255, 255))

And when you are done with drawing (or at least with drawing stuff that should be scaled down smoothly:

pg.transform.smoothscale(temp_screen, (W, H), screen)
Answered By: MegaIng

I’ve found an old blog post that provides a module that works pretty well.

Sub-pixel movement by Will McGugan:
https://www.willmcgugan.com/blog/tech/post/going-sub-pixel-with-pygame/

As mentioned, the code was quite old, and I got issues with premultiplied alpha blending. I came up with this solution that now works for me. I took the difference between the rounded position and the actual float position, scaled it up by 10, corrected the position and smoothscaled it back to normal:

def drawDot(surface, roundPos, realPos, radius, color, scale=10):
# original size
w = radius * 2 
h = radius * 2 
# difference between real and rounded position
dif_x = realPos[0] - roundPos[0]
dif_y = realPos[1] - roundPos[1]
add = 0
if dif_x > dif_y:
    add += abs(dif_x)
else:
    add += abs(dif_y)

# adjusted width and height
aw = w + math.ceil(add*2)
ah = h + math.ceil(add*2)
if aw % 2 != 0:
    aw += 1
if ah % 2 != 0:
    ah += 1
# scaled width and height
sw = aw * scale
sh = ah * scale
# create scaled surface
s = pygame.Surface((sw, sh), pygame.SRCALPHA)
# offset
offset = (round(dif_x*scale), round(dif_y*scale)) 

# draw circle on scaled surface
gfx.aacircle(s, int(sw/2 + offset[0]), int(sh/2 + offset[1]), radius*scale, color)
gfx.filled_circle(s, int(sw/2 + offset[0]), int(sh/2 + offset[1]), radius*scale, color)
# scale down surface to target size for supersampling effect
s2 = pygame.transform.smoothscale(s, (aw, ah))
# blit on screen
surface.blit(s2, (roundPos[0]-aw/2, roundPos[1]-ah/2), special_flags=pygame.BLEND_PREMULTIPLIED)

This is currently working for me. If anyone got a more descent/optimized version, I’m all ears.

Answered By: Stechouse
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.