当前位置: 首页 > article >正文

趣味编程:心形曲线

目录

 1.序言

2.代码展示

3.代码详解

3.1 头文件包含

3.2 绘制坐标轴函数 

3.3 main 函数主体部分

4. 小结


 1.序言

2025年的第一篇博客就用这个笛卡尔心形图开篇吧,寓意着新年大家能够有心有所属,祝诸位程序猿 / 程序媛 能够早点遇到自己的另一半。 

2.代码展示

#define _CRT_SECURE_NO_WARNINGS

#include <graphics.h>
#include <conio.h>
#include <math.h>

#define WIDTH	640					// 窗口宽度
#define HEIGHT	480					// 窗口高度
#define PI		3.14159265			// π
#define DISPLAY 3					// 展示出来动圆与定圆的交点及心脏线当前所在点的尺寸
#define ARROW	5					// 箭头的尺寸
#define COPIES	600					// 份数,看要获得心形线上的多少个点
#define SECONDS 5					// 跑完一圈的秒数
using namespace std;

// 画坐标抽
void drawCoordinateAxis()
{
	setlinecolor(DARKGRAY);
	line(WIDTH / 2, HEIGHT / 10, WIDTH / 2, HEIGHT / 10 * 9);
	line(WIDTH / 2, HEIGHT / 10, WIDTH / 2 + ARROW, HEIGHT / 10 + ARROW);
	line(WIDTH / 2, HEIGHT / 10, WIDTH / 2 - ARROW, HEIGHT / 10 + ARROW);

	line(WIDTH / 5, HEIGHT / 2, WIDTH / 5 * 4, HEIGHT / 2);
	line(WIDTH / 5 * 4, HEIGHT / 2, WIDTH / 5 * 4 - ARROW, HEIGHT / 2 - ARROW);
	line(WIDTH / 5 * 4, HEIGHT / 2, WIDTH / 5 * 4 - ARROW, HEIGHT / 2 + ARROW);
}

int main()
{
	initgraph(WIDTH, HEIGHT);
	BeginBatchDraw();
	setlinecolor(BLUE);

	// 画坐标轴,定圆
	double r = min(WIDTH, HEIGHT) / 9;
	circle(WIDTH / 2, HEIGHT / 2, r);
	drawCoordinateAxis();

	setrop2(R2_XORPEN);

	double lastX = WIDTH / 2.0, lastY = HEIGHT / 2 - r;					// 上一个心形线的点的 x,y 值,初始值为 y 轴正方向上距原点 a 个单位长度的点
	for (double a = 0; !_kbhit(); a += PI / COPIES * 2)					// a 为当前弧度
	{
		double x = cos(3.0 / 2.0 * PI + a) * 2 * r + WIDTH / 2;			// 动圆这一个循环的圆心的 x 值
		double y = sin(3.0 / 2.0 * PI + a) * 2 * r + HEIGHT / 2;		// 动圆这一个循环的圆心的 y 值
		double FixedPoint_X = cos(PI / 2.0 + a * 2) * r + x;			// 当前循环动圆的定点对应的 x 值
		double FixedPoint_Y = sin(PI / 2.0 + a * 2) * r + y;			// 当前循环动圆的定点对应的 y 值
		double Contact_X = cos(PI / 2.0 + a) * r + x;					// 当前循环两圆切点在动圆上对应的 x 值
		double Contact_Y = sin(PI / 2.0 + a) * r + y;					// 当前循环两圆切点在动圆上对应的 y 值

		// 画出心形线,只用画这一个循环的点和上一个循环的点的线就行
		setrop2(R2_COPYPEN);
		setlinecolor(YELLOW);
		line(lastX, lastY, FixedPoint_X, FixedPoint_Y);
		setrop2(R2_XORPEN);
		lastX = FixedPoint_X;
		lastY = FixedPoint_Y;

		// 动圆与定圆的切点
		setfillcolor(GREEN);
		solidcircle(Contact_X, Contact_Y, DISPLAY);

		// 心形线当前点
		setfillcolor(LIGHTRED);
		solidcircle(lastX, lastY, DISPLAY);

		// 动圆
		setlinecolor(BLUE);
		circle(x, y, r);

		FlushBatchDraw();
		Sleep((double)(1000 * SECONDS) / (double)COPIES + 0.5);

		// 消除动圆
		setlinecolor(BLUE);
		circle(x, y, r);

		// 消除动圆与定圆的交点
		setfillcolor(GREEN);
		solidcircle(Contact_X, Contact_Y, DISPLAY);

		// 消除心形线当前所在点
		setfillcolor(LIGHTRED);
		solidcircle(lastX, lastY, DISPLAY);
	}

	_getch();
	EndBatchDraw();
	return 0;
}

