C语言结构体练习-互动粒子仿真
线下的C语言课,教完结构体的知识后,为了加深同学们对结构体知识的理解,带领大家一起实现了一个酷炫的互动粒子仿真小程序:
假设有15个粒子小球,要记录他们的速度、坐标,可以用数组实现:
float ball_x[15],ball_y[15]; // 小球的坐标
float ball_vx[15],ball_vy[15]; // 小球的速度
更好的方法,是利用结构体将一个物体的不同属性集合在一起,使代码更加简洁直观:
struct Ball
{float x,y;float vx,vy;
}
Ball balls[15];
利用结构体,可以逐步实现一个互动粒子仿真的小程序,效果如图所示。
1 静止小球初始化与显示
定义小球结构体Mover,包含颜色、坐标、速度、半径等成员变量,随机初始化结构体数组Mover movers[NUM_MOVERS],并在画面中显示。
#include <graphics.h>
#include <math.h>
#include <time.h>
#define WIDTH 1024 // 屏幕宽
#define HEIGHT 768 // 屏幕高
#define NUM_MOVERS 800 // 小球数量
// 定义小球结构
struct Mover
{COLORREF color; // 颜色
float x, y; // 坐标
float vX, vY; // 速度
float radius; // 半径
};// 定义全局变量
Mover movers[NUM_MOVERS]; // 小球数组
void startup()
{// 设置随机种子
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 = rand()%WIDTH;movers[i].y = rand()%HEIGHT;movers[i].vX = float(cos(float(i))) * (rand() % 34);movers[i].vY = float(sin(float(i))) * (rand() % 34);movers[i].radius = (rand() % 34)/15.0;}initgraph(WIDTH, HEIGHT);BeginBatchDraw();
}void show()
{clearrectangle(0,0,WIDTH-1,HEIGHT - 1); // 清空画面全部矩形区域
for(int i = 0; i < NUM_MOVERS; i++){// 画小球
setcolor(movers[i].color);setfillstyle(movers[i].color);fillcircle(int(movers[i].x + 0.5), int(movers[i].y + 0.5), int(movers[i].radius + 0.5)); // 四舍五入
} FlushBatchDraw();Sleep(2);
}void updateWithoutInput()
{
}
void updateWithInput()
{}
void gameover()
{EndBatchDraw();closegraph();
}int main()
{startup(); // 数据初始化
while (1) // 游戏循环执行
{show(); // 显示画面
updateWithoutInput(); // 与用户输入无关的更新
updateWithInput(); // 与用户输入有关的更新
}gameover(); // 游戏结束、后续处理
return 0;
}
2 小球运动与反弹
利用反弹球的实现思路,实现所有小球碰到边界后反弹。
void updateWithoutInput()
{for(int i = 0; i < NUM_MOVERS; i++) // 对所有小球遍历
{float x = movers[i].x; // 当前小球坐标
float y = movers[i].y;float vX = movers[i].vX; // 当前小球速度
float vY = movers[i].vY;// 根据位置+速度,更新小球的坐标
float nextX = x + vX;float nextY = y + vY;// 小球如果超过上下左右四个边界的话,位置设为边界处,速度反向
if (nextX > WIDTH) { nextX = WIDTH; vX = -1*vX; }else if (nextX < 0) {nextX = 0; vX = -1*vX; }if (nextY > HEIGHT){ nextY = HEIGHT; vY = -1*vY; }else if (nextY < 0) { nextY = 0; vY = -1*vY; }// 更新小球位置、速度的结构体数组
movers[i].vX = vX;movers[i].vY = vY;movers[i].x = nextX;movers[i].y = nextY;}
}
3 小球运动规范化
加入阻尼,模拟真实世界中运动物体受摩擦力逐渐变慢的效果;为了避免小球绝对静止,当小球速度过小时使其速度增大;修改小球的半径,速度越大则半径越大。
#define FRICTION 0.96f // 摩擦力、阻尼系数
void updateWithoutInput()
{for(int i = 0; i < NUM_MOVERS; i++) // 对所有小球遍历
{float x = movers[i].x; // 当前小球坐标
float y = movers[i].y;float vX = movers[i].vX; // 当前小球速度
float vY = movers[i].vY;// 小球运动有一个阻尼(摩擦力),速度逐渐减少
vX = vX * FRICTION;vY = vY * FRICTION; // 速度的绝对值
float avgVX = abs(vX);float avgVY = abs(vY);// 两个方向速度的平均
float avgV = (avgVX + avgVY) * 0.5f; // 因为有上面阻尼的作用,如果速度过小的话,乘以一个[0,3]的随机数,会以比较大的概率让速度变大
if (avgVX < 0.1) vX = vX * float(rand()) / RAND_MAX * 3;if (avgVY < 0.1) vY = vY * float(rand()) / RAND_MAX * 3; // 小球的半径在[0.4,3.5]之间,速度越大,半径越大
float sc = avgV * 0.45f;sc = max(min(sc, 3.5f), 0.4f);movers[i].radius = sc;// 根据位置+速度,更新小球的坐标
float nextX = x + vX;float nextY = y + vY;// 小球如果超过上下左右四个边界的话,位置设为边界处,速度反向
if (nextX > WIDTH) { nextX = WIDTH; vX = -1*vX; }else if (nextX < 0) {nextX = 0; vX = -1*vX; }if (nextY > HEIGHT){ nextY = HEIGHT; vY = -1*vY; }else if (nextY < 0) { nextY = 0; vY = -1*vY; } // 更新小球位置、速度的结构体数组
movers[i].vX = vX;movers[i].vY = vY;movers[i].x = nextX;movers[i].y = nextY;}
}
4 添加鼠标的吸引力、击打斥力、扰动力
增加鼠标对一定范围内小球的吸引力,小球距离鼠标越近,吸引力越大。
当鼠标左键按下时,会对一定范围内的小球产生极大斥力。类似往水塘中扔一个石块,距离越近,影响越大。
实现鼠标移动时对粒子的扰动力。类似在水面拿树枝搅动,搅动速度越快,扰动越大。
最终代码如下:
#include <graphics.h>
#include <math.h>
#include <time.h>
#define WIDTH 1024 // 屏幕宽
#define HEIGHT 768 // 屏幕高
#define NUM_MOVERS 800 // 小球数量
#define FRICTION 0.96f // 摩擦力/阻尼系数
// 定义小球结构
struct Mover
{COLORREF color; // 颜色
float x, y; // 坐标
float vX, vY; // 速度
float radius; // 半径
};// 定义全局变量
Mover movers[NUM_MOVERS]; // 小球数组
int mouseX, mouseY; // 当前鼠标坐标
int prevMouseX, prevMouseY; // 上次鼠标坐标
int mouseVX, mouseVY; // 鼠标速度
int isMouseDown; // 鼠标左键是否按下
// 绝对延时
void delay(DWORD ms)
{static DWORD oldtime = GetTickCount(); while(GetTickCount() - oldtime < ms)Sleep(1); oldtime = GetTickCount();
}void startup()
{// 设置随机种子
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 = rand()%WIDTH;movers[i].y = rand()%HEIGHT;movers[i].vX = float(cos(float(i))) * (rand() % 34);movers[i].vY = float(sin(float(i))) * (rand() % 34);movers[i].radius = (rand() % 34)/15.0;}// 初始化鼠标变量,当前鼠标坐标、上次鼠标坐标都在画布中心
mouseX = prevMouseX = WIDTH / 2;mouseY = prevMouseY = HEIGHT / 2;isMouseDown = 0; // 初始鼠标未按下
initgraph(WIDTH, HEIGHT);BeginBatchDraw();
}void show()
{clearrectangle(0,0,WIDTH-1,HEIGHT - 1); // 清空画面全部矩形区域
for(int i = 0; i < NUM_MOVERS; i++){// 画小球
setcolor(movers[i].color);setfillstyle(movers[i].color);fillcircle(int(movers[i].x + 0.5), int(movers[i].y + 0.5), int(movers[i].radius + 0.5));}FlushBatchDraw();delay(5);
}void updateWithoutInput()
{float toDist = WIDTH * 0.86; // 吸引距离,小球距离鼠标在此范围内,会受到向内的吸力
float blowDist = WIDTH * 0.5; // 打击距离,小球距离鼠标在此范围内,会受到向外的斥力
float stirDist = WIDTH * 0.125; // 扰动距离,小球距离鼠标在此范围内,会受到鼠标的扰动
// 前后两次运行间鼠标移动的距离,即为鼠标的速度
mouseVX = mouseX - prevMouseX;mouseVY = mouseY - prevMouseY;// 更新上次鼠标坐标变量,为记录这次鼠标的坐标
prevMouseX = mouseX;prevMouseY = mouseY;for(int i = 0; i < NUM_MOVERS; i++) // 对所有小球遍历
{float x = movers[i].x; // 当前小球坐标
float y = movers[i].y;float vX = movers[i].vX; // 当前小球速度
float vY = movers[i].vY;float dX = x - mouseX; // 计算当前小球位置和鼠标位置的差
float dY = y - mouseY; float d = sqrt(dX * dX + dY * dY); // 当前小球和鼠标位置的距离
// 下面将dX、dY归一化,仅反映方向,和距离长度无关。
if (d!=0){dX = dX/d;dY = dY/d;}else{dX = 0;dY = 0;}// 小球距离鼠标 < toDist,在此范围内小球会受到鼠标的吸引
if (d < toDist){// 吸引力引起的加速度幅度,小球距离鼠标越近,引起的加速度越大。但吸引力的值明显比上面斥力的值要小很多
float toAcc = (1 - (d / toDist)) * WIDTH * 0.0014f;// 由dX、dY归一化方向信息,加速度幅度值toAcc,得到新的小球速度
vX = vX - dX * toAcc;vY = vY - dY * toAcc; }// 当鼠标左键按下,并且小球距离鼠标 < blowDist(在打击范围内,会受到向外的力)
if (isMouseDown && d < blowDist){// 打击力引起的加速度幅度(Acceleration),这个公式表示小球距离鼠标越近,打击斥力引起的加速度越大
float blowAcc = (1 - (d / blowDist)) * 10;// 由上面得到的dX、dY归一化方向信息,上面的加速度幅度值blowAcc,得到新的小球速度
// float(rand()) / RAND_MAX 产生[0,1]之间的随机数
// 0.5f - float(rand()) / RAND_MAX 产生[-0.5,0.5]之间的随机数,加入一些扰动
vX = vX + dX * blowAcc + 0.5f - float(rand()) / RAND_MAX;vY = vY + dY * blowAcc + 0.5f - float(rand()) / RAND_MAX;}// 小球距离鼠标 < stirDist,在此范围内小球会受到鼠标的扰动
if (d < stirDist){// 扰动力引起的加速度幅度,小球距离鼠标越近,引起的加速度越大。扰动力的值更小
float mAcc = (1 - (d / stirDist)) * WIDTH * 0.00026f;// 鼠标速度越快,引起的扰动力越大
vX = vX + mouseVX * mAcc;vY = vY + mouseVY * mAcc; }// 小球运动有一个阻尼(摩擦力),速度逐渐减少
vX = vX * FRICTION;vY = vY * FRICTION;// 速度的绝对值
float avgVX = abs(vX);float avgVY = abs(vY);// 两个方向速度的平均
float avgV = (avgVX + avgVY) * 0.5f;// 因为有上面阻尼的作用,如果速度过小的话,乘以一个[0,3]的随机数,会以比较大的概率让速度变大
if (avgVX < 0.1) vX = vX * float(rand()) / RAND_MAX * 3;if (avgVY < 0.1) vY = vY * float(rand()) / RAND_MAX * 3;// 小球的半径在[0.4,3.5]之间,速度越大,半径越大
float sc = avgV * 0.45f;sc = max(min(sc, 3.5f), 0.4f);movers[i].radius = sc;// 根据位置+速度,更新小球的坐标
float nextX = x + vX;float nextY = y + vY;// 小球如果超过上下左右四个边界的话,位置设为边界处,速度反向
if (nextX > WIDTH) { nextX = WIDTH; vX = -1*vX; }else if (nextX < 0) {nextX = 0; vX = -1*vX; }if (nextY > HEIGHT){ nextY = HEIGHT; vY = -1*vY; }else if (nextY < 0) { nextY = 0; vY = -1*vY; }// 更新小球位置、速度的结构体数组
movers[i].vX = vX;movers[i].vY = vY;movers[i].x = nextX;movers[i].y = nextY;}
}void updateWithInput()
{MOUSEMSG m; // 定义鼠标消息
while (MouseHit()) //检测当前是否有鼠标消息
{m = GetMouseMsg();if (m.uMsg==WM_MOUSEMOVE) // 鼠标移动的话,更新当前鼠标坐标变量
{mouseX = m.x; mouseY = m.y;}else if (m.uMsg==WM_LBUTTONDOWN) // 鼠标左键按下
isMouseDown = 1;else if (m.uMsg==WM_LBUTTONUP) // 鼠标左键抬起
isMouseDown = 0;}
}void gameover()
{EndBatchDraw();closegraph();
}int main()
{startup(); // 数据初始化
while (1) // 游戏循环执行
{show(); // 显示画面
updateWithInput(); // 与用户输入有关的更新
updateWithoutInput(); // 与用户输入无关的更新
}gameover(); // 游戏结束、后续处理
return 0;
}
更多趣味学C语言教程,可以参考我之前写的图书:
如果对趣味学Python感兴趣,也可以参考:
C语言结构体练习-互动粒子仿真相关推荐
- ARM汇编语言实现peek()_ARM汇编之访问C语言结构体数据
前言 本文的写作目的在于装逼,没有要产生实际价值的意思. 前几天在做编译器的项目,有一个项目团队成员一直在问我ARM汇编能不能读C语言的结构体.我心想,我这生成ARM汇编的代码是用C++写的呀,又不是 ...
- C语言结构体和结构体数组示例 - Win32窗口程序演示
C语言结构体和结构体数组的使用: /* C结构体和结构体数组示例,by bobo */#include <windows.h>LRESULT CALLBACK WndProc (HWND, ...
- C语言结构体-大小,对齐,填充,使用及其他
C语言结构体-大小,对齐 C语言中的结构体(struct)的定义 在C语言中,最常用的数据结构就是结构体了,结构体也是其它数据结构(比如链表等)的基础,结构体的使用非常简单. 比如,定义一个结构体: ...
- 关于c语言结构体偏移的一点思考
注:此处只是利用了编译器的特性来计算结构体偏移 这句话就一笔带过,说得有点牵强附会.以后有时间自己再详细了解一下编译器的特性... more exceptional c++ 中文版 26页 https ...
- C语言结构体指针的使用方法
1.首先定义一个结构体,给它取别名: typedef struct node{ struct node * next://指向下一节点 int data://数据域 }pnode,*linklist; ...
- C语言结构体与联合体
c语言结构体与联合体 结构类型定义和结构变量说明 一.结构的定义 二.结构类型变量的说明 结构变量的赋值 结构变量的初始化 结构数组 结构指针变量 其访问的一般形式为: (*结构指针变量).成员名 结 ...
- C语言结构体对齐的不足
该博文为原创文章,未经博主同意不得转载,如同意转载请注明博文出处 本文章博客地址:https://cplusplus.blog.csdn.net/article/details/105065657 C ...
- C语言结构体占用内存总结
C语言结构体占用内存总结 前几天有个小朋友问了我一下,关于C语言结构体占用空间的问题.觉得以后会对小可爱有点帮助,就打算先写一下. struct Test {int a;char b;int c; } ...
- 深入解析JNA—模拟C语言结构体
原帖:http://blog.csdn.net/shendl/article/details/3599849 深入解析JNA-模拟C语言结构体 前言 前几天写<JNA--JNI终结者>一文 ...
最新文章
- MolCLR | 对比学习在分子图表示任务中大有作为
- asp.net2.0导出pdf文件完美解决方案
- 全国计算机等级考试题库二级C操作题100套(第99套)
- scrt如何切换成英文版_英文版SecureCRT显示乱码解决
- BugkuCTF-Crypto题Crack it
- 登陆窗体相关的控件 1124
- javascript函数执行前期变量环境初始化过程
- 从仁慈的独裁者到微软打工人,Python之父和Python将走向何方?
- 记录最近待学习的内容
- linux 安装 Django
- 博科brocade光纤交换机alias-zone的划分--实操案例
- python文件查重_海量文件查重SimHash和Minhash
- Kubernetes CICD发布架构流程思路
- 小心,家中路由器发出的WiFi信号可能让你家变“透明”
- NAT alg 和 ASPF
- 常见光纤接头LC、FC、SC、ST
- uniapp中使用百度名片识别接口或名片全能王识别接口
- VS1005 HiRes 高清播放器方案
- vivo手机里的log是什么意思?
- 网络安全:网络信息安全的概述.
热门文章
- 计算机视觉——主干网络的学习笔记
- 一行命令配置深度学所需所有环境PyTorch, TensorFlow, CUDA, cuDNN, and NVIDIA Drivers.
- Community宣言
- keras中的loss、optimizer、metrics
- SpringMVC中的Controller默认单例
- [转]论acm与泡妞
- 东大OJ-Max Area
- LeetCode for SQL 176. 第二高的薪水 (ifnull limit order by)
- CleanCodeHandbook Chapter 3: Linked List(20-24)
- 【机房收费系统】--SSTab控件与MSHFlexGrid控件