Создание динамической анимации сердца на языке C

CBeginner
Практиковаться сейчас

Введение

В этом проекте вы научитесь создавать завораживающую динамическую анимацию сердца с использованием языка программирования C. Проект использует X Window System для рендеринга анимированных визуалов. Следуя пошаговым инструкциям, вы настроите проект, сгенерируете данные и создадите увлекательную анимацию, которая оживит динамическое сердце на вашем экране.

👀 Предпросмотр

Динамическое сердце

🎯 Задачи

В этом проекте вы научитесь:

  • Настраивать проект на языке программирования C для создания динамической анимации сердца
  • Использовать библиотеки X Window System для создания и управления графическими окнами
  • Генерировать случайные точки и анимировать их для формирования фигуры сердца
  • Управлять анимацией, чтобы она расширялась и сжималась, создавая завораживающий визуальный эффект

🏆 Достижения

После завершения этого проекта вы сможете:

  • Использовать библиотеки X Window System для графического программирования на языке C
  • Генерировать и манипулировать случайными точками на языке C
  • Создавать динамическую анимацию, используя сочетание методов генерации данных и рендеринга

Создание файлов проекта

Убедитесь, что у вас установлены необходимые библиотеки. Вам понадобятся разработческие библиотеки X11. Вы можете установить их с помощью следующей команды:

sudo apt update
sudo apt-get install libx11-dev

Затем создайте новый файл с именем dynamic_heart.c и откройте его в вашем предпочитаемом текстовом редакторе кода.

cd ~/project
touch dynamic_heart.c
✨ Проверить решение и практиковаться

Определение необходимых переменных

Сначала нам нужно написать код на языке C. Первым шагом является включение заголовочных файлов:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include <stdbool.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <unistd.h>

Определите структуру для хранения информации о точке, включая ее координаты x, y и цвет. Используется для представления точек на графике.

struct Point {
    double x, y;
    unsigned long color;
};

Затем определите некоторые глобальные переменные:

unsigned long colors[7] = {0xff1f53, 0xfcdefa, 0xff0000, 0xff0000, 0xff0202, 0xff0008, 0xff0505};
const int xScreen = 1200;
const int yScreen = 800;
const double PI = 3.1426535159;
const double e = 2.71828;
const double average_distance = 0.162;
const int quantity = 506;
const int circles = 210;
const int frames = 20;
struct Point* origin_points;
struct Point* points;

Используйте переменные, связанные с X Window System, для создания и управления графическими окнами и графическими контекстами.

Display *display;
Window win;
GC gc;

display представляет соединение с X-сервером, win представляет окно, а gc представляет графический контекст, который используется для рисования графических элементов.

✨ Проверить решение и практиковаться

Реализация функций для экранных координат

double screen_x(double x) {
    x += xScreen / 2;
    return x;
}

double screen_y(double y) {
    y = -y + yScreen / 2;
    return y;
}

Цель функций screen_x и screen_y состоит в том, чтобы отобразить фактические координаты на координаты экрана, чтобы обеспечить, чтобы графические элементы в анимации находились в видимой области экрана и отображались в правильном положении. Конкретнее:

  1. screen_x:
  • Эта функция принимает фактическое значение координаты x в качестве аргумента и затем корректирует его, чтобы оно было правильно нарисовано на экране. Она прибавляет входное значение координаты x к половине xScreen, так что оно будет находиться в горизонтальном центре экрана, так как xScreen представляет ширину экрана, деленная на 2 для нахождения горизонтального центра экрана.
  • Функция возвращает скорректированное значение координаты x для рисования точек или фигур на экране.
  1. screen_y:
  • Эта функция аналогична screen_x, но обрабатывает координаты y. Она принимает фактическое значение координаты y в качестве параметра и преобразует его в систему координат экрана. Сначала она инвертирует координату y, чтобы начало координатной системы находилось в верхнем левом углу экрана, затем прибавляет ее к половине yScreen, чтобы координата находилась в вертикальном центре экрана.
  • Функция возвращает скорректированное значение координаты y, чтобы точка или фигура была правильно нарисована на экране.
✨ Проверить решение и практиковаться

Реализация функции генератора случайных чисел

int create_random(int x1, int x2) {
    if (x2 > x1) {
        return rand() % (x2 - x1 + 1) + x1;
    }
    return 0;
}

