这篇文章主要讲解了“C语言如何实现绘制LoveBeat爱心曲线”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C语言如何实现绘制LoveBeat爱心曲线”吧!
心形曲线
给出心形曲线参数方程如下:
x = sin^3(θ)
y = (13 * cos(θ) - 5 * cos(2θ) - 3 * cos(3θ) - cos(4θ)) / 16
对 x, y 同时乘以半径 R,即可对其放大
通过上述方程可得到若干在曲线上的点,记为集合 S
曲线内点的生成
对于内部的点,我们则将S向内扩散,使其符合指数分布,得到S'
令 e ∈ [m, n] 且 e ~ E(λ)
对 P (x, y) ∈ S 作向内扩散得到点 P' ∈ S':
P' = (x, y) * e
扩散程度取决于参数 m, n, λ
曲线外点的生成
对于外部的点,我们则将S向外扩散,使其符合均匀分布,得到S''
令 u ~ U [1, 1 + b]
对 P (x, y) ∈ S 作向外扩散得到点 P'' ∈ S'':
P'' = (x, y) * u
扩散程度取决于参数 b
制作动画
有了上述三个点集合后,就可以描述一个静态的心形图案
现在,我们为其添加周期动画效果
首先引入时间参数 t 和周期函数 T(t) = sin^2(t) (可以是其他周期函数,视效果而定)
对于 P ∈ S U S'
其周期缩放程度与其到原点的距离 d 成反比,可用 R/d 来衡量 (R为心形曲线的半径)
为其添加阶数 i 来扩大距离对缩放程度的影响 (R/d)^i
我们可以得到如下函数
d' = d * (1 + a * T(t) * (R/d)^i)
外部的点在内部点收缩到最小值时,到达最大值,所以和上式相差一个相位
我们可以还做一些额外的处理:
对内部点和外部点添加随机扰动
绘制时,随机点的大小
绘制时,随机点的颜色、亮度
示例代码
// 环境:Visual Studio 2022 C++ 20
// EasyX版本:EasyX 2022-9-1
#include <random>
#include <unordered_set>
#include <graphics.h>
#include <cmath>
#define PINK LIGHTRED | 0x6055ff
#define LIGHTPINK LIGHTRED | 0x6655ff
#define DRAW(vecs, color) for(auto& vec : vecs)Draw(vec, color, distribution(engine))
using namespace std;
constexpr float Pi = 3.1416f;
constexpr float Rad = Pi / 180;
constexpr int ScreenWidth = 800;
constexpr int ScreenHeight = 600;
constexpr int OX = ScreenWidth / 2;
constexpr int OY = ScreenHeight / 2;
static default_random_engine engine;
struct Vec2
{
float X = .0f;
float Y = .0f;
Vec2() {}
Vec2(float x, float y) { X = x; Y = y; }
int GetX() const { return static_cast<int>(X); }
int GetY() const { return static_cast<int>(Y); }
float Magnitude() const { return sqrt(X * X + Y * Y); }
Vec2 operator*(float num) const{ return Vec2(X * num, Y * num); }
struct VecHash
{
size_t operator()(const Vec2& vec) const noexcept
{
return std::hash<float>()(vec.X) ^ std::hash<float>()(vec.Y);
}
};
struct VecCompare
{
bool operator()(const Vec2& vec1, const Vec2& vec2) const noexcept
{
return fabsf(vec1.X - vec2.X) < 1e-2f && fabsf(vec1.Y - vec2.Y) < 1e-2f;
}
};
};
using VecSet = unordered_set<Vec2, Vec2::VecHash, Vec2::VecCompare>;
float CalculateX(float t){ return powf(sin(t), 3.0f); }
float CalculateY(float t){ return -(13 * cosf(t) - 5 * cosf(2 * t) - 2 * cosf(3 * t) - cosf(4 * t)) / 16.0f; }
VecSet InitHeart(float startAngle, float endAngle, float radius, size_t count)
{
VecSet set;
float rad = startAngle * Rad;
float step = (endAngle - startAngle) * Rad / count;
float endRad = endAngle * Rad;
for (; rad < endRad; rad += step)
set.insert(Vec2(CalculateX(rad) * radius, CalculateY(rad) * radius));
return set;
}
VecSet BuildInside(const VecSet& set, size_t frequency, float lambda, float range, float min)
{
VecSet retSet;
exponential_distribution<float> distribution(lambda);
for (size_t i = 0; i < frequency; i++)
{
for (auto& vec : set)
{
float pX = distribution(engine);
float scalarX = (pX < 1.0 ? 1.0f - pX : 1.0f) * range + min;
float pY = distribution(engine);
float scalarY = (pY < 1.0 ? 1.0f - pY : 1.0f) * range + min;
retSet.insert(Vec2(vec.X * scalarX, vec.Y * scalarY));
}
}
return retSet;
}
VecSet BuildOutside(const VecSet& set, size_t frequency, float max)
{
VecSet retSet;
uniform_real_distribution<float> distribution(1.0f, max);
for (size_t i = 0; i < frequency; i++)
{
for (auto& vec : set)
retSet.insert(Vec2(vec.X * distribution(engine), vec.Y * distribution(engine)));
}
return retSet;
}
VecSet Undulate(const VecSet& set, float radius)
{
VecSet retSet;
uniform_real_distribution<float> distribution(-radius, radius);
for (auto& vec : set)
retSet.insert(Vec2(vec.X + distribution(engine), vec.Y + distribution(engine)));
return retSet;
}
VecSet Zoom(const VecSet& set, float factor, float radius, float t, float idx)
{
VecSet retSet;
for (auto& vec : set)
retSet.insert(vec * (1.0f + factor * sin(t * Pi) * powf((radius / vec.Magnitude()), idx)));
return retSet;
}
void Draw(const Vec2& vec, COLORREF color, int radius)
{
putpixel(vec.GetX() + OX, vec.GetY() + OY, color);
if(radius >= 2)
putpixel(vec.GetX() + OX + 1, vec.GetY() + OY, color);
if(radius >= 3)
putpixel(vec.GetX() + OX, vec.GetY() + OY + 1, color);
}
int main()
{
float radius = 160.0f;
auto border = InitHeart(0, 360, radius, 480);
auto inside = BuildInside(border, 30, 5.0f, 0.85f, 0.15f);
initgraph(ScreenWidth, ScreenHeight);
BeginBatchDraw();
float t = .0f;
float tStep = 0.05f;
uniform_int_distribution<int> distribution(1, 3);
ExMessage msg{};
while(!peekmessage(&msg, EX_KEY))
{
auto ps1 = Zoom(border, 0.1f, radius, t, 1.3f);
auto ps2 = Undulate(Zoom(inside, 0.1f, radius, t, 1.3f), 3.0f);
auto ps3 = Undulate(BuildOutside(border, 10 - static_cast<size_t>(sin(t) * 5), 1.35f - sin(t) * 0.15f), 3.0f);
DRAW(ps1, LIGHTPINK);
DRAW(ps2, LIGHTPINK);
DRAW(ps3, PINK);
FlushBatchDraw();
Sleep(40);
t += tStep;
if (t > 1.0f)
t = .0f;
cleardevice();
}
EndBatchDraw();
closegraph();
return 0;
}