线下的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语言结构体练习-互动粒子仿真相关推荐

  1. ARM汇编语言实现peek()_ARM汇编之访问C语言结构体数据

    前言 本文的写作目的在于装逼,没有要产生实际价值的意思. 前几天在做编译器的项目,有一个项目团队成员一直在问我ARM汇编能不能读C语言的结构体.我心想,我这生成ARM汇编的代码是用C++写的呀,又不是 ...

  2. C语言结构体和结构体数组示例 - Win32窗口程序演示

    C语言结构体和结构体数组的使用: /* C结构体和结构体数组示例,by bobo */#include <windows.h>LRESULT CALLBACK WndProc (HWND, ...

  3. C语言结构体-大小,对齐,填充,使用及其他

    C语言结构体-大小,对齐 C语言中的结构体(struct)的定义 在C语言中,最常用的数据结构就是结构体了,结构体也是其它数据结构(比如链表等)的基础,结构体的使用非常简单. 比如,定义一个结构体: ...

  4. 关于c语言结构体偏移的一点思考

    注:此处只是利用了编译器的特性来计算结构体偏移 这句话就一笔带过,说得有点牵强附会.以后有时间自己再详细了解一下编译器的特性... more exceptional c++ 中文版 26页 https ...

  5. C语言结构体指针的使用方法

    1.首先定义一个结构体,给它取别名: typedef struct node{ struct node * next://指向下一节点 int data://数据域 }pnode,*linklist; ...

  6. C语言结构体与联合体

    c语言结构体与联合体 结构类型定义和结构变量说明 一.结构的定义 二.结构类型变量的说明 结构变量的赋值 结构变量的初始化 结构数组 结构指针变量 其访问的一般形式为: (*结构指针变量).成员名 结 ...

  7. C语言结构体对齐的不足

    该博文为原创文章,未经博主同意不得转载,如同意转载请注明博文出处 本文章博客地址:https://cplusplus.blog.csdn.net/article/details/105065657 C ...

  8. C语言结构体占用内存总结

    C语言结构体占用内存总结 前几天有个小朋友问了我一下,关于C语言结构体占用空间的问题.觉得以后会对小可爱有点帮助,就打算先写一下. struct Test {int a;char b;int c; } ...

  9. 深入解析JNA—模拟C语言结构体

    原帖:http://blog.csdn.net/shendl/article/details/3599849 深入解析JNA-模拟C语言结构体 前言 前几天写<JNA--JNI终结者>一文 ...

最新文章

  1. MolCLR | 对比学习在分子图表示任务中大有作为
  2. asp.net2.0导出pdf文件完美解决方案
  3. 全国计算机等级考试题库二级C操作题100套(第99套)
  4. scrt如何切换成英文版_英文版SecureCRT显示乱码解决
  5. BugkuCTF-Crypto题Crack it
  6. 登陆窗体相关的控件 1124
  7. javascript函数执行前期变量环境初始化过程
  8. 从仁慈的独裁者到微软打工人,Python之父和Python将走向何方?
  9. 记录最近待学习的内容
  10. linux 安装 Django
  11. 博科brocade光纤交换机alias-zone的划分--实操案例
  12. python文件查重_海量文件查重SimHash和Minhash
  13. Kubernetes CICD发布架构流程思路
  14. 小心,家中路由器发出的WiFi信号可能让你家变“透明”
  15. NAT alg 和 ASPF
  16. 常见光纤接头LC、FC、SC、ST
  17. uniapp中使用百度名片识别接口或名片全能王识别接口
  18. VS1005 HiRes 高清播放器方案
  19. vivo手机里的log是什么意思?
  20. 网络安全:网络信息安全的概述.

热门文章

  1. 计算机视觉——主干网络的学习笔记
  2. 一行命令配置深度学所需所有环境PyTorch, TensorFlow, CUDA, cuDNN, and NVIDIA Drivers.
  3. Community宣言
  4. keras中的loss、optimizer、metrics
  5. SpringMVC中的Controller默认单例
  6. [转]论acm与泡妞
  7. 东大OJ-Max Area
  8. LeetCode for SQL 176. 第二高的薪水 (ifnull limit order by)
  9. CleanCodeHandbook Chapter 3: Linked List(20-24)
  10. 【机房收费系统】--SSTab控件与MSHFlexGrid控件