Это функция, используемая для генерации случайных целых чисел в заданном диапазоне. Она применяется для случайного определения цвета и положения сгенерированных точек, чтобы повысить визуальную разнообразие анимации.

Здесь x1 и x2 - это два целых числа, передаваемые в качестве аргументов. Функция возвращает случайное целое число между x1 и x2, включая x1 и x2. Если x2 больше x1, то с помощью функции rand() генерируется случайное целое число, которое с использованием операций по модулю ограничивается допустимым диапазоном. Функция rand() обычно возвращает случайное целое число между 0 и RAND_MAX (обычно 32767). Наконец, к результату операции по модулю прибавляется x1, чтобы гарантировать, что случайное целое число попадает в заданный диапазон.

✨ Проверить решение и практиковаться

Инициализация и генерация наборов точек в форме сердца

Функция create_data генерирует данные для анимации. Она вычисляет точки для формы сердца и анимирует их на основе определенного алгоритма.

void create_data() {
    int index = 0;
    double x1 = 0, y1 = 0, x2 = 0, y2 = 0;
    for (double radian = 0.1; radian <= 2 * PI; radian += 0.005) {
        // Calculate the x and y coordinates of the heart
        x2 = 16 * pow(sin(radian), 3);
        y2 = 13 * cos(radian) - 5 * cos(2 * radian) - 2 * cos(3 * radian) - cos(4 * radian);
        double distance = sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2));
        if (distance > average_distance) {
            // Qualifying heart points are stored
            x1 = x2;
            y1 = y2;
            origin_points[index].x = x2;
            origin_points[index++].y = y2;
        }
    }
  • Используется цикл для перебора серии значений радиан (от 0.1 до 2 * PI) и вычисления координат x и y каждой точки.

  • Вычисляется расстояние между двумя точками, и если расстояние больше average_distance, точка сохраняется в массиве origin_points.

✨ Проверить решение и практиковаться

Цветовое кодирование точек для создания анимации

// Inside the create_data() function
index = 0;
for (double size = 0.1, lightness = 1.5; size <= 20; size += 0.1) {
    double success_p = 1 / (1 + pow(e, 8 - size / 2));
    if (lightness > 1) {
        lightness -= 0.0025;
    }

    for (int i = 0; i < quantity; ++i) {
        if (success_p > (double)create_random(0, 100) / 100.0) {
            // Randomly generate the color of the points, coordinates, and store them in the Points array
            points[index].color = colors[create_random(0, 6)];
            points[index].x = size * origin_points[i].x + create_random(-4, 4);
            points[index++].y = size * origin_points[i].y + create_random(-4, 4);
        }
    }
}
  • Используются два вложенных цикла. Внешний цикл постепенно увеличивает значение size, а внутренний цикл обрабатывает каждую из quantity точек.

  • С вероятностью success_p решается, нужно ли сгенерировать точку и сохранить цвет и координаты точки в массиве points.

✨ Проверить решение и практиковаться

Генерация и обновление анимации

// Inside the create_data() function
int points_size = index;

for (int frame = 0; frame < frames; ++frame) {
    for (index = 0; index < points_size; ++index) {
        // The position of the calculated point is increased and the coordinates are updated
        double x = points[index].x, y = points[index].y;
        double distance = sqrt(pow(x, 2) + pow(y, 2));
        double distance_increase = -0.0009 * distance * distance + 0.35714 * distance + 5;
        double x_increase = distance_increase * x / distance / frames;
        double y_increase = distance_increase * y / distance / frames;
        points[index].x += x_increase;
        points[index].y += y_increase;
        // Draw points using XSetForeground and XFillArc
        XSetForeground(display, gc, points[index].color);
        XFillArc(display, win, gc, screen_x(points[index].x), screen_y(points[index].y), 2, 2, 0, 360 * 64);
    }

    for (double size = 17; size < 23; size += 0.3) {
        for (index = 0; index < quantity; ++index) {
            // Randomly generate the coordinates and color of the point according to the condition, and draw the point using XSetForeground and XFillArc
            if ((create_random(0, 100) / 100.0 > 0.6 && size >= 20) || (size < 20 && (double)create_random(0, 100) / 100.0 > 0.95)) {
                double x, y;
                if (size >= 20) {
                    x = origin_points[index].x * size + create_random(-frame * frame / 5 - 15, frame * frame / 5 + 15);
                    y = origin_points[index].y * size + create_random(-frame * frame / 5 - 15, frame * frame / 5 + 15);
                } else {
                    x = origin_points[index].x * size + create_random(-5, 5);
                    y = origin_points[index].y * size + create_random(-5, 5);
                }
                XSetForeground(display, gc, colors[create_random(0, 6)]);
                XFillArc(display, win, gc, screen_x(x), screen_y(y), 2, 2, 0, 360 * 64);
            }
        }
    }
}
  1. points_size используется для получения количества точек в текущем кадре анимации, которое вычисляется на основе предыдущего раздела кода. index - это количество ранее сгенерированных точек.

  2. Внешний цикл for (int frame = 0; frame < frames; ++frame) используется для перебора каждого кадра анимации, а frames определяет общее количество кадров.

  3. Внутренний цикл for (index = 0; index < points_size; ++index) используется для обработки каждой точки в текущем кадре. В каждом кадре выполняются следующие действия:

  • Сначала вычисляется новая позиция каждой точки. Это делается по следующей формуле:
