Understanding computer graphics algorithms. Part 4 – Animation “Salute”

A little more in the piggy bank of beautiful effects and algorithms.

You have probably seen fireworks in your life, when a fireball explodes in the night sky and fires slowly scatter from it in all directions.

Some abstract fireworks
Some abstract fireworks

Let’s try to analyze what we see in terms of geometry, physics and programming.

Imagine that the night sky is our screen. Somewhere at random X,Y an explosion occurs. From this point, luminous particles scatter in different directions. They gradually go out and slowly fly to the ground, leaving a luminous trail behind them.

There can be quite a lot of particles. But we are limited in the virtual world by the power of our computer, so our particles will be finite. For example, let there be 500 of them. The more particles, the slower our program will run, because we need to calculate the trajectory of each particle. But too few particles are bad, it will just be ugly.

So what do we know about each of our particles?

  • She has some coordinates x, yand initially they coincide with the coordinate of the salute explosion.

  • Each particle flies in its own direction, i.e. it has velocities along the coordinate axes dx And dy.

  • Each particle has its own color – colorwhich decays as the particle travels.

All particles gradually fall down, but this property is not unique to a single particle, but common to all. Therefore, we will not consider it separately for each.

How can we describe the explosion of the salute charge and the scattering of particles?

Somehow, you need to randomly give each particle the direction of its flight and the speed with which it will fly. It turns out we need to get something like this picture:

Salute explosion location and particle trajectory
Salute explosion location and particle trajectory

From a central point, particles fly in different directions at different speeds.

Getting a random point where the explosion will start is easy, just random coordinates within the screen boundaries.

Get random speeds by X And Y, also easy – just some small random numbers. But beautiful effects are not born so easily and there are several subtleties.

We will generate random speeds of points expansion in a certain range, for example, from -30 to 30. Divide the resulting number by 10, and we will get a speed in the range from -3.0 to 3.0, with values ​​after the decimal point. So far, nothing suspicious. But let’s imagine, let’s try to depict it graphically:

The red lines indicate the range of possible particle expansion velocities
The red lines indicate the range of possible particle expansion velocities

If we represent the expansion velocities as vectors from the center of the explosion, then they will all be within the boundaries of a certain square, with a side from -3.0 to 3.0. When there are a fairly large number of particles, it will simply be clear that our fireworks are square. And this is strange and completely ugly.

We want to get something like this:

The particle expansion vectors must be inside a certain circle. Then the salute will look like an exploding ball.

The method that is used in this algorithm is as follows: we get a certain particle flight velocity vector using ordinary random numbers (a square version of the salute), and then multiply its length by a certain random number in the range from 0 before Rwhere R and there is the radius of such a circle in which we want to keep our salute.

It sounds like not very difficult, but how to do it in practice?

We will need knowledge of the school course of geometry. We will use two theorems:

1. The Pythagorean theorem. Namely, that the square of the hypotenuse is equal to the sum of the squares of the legs of the triangle.

2. Triangle similarity theorem: If two sides of one triangle are proportional to two sides of another triangle and the angles between these sides are equal, then such triangles are similar.

Let’s figure it out:

Dot FROM, this is the central point of our explosion. Dot A this is the point to which the particle expansion vector is directed when it is generated by random numbers.

Through the random number generator we get the values xa And Ya. Using the Pythagorean theorem, we can calculate the length of the segment CA:

CA = \sqrt{X_a^2 + Y_a^2}

We need to increase the length of the segment CA up to CB. This happens when we multiply the length CA to some random number ranging from 0 before R (I remind you that R is the radius of the circle that the explosion must fit into).

Length CB we know, it remains only to calculate the quantities Xb And Yb. They will be the speeds we need along the axis X And Yso that we get a beautiful salute, with spherical explosions.

This is where the similarity theorem comes to the rescue. She reports that for similar triangles the following relations are true:

\frac{CB}{CA}=\frac{X_b}{X_a} and \frac{CB}{CA}=\frac{Y_b}{Y_a}

and from this we can calculate:

X_b = \frac{X_a * CB}{CA}Y_b = \frac{Y_a*CB}{CA}

In such an unusual way, the triangle similarity theorem came in handy.

What we have at this point: the coordinates of the point from which the salute will explode and the speed of the particles, which are built so that a circular explosion is formed.

Now let’s color our particles. Of course, it can be anything, but it is desirable that it fades with a decrease in brightness. We will use a palette very similar to the one we took to generate the flame (but only a little longer):

In the cycle, we will go through all the particles of our salute. To coordinates X And Y add dX And dY – velocity of particles. And at the same time we will somehow reduce the color value for the particle. But if this is done simply by subtracting the color values, the salute will consist of colored dots that will scatter with decreasing brightness. It doesn’t look very good. A real salute should blaze, be juicy, and, ideally, we should not see individual points at all.

To create the appropriate effect – apply the color smoothing algorithm, we used it when generating the flame.

Let me remind you a little about the essence of the algorithm: the color of a dot on the screen will be the arithmetic mean of four neighboring dots.

The colors of the top, bottom, right, and left dots are added together, and then the result is divided by 4 and written back to the screen. Thus, we will no longer have separate points, and each of them will be surrounded by a halo of light.

Well, the last part – how to deal with gravity, so that all particles tend to fall.

Nothing too complicated here either. Of course, there is no reason for us to program realistic physics, and doing something similar is quite simple.

Let’s set a certain coefficient that will denote the gravitational effect. For example value 0.08. We will not use the real free fall coefficient, it is too powerful for our coordinate system.

