Qt/C++实现的程序演示了使用激光雷达构建地图的整个过程,创建随机地图,使用上下左右键来控制...
Qt/C++实现的程序演示了使用激光雷达构建地图的整个过程,创建随机地图,使用上下左右键来控制小车的运动,小车在运动的过程中右上角会实时创建以点组成的地图,以点的形式显示小车周围的地图,右下角显示小车在场景中的方向,鼠标可以在地图添加图块障碍物,每一步都会有日志进行记录。 源码: 在Windows和Ubuntu 18.04平台下使用Qt5.13.1_MinGW、Qt5.15.1_MinGW编译运行通过,其他版本请自行尝试。
敲下方向键的瞬间,红色小车在屏幕中央划出一道轨迹。右上角的点云地图像墨水晕染般展开,右下角的指南针指针微微颤动——这个用Qt/C++实现的激光雷达模拟器,把SLAM的趣味性直接拉满。

地图生成器藏在Map类里玩骰子:
void Map::generateRandomMap(int width, int height) {
std::uniform_real_distribution<double> dist(0, 1);
for(int x=0; x<width; x+=10) {
for(int y=0; y<height; y+=10) {
if(dist(rng) < 0.15) { // 15%概率生成障碍物
addObstacle(QPointF(x + 5, y + 5)); // 在格子中心生成
}
}
}
}
这段代码用均匀分布随机撒点,10x10的网格里每隔15%的概率种下障碍物。注意这里有个骚操作:障碍物坐标加了5像素偏移,让障碍物刚好落在网格中心,避免后续碰撞检测时出现边缘问题。
键盘事件处理直接挂钩主窗口:
void MainWindow::keyPressEvent(QKeyEvent *event) {
const double step = 3.0; // 步长别太大,会穿墙
QPointF delta(0, 0);
switch(event->key()) {
case Qt::Key_Left: delta.rx() -= step; break;
case Qt::Key_Right: delta.rx() += step; break;
case Qt::Key_Up: delta.ry() -= step; break;
case Qt::Key_Down: delta.ry() += step; break;
}
if(!map->checkCollision(carPos + delta)) { // 预判碰撞
carPos += delta;
updateLidarScan(); // 触发雷达扫描
}
}
方向键处理看似简单,但checkCollision里的射线检测才是精髓。注意移动前先做碰撞预测,避免小车卡进墙里——这点在游戏开发里也是常见套路。

激光雷达的模拟扫描藏着三角函数狂欢:
void Lidar::scan(const QPointF& pos, double yaw) {
points.clear();
for(int angle = 0; angle < 360; angle += 5) { // 5度步进扫描
double rad = qDegreesToRadians(angle + yaw);
QPointF dir(qCos(rad), qSin(rad));
for(double r=0; r<maxRange; r+=0.5) {
QPointF p = pos + dir * r;
if(map->hasObstacle(p)) {
points.append(p);
break; // 找到障碍物就停
}
}
}
}
这段代码模拟了激光束的物理传播过程。角度循环里的0.5步长是个平衡点——太小了计算量爆炸,太大了会漏检。实际工程中可能需要根据帧率动态调整。
实时点云绘制用QGraphicsScene实现起来异常简单:
void MapView::updatePointCloud(const QList<QPointF>& points) {
scene->clear();
for(auto& p : points) {
scene->addEllipse(p.x(), p.y(), 2, 2, QPen(), QBrush(Qt::blue));
}
// 顺便画个小车位置
scene->addEllipse(carPos.x()-5, carPos.y()-5, 10, 10,
QPen(Qt::red), QBrush(Qt::darkRed));
}
虽然每次全量清空重绘有点暴力,但在点数不多的情况下完全hold住。如果要优化,可以用对象池复用已有的图形项。

鼠标交互的障碍物添加直接挂钩场景的鼠标事件:
connect(scene, &QGraphicsScene::mousePressEvent, [this](QGraphicsSceneMouseEvent* event){
if(event->button() == Qt::LeftButton) {
QPointF pos = event->scenePos();
map->addObstacle(pos); // 添加障碍物
log("用户添加障碍物于 " + QString::number(pos.x()) + ", " + QString::number(pos.y()));
}
});
Lambda表达式让事件处理变得直白。注意这里用scenePos而不是屏幕坐标,避免转换失误。
编译时遇到找不到QtCharts的问题?去pro文件里加一句:
QT += core gui widgets charts
这个坑笔者踩过三次才长记性。运行效果嘛——就像在玩简化版的《元气骑士》,只不过地图是你自己亲手“扫”出来的。
更多推荐


所有评论(0)