使用 Makie 制作烟花动画¶
Makie 是 julia 生态系统中一个流行的可视化库。 本示例将涵盖以下主题:
- 使用调色板(colormaps/colorschemes)
- 创建 mp4 动画(作为 Plots.jl 中
@gif
的替代方法) - 构建流程图的功能 (
scatter
):- 大小
- 标记
- 颜色
- 阿尔法通道
作为微分方程系统的敬礼模型¶
本例中的礼炮模型由一个常微分方程(ODE)系统描述,该系统模拟粒子在重力、空气阻力和水平风的影响下的运动。该系统包括四个变量:坐标$x$ 和$y$ 及其时间导数(速度$v_x$ 和$v_y$ )。方程的形式为
-$\frac{dx}{dt} = v_x$ -$\frac{dy}{dt} = v_y$ -$\frac{dv_x}{dt} = -\frac{(k + k_w)}{m} v_x + \frac{k_w}{m} v_w$ -$\frac{dv_y}{dt} = -g - \frac{k}{m} v_y$
$g$ 为自由落体加速度,$k$ 为空气阻力系数,$m$ 为粒子质量,$v_w$ 为风速,$k_w$ 为风阻系数。该系统使用DifferentialEquations.jl
软件包进行数值求解,该软件包可以模拟礼花弹颗粒在爆炸后的真实行为。
安装依赖项和导入库¶
要处理动画和求解微分方程,我们需要DifferentialEquations
、CairoMakie
和Random
这三个软件包。如果尚未安装,让我们安装并导入它们。
import Pkg; Pkg.add(["DifferentialEquations","CairoMakie","Random"])
using DifferentialEquations
using CairoMakie
using Random
# Устанавливаем seed для воспроизводимости
Random.seed!(1945_2025)
模型和参数的定义¶
定义函数particle_motion!
,该函数根据上述 ODE 系统描述粒子运动。我们还定义了模拟参数:自由落体加速度、空气阻力、粒子质量、风速等。
# Функция движения частицы с учетом ветра
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 # фиксированный шаг времени
生成烟花和选择调色板¶
每个礼花的特征是一个随机的起始位置((x_0, y_0))、一个爆炸时间和一组三种调色板。我们使用Makie.ColorSchemes
来创造一种视觉上吸引人的效果,即每个礼花的粒子都被涂成三种不同的颜色方案。
# Список палитр
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
粒子轨迹建模¶
对于每个礼花,我们通过求解 ODE 系统来模拟其粒子的轨迹。每个粒子都会被分配到三个选定的调色板中的一个,以产生多色礼花效果。
# Моделирование траекторий для каждого салюта
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
创建场景并添加星空背景¶
在创建动画之前,我们先创建一个黑色背景的场景,并添加将在整个动画中显示的星星。这样就会产生夜空效果。
# Визуализация
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-координаты
创建动画¶
我们使用CairoMakie
中的record
函数来创建 MP4 动画。在动画的每一帧中,重新绘制已经爆炸的星星和烟花粒子,同时考虑到尾部效果和个别渐变。
# Анимация
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
动画可能需要一些时间才能完成,之后将创建一个文件"firework_with_wind.mp4"
结论¶
在本示例中,我们使用Makie
生态系统和CairoMakie
后端创建了一个烟花动画。我们已经学会了如何使用微分方程建立粒子运动模型、应用调色板实现视觉效果以及创建 MP4 动画。这种方法可用于其他可视化任务,如行星运动建模、模拟爆炸或创建艺术动画。