double x = points[index].x, y = points[index].y;
double distance = sqrt(pow(x, 2) + pow(y, 2));
double distance_increase = -0.0009 * distance * distance + 0.35714 * distance + 5;
double x_increase = distance_increase * x / distance / frames;
double y_increase = distance_increase * y / distance / frames;
points[index].x += x_increase;
points[index].y += y_increase;

Эти вычисления используются для обновления координат x и y точки, чтобы достичь движения точки в анимации. distance_increase контролирует скорость движения точки, которая изменяется в зависимости от расстояния от исходной позиции точки.

  • Точки рисуются с использованием функций XSetForeground и XFillArc. Это рисует точку на экране, XSetForeground используется для установки цвета рисования, XFillArc - для рисования заполненной точки, а координаты центра круга преобразуются функциями screen_x и screen_y.
  1. Второй раздел внутреннего цикла for (double size = 17; size < 23; size += 0.3) используется для генерации дополнительных точек в текущем кадре. В этом цикле каждая точка генерируется, окрашивается и рисуется на экране.
  • Координаты и цвета новых точек генерируются случайным образом в соответствии с следующими условиями:

Если size >= 20 и случайное число больше 0.6, или size < 20 и случайное число больше 0.95, то генерируется новая точка.

  • Координаты x и y сгенерированных точек вычисляются на основе положения исходной точки и некоторых случайных смещений.
double x, y;
if (size >= 20) {
    x = origin_points[index].x * size + create_random(-frame * frame / 5 - 15, frame * frame / 5 + 15);
    y = origin_points[index].y * size + create_random(-frame * frame / 5 - 15, frame * frame / 5 + 15);
} else {
    x = origin_points[index].x * size + create_random(-5, 5);
    y = origin_points[index].y * size + create_random(-5, 5);
}
  • Наконец, используются функции XSetForeground и XFillArc для рисования сгенерированной точки на экране, как и в случае с предыдущей точкой.
✨ Проверить решение и практиковаться

Создание и инициализация X-окна

Основная цель функции main заключается в создании окна X, инициализации данных и последующей генерации и отрисовке каждого кадра анимации в форме сердца в бесконечном цикле.

int main() {
    display = XOpenDisplay(NULL);

    int blackColor = BlackPixel(display, DefaultScreen(display));
    win = XCreateSimpleWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, xScreen, yScreen, 0, blackColor, blackColor);
    XSelectInput(display, win, StructureNotifyMask);
    XMapWindow(display, win);
    gc = XCreateGC(display, win, 0, NULL);
}
  • display: Открывает соединение с сервером X.
  • blackColor: Получает значение черного пикселя для отображения.
  • win: Создает окно и задает его свойства, местоположение, размер и цвет границы.
  • XSelectInput: Задает маску входных событий для окна.
  • XMapWindow: Отображает окно на экране.
  • gc: Создает графический контекст (Graphics Context).
✨ Проверить решение и практиковаться

Инициализация данных и создание групп точек

// Inside the main() function
while (1) {
    XEvent e;
    XNextEvent(display, &e);
    if (e.type == MapNotify)
        break;
}

