3D picture in python with (almost) normal performance

This article can be considered the answer to this one , where it is a question of writing such a thing in C ++, aimed at beginners, that is, with an emphasis on simple readable code instead of high performance.

After reading the article, I had the idea to repeat the program written by the author. I am familiar with C ++, but I never wrote any complicated programs on it, preferring python. Here the idea was born to write on it. I was especially interested in performance - I was almost sure that a couple of frames per second is the limit for python. I was wrong.

image

image

The first attempt can be found here . Here the code is written in full, not counting language differences, in accordance with that ofhaqreu. And because of this, the rendering is O (n ^ 2) - essentially nested loops in angle and distance:

         # iterating alpha
        alpha = player.view - player.fov / 2
        mapFB.drawRectangle(player.y - 1, player.x - 1, player.y + 1, player.x + 1, Color(255, 0, 0))
        rayNum = 0
        while alpha < player.fov / 2 + player.view:
            # iterating distance
            dist = 0
            x = player.x
            y = player.y
            while 0 < x < mapFB.w - 1 and 0 < y < mapFB.h - 1:
                  ...

Because of this, the code is rather slow (I managed to get less than 3-4 frames per second on the 8th generation intel core i5).

An obvious way to speed things up and not complicate the code much is to replace the inner loop with operations of linear complexity. Let's consider everything from the point of view of mathematics: we need to determine the point of intersection of the ray given by the coordinates of the player and the angle of view, and the block specified by the coordinates and size (constant, for simplicity). Next, you need to select the nearest transfer and return it. Below is the corresponding code (Full code is here ):

def block_cross(i, j, k, y_0, alpha, player):
    # cell coordinates
    x_cell = i * H
    y_cell = j * H
    # find collision points
    collisions = []

    if k != 0:
        x = (y_cell - y_0) / k
        y = y_cell
        if x_cell <= x <= x_cell + H and (x - player.x) / cos(alpha) < 0:
            collisions.append((x, y))

    if k != 0:
        x = (y_cell + H - y_0) / k
        y = y_cell + H
        if x_cell <= x <= x_cell + H and (x - player.x) / cos(alpha) < 0:
            collisions.append((x, y))

    x = x_cell
    y = y_0 + x * k
    if y_cell <= y <= y_cell + H and (x - player.x) / cos(alpha) < 0:
        collisions.append((x, y))

    x = x_cell + H
    y = y_0 + (x) * k
    if y_cell <= y <= y_cell + H and (x - player.x) / cos(alpha) < 0:
        collisions.append((x, y))

    # select the closest collision for the block
    dist = 1000 * H
    x = None
    y = None
    for collision in collisions:
        tmp = sqrt((collision[0] - player.x) ** 2 + (collision[1] - player.y) ** 2)
        if tmp < dist:
            dist = tmp;
            x = collision[0]
            y = collision[1]

    return x, y, dist

Such a trivial change of 10 lines gives an acceleration of more than double, that is, about 5-6 frames per second. This is no longer jerks, but a moving picture, but still quite slow.
Looking for ideas about speeding up code, I came across Cython . In short, this is a project that allows you to give python code significant acceleration without seriously changing it. Briefly about him - under the spoiler.

tyts
 cpdef int fun(int num, float val):
    cdef int result
    # do some stuff
    return result

result int( int, , ). , . python-, cdef โ€” python, cython. , python โ€” , :

 cpdef int fun(int num, float val):
    cdef int result
    # do some stuff
    return result

 cdef int fun2(int *arr, float* arr_2):
    cdef int arr_3[10][10]
    # do some stuff
    return result

fun2 python, - fun โ€” .

Cython gave some acceleration, albeit insignificant - just not a couple of frames per second. However, in relative numbers this is not so small - 8-9 pictures per second, that is + 40% to the best option in python and + 200% to the option with a naive algorithm. This is still a very twitchy picture, but normal for educational purposes. In the end, our goal is to write it ourselves and enjoy the process, but for a real game itโ€™s easier to take a library like pygame, or generally put off python and take something more suitable.

PS It would be interesting to see other options for optimizing the code in the comments.

Source: https://habr.com/ru/post/undefined/


All Articles