樱花树盛开的季节,我用简单的C代码绘制了一棵樱花树向她表白~『C/C++&图形库EasyX』

举报
花想云 发表于 2023/05/30 22:56:14 2023/05/30
【摘要】 本文主要内容为,利用图形库与简单的C语言知识实现樱花树。文章涉及的C语言语法并不多,但要求了解简单的递归运用。每一小节都会附有完整代码与实现效果图,有需求的小伙伴也可以直接去复制使用~

💐文章导读

本文主要内容为,利用图形库与简单的C语言知识实现樱花树。文章涉及的C语言语法并不多,但要求了解简单的递归运用。每一小节都会附有完整代码与实现效果图,有需求的小伙伴也可以直接去复制使用~

特别声明——本文内容与代码全部参考书籍《C和C++趣味游戏编程》,当然我也非常推荐这本书。


绘制一根线条

  1. 初始化画板窗口;
  2. 设置画板背景颜色(白色);
  3. 绘制一个线条;
  4. 设置线条颜色(黑色);

2-1

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

#define WIDTH 800 // 画面宽度
#define HEIGHT 600 // 画面高度

int main()
{
	initgraph(WIDTH, HEIGHT); // 初始化窗口
	setbkcolor(RGB(225, 225, 225)); //白色背景

	setlinecolor(RGB(0, 0, 0)); // 设定线条颜色为黑色
	setlinestyle(PS_SOLID, 3);  // 设定线宽

	cleardevice(); // 清屏

	BeginBatchDraw(); // 开始批量绘制

	line(WIDTH / 2, HEIGHT, WIDTH / 2,  150); //绘制线条
	
	FlushBatchDraw(); // 刷新画板
	_getch(); // 等待输入
	closegraph(); // 关闭画板
	return 0;
}

效果图
image.png

绘制一个简易的树干

  1. 使用函数递归来完成树干的绘制;
  2. 利用三角函数来改变每根线条的倾斜度;

2-2

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

#define PI 3.1415926 // 圆周率
#define WIDTH 800 // 画面宽度
#define HEIGHT 600 // 画面高度

// 绘制树干的函数
void branch(float x_start, float y_start, float angle, int generation)
{
	// 利用三角函数求出当前树枝的终点坐标
	float x_end, y_end;
	x_end = x_start + 150 * cos(angle);
	y_end = y_start + 150 * sin(angle);

	line(x_start, y_start, x_end, y_end); // 画出当前枝干

	// 求出子枝干的代数
	int childGeneration = generation + 1;
	
	// 当子枝干的代数<=4,画出当前枝干,并递归调用产生子枝干
	if (childGeneration <= 4)
	{
		// 产生左右的子枝干
		branch(x_end, y_end,angle+PI/6,childGeneration);
		branch(x_end, y_end, angle - PI / 6, childGeneration);

	}
}

int main()
{
	initgraph(WIDTH, HEIGHT); // 初始化窗口
	setbkcolor(RGB(225, 225, 225)); // 白色背景

	setlinecolor(RGB(0, 0, 0)); // 设定线条颜色为黑色
	setlinestyle(PS_SOLID, 3);  // 设定线宽

	cleardevice(); // 清屏

	BeginBatchDraw(); // 开始批量绘制

	branch(WIDTH/2,HEIGHT,-PI/2,1); // 递归绘图

	FlushBatchDraw();
	_getch();
	closegraph();
	return 0;
}

效果图
image.png

优化树干,使其更加细致

  1. 使用比例来控制父枝干与子枝干的长度;
  2. 控制父枝干与子枝干的夹角变化;

2-3

#define PI 3.1415926 // 圆周率
#define WIDTH 800 // 画面宽度
#define HEIGHT 600 // 画面高度
float offsetAngle = PI / 6; // 左右枝干与父枝干偏离的角度
float shortenRate = 0.65; // 左右枝干长度与父枝干长度的比例