XFlush(display);
srand(time(NULL));
origin_points = (struct Point*)malloc(quantity * sizeof(struct Point));
points = (struct Point*)malloc(circles * quantity * sizeof(struct Point));

create_data();
  1. while (1) представляет собой бесконечный цикл, который ожидает завершения отображения (mapping) окна X. Это фаза инициализации перед отображением окна на экране.
  • Определяется структура XEvent для приема событий X.
  • XNextEvent: Ожидает и получает следующее событие X, а затем сохраняет его в переменной e.
  • Используется оператор if для проверки, является ли тип события MapNotify, что означает, что окно было успешно отображено на экране. Если отображение окна завершено (то есть тип события - MapNotify).
  1. XFlush: Очищает выходной буфер сервера X, чтобы гарантировать, что предыдущие команды рисования вступят в силу и не будут отложены до последующего рисования анимации.

  2. srand: Инициализирует генератор случайных чисел. Используется текущее время в качестве семени для генератора случайных чисел, чтобы создать случайные эффекты в анимации.

  3. origin_points: Выделяет память и создает массив из quantity структур Point, которые будут использоваться для хранения исходных координат точек.

  4. points: Опять выделяет память и создает более крупный массив для хранения точек в анимации. circles контролирует количество точек в анимации и является константным значением.

  5. Наконец, вызывается функция create_data() для инициализации данных, генерации и установки координат исходных точек, а также инициализации цвета и начальных координат анимированных точек.

✨ Проверить решение и практиковаться

Основной цикл анимации

Цель этого главного цикла - контролировать состояние сердцеобразного паттерна путем изменения значения frame, чтобы обеспечить эффект расширения и сжатия анимации. Функция usleep используется для контроля частоты кадров, чтобы анимация воспроизводилась с определенной скоростью.

// Inside the main() function
bool extend = true, shrink = false;
for (int frame = 0;;) {
    usleep(20000);
    XClearWindow(display, win);
    if (extend)
        frame == 19? (shrink = true, extend = false) : ++frame;
    else
        frame == 0? (shrink = false, extend = true) : --frame;
}

Сначала определяются два булевых переменных, extend и shrink, и инициализируются значениями true и false соответственно, которые представляют состояния расширения и сжатия сердцеобразного паттерна.

Затем запускается бесконечный цикл, и переменная frame в цикле используется для отслеживания номера текущего кадра анимации.

  • usleep используется для контроля скорости анимации.

  • Весь контент окна очищается с помощью вызова функции X11 XClearWindow, чтобы можно было перерисовать его на следующем кадре.

  • Значение frame увеличивается или уменьшается в зависимости от значений extend и shrink, чтобы обеспечить эффект расширения и сжатия анимации. Для установки булевых значений используется условный оператор ? :.

Если extend равно true, проверяется, равно ли frame значению 19. Если это так, анимация должна перейти из состояния расширения в состояние сжатия, поэтому shrink устанавливается в true, а extend - в false. В противном случае frame увеличивается.

Если extend равно false, проверяется, равно ли frame значению 0. Если это так, анимация должна перейти из состояния сжатия в состояние расширения, поэтому shrink устанавливается в false, а extend - в true. В противном случае frame уменьшается.

В конце освобождаются ресурсы и программа завершается.

// Inside the main() function
XCloseDisplay(display);
free(origin_points);
free(points);
return 0;
✨ Проверить решение и практиковаться

Компиляция и запуск проекта

  1. Откройте терминал и перейдите в директорию проекта.
cd ~/project
  1. Скомпилируйте код с помощью следующей команды:
gcc -o dynamic_heart dynamic_heart.c -lX11 -lm
  1. Запустите приложение:
./dynamic_heart
Динамическое сердце
✨ Проверить решение и практиковаться

Резюме

В этом проекте вы научились создавать увлекательную динамическую анимацию сердца с использованием языка программирования C. Вы настроили проект, определили переменные, реализовали функции для работы с экранными координатами, функцию генератора случайных чисел и функции генерации данных. Программа использует систему окон X (X Window System) для рендеринга анимированного сердца. Наконец, вы создали цикл анимации, закрыли дисплей и освободили выделенную память. Теперь вы можете наслаждаться своей собственной динамической анимацией сердца, созданной на языке C.