参考
- 《C和C++游戏趣味编程》
樱花树
通过鼠标交互设定樱花树的高度和分散程度,鼠标右键点击设置是否显示过程动画,鼠标左键点击开始绘制
绘制过程抽象
(1)绘制一个树干
(2)绘制其左边的子树干,绘制其右边的子树干
(3)当到第n代树干时停止生成子树干
#include <graphics.h>
#include <conio.h>
#include <stdio.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; // 子枝干的代数
if (childGeneration <= 4) // 当子枝干代数小于等于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(255, 255, 255));
setlinecolor(RGB(0, 0, 0));
setlinestyle(PS_SOLID, 3);
cleardevice();
BeginBatchDraw();
branch(WIDTH / 2, HEIGHT, -PI / 2, 1);
FlushBatchDraw();
_getch();
return 0;
}
进一步,使子枝干的长度逐渐变短,画线逐渐变细:
#include <graphics.h>
#include <conio.h>
#include <stdio.h>
#include <math.h>
#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; // 子枝干的长度逐渐变短
if (childLength >= 2 && childGeneration <= 9) // 当子枝干长度大于2,且代数小于等于9,绘制子枝干
{
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);
}
else
{
setlinestyle(PS_SOLID, 1);
setlinecolor(HSVtoRGB(325, 0.3, 1)); // 设定线条颜色为粉色
setfillcolor(HSVtoRGB(325, 0.3, 1)); // 设定填充颜色为粉色
if (childLength <= 4)
{
fillcircle(x_end, y_end, 2);
}
else
{
fillcircle(x_end, y_end, childLength / 2);
}
}
}
int main()
{
initgraph(WIDTH, HEIGHT);
setbkcolor(RGB(255, 255, 255));
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();
return 0;
}
绘制樱花树
当子枝干长度小于2,且代数大于9时,绘制一个粉色填充圆,表示樱花:
setlinestyle(PS_SOLID, 1);
setlinecolor(HSVtoRGB(325, 0.3, 1)); // 设定线条颜色为粉色
setfillcolor(HSVtoRGB(325, 0.3, 1)); // 设定填充颜色为粉色
if (childLength <= 4)
{
fillcircle(x_end, y_end, 2);
}
else
{
fillcircle(x_end, y_end, childLength / 2);
}
再添加一个中间子枝干,使樱花树更稠密:
branch(x_end, y_end, childLength, angle, childThickness, childGeneration);
修改update(),当鼠标移动时调整递归函数参数,鼠标左键点击时绘制一棵新的樱花树:
if (e.message == WM_MOUSEMOVE)
{
offsetAngle = mapValue(e.x, 0, WIDTH, PI / 10, PI / 4); // 鼠标从左到右移动,左右枝干偏离父枝干的角度逐渐变大
shortenRate = mapValue(e.y, 0, HEIGHT, 0.7, 0.3); // 鼠标从上到下移动,子枝干的长度比父枝干的长度缩短得更快
}
if (e.message == WM_LBUTTONDOWN)
{
cleardevice();
branch(WIDTH / 2, HEIGHT, 0.45 * HEIGHT * shortenRate, -PI / 2, 15 * shortenRate, 1);
FlushBatchDraw();
}
引入一些随机性,首先是子枝干的长度逐渐变短的随机性:
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);
然后是产生左、右、中的子枝干的随机性:
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, childLength, 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);
}
接着是树枝越向上,颜色越淡:
COLORREF color = HSVtoRGB(15, 0.75, 0.4 + generation * 0.05); // 枝干颜色
最后是樱花颜色的随机性:
COLORREF color = HSVtoRGB(randBetween(300, 350), randBetween(0.2, 0.3), 1);
setlinecolor(color);
setfillcolor(color);
显示绘制过程动画
设置全局变量isShowAnimation,在branch()中判断,如果isShowAnimation为1,则调用FlushBatchDraw()绘制:
if (isShowAnimation)
{
FlushBatchDraw();
Sleep(20);
}
在update()中添加代码,通过点击鼠标右键切换是否显示中间绘制动画:
if (e.message == WM_RBUTTONDOWN)
{
if (isShowAnimation == 1)
{
isShowAnimation = 0;
}
else if (isShowAnimation == 1)
{
isShowAnimation = 1;
}
}
完整代码
#include <graphics.h>
#include <conio.h>
#include <stdio.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; // 左右枝干长度与父枝干长度的比例
int isShowAnimation = 1; // 是否显示树生成过程动画
float mapValue(float input, float inputMin, float inputMax, float outputMin, float outputMax)
{
float output;
if (fabs(input - inputMin) < 0.000001) // 防止除0
{
output = outputMin;
}
else
{
output = (input - inputMin) * (outputMax - outputMin) / (inputMax - inputMin) + outputMin;
}
return output;
}
float randBetween(float min, float max)
{
float t = rand() / double(RAND_MAX); // 生成[0, 1]的随机小数
float r = mapValue(t, 0, 1, min, max);
return r;
}
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); // 设定当前枝干线宽
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);
if (childLength >= 2 && childGeneration <= 9) // 当子枝干长度大于2,且代数小于等于9,绘制子枝干
{
float childThickness = thickness * 0.8; // 枝干逐渐变细
if (childThickness < 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, childLength, 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);
COLORREF color = HSVtoRGB(randBetween(300, 350), randBetween(0.2, 0.3), 1);
setlinecolor(color);
setfillcolor(color);
if (childLength <= 4)
{
fillcircle(x_end, y_end, 2);
}
else
{
fillcircle(x_end, y_end, childLength / 2);
}
}
if (isShowAnimation)
{
FlushBatchDraw();
Sleep(20);
}
}
void startup()
{
srand(time(0));
initgraph(WIDTH, HEIGHT);
setbkcolor(RGB(255, 255, 255));
cleardevice();
BeginBatchDraw();
branch(WIDTH / 2, HEIGHT, 0.45 * HEIGHT * shortenRate, -PI / 2, 15 * shortenRate, 1);
FlushBatchDraw();
}
void update()
{
ExMessage e;
if (peekmessage(&e))
{
if (e.message == WM_MOUSEMOVE)
{
offsetAngle = mapValue(e.x, 0, WIDTH, PI / 10, PI / 4); // 鼠标从左到右移动,左右枝干偏离父枝干的角度逐渐变大
shortenRate = mapValue(e.y, 0, HEIGHT, 0.7, 0.3); // 鼠标从上到下移动,子枝干的长度比父枝干的长度缩短得更快
}
if (e.message == WM_LBUTTONDOWN)
{
cleardevice();
branch(WIDTH / 2, HEIGHT, 0.45 * HEIGHT * shortenRate, -PI / 2, 15 * shortenRate, 1);
FlushBatchDraw();
}
if (e.message == WM_RBUTTONDOWN)
{
if (isShowAnimation == 1)
{
isShowAnimation = 0;
}
else if (isShowAnimation == 1)
{
isShowAnimation = 1;
}
}
}
}
int main()
{
startup();
while (1)
{
update();
}
return 0;
}