Animation of fireworks using Makie
Makie is a popular visualization library in the julia ecosystem.
In this example, topics such as:
- Using color palettes (colormaps/colorschemes)
- Create mp4 animations (as an alternative
@gifin Plots.jl) - features of building point-by-point graphs (
scatter): - size
- marker
- color
- alpha channel
The salute model as a system of differential equations
The salyut model in this example is described by a system of ordinary differential equations (ODES), which simulates the movement of particles under the influence of gravity, air resistance and horizontal wind. The system includes four variables: coordinates and , as well as their derivatives with respect to time (velocity and ). The equations have the form:
Here — acceleration of free fall, — air resistance coefficient, — the mass of the particle, — wind speed, — wind resistance coefficient. This system is solved numerically using the package DifferentialEquations.jl, which makes it possible to simulate the realistic behavior of fireworks particles after an explosion.
Installing dependencies and importing libraries
To work with animation and solving differential equations, we will need packages DifferentialEquations, CairoMakie and Random. We will install them, if they are not already installed, and import them.
import Pkg; Pkg.add(["DifferentialEquations","CairoMakie","Random"])
using DifferentialEquations
using CairoMakie
using Random
# Устанавливаем seed для воспроизводимости
Random.seed!(1945_2025)
Program structure
Our program consists of several key components: a set of fireworks, each of which contains particles, a trajectory is calculated for each particle, and the trajectory is a set of points. .
Defining the model and parameters
Defining the function particle_motion!, which describes the motion of particles according to the above system of ODES. We also set simulation parameters: acceleration of gravity, air resistance, particle mass, wind speed, and others.
# Функция движения частицы с учетом ветра
function particle_motion!(du, u, p, t)
g, k, m, v_w, k_w = p
du[1] = u[3] # dx/dt = v_x
du[2] = u[4] # dy/dt = v_y
du[3] = -(k + k_w) / m * u[3] + k_w * v_w / m # dv_x/dt = -(k + k_w)/m * v_x + k_w * v_w/m
du[4] = -g - k / m * u[4] # dv_y/dt = -g - k/m * v_y
end
# Параметры
g = 9.81 # ускорение свободного падения
k = 0.1 # коэффициент сопротивления воздуха
m = 0.1 # масса частицы
v_w = 0.1 # скорость горизонтального ветра (м/с, положительная — вправо)
k_w = 0.1 # коэффициент сопротивления ветру
n_fireworks = 3 # количество салютов
n_particles_per_firework = 150 # частиц на салют
v0_values = [1:2.5:25;] # разные начальные скорости
tspan = (0.0, 1.5) # общий временной интервал
saveat = 0.0125 # фиксированный шаг времени
Fireworks generation and color palette selection
Each salute is characterized by a random starting position ((x_0, y_0)), the time of the explosion, and a set of three color palettes. We use Makie.ColorSchemes to create a visually appealing effect, where the particles of each fireworks are painted in three different color schemes.
# Список палитр
available_palettes = [
Makie.ColorSchemes.Reds,
Makie.ColorSchemes.pink,
Makie.ColorSchemes.solar,
Makie.ColorSchemes.Blues
]
# Определение салютов
fireworks = []
for i in 1:n_fireworks
# Случайная начальная позиция
x0 = rand(-12:12)
y0 = rand(5:3:15)
# Случайное время взрыва (между 0.0 и 75% tspan)
explosion_time = rand(tspan[1]:0.1:0.75*tspan[2])
# Выбор трёх случайных палитр для салюта
selected_palettes = shuffle(available_palettes)[1:3] # выбираем 3 разные палитры
push!(fireworks, (x0=x0, y0=y0, explosion_time=explosion_time, palettes=selected_palettes))
end
Modeling of particle trajectories
For each salute, we simulate the trajectories of its particles by solving a system of ODES. Each particle is assigned one of three selected palettes to create a multicolored fireworks effect.
# Моделирование траекторий для каждого салюта
all_trajectories = []
all_particle_palettes = []
for (idx, fw) in enumerate(fireworks)
trajectories = []
particle_palettes = []
# Разделяем частицы на три группы для трёх палитр
particles_per_palette = div(n_particles_per_firework, 3)
for i in 1:n_particles_per_firework
# Выбор начальной скорости из v0_values
n_v = length(v0_values)
v0 = v0_values[ceil(Int, (i - 1) / (n_particles_per_firework / n_v))%n_v+1] + rand(-2:0.25:2)
θ = 2π * (i - 1) / (n_particles_per_firework / n_v) + rand(-0.1:0.001:0.1) # случайное отклонение угла
u0 = [fw.x0, fw.y0, v0 * cos(θ), v0 * sin(θ)] # начальная позиция и скорость
prob = ODEProblem(particle_motion!, u0, tspan, (g, k, m, v_w, k_w))
sol = solve(prob, Tsit5(), saveat=saveat)
push!(trajectories, [(u[1], u[2]) for u in sol.u])
# Назначаем палитру в зависимости от группы частицы
palette_idx = (i - 1) ÷ particles_per_palette + 1
if palette_idx > 3 # для последнего набора частиц
palette_idx = 3
end
push!(particle_palettes, fw.palettes[palette_idx])
end
push!(all_trajectories, trajectories)
push!(all_particle_palettes, particle_palettes)
end
Creating a scene and adding a starry background
Before creating the animation, we set a scene with a black background and add stars that will be displayed throughout the animation. This creates a night sky effect.
# Визуализация
fig = Figure(backgroundcolor=:black)
ax = Axis(fig[1, 1], aspect=1, limits=(-20, 20, -20, 20),
backgroundcolor=:black, xgridvisible=false, ygridvisible=false,
xticksvisible=false, yticksvisible=false,
xticklabelsvisible=false, yticklabelsvisible=false)
# Добавление звёзд на задний фон
n_stars = 50 # Количество звёзд
x_stars = rand(-20:0.1:20, n_stars) # Случайные x-координаты
y_stars = rand(-20:0.1:20, n_stars) # Случайные y-координаты
Creating animations
Using the function record from CairoMakie to create MP4 animations. In each animation frame, we redraw the stars and fireworks particles that have already exploded, taking into account the tail effect and individual fading.
# Анимация
total_frames = ceil((tspan[2] - tspan[1]) / saveat) + 1 # общее количество кадров
record(fig, "firework_with_wind.mp4", 1:total_frames-1) do frame_idx
empty!(ax)
# Перерисовываем звёзды
CairoMakie.scatter!(ax, x_stars, y_stars, marker=:star5, color=:yellow, markersize=5, alpha=0.5)
# Текущее время в анимации
current_time = (frame_idx - 1) * saveat
# Проходим по каждому салюту
for firework_idx in 1:n_fireworks
fw = fireworks[firework_idx]
trajectories = all_trajectories[firework_idx]
particle_palettes = all_particle_palettes[firework_idx]
# Проверяем, взорвался ли салют
time_since_explosion = current_time - fw.explosion_time
if time_since_explosion >= 0
# Вычисляем индекс времени относительно времени взрыва
i = Int(round(time_since_explosion / saveat)) + 1
# Ограничиваем i длиной траектории
i = min(i, length(trajectories[1]))
# Индивидуальное выцветание для каждого салюта
if time_since_explosion >= 0
fading_start = 0.6 # 60% от времени видимости
fading_duration = 0.4 # оставшиеся 40% для выцветания
k_fading = time_since_explosion < fading_start ? 1.0 : max(0.0, 1.0 - (time_since_explosion - fading_start) / fading_duration)
# Ограничиваем длину хвоста: отображаем последние 24 позиции
for (traj, palette) in zip(trajectories, particle_palettes)
for j in max(1, i - 24):i
alpha = k_fading * (0.1 + 0.9 * exp(-0.4 * (i - j)))
markersize = 2 + 3 * exp(-0.5 * (i - j))
t = i > 1 ? (j - max(1, i - 14)) / (i - max(1, i - 14)) : 1.0
color = palette[t]
CairoMakie.scatter!(ax, [traj[j]], markersize=markersize, color=color, alpha=alpha)
end
end
end
end
end
end
The animation may take a certain amount of time, after which the file will be created. "firework_with_wind.mp4"
Conclusion
In this example, we created a fireworks animation using the ecosystem Makie with a backend CairoMakie. We've learned how to simulate particle motion using differential equations, apply color palettes for visual effect, and create MP4 animations. This approach can be adapted for other visualization tasks, such as modeling planetary motion, simulating explosions, or creating artistic animations.