void branch(float x_start, float y_start, float length,float angle,float thickness, int generation)
{
	// 利用三角函数求出当前树枝的终点坐标
	float x_end, y_end;
	x_end = x_start + length * cos(angle);
	y_end = y_start + length * sin(angle);

	setlinestyle(PS_SOLID, thickness); //设置当前枝干的宽

	setlinecolor(RGB(0, 0, 0)); // 设置当前枝干的颜色

	line(x_start, y_start, x_end, y_end); // 画出当前枝干

	// 求出子枝干的代数
	int childGeneration = generation + 1;
	
	float childLength = shortenRate * length; // 生成的枝干的长度逐渐变短


	// 当子枝干的长度>=2,且当子枝干的代数<10,画出当前枝干,并递归调用产生子枝干
	if (childLength >= 2 && childGeneration <10)
	{
		// 生成子枝干的宽度逐渐变细
		float childThickness = thickness * 0.8;

		if (childThickness < 2)
		{
			childThickness = 2;
		}
		// 产生左右的子枝干
		branch(x_end, y_end, childLength, angle + offsetAngle, childThickness, childGeneration);
		branch(x_end, y_end, childLength, angle - offsetAngle, childThickness, childGeneration);
	}
}

int main()
{
	initgraph(WIDTH, HEIGHT); // 初始化窗口
	setbkcolor(RGB(225, 225, 225)); //白色背景

	setlinecolor(RGB(0, 0, 0)); //设定线条颜色为黑色
	setlinestyle(PS_SOLID, 3);  //设定线宽

	cleardevice(); // 清屏

	BeginBatchDraw(); // 开始批量绘制

	branch(WIDTH/2,HEIGHT,0.45*HEIGHT*shortenRate,-PI/2, 15 * shortenRate, 1); // 递归绘图

	FlushBatchDraw();
	_getch();
	closegraph();
	return 0;
}

image.png

绘制樱花树

  1. 修改树干颜色为褐色;
  2. 为末端的树干添上樱花(实际为粉色的小圆);

2-4

#define PI 3.1415926 // 圆周率
#define WIDTH 800 // 画面宽度
#define HEIGHT 600 // 画面高度
float offsetAngle = PI / 6; // 左右枝干与父枝干偏离的角度
float shortenRate = 0.65; // 左右枝干长度与父枝干长度的比例


// 枝干生成和绘制递归函数
// 输入参数:枝干起始x y坐标,枝干长度,枝干角度,枝干绘图线条宽度,第几代
void branch(float x_start, float y_start, float length, float angle, float thickness, int generation)
{
	// 利用三角函数求出当前枝干的终点x,y坐标
	float x_end, y_end;
	x_end = x_start + length * cos(angle);
	y_end = y_start + length * sin(angle);
	setlinestyle(PS_SOLID, thickness); // 设定当前枝干线宽
	setlinecolor(HSVtoRGB(15, 0.75, 0.5)); // 设定当前枝干颜色为褐色
	line(x_start, y_start, x_end, y_end); // 画出当前枝干

	// 求出子枝干的代数
	int childGeneration = generation + 1;
	// 生成子枝干的长度,逐渐变短
	float childLength = shortenRate * length;

	// 当子枝干长度大于等于2,并且代数小于等于10,递归调用产生子枝干
	if (childLength >= 2 && childGeneration <= 9)
	{
		// 生成子枝干的粗细,逐渐变细
		float childThickness = thickness * 0.8;
		if (childThickness < 2) // 枝干绘图最细的线宽为2
			childThickness = 2;

		// 产生左右的子枝干
		branch(x_end, y_end, childLength, angle + offsetAngle, childThickness, childGeneration);
		branch(x_end, y_end, childLength, angle - offsetAngle, childThickness, childGeneration);
		// 产生中间的子枝干
		branch(x_end, y_end, childLength, angle, childThickness, childGeneration);
	}
	else  // 画出最末端的樱花
	{
		setlinestyle(PS_SOLID, 1); // 线宽
		setlinecolor(HSVtoRGB(325, 0.3, 1)); // 设定线条颜色 粉色
		setfillcolor(HSVtoRGB(325, 0.3, 1)); // 设定填充颜色 粉色
		if (childLength <= 4) // 如果子枝干长度小于等于4
			fillcircle(x_end, y_end, 2); // 圆的半径为2(再小就看不清了)
		else
			fillcircle(x_end, y_end, childLength / 2); // 画一个圆,半径为子枝干长度的一半
	}
}

