流体

流体

最近看到一些看好玩的东西,
先看效果图

简要说明:
屏幕上的球随鼠标的移动而像流体一般的移动,当鼠标不动时,所有的球重新聚在鼠标的位置,而当单击鼠标左键时,所有的球被排斥

这个工程涉及到数学模型的思想,这里面在代码中将其转为参数化,具体要看代码,注释比较详细,上代码:

///
// 程序名称:流体(Liquid)
// 编译环境:VS 2013,EasyX 2017-9-19
// 原 作 品:http://spielzeugz.de/html5/liquid-particles.html (HTML5)
// 最后修改:2018-12-9
//
#include <graphics.h>
#include <math.h>
#include <time.h>

#define WIDTH		1024		// 屏幕宽
#define HEIGHT		576			// 屏幕高
#define NUM_MOVERS	600			// 小球数量 
#define	FRICTION	0.96f		// 摩擦力


// 定义小球结构
struct Mover
{
	COLORREF	color;			// 颜色
	float		x, y;			// 坐标
	float		vX, vY;			// 速度
};


// 定义全局变量
Mover	movers[NUM_MOVERS];			// 小球数组
int		mouseX, mouseY;			// 当前鼠标坐标
int		mouseVX, mouseVY;		// 鼠标速度
int		prevMouseX, prevMouseY;		// 上次鼠标坐标
bool	isMouseDown;				// 鼠标左键是否按下
DWORD*	pBuffer;					// 显存指针


// 初始化
void init()
{

	// 设置随机种子
	srand((unsigned int)time(NULL));

	// 初始化小球数组
	for (int i = 0; i < NUM_MOVERS; i++)
	{
		movers[i].color = RGB(rand() % 256, rand() % 256, rand() % 256);			//随机分布小球的颜色
		movers[i].x = WIDTH * 0.5;												   //初始时 所有小球都在中间
		movers[i].y = HEIGHT * 0.5;												  //
		movers[i].vX = float(cos(float(i))) * (rand() % 34);				     //初始小球的速度在0-34之间随机
		movers[i].vY = float(sin(float(i))) * (rand() % 34);
	}

	// 初始化鼠标变量
	mouseX = prevMouseX = WIDTH / 2;										 //	初始鼠标位置在中间									
	mouseY = prevMouseY = HEIGHT / 2;

	// 获取显存指针
	pBuffer = GetImageBuffer(NULL);
}


// 全屏变暗 50%
void darken()
{
	for (long int i = WIDTH * HEIGHT - 1; i >= 0; i--)
	if (pBuffer[i] != 0)
		pBuffer[i] = RGB(GetRValue(pBuffer[i]) >> 1, GetGValue(pBuffer[i]) >> 1, GetBValue(pBuffer[i]) >> 1);		//>>1  等价于 ÷(2¹)
}


// 绝对延时
void delay(DWORD ms)
{
	static DWORD oldtime = GetTickCount();

	while (GetTickCount() - oldtime < ms)
		Sleep(1);

	oldtime = GetTickCount();
}