3.代码详解

3.1 头文件包含
  • 包含了<graphic.h>头文件,这是用于图形绘制相关操作的库
  • <conio.h>头文件提供了一些控制台输入输出相关的函数,例如:_kbit_getch 等,用于检测键盘输入以及获取字符等操作。
  • <math.h>头文件包含数学相关的函数,像 sin ,cos 等三角函数在这里都会被用到。

3.2 绘制坐标轴函数 
// 画坐标抽
void drawCoordinateAxis()
{
    setlinecolor(DARKGRAY);
    line(WIDTH / 2, HEIGHT / 10, WIDTH / 2, HEIGHT / 10 * 9);
    line(WIDTH / 2, HEIGHT / 10, WIDTH / 2 + ARROW, HEIGHT / 10 + ARROW);
    line(WIDTH / 2, HEIGHT / 10, WIDTH / 2 - ARROW, HEIGHT / 10 + ARROW);

    line(WIDTH / 5, HEIGHT / 2, WIDTH / 5 * 4, HEIGHT / 2);
    line(WIDTH / 5 * 4, HEIGHT / 2, WIDTH / 5 * 4 - ARROW, HEIGHT / 2 - ARROW);
    line(WIDTH / 5 * 4, HEIGHT / 2, WIDTH / 5 * 4 - ARROW, HEIGHT / 2 + ARROW);
}

这个函数的功能是绘制坐标轴。首先通过 setlinecolor(DARKGRAY)设置线条颜色为深灰色,然后绘制纵轴:

  • 从窗口高度的十分之一位置到十分之九位置绘制一条垂直的直线作为纵轴主体(line(WIDTH)/ 2,HEIGHT / 10,HEIGHT / 10 * 9)
  • 接着在纵轴顶端绘制向上和向下的箭头,通过两条斜线来实现

 在绘制横轴:

  • 从窗口的五分之一位置到五分之四位置绘制一条水平的直线作为横轴主体。(line(WIDTH / 5,HEIGHT / 2,WIDTH / 5 *4,HEIGHT / 2))
  • 最后在横轴有段绘制向左或向右的箭头
3.3 main 函数主体部分
  • 初始化图形窗口与相关设置