int main()
{
	initgraph(WIDTH, HEIGHT); // 初始化窗口
	setbkcolor(RGB(225, 225, 225)); //白色背景

	setlinecolor(RGB(0, 0, 0)); // 设定线条颜色为黑色
	setlinestyle(PS_SOLID, 3);  // 设定线宽

	cleardevice(); // 清屏
	BeginBatchDraw(); // 开始批量绘制

	branch(WIDTH/2,HEIGHT,0.45*HEIGHT*shortenRate,-PI/2, 15 * shortenRate, 1); // 递归绘图

	FlushBatchDraw();
	_getch();
	closegraph();
	return 0;
}

效果图
image.png

增加随机树形与渐变色效果

将樱花树的各个参数修改为随机数,生成各种形态不同的樱花树;

  1. 控制树干的颜色渐变(越往上枝干越亮);
  2. 设置花瓣的随机颜色;
  3. 控制树干的长度与比例具有随机性;

如何设置随机数

  • 使用rand函数来生成随机数;
  • 定义mapvalue函数来将生成的随机数映射到某一区间;
float mapValue(float input, float inputMin, float inputMax, float outputMin, float outputMax)
{
	float output;
	if (abs((float)(input - inputMin) < 0.000001)) // 防止除以零的bug
		output = outputMin;
	else
		output = (input - inputMin) * (outputMax - outputMin) / (inputMax - inputMin) + outputMin;
	return output;
}

// 生成[min,max]之间的随机小数
float randBetween(float min, float max)
{
	float t = rand() / double(RAND_MAX); // 生成[0,1]的随机小数
	// 调用mapValue函数,把值范围从[0,1]映射到[min,max]
	float r = mapValue(t, 0, 1, min, max);
	return r;
}

2-5

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

#define  PI 3.1415926
#define  WIDTH 800   // 画面宽度
#define  HEIGHT 600  // 画面高度度

float offsetAngle = PI / 6; // 左右枝干和父枝干偏离的角度
float shortenRate = 0.65;  // 子枝干比父枝干变短的倍数

// 把[inputMin,inputMax]范围的input变量,映射为[outputMin,outputMax]范围的output变量
float mapValue(float input, float inputMin, float inputMax, float outputMin, float outputMax)
{
	float output;
	if (abs((float)(input - inputMin) < 0.000001)) // 防止除以零的bug
		output = outputMin;
	else
		output = (input - inputMin) * (outputMax - outputMin) / (inputMax - inputMin) + outputMin;
	return output;
}

// 生成[min,max]之间的随机小数
float randBetween(float min, float max)
{
	float t = rand() / double(RAND_MAX); // 生成[0,1]的随机小数
	// 调用mapValue函数,把值范围从[0,1]映射到[min,max]
	float r = mapValue(t, 0, 1, min, max);
	return r;
}

// 枝干生成和绘制递归函数
// 输入参数:枝干起始x y坐标,枝干长度,枝干角度,枝干绘图线条宽度,第几代
void branch(float x_start, float y_start, float length, float angle, float thickness, int generation)
{
	// 利用三角函数求出当前枝干的终点x,y坐标
	float x_end, y_end;
	x_end = x_start + length * cos(angle);
	y_end = y_start + length * sin(angle);

	// 画线条枝干
	setlinestyle(PS_SOLID, thickness); // 设定当前枝干线宽
	// 设置枝干为灰褐色,主树干最黑,子枝干逐渐变亮
	COLORREF  color = HSVtoRGB(15, 0.75, 0.4 + generation * 0.05);
	setlinecolor(color); // 设定当前枝干颜色

	line(x_start, y_start, x_end, y_end); // 画出当前枝干(画线)

	// 求出子枝干的代数
	int childGeneration = generation + 1;
	// 生成左、右、中间三个子枝干的长度,逐渐变短,并有一定随机性
	float childLength = shortenRate * length;
	float leftChildLength = childLength * randBetween(0.9, 1.1);
	float rightChildLength = childLength * randBetween(0.9, 1.1);
	float centerChildLength = childLength * randBetween(0.8, 1.1);

	// 当子枝干长度大于2,并且代数小于等于10,递归调用产生子枝干
	if (childLength >= 2 && childGeneration <= 9)
	{
		// 生成子枝干的粗细,逐渐变细
		float childThickness = thickness * 0.8;
		if (childThickness < 2) // 枝干绘图最细的线宽为2
			childThickness = 2;

		// 一定概率产生左、右、中子枝干
		if (randBetween(0, 1) < 0.95)
			branch(x_end, y_end, leftChildLength, angle + offsetAngle * randBetween(0.5, 1), childThickness, childGeneration);
		if (randBetween(0, 1) < 0.95)
			branch(x_end, y_end, rightChildLength, angle - offsetAngle * randBetween(0.5, 1), childThickness, childGeneration);
		if (randBetween(0, 1) < 0.85)
			branch(x_end, y_end, centerChildLength, angle + offsetAngle / 5 * randBetween(-1, 1), childThickness, childGeneration);
	}
	else // 最末端绘制樱花,画一个粉色填充圆
	{
		setlinestyle(PS_SOLID, 1); // 线宽
		// 樱花粉色HSVtoRGB(325,0.3,1),有一定随机性
		COLORREF  color = HSVtoRGB(randBetween(300, 350), randBetween(0.2, 0.3), 1);
		setlinecolor(color); // 设定线条颜色
		setfillcolor(color); // 设定填充颜色
		if (childLength <= 4) // 如果子枝干长度小于等于4
			fillcircle(x_end, y_end, 2); // 圆的半径为2(再小就看不清了)
		else
			fillcircle(x_end, y_end, childLength / 2); // 画一个圆,半径为子枝干长度的一半
	}
}

