#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <SDL2/SDL.h>
#define HEART_POINTS_NUM 2000
#define EDGE_POINTS_NUM 6000
#define CENTER_POINTS_NUM 8000
#define FRAMES_NUM 20
#define FRAMES_SIZE 20466 // HEART_POINTS_NUM + EDGE_POINTS_NUM + CENTER_POINTS_NUM + halo_num 的最大可能值(见 create_heart 函数)
SDL_Point frames[FRAMES_NUM][FRAMES_SIZE];
const int speed[4] = { 8, 16, 32, 64 };
int current_speed_index = 0; // [0, speed array's size]
int current_render_frame = 0; // [0, FRAMES_NUM]
int pause = 0; // 是否暂停
//屏幕宽度
const int WINDOW_WIDTH = 1080;
//屏幕高度
const int WINDOW_HEIGHT = 1920;
const int ORIGIN_X = WINDOW_WIDTH / 2;
const int ORIGIN_Y = WINDOW_HEIGHT / 2;
const int IMAGE_ENLARGE = 20;
// Utils
double random_double(double min, double max) {
double range = (max - min);
double div = RAND_MAX / range;
return min + (rand() / div);
}
int random_int(int min, int max) {
return min + rand() / (RAND_MAX / (max - min + 1) + 1);
}
// x = 16sin^3(t)
// y = 13cos(t) - 5cos(2t) - 2cos(3t) - cos(4t)
// t: [0, 2pi]
SDL_Point heart_point(double t, double shrink_ratio) {
double x = 16 * pow(sin(t), 3);
double y = -(13 * cos(t) - 5 * cos(2 * t) - 2 * cos(3 * t) - cos(4 * t));
// enlarge
x *= shrink_ratio;
y *= shrink_ratio;
// shift
x += ORIGIN_X;
y += ORIGIN_Y;
SDL_Point p = { x, y };
return p;
}
SDL_Point scatter_inside(int x, int y, double beta) {
double ratio_x = -beta * log(random_double(0.0, 1.0));
double ratio_y = -beta * log(random_double(0.0, 1.0));
double dx = ratio_x * (x - ORIGIN_X);
double dy = ratio_y * (y - ORIGIN_Y);
SDL_Point p = { x - dx, y - dy };
return p;
}
SDL_Point shrink(int x, int y, double ratio) {
double force = -1.0 / (pow(pow(x - ORIGIN_X, 2) + pow(y - ORIGIN_Y, 2), 0.6));
int dx = ratio * force * (x - ORIGIN_X);
int dy = ratio * force * (y - ORIGIN_Y);
SDL_Point p = { x - dx, y - dy };
return p;
}
double curve(double p) {
return 2.0 * (2.0 * sin(4.0 * p)) / (2.0 * M_PI);
}
SDL_Point calc_position(int x, int y, double ratio) {
double force = 1.0 / (pow(pow(x - ORIGIN_X, 2) + pow(y - ORIGIN_Y, 2), 0.520));
double dx = ratio * force * (x - ORIGIN_X) + random_int(-1, 1);
double dy = ratio * force * (y - ORIGIN_Y) + random_int(-1, 1);
SDL_Point p = { x - dx, y - dy };
return p;
}
void create_heart() {
// 基础爱心
SDL_Point heart_points[HEART_POINTS_NUM];
for (int i = 0; i < HEART_POINTS_NUM; ++i) {
double t = random_double(0.0, 2 * M_PI);
heart_points[i] = heart_point(t, IMAGE_ENLARGE);
}
// 基础爱心上每个点向内扩散3个点
SDL_Point edge_points[EDGE_POINTS_NUM];
int index = 0;
for (int i = 0; i < HEART_POINTS_NUM; ++i) {
for (int j = 0; j < 3; ++j) {
edge_points[index] = scatter_inside(heart_points[i].x, heart_points[i].y, 0.05);
index++;
}
}
// 向更中心位置添加4000个点,随机选取基础爱心上的点进行扩散
SDL_Point center_points[CENTER_POINTS_NUM];
for (int j = 0; j < CENTER_POINTS_NUM; ++j) {
SDL_Point random_heart_point = heart_points[random_int(0, HEART_POINTS_NUM)];
center_points[j] = scatter_inside(random_heart_point.x, random_heart_point.y, 0.17);
}
// 生成所有帧
for (int i = 0; i < FRAMES_NUM; ++i) {
int index2 = 0;
// 外侧光晕
int halo_num = 3000 + (int)(4000 * pow(curve(i * 1.0 / 10.0 * M_PI), 2)); // 3000-4466
int halo_radius = 4 + 6 * (1 + curve(i / 10 * M_PI));
for (int j = 0; j < halo_num; ++j) {
double t = random_double(0.0, 2 * M_PI);
SDL_Point tmp_p = heart_point(t, IMAGE_ENLARGE-2);
tmp_p = shrink(tmp_p.x, tmp_p.y, halo_radius);
SDL_Point p = {
tmp_p.x + random_int(-14, 14),
tmp_p.y + random_int(-14, 14)
};
frames[i][index2] = p;
index2++;
}
double ratio = 10.0 * curve(i / 10.0 * M_PI); // 圆滑的周期的缩放比例
// 根据缩放比例映射基础爱心、内沿和中心所有的点
for (int j = 0; j < HEART_POINTS_NUM; ++j) {
frames[i][index2] = calc_position(heart_points[j].x, heart_points[j].y, ratio);
index2++;
}
for (int j = 0; j < EDGE_POINTS_NUM; ++j) {
frames[i][index2] = calc_position(edge_points[j].x, edge_points[j].y, ratio);
index2++;
}
for (int j = 0; j < CENTER_POINTS_NUM; ++j) {
frames[i][index2] = calc_position(center_points[j].x, center_points[j].y, ratio);
index2++;
}
}
}
Uint32 lastupdate = 0;
void update() {
Uint32 current = SDL_GetTicks();
const Uint32 per_frame_in_ms = 1000 / speed[current_speed_index];
if (current - lastupdate >= per_frame_in_ms) {
lastupdate = current;
// Core update logic
current_render_frame++;
if (current_render_frame >= FRAMES_NUM) {
current_render_frame = 0;
}
}
}
void draw(SDL_Renderer* renderer) {
SDL_SetRenderDrawColor(renderer, 252, 139, 171, 255);
SDL_RenderDrawPoints(renderer, frames[current_render_frame], FRAMES_SIZE);
}
int main(int argv, char** args) {
srand(time(NULL));
// Initial SDL
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
printf("SDL_Init HAS FAILED. ERROR: %s\n", SDL_GetError());
return 1;
}
// Initial window
SDL_Window* window = SDL_CreateWindow(
"DRAW HEART",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
WINDOW_WIDTH,
WINDOW_HEIGHT,
0
);
if (!window) {
printf("SDL_CreateWindow HAS FAILED. ERROR: %s\n", SDL_GetError());
return 1;
}
// Initial renderer
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (!renderer) {
printf("SDL_CreateRenderer HAS FAILED. ERROR: %s\n", SDL_GetError());
return 1;
}
// Initial
create_heart();
// Main loop
int running = 1;
while (running) {
// Handle events
SDL_Event e;
while (SDL_PollEvent(&e)) {
if (e.type == SDL_QUIT) {
running = 0;
} else if (e.type == SDL_KEYUP) {
switch (e.key.keysym.sym) {
case SDLK_TAB:
current_speed_index++;
if (current_speed_index >= 4) {
current_speed_index = 0;
}
break;
default:
break;
}
} else if (e.type == SDL_KEYDOWN) {
switch (e.key.keysym.sym) {
case SDLK_SPACE:
pause = !pause;
break;
default:
break;
}
}
}
// Update
if (!pause) {
update();
}
// Start draw
// Erase the last frame in dark
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
// Draw stuff
draw(renderer);
// Draw current frame to screen
SDL_RenderPresent(renderer);
}
// Clean up
SDL_DestroyWindow(window);
SDL_DestroyRenderer(renderer);
SDL_Quit();
return 0;
}