int main()
{
    initgraph(WIDTH, HEIGHT);
    BeginBatchDraw();
    setlinecolor(BLUE);

首先通过 initgraph(WIDTH,HIGHT) 初始化一个指定宽度和高度(由前面宏定义 WIDTHHEIGHT 确定)。接着调用 BeginBatchDraw() 开始批量绘图模式,这种模式可以减少图形绘制过程中的闪烁现象,提高显示效果。追后通过 setlinecolor(BLUE)设置后续绘制线条的颜色为蓝色。

  • 设置绘图模式与初始化相关变量 
    setrop2(R2_XORPEN);
    double lastX = WIDTH / 2.0, lastY = HEIGHT / 2 - r;                  // 上一个心形线的点的 x,y 值,初始值为 y 轴正方向上距原点 a 个单位长度的点

通过 setrop2(R2_XORPEN) 设置绘图的光栅操作模式后为异或模式(XOR),这种模式在后续绘制图形是可以方便地实现图形的叠加和擦除效果。然后初始化 lastX lastY ,它们用于记录上一个绘制的心形线点的坐标,初始值设定为在 y 轴正方向上距离原点定圆半径 r 长度的点坐标(也就是心形线起始点在 y轴 正半轴上的情况)。

  • 循环绘制心形线及相关图形
    for (double a = 0;!_kbhit(); a += PI / COPIES * 2)                  // a 为当前弧度
    {
        double x = cos(3.0 / 2.0 * PI + a) * 2 * r + WIDTH / 2;          // 动圆这一个循环的圆心的 x 值
        double y = sin(3.0 / 2.0 * PI + a) * 2 * r + HEIGHT / 2;          // 动圆这一个循环的圆心的 y 值
        double FixedPoint_X = cos(PI / 2.0 + a * 2) * r + x;              // 当前循环动圆的定点对应的 x 值
        double FixedPoint_Y = sin(PI / 2.0 + a * 2) * r + y;              // 当前循环动圆的定点对应的 y 值
        double Contact_X = cos(PI / 2.0 + a) * r + x;                    // 当前循环两圆切点在动圆上对应的 x 值
        double Contact_Y = sin(PI / 2.0 + a) * r + y;                    // 当前循环两圆切点在动圆上对应的 y 值

这是一个关键的循环,循环条件是!_kbhit(),即只要没有键盘按键按下就一直循环,每次循环中a(代表弧度)按一定的增量(PI / COPIES * 2,也就是将整个圆周按照COPIES份进行细分,每次增加对应的弧度值)增加。
在循环内:

  • 首先根据当前弧度a计算动圆在这一时刻的圆心坐标(x, y),这里利用三角函数结合定圆半径r以及窗口中心坐标来计算,动圆的圆心运动轨迹是围绕着一个特定的路径(从代码中的三角函数表达式可以看出是符合一定规律的圆形轨迹,且与最终要绘制的心形线相关)。
  • 接着计算当前循环下动圆的定点坐标(FixedPoint_X, FixedPoint_Y),这个定点是与绘制心形线相关的一个关键位置点,通过特定的三角函数关系结合动圆圆心坐标和定圆半径计算得出。
  • 然后计算当前循环两圆切点在动圆上对应的坐标(Contact_X, Contact_Y),同样是基于三角函数、动圆圆心坐标以及定圆半径来确定。
        // 画出心形线,只用画这一个循环的点和上一个循环的点的线就行
        setrop2(R2_COPYPEN);
        setlinecolor(YELLOW);
        line(lastX, lastY, FixedPoint_X, FixedPoint_Y);
        setrop2(R2_XORPEN);
        lastX = FixedPoint_X;
        lastY = FixedPoint_Y;

先将绘图模式切换为R2_COPYPEN正常的绘制模式,直接绘制图形),设置线条颜色为黄色,然后通过line函数绘制从上一个心形线点(坐标(lastX, lastY))到当前循环计算出的心形线点(坐标(FixedPoint_X, FixedPoint_Y))的线段,这样逐步绘制线段就可以呈现出心形线的形状。绘制完后再切换回R2_XORPEN模式,并且更新lastXlastY为当前绘制的心形线点坐标,用于下一次循环绘制时作为上一个点的坐标。

4. 小结

以上便是本篇博客的所有内容了,最后,祝大家新的一年,愿你我笑容灿烂如花,生活甜蜜如蜜糖。过去的遗憾就让它过去,未来的美好我们一同迎接。跨年之夜,祝你我岁岁平安,事事顺心,步步高升。


http://www.kler.cn/a/466294.html

相关文章:

  • GESP202406 二级【计数】题解(AC)
  • 家教老师预约平台小程序系统开发方案
  • VBA 64位API声明语句第005讲
  • 自组织映射 (Self-Organizing Map, SOM) 算法详解与PyTorch实现
  • 沙箱模拟支付宝支付3--支付的实现
  • RAG实战:本地部署ragflow+ollama(linux)
  • Linux环境(CentOs7) 安装 Node环境
  • 最近学习shader的一些总结
  • npm ERR! ECONNRESET 解决方法
  • Celeborn Spark 集成最新进展
  • 滤波器的主要参数
  • Flutter路由钩子
  • 1月2日作业
  • 酒店管理系统|Java|SSM|VUE| 前后端分离
  • [文献阅读] Reducing the Dimensionality of Data with Neural Networks
  • 查找最长回文子串
  • 七种改进爬山算法的方法
  • 阿里巴巴Java 面试突击手册开源(涵盖 p5-p8 技术栈)
  • 数据结构排序
  • C. Line: 用扩展欧几里得算法求解整数解
  • 京东一面:MySQL 主备延迟有哪些坑?主备切换策略
  • TypyScript从入门到精通
  • 如何根据数据分布特性,选择合适的聚类算法,以优化K-Means算法对非球形分布数据的聚类效果?
  • 3blue1brow线代笔记
  • 使用 HEIC/HEIF 编码器将 HEIC 转换为 JPEG
  • SQLite 进阶:扩展功能与最佳实践