本文将深入解析一个基于 Qt/C++ 的像素点模拟程序。程序通过 重力作用,将随机分布的像素点下落并水平堆叠,同时支持窗口动态拉伸后重新计算像素点分布。
程序功能概述
- 随机生成像素点:程序在初始化时随机生成一定数量的像素点,每个像素点有随机的初始位置和速度。
- 重力模拟:像素点在重力作用下不断下落,并与地面发生弹性碰撞。
- 水平堆叠:像素点在下落后静止并水平堆叠,模拟堆积效果。
- 碰撞检测:像素点之间检测是否发生碰撞并进行弹性处理。
- 窗口自适应:当窗口大小变化时,自动重新分布像素点。
核心实现细节
1. 像素点结构体
像素点通过 Pixel
结构体定义,包含以下属性:
x
和y
:像素点的二维位置。vx
和vy
:像素点的水平和垂直速度。isStatic
:标记像素点是否已经静止。
定义如下:
struct Pixel {
double x, y; // 像素点位置
double vx, vy; // 像素点速度
bool isStatic; // 是否已经静止
};
2. 重力与碰撞模拟
重力计算
每个像素点的垂直速度受重力影响,逐帧增加重力值:
p.vy += gravity;
像素点的速度更新后,其位置随之更新:
p.x += p.vx;
p.y += p.vy;
边界检测
为了防止像素点越过窗口边界,加入边界检测与反弹逻辑:
if (p.x < radius || p.x > width() - radius) {
p.vx *= -restitution; // 水平方向的反弹
p.x = clamp(p.x, (double)radius, (double)(width() - radius));
}
在这里:
radius
是像素点的半径,防止越界。restitution
控制反弹的弹性,值为0-1
,1
表示完全弹性,0
表示无弹性。
碰撞地面
当像素点接触地面时,垂直速度反弹,同时逐渐水平减速:
if (p.y > groundLevel) {
p.vy *= -restitution; // 垂直方向反弹
p.y = groundLevel; // 防止越过地面
p.vx *= damping;
p.vy *= damping;
}
groundLevel
表示地面高度。damping
表示速度阻尼系数,用于减小速度,模拟摩擦力。
3. 静止与堆叠机制
像素点在速度足够小的情况下会被标记为静止:
if (std::abs(p.vx) < staticThreshold && std::abs(p.vy) < staticThreshold) {
p.vx = 0;
p.vy = 0;
p.isStatic = true;
}
静止后,像素点不再更新速度和位置,从而节省计算资源。
水平堆叠的网格机制
为了实现堆叠效果,使用了一个二维网格 grid
来记录像素点的静止位置:
std::vector<std::vector<bool>> grid(gridWidth, std::vector<bool>(gridHeight, false));
每个网格单元表示一个像素点的静止状态。当像素点静止后,更新对应的网格状态:
int gridX = p.x / (2 * radius);
int gridY = groundLevel / (2 * radius);
if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) {
grid[gridX][gridY] = true;
}
通过检测 grid
的状态,确保新下落的像素点不会与静止点重叠。
4. 碰撞处理
当两个像素点之间的距离小于其直径时,认为发生了碰撞。处理逻辑如下:
double distance = std::sqrt(dx * dx + dy * dy);
if (distance < 2 * radius && distance > 1e-6) { // 避免除以零
double overlap = 2 * radius - distance;
double nx = dx / distance;
double ny = dy / distance;
p.x += nx * overlap / 2;
p.y += ny * overlap / 2;
other.x -= nx * overlap / 2;
other.y -= ny * overlap / 2;
// 水平速度调整
p.vx *= damping;
other.vx *= damping;
}
这里:
overlap
表示像素点之间的重叠距离。nx
和ny
是碰撞方向的单位向量。- 调整每个像素点的位置以消除重叠,并减小速度。
5. 窗口自适应
窗口大小改变事件
当窗口大小改变时,重新初始化像素点,以适应新的窗口尺寸。通过重载 resizeEvent
实现:
void GravityWidget::resizeEvent(QResizeEvent *event) {
initializePixels(); // 重新初始化像素点
QWidget::resizeEvent(event);
}
像素点重新初始化
重新生成随机分布的像素点:
void GravityWidget::initializePixels() {
pixels.clear();
for (int i = 0; i < 200; ++i) {
Pixel p;
p.x = std::rand() % width();
p.y = std::rand() % height();
p.vx = 0;
p.vy = 0;
p.isStatic = false;
pixels.push_back(p);
}
}
6. 图形绘制
通过重载 paintEvent
实现像素点的绘制:
void GravityWidget::paintEvent(QPaintEvent *) {
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setBrush(Qt::blue);
for (const auto &p : pixels) {
painter.drawEllipse(QPointF(p.x, p.y), 3, 3); // 绘制小圆点
}
}
运行效果
- 重力下落:随机生成的像素点在重力作用下逐渐下落。
- 弹性碰撞:像素点彼此碰撞时会弹开,最终逐渐稳定。
- 水平堆叠:像素点在地面静止后自然堆叠,形成类似水波的排列效果。
- 窗口自适应:拉伸或缩小窗口时,像素点会重新生成并分布。