Отчёт лаб3
1
lab3/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
!report
|
||||||
6
lab3/report/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
*
|
||||||
|
|
||||||
|
!.gitignore
|
||||||
|
!*.tex
|
||||||
|
!*.png
|
||||||
|
!*.jpg
|
||||||
BIN
lab3/report/pic1.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
lab3/report/pic10.png
Normal file
|
After Width: | Height: | Size: 949 KiB |
BIN
lab3/report/pic11.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
lab3/report/pic2.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
lab3/report/pic3.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
lab3/report/pic4.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
lab3/report/pic5.png
Normal file
|
After Width: | Height: | Size: 208 KiB |
BIN
lab3/report/pic6.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
lab3/report/pic7.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
lab3/report/pic8.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
lab3/report/pic9.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
833
lab3/report/Лаб3. Тищенко.tex
Normal file
@@ -0,0 +1,833 @@
|
|||||||
|
|
||||||
|
\documentclass[a4paper, final]{article}
|
||||||
|
\usepackage[14pt]{extsizes} % для того чтобы задать нестандартный 14-ый размер шрифта
|
||||||
|
\usepackage[T2A]{fontenc}
|
||||||
|
\usepackage[utf8]{inputenc}
|
||||||
|
\usepackage[russian]{babel}
|
||||||
|
\usepackage{ragged2e}
|
||||||
|
\usepackage{algorithmic}
|
||||||
|
\usepackage{amsmath}
|
||||||
|
\usepackage{multicol}
|
||||||
|
\usepackage{nccmath}
|
||||||
|
\usepackage{tikz}
|
||||||
|
\usepackage{wrapfig}
|
||||||
|
\usepackage{caption}
|
||||||
|
\usepackage{tabularx}
|
||||||
|
\usepackage{array}
|
||||||
|
\usepackage[left=25mm, top=20mm, right=20mm, bottom=20mm, footskip=10mm]{geometry}
|
||||||
|
\usepackage{ragged2e} %для растягивания по ширине
|
||||||
|
\usepackage{setspace} %для межстрочного интервала
|
||||||
|
\usepackage{moreverb} %для работы с листингами
|
||||||
|
\usepackage{indentfirst} % для абзацного отступа
|
||||||
|
\usepackage{moreverb} %для печати в листинге исходного кода программ
|
||||||
|
\renewcommand\verbatimtabsize{4\relax}
|
||||||
|
\renewcommand\listingoffset{0.2em}
|
||||||
|
\renewcommand{\arraystretch}{1.4} % изменяю высоту строки в таблице
|
||||||
|
\usepackage[font=small, singlelinecheck=false, justification=raggedleft, format=plain, labelsep=period]{caption} %для настройки заголовка таблицы
|
||||||
|
%\usepackage[dvips]{graphicx} % Для вставки графических изображений
|
||||||
|
%\usepackage{color} %% это для отображения цвета в коде
|
||||||
|
%\usepackage{xcolor} % цвета
|
||||||
|
\usepackage{listingsutf8}
|
||||||
|
\usepackage{hyperref}% для гиперссылок
|
||||||
|
\usepackage{enumitem} %для перечислений
|
||||||
|
\usepackage{pdflscape} %для pdf
|
||||||
|
\usepackage{pdfpages} %для pdf
|
||||||
|
|
||||||
|
\definecolor{apricot}{HTML}{FFF0DA}
|
||||||
|
\definecolor{mygreen}{rgb}{0,0.6,0}
|
||||||
|
\definecolor{string}{HTML}{B40000} % цвет строк в коде
|
||||||
|
\definecolor{comment}{HTML}{008000} % цвет комментариев в коде
|
||||||
|
\definecolor{keyword}{HTML}{1A00FF} % цвет ключевых слов в коде
|
||||||
|
\definecolor{morecomment}{HTML}{8000FF} % цвет include и других элементов в коде
|
||||||
|
\definecolor{captiontext}{HTML}{FFFFFF} % цвет текста заголовка в коде
|
||||||
|
\definecolor{captionbk}{HTML}{999999} % цвет фона заголовка в коде
|
||||||
|
\definecolor{bk}{HTML}{FFFFFF} % цвет фона в коде
|
||||||
|
\definecolor{frame}{HTML}{999999} % цвет рамки в коде
|
||||||
|
\definecolor{brackets}{HTML}{B40000} % цвет скобок в коде
|
||||||
|
|
||||||
|
\setlist[enumerate,itemize]{leftmargin=1.2cm}
|
||||||
|
|
||||||
|
\hypersetup{colorlinks,
|
||||||
|
pdftitle={Лабораторная №3},
|
||||||
|
pdfauthor={Тищенко А. А.},
|
||||||
|
allcolors=[RGB]{010 090 200}}
|
||||||
|
% подгружаемые языки — подробнее в документации listings
|
||||||
|
\lstloadlanguages{bash}
|
||||||
|
% включаем кириллицу и добавляем кое−какие опции
|
||||||
|
%\lstset{language =[LaTeX] TeX, % выбираем язык по умолчанию
|
||||||
|
%extendedchars=true , % включаем не латиницу
|
||||||
|
%escapechar = | , % |«выпадаем» в LATEX|
|
||||||
|
%frame=tb , % рамка сверху и снизу
|
||||||
|
%commentstyle=\itshape , % шрифт для комментариев
|
||||||
|
%stringstyle =\bfseries} % шрифт для строк
|
||||||
|
|
||||||
|
\textheight=24cm % высота текста
|
||||||
|
\textwidth=16cm % ширина текста
|
||||||
|
\oddsidemargin=0pt % отступ от левого края
|
||||||
|
\topmargin=-1.5cm % отступ от верхнего края
|
||||||
|
\parindent=24pt % абзацный отступ
|
||||||
|
\parskip=0pt % интервал между абзацами
|
||||||
|
\tolerance=2000 % терпимость к "жидким" строкам
|
||||||
|
\flushbottom % выравнивание высоты страниц
|
||||||
|
|
||||||
|
\addto\captionsrussian{\def\refname{\hspace{1.15cm} Список литературы}}
|
||||||
|
|
||||||
|
\begin{document} % начало документа
|
||||||
|
|
||||||
|
\lstset{
|
||||||
|
language=Python, % Язык кода по умолчанию
|
||||||
|
morekeywords={*,...}, % если хотите добавить ключевые слова, то добавляйте
|
||||||
|
% Цвета
|
||||||
|
keywordstyle=\color{keyword}\ttfamily\bfseries,
|
||||||
|
%stringstyle=\color{string}\ttfamily,
|
||||||
|
stringstyle=\ttfamily\color{red!50!brown},
|
||||||
|
commentstyle=\color{comment}\ttfamily,
|
||||||
|
morecomment=[l][\color{morecomment}]{\#},
|
||||||
|
% Настройки отображения
|
||||||
|
breaklines=true, % Перенос длинных строк
|
||||||
|
basicstyle=\ttfamily\footnotesize, % Шрифт для отображения кода
|
||||||
|
backgroundcolor=\color{bk}, % Цвет фона кода
|
||||||
|
frame=lrb,xleftmargin=\fboxsep,xrightmargin=-\fboxsep, % Рамка, подогнанная к заголовку
|
||||||
|
rulecolor=\color{frame}, % Цвет рамки
|
||||||
|
tabsize=3, % Размер табуляции в пробелах
|
||||||
|
% Настройка отображения номеров строк. Если не нужно, то удалите весь блок
|
||||||
|
numbers=left, % Слева отображаются номера строк
|
||||||
|
stepnumber=1, % Каждую строку нумеровать
|
||||||
|
numbersep=5pt, % Отступ от кода
|
||||||
|
numberstyle=\small\color{black}, % Стиль написания номеров строк
|
||||||
|
% Для отображения русского языка
|
||||||
|
extendedchars=true,
|
||||||
|
literate={Ö}{ {\"O} }1
|
||||||
|
{~}{ {\textasciitilde} }1
|
||||||
|
{а}{ {\selectfont\char224} }1
|
||||||
|
{б}{ {\selectfont\char225} }1
|
||||||
|
{в}{ {\selectfont\char226} }1
|
||||||
|
{г}{ {\selectfont\char227} }1
|
||||||
|
{д}{ {\selectfont\char228} }1
|
||||||
|
{е}{ {\selectfont\char229} }1
|
||||||
|
{ё}{ {\"e} }1
|
||||||
|
{ж}{ {\selectfont\char230} }1
|
||||||
|
{з}{ {\selectfont\char231} }1
|
||||||
|
{и}{ {\selectfont\char232} }1
|
||||||
|
{й}{ {\selectfont\char233} }1
|
||||||
|
{к}{ {\selectfont\char234} }1
|
||||||
|
{л}{ {\selectfont\char235} }1
|
||||||
|
{м}{ {\selectfont\char236} }1
|
||||||
|
{н}{ {\selectfont\char237} }1
|
||||||
|
{о}{ {\selectfont\char238} }1
|
||||||
|
{п}{ {\selectfont\char239} }1
|
||||||
|
{р}{ {\selectfont\char240} }1
|
||||||
|
{с}{ {\selectfont\char241} }1
|
||||||
|
{т}{ {\selectfont\char242} }1
|
||||||
|
{у}{ {\selectfont\char243} }1
|
||||||
|
{ф}{ {\selectfont\char244} }1
|
||||||
|
{х}{ {\selectfont\char245} }1
|
||||||
|
{ц}{ {\selectfont\char246} }1
|
||||||
|
{ч}{ {\selectfont\char247} }1
|
||||||
|
{ш}{ {\selectfont\char248} }1
|
||||||
|
{щ}{ {\selectfont\char249} }1
|
||||||
|
{ъ}{ {\selectfont\char250} }1
|
||||||
|
{ы}{ {\selectfont\char251} }1
|
||||||
|
{ь}{ {\selectfont\char252} }1
|
||||||
|
{э}{ {\selectfont\char253} }1
|
||||||
|
{ю}{ {\selectfont\char254} }1
|
||||||
|
{я}{ {\selectfont\char255} }1
|
||||||
|
{А}{ {\selectfont\char192} }1
|
||||||
|
{Б}{ {\selectfont\char193} }1
|
||||||
|
{В}{ {\selectfont\char194} }1
|
||||||
|
{Г}{ {\selectfont\char195} }1
|
||||||
|
{Д}{ {\selectfont\char196} }1
|
||||||
|
{Е}{ {\selectfont\char197} }1
|
||||||
|
{Ё}{ {\"E} }1
|
||||||
|
{Ж}{ {\selectfont\char198} }1
|
||||||
|
{З}{ {\selectfont\char199} }1
|
||||||
|
{И}{ {\selectfont\char200} }1
|
||||||
|
{Й}{ {\selectfont\char201} }1
|
||||||
|
{К}{ {\selectfont\char202} }1
|
||||||
|
{Л}{ {\selectfont\char203} }1
|
||||||
|
{М}{ {\selectfont\char204} }1
|
||||||
|
{Н}{ {\selectfont\char205} }1
|
||||||
|
{О}{ {\selectfont\char206} }1
|
||||||
|
{П}{ {\selectfont\char207} }1
|
||||||
|
{Р}{ {\selectfont\char208} }1
|
||||||
|
{С}{ {\selectfont\char209} }1
|
||||||
|
{Т}{ {\selectfont\char210} }1
|
||||||
|
{У}{ {\selectfont\char211} }1
|
||||||
|
{Ф}{ {\selectfont\char212} }1
|
||||||
|
{Х}{ {\selectfont\char213} }1
|
||||||
|
{Ц}{ {\selectfont\char214} }1
|
||||||
|
{Ч}{ {\selectfont\char215} }1
|
||||||
|
{Ш}{ {\selectfont\char216} }1
|
||||||
|
{Щ}{ {\selectfont\char217} }1
|
||||||
|
{Ъ}{ {\selectfont\char218} }1
|
||||||
|
{Ы}{ {\selectfont\char219} }1
|
||||||
|
{Ь}{ {\selectfont\char220} }1
|
||||||
|
{Э}{ {\selectfont\char221} }1
|
||||||
|
{Ю}{ {\selectfont\char222} }1
|
||||||
|
{Я}{ {\selectfont\char223} }1
|
||||||
|
{\{}{ { {\color{brackets}\{} } }1 % Цвет скобок {
|
||||||
|
{\} }{ { {\color{brackets}\} } } }1 % Цвет скобок }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
% НАЧАЛО ТИТУЛЬНОГО ЛИСТА
|
||||||
|
\thispagestyle{empty}
|
||||||
|
\begin{center}
|
||||||
|
\normalsize{МИНИСТЕРСТВО НАУКИ И ВЫСШЕГО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ \\ ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ АВТОНОМНОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО \\ ОБРАЗОВАНИЯ\\ «Санкт-Петербургский политехнический университет Петра Великого»}
|
||||||
|
\normalsize{Институт компьютерных наук и кибербезопасности}\\[10pt]
|
||||||
|
\normalsize{Высшая школа технологий искусственного интеллекта}\\[10pt]
|
||||||
|
\normalsize{Направление: 02.03.01 Математика и компьютерные науки}\\
|
||||||
|
\hfill \break
|
||||||
|
\hfill \break
|
||||||
|
\hfill \break
|
||||||
|
\hfill \break
|
||||||
|
\hfill \break
|
||||||
|
\hfill \break
|
||||||
|
\normalsize{Отчет о выполнении лабораторной работы №3 по дисциплине}\\[3pt]
|
||||||
|
\normalsize{«Алгоритмические основы компьютерной графики»}\\[3pt]
|
||||||
|
\normalsize{по теме:}\\[3pt]
|
||||||
|
\normalsize{«Визуализация физического процесса»}\\[10pt]
|
||||||
|
\end{center}
|
||||||
|
\hfill \break
|
||||||
|
\hfill \break
|
||||||
|
\hfill \break
|
||||||
|
\hfill \break
|
||||||
|
\begin{tabular}{lcrl}
|
||||||
|
\!\!\!Студент, & \hspace{2cm} & & \\
|
||||||
|
\!\!\!группы 5130201/20101 & \hspace{2.13cm} & \underline{\hspace{3cm}} &Тищенко А. А. \\\\
|
||||||
|
\!\!\!Преподаватель & \hspace{2cm} & \underline{\hspace{3cm}} & Курочкин М. А. \\\\
|
||||||
|
&&\hspace{5cm}
|
||||||
|
|
||||||
|
\end{tabular}
|
||||||
|
\begin{flushright}
|
||||||
|
<<\underline{\hspace{1cm}}>>\underline{\hspace{2.5cm}} 2025 г.
|
||||||
|
\end{flushright}
|
||||||
|
\hfill \break
|
||||||
|
\hfill \break
|
||||||
|
\begin{center} \small{Санкт-Петербург, 2025} \end{center}
|
||||||
|
\newpage %Содержание% выключаем отображение номера для этой страницы
|
||||||
|
|
||||||
|
% КОНЕЦ ТИТУЛЬНОГО ЛИСТА
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
|
||||||
|
\tableofcontents
|
||||||
|
|
||||||
|
% \newpage
|
||||||
|
% \addcontentsline{toc}{section}{Введение}
|
||||||
|
|
||||||
|
% \newpage
|
||||||
|
% \subsection*{Введение}
|
||||||
|
|
||||||
|
% Компьютерная графика служит мощным средством для отображения физических явлений, обеспечивая возможность их визуализации и изучения динамических свойств. Одним из значимых направлений в этой области является визуализация жидкостей, которая позволяет имитировать реальные физические явления, в частности, движение воды.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
% Процедурные методы визуализации занимают особую нишу среди подходов к созданию графики, поскольку их принцип основан на применении математических алгоритмов и физических закономерностей. В отличие от традиционного подхода, требующего ручной работы над каждым кадром, процедурные методы автоматизируют генерацию изменений, что делает их оптимальными для воспроизведения таких сложных систем, как:
|
||||||
|
|
||||||
|
% \begin{itemize}
|
||||||
|
% \item Деформация и течение потоков.
|
||||||
|
% \item Искажение изображений через водную поверхность.
|
||||||
|
% \end{itemize}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
% Настоящая работа посвящена визуализации процесса течения воды. В качестве основы для создания визуализации был использован фрагмент видеозаписи течения воды из крана, что дает возможность сравнить результаты цифровой визуализации с поведением жидкости в естественных условиях.
|
||||||
|
|
||||||
|
% \vspace{5pt}
|
||||||
|
|
||||||
|
% Ключевые аспекты реализации:
|
||||||
|
|
||||||
|
% \begin{itemize}
|
||||||
|
% \item \textbf{Динамика во времени}\\
|
||||||
|
% Течение жидкости — это непрерывный процесс, в котором форма и объем потока постоянно меняются. Для достижения реалистичности требуется последовательная и поэтапная обработка генерации на различных участках струи.
|
||||||
|
|
||||||
|
% \item\textbf{ Упрощенный подход к визуализации}\\
|
||||||
|
% Несмотря на то, что в действительности движение жидкостей регулируется сложными законами гидродинамики, в данной работе основное внимание уделяется внешней, визуальной стороне процесса. Такой подход позволяет добиться высокой степени достоверности изображения без необходимости выполнения сложных вычислений.
|
||||||
|
|
||||||
|
% \end{itemize}
|
||||||
|
% Основная цель работы — создание реалистичной анимации процесса течения воды из крана, демонстрирующей ключевые физические свойства вещества при минимальных упрощениях.
|
||||||
|
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\section{Постановка задачи}
|
||||||
|
|
||||||
|
Дано: Видеоролик, демонстрирующий физический процесс --- течение воды из крана. На записи также видно формирование и падение нескольких капель с головки крана. Один из кадров данного процесса показан на рисунке \hyperref[pic1]{1}.\par
|
||||||
|
|
||||||
|
Цель работы: программными средствами визуализировать наблюдаемый на видеозаписи процесс.
|
||||||
|
|
||||||
|
\vspace{5pt}
|
||||||
|
|
||||||
|
Для достижения цели требуется выполнить следующие шаги:
|
||||||
|
|
||||||
|
\vspace{-10pt}
|
||||||
|
|
||||||
|
\begin{enumerate}
|
||||||
|
\item Изучить видеоматериал и выделить ключевые стадии динамики водного потока.
|
||||||
|
\item Разработать алгоритмы и подходы для визуальной реконструкции процесса.
|
||||||
|
\item Реализовать визуализацию с применением графических библиотек, обеспечив:
|
||||||
|
\begin{enumerate}
|
||||||
|
\item естественное отображение колебаний и искажений водной струи;
|
||||||
|
\item достоверную анимацию падения отдельных капель с учётом их прозрачности и размеров;
|
||||||
|
\item возможность параметрической настройки характеристик струи (амплитуда колебаний, скорость появления капель).
|
||||||
|
\end{enumerate}
|
||||||
|
\end{enumerate}
|
||||||
|
|
||||||
|
\begin{figure}[h]
|
||||||
|
\centering{
|
||||||
|
\includegraphics[width=80mm]{pic1.png}
|
||||||
|
\caption{\centering{{Кадр видеофрагмента реального процесса}}}
|
||||||
|
}
|
||||||
|
\label{pic1}
|
||||||
|
\end{figure}\par
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\section{Описание физического процесса}
|
||||||
|
|
||||||
|
Видео иллюстрирует физический процесс: течение воды из крана, который нобходимо было воспроизвести средствами процедурной анимации. Видео имеет разрешение $1600 \times 913$ пикселей, этого достаточно, чтобы заметить даже небольшие детали процесса, которые необходимо учесть при визуализации:
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\item \textbf{Динамика струи воды:}
|
||||||
|
\begin{itemize}
|
||||||
|
\item В начальный момент из крана выходит непрерывный поток воды, формирующий вытянутую струю.
|
||||||
|
\item Струя не является идеально ровной --- под действием колебаний и турбулентности она искажается, создавая небольшую рябь на границах струи.
|
||||||
|
\item Наблюдается постепенное движение воды влево и вправо, всего наблюдается 4 наиболее заметных и несколько небольших колебаний.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\item \textbf{Образование капель:}
|
||||||
|
\begin{itemize}
|
||||||
|
\item На видео наблюдается процесс формирования и падения двух капель.
|
||||||
|
\item Капли имеют разный размер и форму, а также формируются в разных местах и с разной скоростью на головке крана.
|
||||||
|
\item После формирования капли очень быстро падают вниз. Падение капель видно лишь на нескольких кадрах из-за небольшой частоты кадров видеозаписи.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\item \textbf{Влияние фонового изображения:}
|
||||||
|
\begin{itemize}
|
||||||
|
\item Вода частично прозрачна, поэтому за струёй виден фон.
|
||||||
|
\item Фоновые объекты, видимые через струю, сильно искажаются из-за преломления света в воде.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\section{Общая структура визуализации}
|
||||||
|
|
||||||
|
\textit{Фон и окружение:} Для большего соответствия исходному видеоролику используется фоновое изображение, поверх которого накладывается визуализируемая струя воды. Это позволяет интегрировать анимацию в контекст сцены и повысить реализм изображения.
|
||||||
|
|
||||||
|
\textit{Струя воды:} Основной элемент визуализации --- поток воды из крана, основные положения которой формируются по контурам, заданным масками. Для каждого шага анимации контуры интерполируются, что обеспечивает плавное изменение формы струи во времени.
|
||||||
|
|
||||||
|
\textit{Дополнительные эффекты:} Во время течения воды, появляются капли, которые отрываются от верхней границы струи и движутся вниз, усиливая динамику сцены.
|
||||||
|
|
||||||
|
\subsection{Подход к формированию струи}
|
||||||
|
|
||||||
|
Визуализация строится на {геометрических контурах}, которые задаются масками. Каждая маска определяет форму и положения струи в пространстве в определенный момент времени. Плавность перехода между масками обеспечивается интерполяцией контуров.
|
||||||
|
|
||||||
|
На форму добавляются синусоидальные колебания и случайные шумовые сдвиги, что позволяет создать характерную «живую» динамику воды.
|
||||||
|
|
||||||
|
Для придания глубины и текстуры поток заливается цветным градиентом, плавно переходящим от более тёмных тонов к светлым.
|
||||||
|
|
||||||
|
\subsubsection{Маски формы струи}
|
||||||
|
Каждая маска задаёт положение струи на определённой фазе. Пример такой маски приведен на \hyperref[pic2]{рисунке 2} и \hyperref[pic3]{рисунке 3}. Последовательность масок определяет динамику движения струи влево и вправо.
|
||||||
|
|
||||||
|
\begin{figure}[h!]
|
||||||
|
\begin{multicols}{2}
|
||||||
|
\hfill
|
||||||
|
\includegraphics[width=100mm]{pic2.png}
|
||||||
|
\caption{\centering{Начальная маска положения струи}}
|
||||||
|
\label{pic2}
|
||||||
|
\hfill
|
||||||
|
\includegraphics[width=100mm]{pic3.png}
|
||||||
|
\caption{\centering{Маска сдвинутого положения струи}}
|
||||||
|
\label{pic3}
|
||||||
|
\end{multicols}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\subsubsection{Интерполяция между масками}
|
||||||
|
При переходе от одной маски к другой вычисляются промежуточные контуры, которые подвергаются волновым и шумовым искажениям. Это позволяет струе двигаться и «колыхаться», имитируя естественное течение воды.
|
||||||
|
|
||||||
|
\subsection{Динамические эффекты}
|
||||||
|
|
||||||
|
\textbf{Искажения:} Для отрисовки бликов и неоднородной структуры потока под саму струю была подложена картинка бликов, изображенная на \hyperref[pic4]{рисунке 4}. Для получения эффекта размытия на подложку струи накладывается горизонтальное синусоидальное смещение строк изображения, что создаёт эффект размытости и движения.
|
||||||
|
|
||||||
|
\textbf{Градиентная заливка:} Поток закрашивается набором полигонов с плавным изменением цвета и прозрачности, формируя характерное изменение цвета и прозрачности воды по времени ее стекания из крана.
|
||||||
|
|
||||||
|
\textbf{Формирование капель:} На верхнем крае струи периодически появляются капли. Капли визуализируются как вытянутые эллипсы с частичной прозрачностью.
|
||||||
|
|
||||||
|
\begin{figure}[h]
|
||||||
|
\centering{
|
||||||
|
\includegraphics[width=100mm]{pic4.png}
|
||||||
|
\caption{\centering{{Подложка под струю, имитирующая блики и отражения окружающей среды}}}
|
||||||
|
}
|
||||||
|
\label{pic4}
|
||||||
|
\end{figure}\par
|
||||||
|
|
||||||
|
% \begin{figure}[h]
|
||||||
|
% \centering{
|
||||||
|
% \includegraphics[width=80mm]{pic5.png}
|
||||||
|
% \caption{\centering{{Пример падения капли}}}
|
||||||
|
% }
|
||||||
|
% \label{pic5}
|
||||||
|
% \end{figure}\par
|
||||||
|
\subsection{Алгоритм анимации и физические эффекты}
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\item На каждом кадре выбираются начальный и конечный контур струи и вычисляется их интерполяция.
|
||||||
|
\item К координатам контуров добавляются синусоидальные колебания и случайный шум.
|
||||||
|
\item По получённым точкам строится маска, которая ограничивает область видимости подложки.
|
||||||
|
\item Внутренняя область струи окрашивается с использованием цветового градиента.
|
||||||
|
\item На верхнем крае струи (где она вытекает из под крана) периодически формируются капли. После форимрования капли падают вниз и скрываются за границей экрана.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
Таким образом, реализованная структура визуализации позволяет имитировать динамику потока воды из крана с учётом геометрических деформаций, колебаний и отрыва капель, что делает анимацию достаточно реалистичной.
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\section{Процесс визуализации}
|
||||||
|
|
||||||
|
Визуализация воспроизводит процесс течения воды из крана с последующим формированием струи и отдельных капель. Здесь применён геометрический подход: поток описывается через набор контуров, получаемых из масок и подвергаемых интерполяции и искажениям. Такая схема позволяет имитировать естественные колебания, разрывы и изменения формы водного потока.
|
||||||
|
|
||||||
|
\subsection{Последовательность процесса визуализации}
|
||||||
|
|
||||||
|
\subsubsection{Формирование и динамика струи}
|
||||||
|
\begin{itemize}
|
||||||
|
\item \textbf{Начальное появление:}
|
||||||
|
Струя формируется на основе исходной маски (\hyperref[pic2]{рисунок 2}), которая задаёт начальную геометрию потока.
|
||||||
|
\item \textbf{Изменение формы:}
|
||||||
|
При переходе между масками используется интерполяция контуров. Дополнительно применяются синусоидальные колебания и шумовые искажения, что создаёт эффект подвижной струи с характерными колебаниями и неровностями.
|
||||||
|
\item \textbf{Генерация капель:}
|
||||||
|
На верхнем крае потока периодически появляются отдельные капли, которые движутся вниз под действием гравитации. Их размер и прозрачность варьируются, что делает анимацию более естественной.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\subsubsection{Визуальные эффекты}
|
||||||
|
\begin{itemize}
|
||||||
|
\item \textbf{Прозрачность и цвет:}
|
||||||
|
Поток и капли визуализируются как полупрозрачные объекты с градиентной заливкой, что позволяет передать глубину, световые переходы и эффект преломления.
|
||||||
|
\item \textbf{Фоновое изображение:}
|
||||||
|
На задний план накладывается статическое фото, что усиливает реалистичность сцены и создаёт контекст окружающей среды.
|
||||||
|
\item \textbf{Искажения:}
|
||||||
|
Кадры подвергаются горизонтальному синусоидальному смещению, которое усиливает впечатление движения и дрожания струи.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\subsubsection{Завершение анимации}
|
||||||
|
\begin{itemize}
|
||||||
|
\item \textbf{Окончание симуляции:}
|
||||||
|
Визуализация продолжается до достижения последней маски либо заданного числа кадров. Каждый кадр сохраняется для последующего формирования видеоряда.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\subsection{Используемые технологии и библиотеки}
|
||||||
|
Python --- основной язык программирования.
|
||||||
|
|
||||||
|
\subsubsection{Используемые библиотеки}
|
||||||
|
\begin{itemize}
|
||||||
|
\item \textbf{Pygame} --- для создания окна, загрузки изображений, построения анимации и работы с масками.
|
||||||
|
\begin{itemize}
|
||||||
|
\item \texttt{pygame.init()} --- инициализирует все основные модули Pygame.
|
||||||
|
\item \texttt{pygame.display.set\_mode()} --- создаёт окно приложения и поверхность для отрисовки.
|
||||||
|
\item \texttt{pygame.image.load()} --- загружает изображения фона и масок.
|
||||||
|
\item \texttt{pygame.Surface()} --- создаёт поверхности с поддержкой прозрачности, используемые для наложения эффектов.
|
||||||
|
\item \texttt{pygame.draw.polygon()} и \texttt{pygame.draw.ellipse()} --- для отрисовки контура струи и капель воды.
|
||||||
|
\item \texttt{pygame.display.flip()} --- обновляет окно, показывая отрисованный кадр.
|
||||||
|
\item \texttt{pygame.time.Clock()} --- управляет частотой кадров анимации.
|
||||||
|
\item \texttt{pygame.event.get()} --- отслеживает события (например, закрытие окна).
|
||||||
|
\item \texttt{pygame.quit()} --- завершает работу программы и освобождает ресурсы.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\item \textbf{NumPy} --- для преобразования массива пикселей поверхности в формат, совместимый с записью видео.
|
||||||
|
|
||||||
|
\item \textbf{Imageio} --- для записи последовательности кадров анимации в файл \texttt{.mp4}.
|
||||||
|
|
||||||
|
\item \textbf{math} --- для вычисления синусоидальных искажений струи.
|
||||||
|
|
||||||
|
\item \textbf{random} --- для внесения случайных колебаний в контуры и траектории капель.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\subsection{Основные числовые параметры}
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\item \textit{Размер окна:} определяется автоматически по загруженному изображению фона (например, $400 \times 600$ пикселей).
|
||||||
|
|
||||||
|
\item \textit{Частота кадров:} \texttt{FPS = 60} --- обеспечивает плавность анимации.
|
||||||
|
|
||||||
|
\item \textit{Цвета струи:}
|
||||||
|
\begin{itemize}
|
||||||
|
\item Начальный цвет: \texttt{(55, 50, 45, 100)} --- тёмно-серый полупрозрачный.
|
||||||
|
\item Конечный цвет: \texttt{(180, 180, 180, 50)} --- светло-серый с большей прозрачностью.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\item \textit{Анимация:}
|
||||||
|
\begin{itemize}
|
||||||
|
\item \texttt{animation\_speed = 0.005} --- скорость перехода между фазами.
|
||||||
|
\item \texttt{amplitude = 2} --- амплитуда волнового искажения контура.
|
||||||
|
\item \texttt{frequency = 0.05} --- частота волны.
|
||||||
|
\item \texttt{t += 9} --- параметр времени, задающий движение искажения.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\item \textit{Капли воды:}
|
||||||
|
\begin{itemize}
|
||||||
|
\item Размер: \texttt{18} пикселей.
|
||||||
|
\item Скорость падения: \texttt{35} пикселей за кадр.
|
||||||
|
\item Цвет: полупрозрачный бело-голубой \texttt{(60, 55, 55, 30)}.
|
||||||
|
\item Частота появления: \texttt{drop\_frequency = 1}.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\item \textit{Фазы анимации:}
|
||||||
|
\begin{itemize}
|
||||||
|
\item Используются 4 маски (\texttt{mask\_1.png} ... \texttt{mask\_4.png}), определяющие форму струи на каждом этапе.
|
||||||
|
\item Интерполяция контуров плавно изменяет форму между фазами.
|
||||||
|
\item Переключение фаз происходит при завершении цикла интерполяции.
|
||||||
|
\end{itemize}
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\vspace{15pt}
|
||||||
|
Таким образом, разработанная программа выполняет визуализацию движения водной струи с использованием масок, градиентной заливки и реалистичных капель, обеспечивая плавную анимацию и экспорт результата в видеофайл.
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\section{Результаты работы}
|
||||||
|
Ниже на рисунках 5-10 приведено сравнение кадров из реального видеоролика
|
||||||
|
и собственной реализации, в различные моменты времени.
|
||||||
|
\begin{figure}[h!]
|
||||||
|
\begin{multicols}{2}
|
||||||
|
\hfill
|
||||||
|
\includegraphics[width=80mm]{pic6.png}
|
||||||
|
\caption{\centering{Начальный момент оригинального видео}}
|
||||||
|
\label{pic6}
|
||||||
|
\hfill
|
||||||
|
\includegraphics[width=80mm]{pic7.png}
|
||||||
|
\hfill
|
||||||
|
\caption{\centering{Начальный момент реализации}}
|
||||||
|
\label{pic7}
|
||||||
|
\end{multicols}
|
||||||
|
\end{figure}
|
||||||
|
\begin{figure}[h!]
|
||||||
|
\begin{multicols}{2}
|
||||||
|
\hfill
|
||||||
|
\includegraphics[width=80mm]{pic8.png}
|
||||||
|
\caption{\centering{Серединный момент оригинального видео}}
|
||||||
|
\label{pic8}
|
||||||
|
\hfill
|
||||||
|
\includegraphics[width=80mm]{pic9.png}
|
||||||
|
\hfill
|
||||||
|
\caption{\centering{Серединный момент реализации}}
|
||||||
|
\label{pic9}
|
||||||
|
\end{multicols}
|
||||||
|
\end{figure}
|
||||||
|
\begin{figure}[h!]
|
||||||
|
\begin{multicols}{2}
|
||||||
|
\hfill
|
||||||
|
\includegraphics[width=80mm]{pic10.png}
|
||||||
|
\caption{\centering{Конечный момент оригинального видео}}
|
||||||
|
\label{pic10}
|
||||||
|
\hfill
|
||||||
|
\includegraphics[width=80mm]{pic11.png}
|
||||||
|
\hfill
|
||||||
|
\caption{\centering{Конечный момент реализации}}
|
||||||
|
\label{pic11}
|
||||||
|
\end{multicols}
|
||||||
|
\end{figure}
|
||||||
|
\newpage
|
||||||
|
\hfill
|
||||||
|
\newpage
|
||||||
|
\addcontentsline{toc}{section}{Заключение}
|
||||||
|
\section*{Заключение}
|
||||||
|
В ходе выполнения лабораторной работы был проведён анализ видеозаписи, отображающей физический процесс вытекания воды из крана и её дальнейшего движения. На основе изучения динамики струи были определены ключевые особенности: постепенное изменение формы потока, появление колебаний, формирование капель и влияние случайных искажений на контур.
|
||||||
|
|
||||||
|
Для воспроизведения наблюдаемого эффекта была реализована программная визуализация на языке Python. Визуализация основана на интерполяции масок, описывающих форму струи на различных этапах, а также на применении дополнительных эффектов, таких как волновое искажение, градиентная заливка и генерация падающих капель.
|
||||||
|
|
||||||
|
В технической части использованы следующие приёмы:
|
||||||
|
\begin{enumerate}
|
||||||
|
\item Применение альфа-канала для создания эффекта прозрачности воды;
|
||||||
|
\item Использование масок для задания формы струи и плавной смены фаз;
|
||||||
|
\item Добавление случайных искажений, формирующих реалистичные колебания потока;
|
||||||
|
\item Генерация отдельных капель, усиливающих правдоподобность анимации;
|
||||||
|
\item Экспорт последовательности кадров в видеофайл с помощью библиотеки \texttt{imageio}.
|
||||||
|
\end{enumerate}
|
||||||
|
|
||||||
|
Разработанная визуализация позволяет достоверно отобразить процесс формирования и движения струи воды, включая её деформацию и каплеобразование. Программная реализация на базе библиотек \texttt{Pygame}, \texttt{NumPy}, \texttt{imageio}, \texttt{math} и \texttt{random} обеспечила гибкость настройки и высокую наглядность результата.
|
||||||
|
|
||||||
|
Созданная программа может быть использована как демонстрационный инструмент при изучении явлений гидродинамики, а также как основа для дальнейших экспериментов по визуализации жидкостей в компьютерной графике. Полный исходный код представлен в приложении~A.
|
||||||
|
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\newpage
|
||||||
|
\newpage
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
\lstset{
|
||||||
|
backgroundcolor=\color{white},
|
||||||
|
frame=single
|
||||||
|
}
|
||||||
|
\newpage
|
||||||
|
\addcontentsline{toc}{section}{Приложение А. Код реализации программы}
|
||||||
|
\addcontentsline{toc}{subsection}{А.1 waterflow\_visualization.py}
|
||||||
|
\section*{Приложение А. Код реализации программы}
|
||||||
|
\subsection*{А.1 waterflow\_visualization.py}
|
||||||
|
\begin{lstlisting}[language=Python, caption={Визуализация течения воды из крана}]
|
||||||
|
import pygame
|
||||||
|
import math
|
||||||
|
import random
|
||||||
|
import imageio
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
pygame.init()
|
||||||
|
|
||||||
|
# --- Класс для капель воды ---
|
||||||
|
class WaterDrop:
|
||||||
|
def __init__(self, x, y):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.size = 18
|
||||||
|
self.speed = 35
|
||||||
|
|
||||||
|
# Прозрачный бело-голубой цвет (альфа 120)
|
||||||
|
self.color = (60, 55, 55, 30)
|
||||||
|
|
||||||
|
self.ellipse_width = self.size
|
||||||
|
self.ellipse_height = self.size * 1.5
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.y += self.speed
|
||||||
|
|
||||||
|
def draw(self, screen):
|
||||||
|
drop_rect = pygame.Rect(
|
||||||
|
self.x - self.ellipse_width / 2,
|
||||||
|
self.y - self.ellipse_height / 2,
|
||||||
|
self.ellipse_width,
|
||||||
|
self.ellipse_height,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Временная поверхность с альфой
|
||||||
|
drop_surface = pygame.Surface(
|
||||||
|
(self.ellipse_width, self.ellipse_height), pygame.SRCALPHA
|
||||||
|
)
|
||||||
|
|
||||||
|
# Рисуем каплю на этой поверхности
|
||||||
|
pygame.draw.ellipse(drop_surface, self.color, (0, 0, self.ellipse_width, self.ellipse_height))
|
||||||
|
|
||||||
|
# Накладываем на экран
|
||||||
|
screen.blit(drop_surface, drop_rect.topleft)
|
||||||
|
|
||||||
|
def is_offscreen(self, screen_height):
|
||||||
|
return self.y > screen_height
|
||||||
|
|
||||||
|
|
||||||
|
# --- Настройка экрана и загрузка ресурсов ---
|
||||||
|
try:
|
||||||
|
temp_background_image = pygame.image.load("waterflow_background.png")
|
||||||
|
width, height = temp_background_image.get_size()
|
||||||
|
use_background = True
|
||||||
|
except pygame.error as e:
|
||||||
|
print(f"Error loading background image: {e}")
|
||||||
|
width, height = 400, 600
|
||||||
|
use_background = False
|
||||||
|
|
||||||
|
screen = pygame.display.set_mode((width, height))
|
||||||
|
|
||||||
|
if use_background:
|
||||||
|
background_image = temp_background_image.convert()
|
||||||
|
|
||||||
|
clock = pygame.time.Clock()
|
||||||
|
|
||||||
|
masks = []
|
||||||
|
try:
|
||||||
|
mask1_image = pygame.image.load("mask_1.png").convert_alpha()
|
||||||
|
mask2_image = pygame.image.load("mask_2.png").convert_alpha()
|
||||||
|
mask3_image = pygame.image.load("mask_3.png").convert_alpha()
|
||||||
|
mask4_image = pygame.image.load("mask_4.png").convert_alpha()
|
||||||
|
masks = [mask1_image, mask2_image, mask3_image, mask4_image]
|
||||||
|
except pygame.error as e:
|
||||||
|
print(f"Error loading mask images: {e}")
|
||||||
|
pygame.quit()
|
||||||
|
exit()
|
||||||
|
|
||||||
|
# --- Вырезаем базовую подложку из отдельной картинки ---
|
||||||
|
if use_background:
|
||||||
|
base_mask = pygame.image.load("moving_image_4.png").convert_alpha()
|
||||||
|
stream_base = pygame.Surface((width, height), pygame.SRCALPHA)
|
||||||
|
stream_base.blit(background_image, (0, 0))
|
||||||
|
stream_base.blit(base_mask, (0, 0), special_flags=pygame.BLEND_RGBA_MULT)
|
||||||
|
|
||||||
|
|
||||||
|
# --- ФУНКЦИИ ---
|
||||||
|
def distort_only_stream(base, mask, t):
|
||||||
|
"""Размывает и искажает подложку по маске"""
|
||||||
|
w, h = base.get_size()
|
||||||
|
distorted = pygame.Surface((w, h), pygame.SRCALPHA)
|
||||||
|
|
||||||
|
for y in range(0, h, 2):
|
||||||
|
offset = int(5 * math.sin(0.05 * y + t * 0.1)) # сдвиг строки
|
||||||
|
distorted.blit(base, (offset, y), (0, y, w, 2))
|
||||||
|
|
||||||
|
# применяем маску струи
|
||||||
|
distorted.blit(mask, (0, 0), special_flags=pygame.BLEND_RGBA_MULT)
|
||||||
|
return distorted
|
||||||
|
|
||||||
|
|
||||||
|
def get_stream_contours(mask_surface):
|
||||||
|
left_contour = []
|
||||||
|
right_contour = []
|
||||||
|
rows_data = {}
|
||||||
|
for y in range(mask_surface.get_height()):
|
||||||
|
left_x = -1
|
||||||
|
right_x = -1
|
||||||
|
for x in range(mask_surface.get_width()):
|
||||||
|
if mask_surface.get_at((x, y))[0] < 50:
|
||||||
|
if left_x == -1:
|
||||||
|
left_x = x
|
||||||
|
right_x = x
|
||||||
|
if left_x != -1:
|
||||||
|
rows_data[y] = (left_x, right_x)
|
||||||
|
for y in sorted(rows_data.keys()):
|
||||||
|
left_x, right_x = rows_data[y]
|
||||||
|
left_contour.append((left_x, y))
|
||||||
|
right_contour.append((right_x, y))
|
||||||
|
return left_contour, right_contour
|
||||||
|
|
||||||
|
|
||||||
|
def make_stream_mask(left, right, width, height):
|
||||||
|
mask_surface = pygame.Surface((width, height), pygame.SRCALPHA)
|
||||||
|
polygon_points = left + right[::-1]
|
||||||
|
pygame.draw.polygon(mask_surface, (255, 255, 255, 255), polygon_points)
|
||||||
|
return mask_surface
|
||||||
|
|
||||||
|
|
||||||
|
def interpolate_countours(
|
||||||
|
start_left_contour_, start_right_contour_, end_left_contour_, end_right_contour_
|
||||||
|
):
|
||||||
|
interpolated_left = []
|
||||||
|
interpolated_right = []
|
||||||
|
min_len_left = min(len(start_left_contour_), len(end_left_contour_))
|
||||||
|
min_len_right = min(len(start_right_contour_), len(end_right_contour_))
|
||||||
|
|
||||||
|
for i in range(min_len_left):
|
||||||
|
start_x, start_y = start_left_contour_[i]
|
||||||
|
end_x, end_y = end_left_contour_[i]
|
||||||
|
interp_x = start_x + (end_x - start_x) * animation_progress
|
||||||
|
wave1 = amplitude * math.sin(frequency * start_y + t)
|
||||||
|
wave2 = (amplitude / 2) * math.sin(2 * frequency * start_y + 1.5 * t)
|
||||||
|
noise = random.uniform(-0.5, 0.5)
|
||||||
|
x_offset = wave1 + wave2 + noise
|
||||||
|
interpolated_left.append((interp_x + x_offset, start_y))
|
||||||
|
|
||||||
|
for i in range(min_len_right):
|
||||||
|
start_x, start_y = start_right_contour_[i]
|
||||||
|
end_x, end_y = end_right_contour_[i]
|
||||||
|
interp_x = start_x + (end_x - start_x) * animation_progress
|
||||||
|
wave1 = amplitude * math.sin(frequency * start_y + t)
|
||||||
|
wave2 = (amplitude / 2) * math.sin(2 * frequency * start_y + 1.5 * t)
|
||||||
|
noise = random.uniform(-0.5, 0.5)
|
||||||
|
x_offset = wave1 + wave2 + noise
|
||||||
|
interpolated_right.append((interp_x + x_offset, start_y))
|
||||||
|
|
||||||
|
return interpolated_left, interpolated_right
|
||||||
|
|
||||||
|
|
||||||
|
# --- Контуры масок ---
|
||||||
|
contours = []
|
||||||
|
for mask in masks:
|
||||||
|
left, right = get_stream_contours(mask)
|
||||||
|
contours.append([left, right])
|
||||||
|
|
||||||
|
# --- Параметры ---
|
||||||
|
start_color = (55, 50, 45, 100)
|
||||||
|
end_color = (180, 180, 180, 50)
|
||||||
|
animation_progress = 0
|
||||||
|
animation_speed = 0.005
|
||||||
|
amplitude = 2
|
||||||
|
frequency = 0.05
|
||||||
|
t = 0
|
||||||
|
reverse = False
|
||||||
|
phase = 1
|
||||||
|
drops = []
|
||||||
|
FPS = 60
|
||||||
|
drop_frequency = 1
|
||||||
|
drop_counter = 0
|
||||||
|
|
||||||
|
writer = imageio.get_writer("animation_with_blur.mp4", fps=FPS)
|
||||||
|
|
||||||
|
# --- Главный цикл ---
|
||||||
|
running = True
|
||||||
|
while running:
|
||||||
|
for event in pygame.event.get():
|
||||||
|
if event.type == pygame.QUIT:
|
||||||
|
running = False
|
||||||
|
|
||||||
|
# --- Отрисовка фона ---
|
||||||
|
if use_background:
|
||||||
|
screen.blit(background_image, (0, 0))
|
||||||
|
else:
|
||||||
|
screen.fill((255, 255, 255))
|
||||||
|
|
||||||
|
# Интерполяция контура струи
|
||||||
|
interpolated_left, interpolated_right = interpolate_countours(
|
||||||
|
contours[0][1], contours[0][0], contours[phase][1], contours[phase][0]
|
||||||
|
)
|
||||||
|
stream_mask = make_stream_mask(interpolated_left, interpolated_right, width, height)
|
||||||
|
|
||||||
|
# --- Размытая и искажённая подложка ---
|
||||||
|
distorted_stream = distort_only_stream(stream_base, stream_mask, t)
|
||||||
|
screen.blit(distorted_stream, (0, 0))
|
||||||
|
|
||||||
|
# --- Цветная заливка для струи ---
|
||||||
|
polygon_points = interpolated_left + interpolated_right[::-1]
|
||||||
|
if interpolated_left and interpolated_right:
|
||||||
|
stream_surface = pygame.Surface((width, height), pygame.SRCALPHA)
|
||||||
|
num_points = len(interpolated_left)
|
||||||
|
for i in range(num_points - 1):
|
||||||
|
r = int(start_color[0] + (end_color[0] - start_color[0]) * (i / num_points) ** 0.6)
|
||||||
|
g = int(start_color[1] + (end_color[1] - start_color[1]) * (i / num_points) ** 0.6)
|
||||||
|
b = int(start_color[2] + (end_color[2] - start_color[2]) * (i / num_points) ** 0.6)
|
||||||
|
a = int(start_color[3] + (end_color[3] - start_color[3]) * (i / num_points) ** 0.2)
|
||||||
|
points = [
|
||||||
|
interpolated_left[i],
|
||||||
|
interpolated_left[i + 1],
|
||||||
|
interpolated_right[i + 1],
|
||||||
|
interpolated_right[i],
|
||||||
|
]
|
||||||
|
pygame.draw.polygon(stream_surface, (r, g, b, a), points)
|
||||||
|
screen.blit(stream_surface, (0, 0))
|
||||||
|
|
||||||
|
coef = 3 if phase == 0 or phase == 1 else 1.5
|
||||||
|
# --- Анимация прогресса ---
|
||||||
|
animation_progress += animation_speed if not reverse else animation_speed * coef
|
||||||
|
if animation_progress >= 1.0 or animation_progress <= 0.0:
|
||||||
|
animation_speed *= -1
|
||||||
|
if animation_progress <= 0.0:
|
||||||
|
phase = min(3, phase + 1)
|
||||||
|
drop_counter +=1
|
||||||
|
reverse = not reverse
|
||||||
|
animation_progress = max(0.0, min(1.0, animation_progress))
|
||||||
|
|
||||||
|
t += 9
|
||||||
|
if drop_counter >= drop_frequency:
|
||||||
|
last_point_y = min(p[1] for p in polygon_points) + 25
|
||||||
|
bottom_points_x = [p[0] for p in polygon_points if p[1] == last_point_y]
|
||||||
|
if bottom_points_x:
|
||||||
|
spawn_x = sum(bottom_points_x) / len(bottom_points_x)
|
||||||
|
drops.append(WaterDrop(spawn_x + random.randint(-5, 5), last_point_y))
|
||||||
|
drop_counter = 0
|
||||||
|
|
||||||
|
new_drops_list = []
|
||||||
|
for drop in drops:
|
||||||
|
drop.update()
|
||||||
|
drop.draw(screen)
|
||||||
|
if not drop.is_offscreen(height):
|
||||||
|
new_drops_list.append(drop)
|
||||||
|
drops = new_drops_list
|
||||||
|
pygame.display.flip()
|
||||||
|
|
||||||
|
# --- Запись кадра ---
|
||||||
|
frame = pygame.surfarray.array3d(screen)
|
||||||
|
frame = np.swapaxes(frame, 0, 1)
|
||||||
|
writer.append_data(frame)
|
||||||
|
|
||||||
|
clock.tick(FPS)
|
||||||
|
|
||||||
|
writer.close()
|
||||||
|
pygame.quit()
|
||||||
|
|
||||||
|
\end{lstlisting}
|
||||||
|
\end{document}
|
||||||