桌面应用环境搭建及实现图形界面(2021030200010003)
本文基于2020年全国大学生数学建模大赛B题题目软件编写(总)(2021022100010001)对其进行应用环境搭建。
文章目录
一、涉及内容
- QT 4.3.0
- C++
- drawPolygon()函数
- CAXA建模(主要是CAD过期了懒得再弄软件,用3d建模软件代替实现地图建模)
- MATLAB
二、实际效果演示
题目中要求的地图:
三、实现过程
1、C++基础以及Qt库的使用
第一步当然是必须要会的基础啦!限于篇幅也无法具体介绍,有困难的朋友可以先自行补充一下相关知识。当然软件编程语言也可以使用Python、Java等其他语言代替,大佬们可自行使用。
2、地图节点获取
由于地图是他人提供的,本身信息非常稀少,必须获得有效信息——节点,才能更好的完成其他操作。
方案一: 利用OpenCV进行图片处理,读取RGB参数并涉及算法求出各个节点。
该方案设计知识点较多,且图片本身有效线段像素点并不多,误差较大。已经抓取点坐标后还需要设计算法进行节点获取,本人较懒最后使用了方案二。
方案二: 通过建模软件仿照题目地图进行平面建模,再利用软件直接获取各个点的坐标。最后利用MATLAB进行简单地验证。
(1)CAXA草图建模
(2) 节点坐标收集
(3) Matlab绘图(附代码)
main.m
clear,clc,clf
MyData = xlsread('.\MapData','Map1');
BlockNumber = size(MyData,1); % 地图块个数为MyData的行数
PointNumber = zeros(1, BlockNumber); % 存每个地图块点的个数
for i = 1:BlockNumber
PointNumber(1, i) = MyData(i, 1); % MyData
end
% 一个地图块一个地图块画
for i = 1:BlockNumber
PaintPolygon(MyData(i,:), PointNumber(1,i));
end
PaintPolygon.m
function PaintPolygon(Arr, PNumber)
figure(1) % 设置画布
axis ij % 设置画布原点为左上角
axis([0,960,0,1280])
for i = 2:2:PNumber*2
if i == PNumber*2
x1 = Arr(1, i); y1 = Arr(1, i+1);
x2 = Arr(1, 2); y2 = Arr(1, 3);
else
x1 = Arr(1, i); y1 = Arr(1, i+1);
x2 = Arr(1, i+2); y2 = Arr(1, i+3);
end
line([x1,x2],[y1,y2],'linestyle','-',...
'color','r');
end
3、数据导入Qt并利用drawPolygon()绘制多边形
int PointNumber[]; // 记录每个多边形的顶点个数
int BlockNumber; // 记录地图中地图块的个数
QPoint pointArr[x][y]; // 记录x个地图块的 y/2 个坐标
// 画地图
QPen pen = QPen(QColor(this->mPenColor)); // 创建画笔对象
pen.setWidth(5);
this->mPainter->setPen(pen); // 给画家对象画笔
//画多边形
for (int i = 0; i < BlockNumber; i++) // 遍历
{
// 如果天气为沙暴,除了自身为蓝色外 其他的均为红色
// 可走路径为绿色,不可走路径正常为黄色
if(i == this->PlayerPosNow)
this->mPainter->setBrush(Qt::green);
else
this->mPainter->setBrush(Qt::yellow);
// qDebug() << "开始填充: " << QString::number(i) << "号地图块";
this->mPainter->drawPolygon(pointArr[i],PointNumber[i]);
}
4、鼠标点击地图块有效判断
(1)取得外轮廓节点坐标数据
(2)判断理论依据
(3)实际代码与效果演示
bool MainWindow::IsClickedInside()
{
// 1. 得到鼠标点击位置的水平方程 y=?
// 2. 求直线与线段的交点
// 3. 把交点坐标 按照 x坐标排序
// 4. 判断内外
}
GIF动图展示获取轮廓交点
只需要利用鼠标点击事件调用图片刷新并在画图事件中使用如下代码即可实现:
bool MainWindow::IsClickedInside()
{
QPen pen = QPen(QColor(Qt::red)); // 创建画笔对象
pen.setWidth(10);
this->mPainter->setPen(pen); // 给画家对象画笔
int xArr[50], xArr_Num = 0;
// 1. 得到鼠标点击位置的水平方程 y=?
// 2. 求直线与线段的交点
for(int i = 0; i < OutlineNumber; i++)
{
int mfirst, msecond; // 获得每次计算用的轮廓端点
if(i == OutlineNumber - 1)
mfirst = i, msecond = 0;
else
mfirst = i, msecond = i + 1;
if((this->MousePos.y() >= OutlineArr[mfirst].y() && this->MousePos.y() <= OutlineArr[msecond].y()) ||
(this->MousePos.y() >= OutlineArr[msecond].y() && this->MousePos.y() <= OutlineArr[mfirst].y()))
{
// qDebug() << "第[" << QString::number(i+1) << "]组轮廓节点有效";
// 如果y在任意两轮廓点的纵坐标内 则有可能有效进行计算 否则直接跳过
if(OutlineArr[mfirst].x() == OutlineArr[msecond].x())
{
// 直接记录
qDebug() << "直接记录!";
xArr[xArr_Num] = OutlineArr[mfirst].x();
mPainter->drawEllipse(QPoint(xArr[xArr_Num],this->MousePos.y()),3,3);
xArr_Num = xArr_Num + 1;
continue;
}
else if(OutlineArr[mfirst].y() == OutlineArr[msecond].y())
{
// 斜率为0 说明两轮廓点平行 判断 x 看在线段外还是内 如果在线段外则算一个 线段中则直接返回结论
if((this->MousePos.x() >= OutlineArr[mfirst].x() && this->MousePos.x() <= OutlineArr[msecond].x()) ||
(this->MousePos.x() >= OutlineArr[msecond].x() && this->MousePos.x() <= OutlineArr[mfirst].x()))
{
// 说明鼠标点击位置在线段上 判断为在地图外
qDebug() << "在轮廓上 判断为在地图外!";
return false;
}
else
{
// 随便记录一个线段端点
qDebug() << "随便记录!";
xArr[xArr_Num] = OutlineArr[mfirst].x();
mPainter->drawEllipse(QPoint(xArr[xArr_Num],this->MousePos.y()),3,3);
xArr_Num = xArr_Num + 1;
continue;
}
}
else
{
qDebug() << "求x坐标记录!";
// 求交点 x坐标 并判断是否满足条件
float k = (float)(OutlineArr[mfirst].y() - OutlineArr[msecond].y()) / (float)(OutlineArr[mfirst].x() - OutlineArr[msecond].x());
qDebug() << "mfirst x:" << QString::number(OutlineArr[mfirst].x()) << " y:" << QString::number(OutlineArr[mfirst].y())
<< "msecond x:" << QString::number(OutlineArr[msecond].x()) << " y:" << QString::number(OutlineArr[msecond].y())
<< "k :" << QString("%1").arg(k);
// 计算 x坐标
xArr[xArr_Num] = (this->MousePos.y()-OutlineArr[mfirst].y())/k + OutlineArr[mfirst].x();
mPainter->drawEllipse(QPoint(xArr[xArr_Num],this->MousePos.y()),3,3);
xArr_Num = xArr_Num + 1;
}
}
else
{
// qDebug() << "第[" << QString::number(i+1) << "]组轮廓节点无效";
continue;
}
}
for(int i = 0; i < xArr_Num; i++)
{
qDebug() << "第[" << QString::number(i+1) << "]个x坐标为:" << QString::number(xArr[i]);
}
return false;
// 3. 把交点坐标 按照 x坐标排序
// 4. 判断内外
}
判断鼠标点击位置在地图内or外
稍加修改 传入目标轮廓数组及数组大小即可判断任意鼠标点击位置是否在任意闭合图形内外:
bool MainWindow::IsClickedInside(QPoint* mArr, int bNum)
{
// QPen pen = QPen(QColor(Qt::red)); // 创建画笔对象
// pen.setWidth(10);
// this->mPainter->setPen(pen); // 给画家对象画笔
int xArr[50], xArr_Num = 0;
// 1. 得到鼠标点击位置的水平方程 y=?
// 2. 求直线与线段的交点
for(int i = 0; i < bNum; i++)
{
int mfirst, msecond; // 获得每次计算用的轮廓端点
if(i == bNum - 1)
mfirst = i, msecond = 0;
else
mfirst = i, msecond = i + 1;
if((this->MousePos.y() >= mArr[mfirst].y() && this->MousePos.y() <= mArr[msecond].y()) ||
(this->MousePos.y() >= mArr[msecond].y() && this->MousePos.y() <= mArr[mfirst].y()))
{
// qDebug() << "第[" << QString::number(i+1) << "]组轮廓节点有效";
// 如果y在任意两轮廓点的纵坐标内 则有可能有效进行计算 否则直接跳过
if(mArr[mfirst].x() == mArr[msecond].x())
{
// 直接记录
// qDebug() << "直接记录!";
xArr[xArr_Num] = mArr[mfirst].x();
mPainter->drawEllipse(QPoint(xArr[xArr_Num],this->MousePos.y()),3,3);
xArr_Num = xArr_Num + 1;
continue;
}
else if(mArr[mfirst].y() == mArr[msecond].y())
{
// 斜率为0 说明两轮廓点平行 判断 x 看在线段外还是内 如果在线段外则算一个 线段中则直接返回结论
if((this->MousePos.x() >= mArr[mfirst].x() && this->MousePos.x() <= mArr[msecond].x()) ||
(this->MousePos.x() >= mArr[msecond].x() && this->MousePos.x() <= mArr[mfirst].x()))
{
// 说明鼠标点击位置在线段上 判断为在地图外
// qDebug() << "在轮廓上 判断为在地图外!";
return false;
}
else
{
// 随便记录一个线段端点
// qDebug() << "随便记录!";
xArr[xArr_Num] = mArr[mfirst].x();
mPainter->drawEllipse(QPoint(xArr[xArr_Num],this->MousePos.y()),3,3);
xArr_Num = xArr_Num + 1;
continue;
}
}
else
{
// qDebug() << "求x坐标记录!";
// 求交点 x坐标 并判断是否满足条件
float k = (float)(mArr[mfirst].y() - mArr[msecond].y()) / (float)(mArr[mfirst].x() - mArr[msecond].x());
// qDebug() << "mfirst x:" << QString::number(mArr[mfirst].x()) << " y:" << QString::number(mArr[mfirst].y())
// << "msecond x:" << QString::number(mArr[msecond].x()) << " y:" << QString::number(mArr[msecond].y())
// << "k :" << QString("%1").arg(k);
// 计算 x坐标
xArr[xArr_Num] = (this->MousePos.y()-mArr[mfirst].y())/k + mArr[mfirst].x();
mPainter->drawEllipse(QPoint(xArr[xArr_Num],this->MousePos.y()),3,3);
xArr_Num = xArr_Num + 1;
}
}
else
{
// qDebug() << "第[" << QString::number(i+1) << "]组轮廓节点无效";
continue;
}
}
// 3. 把交点坐标 按照 x坐标排序 并剔除相同的
int buf;
for(int i = 0; i < xArr_Num-1; i++)
{
for(int j = 0; j < xArr_Num-1-i; j++)
{
if(xArr[j] > xArr[j+1])
{
buf = xArr[j];
xArr[j] = xArr[j+1];
xArr[j+1] = buf;
}
}
}
// for(int i = 0; i<xArr_Num; i++)
// qDebug() << "第[" << QString::number(i+1) << "]个x坐标为:" << QString::number(xArr[i]);
int xData[50], xDataNum = 0;
for(int i = 0; i<xArr_Num; i++)
{
if(xDataNum != 0 && xData[xDataNum-1] == xArr[i]) // 如果当前x 和 上一个存进去的x 相同 说明重复 直接continue
continue;
xData[xDataNum] = xArr[i];
xDataNum = xDataNum + 1;
}
int xleft = 0;
for(int i = 0; i<xDataNum; i++)
{
// qDebug() << "第[" << QString::number(i+1) << "]个x坐标为:" << QString::number(xData[i]);
if(xData[i] < this->MousePos.x())
{
xleft = xleft + 1;
}
}
// 4. 判断内外
if(xleft%2) // 奇数个
return true;
else
return false;
}
获得鼠标点击位置实际地图块:
if(this->IsClickedInside(OutlineArr, OutlineNumber)) // 如果鼠标点击在内 则判断点击的是哪个地图块
{
for(int i = 0; i < BlockNumber; i++)
{
if(this->IsClickedInside(pointArr[i], PointNumber[i]))
{
qDebug() << "在【" << QString::number(i+1) << "】号地图块";
}
}
// qDebug() << "在地图内";
}