傅里叶圆圈画图

令 k = ( -N , ... , -2, -1, 0, 1, 2, ... N ) ,我们设置 2N + 1 个向量,每个向量旋转速度是 k 圈每秒,我们只需要设置这些向量的模长和初始位置,理论上就能画出任意图形。

具体请观看 3Blue1Brown 视频

运行截图

/*
*  作者:孙小鱼
*  QQ:1226598193
*实现原理:https://www.bilibili.com/video/BV1vt411N7Ti
*/
#include <graphics.h>
#include <conio.h>
#include <vector>
#include <cmath>
#include <algorithm>


#define X(x) ( (x) + (xScreen * 0.5f))
#define Y(y) (-(y) + (yScreen * 0.5f))		// 绘图坐标转换

using namespace std;

const int xScreen = 800;					// 屏幕宽度
const int yScreen = 800;					// 屏幕高度
const float PI = 3.1415927f;			

#pragma region 图形绘制
struct point
{
	float x, y;
};

void syLine(const point& start, const point& end, COLORREF color = WHITE)
{
	setlinecolor(color);
	line(X((int)(start.x + 0.5f)), Y((int)(start.y + 0.5f)), X((int)(end.x + 0.5f)), Y((int)(end.y + 0.5f)));
}

void syCircle(const point& center, float radius, COLORREF color = WHITE)
{
	setlinecolor(color);
	circle(X((int)(center.x + 0.5f)), Y((int)(center.y + 0.5f)), (int)(radius + 0.5f));
}

#pragma endregion

#pragma region 数学

/* 复数结构 */
struct complex
{
	union
	{
		struct { float r, i; };
		struct { float really, imaginary; };	// 实部、虚部
	};

	complex(float _really = 0.0f, float _imaginary = 0.0f) 
		: r(_really), i(_imaginary)
	{

	}
	complex(const complex& cmp)
	{ 
		r = cmp.r; 
		i = cmp.i; 
	}
	complex& operator=(const complex& cmp) 
	{ 
		r = cmp.r; 
		i = cmp.i; 
		return *this; 
	}
	

	point ToPoint() 
	{ 
		return { r, i }; 
	}

	float abs() 
	{
		return sqrtf(r * r + i * i); 
	}
};

inline complex operator*(const complex& a, const complex& b)
{
	return { a.r * b.r - a.i * b.i, a.r * b.i + a.i * b.r };
}

inline complex operator+(const complex& a, const complex& b)
{
	return { a.r + b.r, a.i + b.i };
}

inline complex operator/(const complex& a, float b)
{
	if (b != 0.0f)
	{
		return { a.r / b, a.i / b };
	}
	return a;
}

// 欧拉公式
complex ExpComplex(float x)
{
	return { cosf(x), sinf(x) };
}

// 求 [0,1)区间上的积分, k E [-N, N],(N = ft.size())
complex Integrate(vector<complex> ft, int k)
{
	complex ret = { 0.0f, 0.0f };
	float N = (float)ft.size();
	float dt = 1.0f / N;
	for (int i = 0; i < ft.size(); ++i)
	{
		ret = ret + ft[i] * ExpComplex(-2.0f * PI * k * ((i+1) / N)) * dt;
	}
	return ret;
}

// 对 in 做离散傅里叶变换,out 为变换的结果,返回变换是否成功
bool DFT(vector<complex>& in, vector<complex>& out)
{
	int N = in.size();
	int M = out.size() / 2;
	for (int k = -M; k <= M; ++k)
	{
		int index = k + M;
		out[index] = Integrate(in, k);
	}
	return true;
}
#pragma endregion

// 自定义需要绘画图形的外轮廓点(坐标取复平面坐标),这里我们以 4 角星的轮廓做例子,当然你可以修改为任意其它坐标点,坐标单位(像素点),画图坐标已经做了笛卡尔坐标系的转换
vector<complex> origin = 
{
	{  80,  160}, {  60,  220}, {  40,  280}, {  20,  340}, {   0,  400}, { -20,  340}, { -40,  280}, { -60,  220}, { -80,  160}, {-100,  100},
	{-160,   80}, {-220,   60}, {-280,   40}, {-340,   20}, {-400,    0}, {-340,  -20}, {-280,  -40}, {-220,  -60}, {-160,  -80}, {-100, -100},
	{ -80, -160}, { -60, -220}, { -40, -280}, { -20, -340}, {   0, -400}, {  20, -340}, {  40, -280}, {  60, -220}, {  80, -160}, { 100, -100},
	{ 160,  -80}, { 220,  -60}, { 280,  -40}, { 340,  -20}, { 400,    0}, { 340,   20}, { 280,   40}, { 220,   60}, { 160,   80}, { 100,  100}
};


int main()
{
	initgraph(xScreen, yScreen);
	BeginBatchDraw();

	vector<complex> initParam((origin.size() / 2) * 2 + 1);
	vector<complex> allCircle(initParam.size());
	// 傅里叶变换,得到圆圈的相位、幅值信息
	DFT(origin, initParam);			

	IMAGE buffer(xScreen, yScreen);
	DWORD* pBuffer = GetImageBuffer(&buffer);
	complex start, end, center;
	complex f;
	bool first = true;
	float t = 0.0f;
	while (!_kbhit())
	{
		// 清屏、初始化中间变量
		cleardevice();
		center = { 0.0f, 0.0f };
		f = 0.0f;

		// 获取线条
		setlinecolor(WHITE);
		putimage(0, 0, &buffer);
		int M = initParam.size() / 2;
		for (int k = -M; k <= M; ++k)
		{
			int index = k + M;
			allCircle[index] = initParam[index] * ExpComplex(2 * PI * k * t);
			f = f + allCircle[index];
		}
		if (first)
		{
			start = end = f;
			first = false;
		}
		else
		{
			end = f;
			syLine(start.ToPoint(), end.ToPoint(), YELLOW);
			start = f;
		}
		getimage(&buffer, 0, 0, xScreen, yScreen);
		cleardevice();

		// 绘制圆圈
		putimage(0, 0, &buffer);
		std::sort(allCircle.begin(), allCircle.end(), [](complex a, complex b)->bool {return a.abs() > b.abs(); });
		for (int i = 0; i < allCircle.size(); ++i)
		{
			syLine(center.ToPoint(), (center + allCircle[i]).ToPoint());
			syCircle(center.ToPoint(), allCircle[i].abs(), BLUE);
			center = center + allCircle[i];
		}

		// 每次绘制的 dt, 可以控制绘制的快慢
		t += 0.0001f;

		// 显示
		FlushBatchDraw();
	}
	EndBatchDraw();
	closegraph();
	return 0;
}
分享到

添加评论