void startup()  // 初始化
{
	srand(time(0)); // 随机初始化
	initgraph(WIDTH, HEIGHT); // 新开一个画面
	setbkcolor(RGB(255, 255, 255)); // 白色背景
	cleardevice(); // 清屏
	BeginBatchDraw(); // 开始批量绘制
	brunch(WIDTH / 2, HEIGHT, 0.45 * HEIGHT * shortenRate, -PI / 2, 15 * shortenRate, 1); // 递归函数调用
	FlushBatchDraw(); // 批量绘制
}

void update()  // 每帧更新
{
	offsetAngle = randBetween(PI/10,PI/6);
	shortenRate = randBetween(0.7, 0.3);

	cleardevice(); // 清屏
	branch(WIDTH / 2, HEIGHT, 0.45 * HEIGHT * shortenRate, -PI / 2, 15 * shortenRate, 1); // 递归调用
}

int main() // 主函数
{
	startup();  // 初始化 	
	while (1)  // 重复循环
		update();  // 每帧更新
	return 0;
}

效果图
image.png
image.png
image.png

进阶——通过鼠标点击来控制生成樱花树

***2-5***版本缺点在于每次运行程序只能生成一棵樱花树。我们还可以引进鼠标点击的功能来实现每次鼠标点击生成不同的樱花树。

  • 检测鼠标是否发生移动,从而更新递归函数的参数;

2-6

if (m.uMsg == WM_MOUSEMOVE) // 当鼠标移动时,设定递归函数的参数
		{
			// 鼠标从左到右,左右子枝干偏离父枝干的角度逐渐变大
			offsetAngle = mapValue(m.x, 0, WIDTH, PI / 10, PI / 4);
			// 鼠标从上到下,子枝干比父枝干的长度缩短的更快
			shortenRate = mapValue(m.y, 0, HEIGHT, 0.7, 0.3);
		}
  • 检测鼠标是否发生点击动作,若点击则开始绘制;
f (m.uMsg == WM_LBUTTONDOWN) // 当鼠标左键点击时,以当前参数开始绘制一棵新数
		{
			cleardevice(); // 清屏
			branch(WIDTH / 2, HEIGHT, 0.45 * HEIGHT * shortenRate, -PI / 2, 15 * shortenRate, 1); // 递归调用
			FlushBatchDraw(); // 批量绘制
		}

效果图
image.png
image.png
image.png

进阶——生成樱花树并展示生长过程

  • 使用sleep函数与FlushBatchDraw将每次绘制的结果间隔一秒刷新,形成樱花树生长的动画。

2-7

void branch(float x_start, float y_start, float length, float angle, float thickness, int generation)
{
	//....

	if (isShowAnimation) // 如果为1,绘制樱花树生成的过程动画
	{
		FlushBatchDraw(); // 批量绘制
		Sleep(1); // 暂停
	}
}

效果图
image.png
image.png
image.png

快去向你喜欢的人展示樱花树叭~

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。