// 绘制动画(一帧)
void run()
{
	darken();	// 全屏变暗

	mouseVX = mouseX - prevMouseX;		//鼠标速度=鼠标当前位置 - 上次鼠标位置	。  mouseX,mouseY  随鼠标改变而改变
	mouseVY = mouseY - prevMouseY;
	prevMouseX = mouseX;				//将当前位置变为过去时
	prevMouseY = mouseY;

	float toDist = WIDTH * 0.86f;		// 0.86 的屏幕宽
	float stirDist = WIDTH * 0.125f;	// 1/8 的屏幕宽
	float blowDist = WIDTH * 0.5f;		//屏幕宽度的一半

	for (int i = 0; i < NUM_MOVERS; i++)
	{
		float x = movers[i].x;			//小球的位置,初始时,小球位置为屏幕中间
		float y = movers[i].y;
		float vX = movers[i].vX;		//movers[i].vX 小球x的速度,初始时,小球速度随机于0-34  下同
		float vY = movers[i].vY;

		float dX = x - mouseX;			//前后两次鼠标位置x轴差值
		float dY = y - mouseY;			//前后两次鼠标位置y轴差值
		float d = (float)sqrt(dX * dX + dY * dY);	//前后两次鼠标位置之间的距离
		dX = d ? dX / d : 0;						//d>0 则 dX=dX/d,,,否 dX=0	下同
		dY = d ? dY / d : 0;

		if (isMouseDown && d < blowDist)			//鼠标被按下, 且  d < 屏幕宽度的一半
		{
			float blowAcc = (1 - (d / blowDist)) * 14;	//公式计算
			vX += dX * blowAcc + 0.5f - float(rand()) / RAND_MAX;
			vY += dY * blowAcc + 0.5f - float(rand()) / RAND_MAX;
		}

		if (d < toDist)			// 0.86 的屏幕宽
		{						//公式计算
			float toAcc = (1 - (d / toDist)) * WIDTH * 0.0014f;
			vX -= dX * toAcc;
			vY -= dY * toAcc;
		}

		if (d < stirDist)		// 1/8 的屏幕宽
		{						//公式计算
			float mAcc = (1 - (d / stirDist)) * WIDTH * 0.00026f;
			vX += mouseVX * mAcc;
			vY += mouseVY * mAcc;
		}

		vX *= FRICTION;		//0.96f  摩擦力
		vY *= FRICTION;		//0.96f

		float avgVX = (float)fabs(vX);		//取绝对值
		float avgVY = (float)fabs(vY);
		float avgV = (avgVX + avgVY) * 0.5f;	//取x 和 y 方向速度的平均数

		if (avgVX < 0.1) vX *= float(rand()) / RAND_MAX * 3;	//vX=vX*[0,3];  下同
		if (avgVY < 0.1) vY *= float(rand()) / RAND_MAX * 3;

		float sc = avgV * 0.45f;		//球的尺寸
		sc = max(min(sc, 3.5f), 0.4f);	//取值于  [0.4,3.5]

		float nextX = x + vX;			//球的x 位置 = 鼠标x的位置  +  x方向的速度  下同	
		float nextY = y + vY;

		//确保在屏幕内
		if (nextX > WIDTH)	{ nextX = WIDTH;	vX *= -1; }
		else if (nextX < 0)		{ nextX = 0;		vX *= -1; }
		if (nextY > HEIGHT){ nextY = HEIGHT;	vY *= -1; }
		else if (nextY < 0)		{ nextY = 0;		vY *= -1; }

		movers[i].vX = vX;		//赋值给小球的速度
		movers[i].vY = vY;
		movers[i].x = nextX;	//赋值给小球的位置 
		movers[i].y = nextY;

		// 画小球
		setcolor(movers[i].color);		//设置当前绘图前景色
		setfillstyle(movers[i].color);	// 设置当前填充样式
		fillcircle(int(nextX + 0.5), int(nextY + 0.5), int(sc + 0.5));
	}
}


// 主函数
void main()
{
	// 创建绘图窗口
	initgraph(WIDTH, HEIGHT);
	// 启用批绘图模式
	BeginBatchDraw();

	// 初始化
	init();

	// 鼠标消息变量
	MOUSEMSG m;

	while (true)
	{
		// 处理鼠标消息
		while (MouseHit())			// 检查是否存在鼠标消息
		{
			m = GetMouseMsg();		//获取鼠标信息
			//		WM_MOUSEMOVE		鼠标移动
			//		WM_MOUSEWHEEL		鼠标滚轮拨动
			//		WM_LBUTTONDOWN		左键按下
			//		WM_LBUTTONUP		左键弹起
			//		WM_LBUTTONDBLCLK	左键双击
			//		WM_MBUTTONDOWN		中键按下
			//		WM_MBUTTONUP		中键弹起
			//		WM_MBUTTONDBLCLK	中键双击
			//		WM_RBUTTONDOWN		右键按下
			//		WM_RBUTTONUP		右键弹起
			//		WM_RBUTTONDBLCLK	右键双击
			switch (m.uMsg)			//当前鼠标消息
			{
			case WM_MOUSEMOVE:		mouseX = m.x;	mouseY = m.y;	break;		//赋值当前鼠标位置
			case WM_LBUTTONDOWN:	isMouseDown = true;				break;
			case WM_LBUTTONUP:		isMouseDown = false;			break;
			}
		}

		// 绘制动画
		run();

		// 显示缓存的绘制内容
		FlushBatchDraw();

		// 延时 20 毫秒
		delay(20);
	}

	// 关闭
	EndBatchDraw();
	closegraph();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@Hwang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值