流体
最近看到一些看好玩的东西,
先看效果图
简要说明:
屏幕上的球随鼠标的移动而像流体一般的移动,当鼠标不动时,所有的球重新聚在鼠标的位置,而当单击鼠标左键时,所有的球被排斥
这个工程涉及到数学模型的思想,这里面在代码中将其转为参数化,具体要看代码,注释比较详细,上代码:
///
// 程序名称:流体(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();
}