At each step of the particle movement, we will add the gravity coefficient to the value of the movement speed dY along the axis Y. This will give us the fact that the points flying up will slow down, and down will accelerate. Since the value dX we do not touch, then the particles will fly in an arc.

And for “quite beauty”, let’s make them bounce when they fall to the ground.

To do this, if the coordinate Y reached the bottom of the window, then we multiply the speed dY on the -one, i.e. change its direction to the opposite, and also reduce it to 2 times, i.e. divide by 2. This will give the effect of bouncing off the surface, with a decrease in speed after the bounce.

Well, the code itself, of course, it already turns out to be more complex than in the previous examples:

import pygame
import random
import math
import copy

MX = MY = 128		# Размер массива для взрыва

scale = 5               # Масштаб точек для вывода на экран

SX = MX * scale         # Размер экрана исходя из размера плазмы и ее масштаба
SY = MX * scale

scr = []                # Промежуточный список для хранения экрана
line = [0] * MX         # Создаем список из нулей длиной MX
scr = []                # Создаем список списков из нулей длиной MY, в итоге получится квадратная таблица из нулей.
for y in range(0, MY):
    scr.append(copy.deepcopy(line))

pygame.init()
screen = pygame.display.set_mode((SX, SY))
running = True

pal = []                # Палитра для графического эффекта
                        # Палитра почти как для пламени, но немного больше.
for i in range(0, 64):
    pal.append([i*4, 0, 0])
for i in range(64, 128):
    pal.append([255, i*4 - 255, 0])
for i in range(128, 255):
    pal.append([255, 255, round((i*4-128)/4)])

numParticle = 500       # Общее количество частиц

gravity = 0.08          # Коэффициент гравитации

particles = []                          # Список с частицами
for i in range(0, numParticle):         # Инициализируем список пустыми значениями
    particles.append([0, 0, 0, 0, 0])

# Для простоты ориентации в списке частиц, сделаем отдельные переменные для номеров ячеек отдельной частицы:
_x = 0              # номер координаты X
_y = 1              # номер координаты Y
_dirx = 2           # номер направления по X
_diry = 3           # номер направления по Y
_color = 4          # номер цвета

time = 0            # Счетчик времени существования взрыва на экране

# -------------------------------------------------------------------------------------------------------
# Генерация нового взрыва в указанных координатах.
# -------------------------------------------------------------------------------------------------------
def Boom(x, y):
    for i in range(0, numParticle):
        particles[i][_x] = x                # Задаем точку, откуда взорветса салют
        particles[i][_y] = y
                                            # Генерируем случайные скорости разлета частицы в диапазоне от -3.0 до 3.0
        particles[i][_dirx] = random.randint(-30, 30)/10.0
        particles[i][_diry] = random.randint(-30, 30)/10.0
                                            # Генерируем случайное число внутри радиуса от 0 до 5.0, для придания сферической формы взрыву
        dist = random.randint(0, 50)/10.0
                                            # Вычисляем диагональ треугольника верктора скорости до увеличения.
        mlen = math.sqrt(particles[i][_dirx]**2 + particles[i][_diry]**2)
        if mlen != 0:
            mlen = 1.0 / mlen
                                            # Используя теорему подобия вычисляем новые значения скоростей.
        particles[i][_dirx] *= mlen * dist
        particles[i][_diry] *= mlen * dist
                                            # Задаем начальный цвет точки - он самый яркий, т.к. это только начало взрыва
        particles[i][_color] = 254

# -------------------------------------------------------------------------------------------------------
#  Отрисовка закрашенного квадрата в нужных координатах, определенного размера.
# -------------------------------------------------------------------------------------------------------
def drawBox(x, y, size, color):
    pygame.draw.rect(screen, pal[color], (x, y, size, size))

# -------------------------------------------------------------------------------------------------------

Boom(MX/2, MY/2)

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

    for i in range(0, numParticle):             # Перебираем все частицы
        x = round(particles[i][_x])
        y = round(particles[i][_y])
                                                # Если координаты входят в экран - выводим
        if (x in range(1, MX-1)) and (y in range(1, MY-1)):
            scr[y][x] = particles[i][_color]
                                                # Изменяем координаты частицы в зависимости от скорости
        particles[i][_x] += particles[i][_dirx]
        particles[i][_y] += particles[i][_diry]

                                                # Реализуем отскок от земли
        if particles[i][_y] > MY:
            particles[i][_y] = MY
            particles[i][_diry] = -particles[i][_diry] / 2.0
        else:
            particles[i][_diry] += gravity      # Применяем к скрости частицы - гравитацию

    # Осуществляем размытие экрана по 4 соседним точкам
    for y in range(1, MY-1):
        for x in range(1, MX-1):
            color = round(((scr[y][x+1] + scr[y][x-1] + scr[y+1][x] + scr[y-1][x]) / 4.0) - 2)
            if color < 0:
                color = 0
            scr[y][x] = color
            drawBox(x*scale, y*scale, scale, color)

    # Для генерации нового взрыва используем счетчик, как только он превышен, новый взрыв и
    # перестартуем счетчик
    time += 1
    if time > 70:
        time = 0
        Boom(random.randint(1, MX), random.randint(1, MY))

    pygame.display.flip()

pygame.quit()
The final animation of the salute (the gif was made through the frame, to reduce the size)
The final animation of the salute (the gif was made through the frame, to reduce the size)

If we did not make a fireworks explosion in a circle, multiplying by a certain radius of the speed of particle expansion, we would get the following picture:

"square" salute
“square” salute

Next time we will try to analyze the algorithm for generating the simplest “plasma” – the “shade bob” algorithm

Link to part 3 – animation “Flame”

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *