机器人巡检
一、整体框架
主要包括三个类:场景类(CChangJing);机器人类(小车(CCar)、无人机(CWuRenJi));煤气泄露类(CMeiQiXieLou)。
二、场景类
目标主要是数据的测量。通过读取文本文件中测量的数据,将建筑和路面在屏幕上显示出来。同时还要实现场景的缩放以及拖动。
所有的一切都是以“数据”为核心,对数据的测量务必要持严谨的精神。通过百度地图进行测量,测量方法如下图所示。
主要是四个函数:画建筑、画路面、初始化建筑、初始化路面。
(1)画建筑,用四边形表示,for循环,MoveTo,LineTo即可,同时还要输出建筑名。
(2)画路面,用两条线表示,比如弯曲的路,用“点”表示弯曲点,for循环次数也就是线上点的个数,同样MoveTo,LineTo。
(3)初始化建筑,用fscanf从文本文件中读取测量好的数据。
**注意:**格式!如果格式稍微错一点,可能就读取不出来,这是用fscanf读取文件时千万要注意的地方。同时,由于建筑名是中文,所以还需要在记事本中将文件另存为下面的编码格式改为“ANSI”,这样读取中文时就不会乱码。
(4)初始化路面,用CStdioFile类定义一个文件,ReadString每次读一行字符串。文件中,每行“:”前面是路名,找到“:”后,将左边的提取出来并删除;冒号后面第一个数据代表“路上点的个数”,再后两位是“路名位置坐标”,剩下的是“路上点的坐标”。后面提取的做法是找“空格”,当找到结尾的时候,会返回一个-1,用一个if条件判断,输出最后一个数据。
注意:核心是如何去解析每一行的字符串。
(5)添加鼠标滚轮和鼠标移动事件,实现平面图的缩放及拖动平移,主要是改变比例尺。
1、新建一个MFC单文档应用程序,如下图所示。
2、鼠标右击“JQRXJ classes”,选择第二项“New Class…”,点击,如下图所示。
3、这个类要有坐标原点,比例尺,建筑和路面数组。定义好这些变量后,马上对其进行初始化,如下图所示。
在构造函数里的初始化代码如下:
CChangJing::CChangJing()
{
m_YD.x = 950;
m_YD.y = 450;//假设原点在屏幕的正中间(分辨率是1900*900)
m_kx = 1900.0/1600;//东西长1.6公里
m_ky = -900.0/1000;//南北长1公里
m_nJZ = 0;
InitialDataJianZhu();
InitialDataLuMian();
}
4、如果将建筑和路面的初始化代码也放在构造函数里,将会有大量的重复代码,为了提高代码质量,可分别另写两个初始化建筑和路面的函数,如下图所示。
(1)初始化建筑
代码如下:
void CChangJing::InitialDataJianZhu()
{
int i = 0;
FILE *fp;
fp = fopen("G:\\ JianZhu.txt","r");
while(1)
{
if( fscanf(fp,"%s %f %f %f %f %f %f %f %f %f %f",m_JZ[i].str,&m_JZ[i].Pos[0],&m_JZ[i].Pos[1],&m_JZ[i].DD[0][0],&m_JZ[i].DD[0][1],&m_JZ[i].DD[1][0],&m_JZ[i].DD[1][1],&m_JZ[i].DD[2][0],&m_JZ[i].DD[2][1],&m_JZ[i].DD[3][0],&m_JZ[i].DD[3][1]) != EOF)
i++;
else
break;
}
m_nJZ = i;
fclose(fp);
}
建筑的测量数据如下图所示。
(2)初始化路面
代码如下:
void CChangJing::InitialDataLuMian()
{
int i = 0,j;
int n;
CString str,luming,temp;//luming—路名
CStdioFile f;
f.Open("G:\\ LuMian.txt",CFile::modeRead);
while (f.ReadString(str))
{
//路名
n = str.Find(':');
m_LM[i].Name = str.Left(n);//取n值左边的路名
str.Delete(0,n+2);//从0开始删除,删到:后面的空格(删除的内容有“路名”、“:”、“空格”),删n+2个
//路边的点的个数n
n = str.Find(' ');//找空格
temp = str.Left(n);//输出一条路的其中一条线上点的个数
m_LM[i].n = atoi(temp);//直接将字符串转化为一个整数
//路名的位置坐标
str.Delete(0,n+1);//将一条路的一条线上点的个数以及后面的空格删除
n = str.Find(' ');//还是找空格
temp = str.Left(n);
m_LM[i].Pos[0] = atof(temp);//直接将字符串转化为一个实数
str.Delete(0,n+1);//将一条路的一条线上点的个数以及后面的空格删除
n = str.Find(' ');//还是找空格
temp = str.Left(n);
m_LM[i].Pos[1] = atof(temp);//直接将字符串转化为一个实数
//路上点的坐标(用两条线画路)
for (j=0;j<m_LM[i].n;j++)
{
str.Delete(0,n+1);
n = str.Find(' ');
temp = str.Left(n);
m_LM[i].DD1[j][0] = atof(temp);//第i条路的第j个点的横坐标
str.Delete(0,n+1);
n = str.Find(' ');
if(-1 == n)//找到了结尾(结尾必定是纵坐标)
temp = str;
else
temp = str.Left(n);
m_LM[i].DD1[j][1] = atof(temp);//第i条路的第j个点的纵坐标
}
for (j=0;j<m_LM[i].n;j++)
{
str.Delete(0,n+1);
n = str.Find(' ');
temp = str.Left(n);
m_LM[i].DD2[j][0] = atof(temp);//第i条路的第j个点的横坐标
str.Delete(0,n+1);
n = str.Find(' ');
if(-1 == n)//找到了结尾(结尾必定是纵坐标)
temp = str;
else
temp = str.Left(n);
m_LM[i].DD2[j][1] = atof(temp);//第i条路的第j个点的纵坐标
}
i++;
}
m_nLM = i;
}
路面的测量数据如下图所示。
5、接下来就是画建筑和路面,如下图所示。
(1)Draw(CDC *p)
代码如下:
void CChangJing::Draw(CDC *p)
{
int x,y,r;
pDC = p;
x = m_YD.x;
y = m_YD.y;
r = 10;
pDC->Ellipse(x-r,y-r,x+r,y+r);//画中心点
DrawJianZhu();
DrawLuMian();
}
(2)DrawJianZhu()
代码如下:
void CChangJing::DrawJianZhu()
{
int x,y;
int i,j;
for (i=0;i<m_nJZ;i++)
{
x = m_YD.x + m_kx*m_JZ[i].DD[3][0];
y = m_YD.y + m_ky*m_JZ[i].DD[3][1];//从3开始循环可以少画一条重复的线
pDC->MoveTo(x,y);
for (j=0;j<4;j++)
{
x = m_YD.x + m_kx*m_JZ[i].DD[j][0];
y = m_YD.y + m_ky*m_JZ[i].DD[j][1];
pDC->LineTo(x,y);//画建筑
x = m_YD.x + m_kx*m_JZ[i].Pos[0];
y = m_YD.y + m_ky*m_JZ[i].Pos[1];
pDC->TextOut(x,y,m_JZ[i].str);//输出建筑名
}
}
}
(3)DrawLuMian()
代码如下:
void CChangJing::DrawLuMian()
{
int x,y,r;
int i,j;
for (i=0;i<m_nLM;i++)
{
x = m_YD.x + m_kx*m_LM[i].DD1[0][0];
y = m_YD.y + m_ky*m_LM[i].DD1[0][1];
pDC->MoveTo(x,y);
for (j=1;j<m_LM[i].n;j++)
{
x = m_YD.x + m_kx*m_LM[i].DD1[j][0];
y = m_YD.y + m_ky*m_LM[i].DD1[j][1];
pDC->LineTo(x,y);//画路面其中一条线
}
x = m_YD.x + m_kx*m_LM[i].Pos[0];
y = m_YD.y + m_ky*m_LM[i].Pos[1];
//=================================================
x = m_YD.x + m_kx*m_LM[i].DD2[0][0];
y = m_YD.y + m_ky*m_LM[i].DD2[0][1];
pDC->MoveTo(x,y);
for (j=1;j<m_LM[i].n;j++)
{
x = m_YD.x + m_kx*m_LM[i].DD2[j][0];
y = m_YD.y + m_ky*m_LM[i].DD2[j][1];
pDC->LineTo(x,y);//画路面其中一条线
}
x = m_YD.x + m_kx*m_LM[i].Pos[0];
y = m_YD.y + m_ky*m_LM[i].Pos[1];
//pDC->TextOut(x,y,m_LM[i].Name);
}
}
6、画好之后,就去CJQRXJView里调用,如下图所示。
7、最后在OnDraw里调用,代码如下:
void CJQRXJView::OnDraw(CDC* pDC)
{
CJQRXJDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
m_ChangJing.Draw(pDC);
}
8、建筑和路面已经画好了,现在来实现鼠标滚轮的缩放以及平面图的平移,如下图所示。
(1)鼠标滚轮缩放
鼠标右击“CJQRXJView”,选中第五项“Add Windows Message Handler…”,添加“WM_MOUSEWHEEL”,如下图所示。
代码如下:
BOOL CJQRXJView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
// TODO: Add your message handler code here and/or call default
if (zDelta > 0)
{
m_ChangJing.m_kx *= 1.1;
m_ChangJing.m_ky *= 1.1;
}
if (zDelta < 0)
{
m_ChangJing.m_kx *= 0.9;
m_ChangJing.m_ky *= 0.9;
}
Invalidate(TRUE);
return CView::OnMouseWheel(nFlags, zDelta, pt);
}
(2)平移
在CJQRXJView类里定义一些标记,并初始化,如下图所示。
鼠标右击“CJQRXJView”,选中第五项“Add Windows Message Handler…”,添加“WM_LBUTTONDOWN、WM_LBUTTONUP、WM_MOUSEMOVE”,如下图所示。
代码如下:
void CJQRXJView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
m_BJ_LBD =1;
m_TempYD = m_ChangJing.m_YD;
m_P_LBD = point;
CView::OnLButtonDown(nFlags, point);
}
void CJQRXJView::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
m_BJ_LBD = 0;
Invalidate(TRUE);
CView::OnLButtonUp(nFlags, point);
}
void CJQRXJView::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
if (m_BJ_LBD)
{
m_ChangJing.m_YD = m_TempYD + (point - m_P_LBD);
Invalidate(TRUE);
}
CView::OnMouseMove(nFlags, point);
}
9、此时,第一模块就完成了,编译运行结果如下图所示。
(1)不缩放且不平移的画面
(2)缩小且平移后的画面
(3)放大且平移后的画面
三、机器人类
(一)小车类(CCar)
1、鼠标右击“JQRXJ classes”,选择第二项“New Class…”,点击,新建一个CCar类,如下图所示。
2、添加变量,并进行初始化,如下图所示。
3、从简单的入手,先将小车画出来,如下图所示。
代码如下:
//画小车外形
void CCar::DrawWaiXing()
{
int x,y,r,r1,r2;
x = m_YD.x + m_x*m_kx;
y = m_YD.y + m_y*m_ky;//将“米”转化为“像素”
r = m_r*m_kx;
r1 = m_L/2 * m_kx;
r2 = m_H/2 * m_ky;
CBrush brush,*pOldBrush;//定义画刷对象及旧画刷指针
brush.CreateSolidBrush(RGB(255,0,0));//创建画刷颜色
pOldBrush = pDC->SelectObject(&brush);
pDC->Rectangle(x-r1,y-r2,x+r1,y+r2);//画车身
x = m_YD.x + (m_x - m_L/4)*m_kx;
y = m_YD.y + (m_y - m_H/2 - m_r)*m_ky;
r = m_r*m_kx;
pDC->Ellipse(x-r,y-r,x+r,y+r);//画小车左轮
x = m_YD.x + (m_x + m_L/4)*m_kx;
pDC->Ellipse(x-r,y-r,x+r,y+r);//画小车右轮
brush.DeleteObject();
pDC->SelectObject(pOldBrush);
}
4、画路线,让小车按路线行驶,如下图所示。
代码如下:
//画路线(让小车按路线行驶)
void CCar::DrawLuXian()
{
int x,y;
int i;
x = m_YD.x + m_LuXian[0][0]*m_kx;
y = m_YD.y + m_LuXian[0][1]*m_ky;
pDC->MoveTo(x,y);
for (i=1;i<m_nLX;i++)
{
CPen cpen;//声明画笔
cpen.CreatePen(PS_SOLID,2,RGB(255,0,0));
pDC->SelectObject(&cpen);
x = m_YD.x + m_LuXian[i][0]*m_kx;
y = m_YD.y + m_LuXian[i][1]*m_ky;
pDC->LineTo(x,y);
}
}
5、添加Draw(CDC *p)函数,调用上面画的两个函数,如下图所示。
代码如下:
void CCar::Draw(CDC *p)
{
pDC = p;
DrawWaiXing();
DrawLuXian();
}
6、然后在CJQRXJView类里嵌入相应头文件,并在OnDraw里调用,如下图所示。
7、补充:为了让画出的小车能够随着地图的缩放及平移而相对位置保持不变,需要将场景类的原点赋给小车类的原点,将场景类的比例尺赋给小车类的比例尺。可以在CJQRXJView类的OnDraw()函数里赋值,但是不建议这样做,因为这几行代码只有在缩放和平移的时候才触发,如果放在OnDraw()里,会被反复执行,效率没有放在下图所示位置处好。
8、做到这,可以运行看看效果,如下图所示。
9、接下来,让小车动起来,包括“指哪到哪”、“按路线行驶”、“匀速到目标点”。添加菜单,并建立类向导,如下图所示。
10、为了后面在CJQRXJView里更好的调用,先在CCar类里添加“指哪到哪”、“按路线行驶(设定点、移动)”、“匀速到目标点”所涉及到的函数。
11、添加“Move(float deltat)”函数,如下图所示。
代码如下:
int CCar::Move(float deltat)
{
float d;//距离
m_x += m_vx*deltat;
m_y += m_vy*deltat;
d = sqrt( (m_MBDx - m_x)*(m_MBDx - m_x) + (m_MBDy - m_y)*(m_MBDy - m_y) );
if(d < 3)
return 1;
else
return 0;
}
12、添加“指哪到哪”函数,如下图所示。
代码如下:
//指哪到哪
void CCar::ZhiNaDaoNa(CPoint point)
{
m_x = (point.x - m_YD.x)/m_kx;//由point.x = m_YD.x + m_x*m_kx(将“米”转化为“像素”)变形而来
m_y = (point.y - m_YD.y)/m_ky;//将“像素”转化为“米”
}
13、添加“匀速到目标点”函数,如下图所示。
代码如下:
//匀速到目标点
void CCar::YunSuDaoMuBiaoDian(CPoint point)
{
float d;//距离
m_v = 40;
m_MBDx = (point.x - m_YD.x)/m_kx;
m_MBDy = (point.y - m_YD.y)/m_ky;
d = sqrt( (m_MBDx - m_x)*(m_MBDx - m_x) + (m_MBDy - m_y)*(m_MBDy - m_y) );
m_vx = m_v * (m_MBDx - m_x)/d;
m_vy = m_v * (m_MBDy - m_y)/d;
}
14、添加“按路线行驶(设定点、移动)”函数,如下图所示。
(1)按路线设定点
代码如下:
void CCar::AnLuXian_SheDing(CPoint point)
{
m_LuXian[m_nLX][0] = (point.x - m_YD.x)/m_kx;
m_LuXian[m_nLX][1] = (point.y - m_YD.y)/m_ky;
m_nLX++;
}
(2)按路线移动
代码如下:
int CCar::AnLuXian_Move(float deltat)
{
float d;//距离
m_v = 40;
m_MBDx = m_LuXian[m_nMBDLX][0];
m_MBDy = m_LuXian[m_nMBDLX][1];
d = sqrt( (m_MBDx - m_x)*(m_MBDx - m_x) + (m_MBDy - m_y)*(m_MBDy - m_y) );
if (d < 3)
{
m_nMBDLX++;
if (m_nMBDLX == m_nLX)
{
m_nLX = 0;
return 1;
}
}
else
{
m_vx = m_v * (m_MBDx - m_x)/d;
m_vy = m_v * (m_MBDy - m_y)/d;
Move(deltat);
}
return 0;
}
15、接下来的工作就是如何在CJQRXJView类里正确调用。
16、在CJQRXJView类里添加一些标记,可以采用枚举,并在构造函数里进行初始化,如下图所示。
17、然后在OnLButtonDown里判断,如下图所示。
(注:在最后加个Invalidate(TRUE);)
18、添加菜单响应代码,如下。
void CJQRXJView::OnMCarZhiNaDaoNa()
{
// TODO: Add your command handler code here
m_BJ = CarZNDN;//标记是指哪到哪
KillTimer(CarALX);//将按路线时钟关闭
KillTimer(CarYSDMBD);//将匀速到目标点时钟关闭
}
void CJQRXJView::OnMCarAnLuXianXingShi()
{
// TODO: Add your command handler code here
m_BJ = CarALX;//标记是按路线
m_Car.m_nLX = 0;//开始一条新路线,将旧路线清零
KillTimer(CarYSDMBD);//将匀速到目标点时钟关闭
}
void CJQRXJView::OnMCarYunSuDaoMuBiaoDian()
{
// TODO: Add your command handler code here
m_BJ = CarYSDMBD;//标记是匀速到目标点
KillTimer(CarALX);//将按路线时钟关闭
}
19、鼠标右击“CJQRXJView”,选中第五项“Add Windows Message Handler…”,添加“WM_LBUTTONDBLCLK、WM_TIMER”,如下图所示。
20、在鼠标双击OnLButtonDblClk里添加代码,如下。
void CJQRXJView::OnLButtonDblClk(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
if (m_BJ == CarALX)
{
m_Car.m_x = m_Car.m_LuXian[0][0];
m_Car.m_y = m_Car.m_LuXian[0][1];//双击鼠标让小车瞬间到路线起点
SetTimer(CarALX,100,NULL);
m_Car.m_nMBDLX = 0;//目标点路线置零
m_BJ = CJKZ;
}
CView::OnLButtonDblClk(nFlags, point);
}
21、在OnTimer里添加代码,如下。
void CJQRXJView::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
switch(nIDEvent)
{
case CarALX:
if(m_Car.AnLuXian_Move(0.1))
KillTimer(CarALX);
break;
case CarYSDMBD:
if(m_Car.Move(0.1))
KillTimer(CarYSDMBD);
break;
}
Invalidate(TRUE);
CView::OnTimer(nIDEvent);
}
22、此时,小车类就全部写好了,编译运行后的结果如下图所示。
(1)指哪到哪
(2)按路线行驶
(3)匀速到目标点
(二)无人机类(CWuRenJi)
无人机类唯一的区别就是外形跟小车类不一样,所以完全可以继承小车类,只用在CWuRenJi类里添加DrawWaiXing即可,在CJQRXJView类的调用与小车类相似。
1、新建一个无人机类,该类从小车类继承,如下图所示。
2、在构造函数里初始化,代码如下。
CWuRenJi::CWuRenJi()
{
m_x = 50;
m_y = -210;//无人机初始位置
m_vx = 0;
m_vy = 0;
m_v = 50;//速度
m_L = 40;//横轴长
m_H = 60;//纵轴高
m_r = 10;//机身圆形半径
m_YD.x = 950;
m_YD.y = 450;//屏幕中心
m_kx = 1900.0/1600;//东西长1600米
m_ky = -900.0/1000;//南北长1000米
}
3、将无人机外形画出来,如下图所示。
代码如下:
void CWuRenJi::DrawWaiXing()
{
CPen cPen;//声明画笔
cPen.CreatePen(PS_SOLID,3,RGB(0,0,0)) ;
pDC->SelectObject(&cPen);
int x,y,r;
x = m_YD.x + m_x*m_kx;
y = m_YD.y + m_y*m_ky;
r = m_r*m_kx;
CBrush brush,*pOldBrush;
brush.CreateSolidBrush(RGB(255,0,0));
pOldBrush = pDC->SelectObject(&brush);
pDC->Ellipse(x-r,y-r,x+r,y+r);//画机身
brush.DeleteObject();
pDC->SelectObject(pOldBrush);
//=========================================
x = m_YD.x + (m_x - m_L/2)*m_kx;
y = m_YD.y + m_y*m_ky;;
pDC->MoveTo(x,y);
x = m_YD.x + (m_x + m_L/2)*m_kx;
pDC->LineTo(x,y);//画无人机中心横轴
//=========================================
x = m_YD.x + m_x*m_kx;
y = m_YD.y + (m_y - m_H/2)*m_ky;
pDC->MoveTo(x,y);
y = m_YD.y + (m_y + m_H/2)*m_ky;
pDC->LineTo(x,y);//画无人机中心竖轴
//==================画螺旋桨=======================
//左边螺旋桨
x = m_YD.x + (m_x - m_L/2 - m_r/3)*m_kx;
y = m_YD.y + (m_y - m_r/2)*m_ky;
pDC->MoveTo(x,y);
x += (2*m_r/3)*m_kx;
y += (2*m_r/2)*m_ky;
pDC->LineTo(x,y);
x = m_YD.x + (m_x - m_L/2 - m_r/3)*m_kx;
y = m_YD.y + (m_y + m_r/2)*m_ky;
pDC->MoveTo(x,y);
x += (2*m_r/3)*m_kx;
y -= (2*m_r/2)*m_ky;
pDC->LineTo(x,y);
//右边螺旋桨
x = m_YD.x + (m_x + m_L/2 - m_r/3)*m_kx;
y = m_YD.y + (m_y - m_r/2)*m_ky;
pDC->MoveTo(x,y);
x += (2*m_r/3)*m_kx;
y += (2*m_r/2)*m_ky;
pDC->LineTo(x,y);
x = m_YD.x + (m_x + m_L/2 - m_r/3)*m_kx;
y = m_YD.y + (m_y + m_r/2)*m_ky;
pDC->MoveTo(x,y);
x += (2*m_r/3)*m_kx;
y -= (2*m_r/2)*m_ky;
pDC->LineTo(x,y);
//上边螺旋桨
x = m_YD.x + (m_x - m_r/3)*m_kx;
y = m_YD.y + (m_y + m_H/2 - m_r/2)*m_ky;
pDC->MoveTo(x,y);
x += (2*m_r/3)*m_kx;
y += (2*m_r/2)*m_ky;
pDC->LineTo(x,y);
x = m_YD.x + (m_x - m_r/3)*m_kx;
y = m_YD.y + (m_y + m_H/2 + m_r/2)*m_ky;
pDC->MoveTo(x,y);
x += (2*m_r/3)*m_kx;
y -= (2*m_r/2)*m_ky;
pDC->LineTo(x,y);
//下边螺旋桨
x = m_YD.x + (m_x - m_r/3)*m_kx;
y = m_YD.y + (m_y - m_H/2 - m_r/2)*m_ky;
pDC->MoveTo(x,y);
x += (2*m_r/3)*m_kx;
y += (2*m_r/2)*m_ky;
pDC->LineTo(x,y);
x = m_YD.x + (m_x - m_r/3)*m_kx;
y = m_YD.y + (m_y - m_H/2 + m_r/2)*m_ky;
pDC->MoveTo(x,y);
x += (2*m_r/3)*m_kx;
y -= (2*m_r/2)*m_ky;
pDC->LineTo(x,y);
}
4、添加Draw函数,如下图所示。
代码如下:
void CWuRenJi::Draw(CDC *p)
{
pDC = p;
DrawWaiXing();
DrawLuXian();//继承自小车类
}
5、在CJQRXJView里嵌入无人机的头文件,并在OnDraw里调用画无人机函数,如下图所示。
6、写到这,编译运行虽然可以画出无人机,但是却不能跟随地图的缩放及平移而改变,在OnMouseWheel()和OnMouseMove()里添加代码,如下图所示。
7、编译运行后结果如下图所示。
8、与上面添加小车菜单一样,添加无人机菜单,并建立类向导,如下图所示。
9、在枚举里加上下图所示的三个量。
10、在OnLButtonDblClk里加上如下图所示的代码。
11、在OnLButtonDown里加上如下图所示的代码。
12、添加无人机菜单响应代码,如下。
void CJQRXJView::OnMWuRenJiZhiNaDaoNa()
{
// TODO: Add your command handler code here
m_BJ = WRJZNDN;//标记是指哪到哪
KillTimer(WRJYSDMBD);//将匀速到目标点时钟关闭
KillTimer(WRJALX);//将按路线时钟关闭
}
void CJQRXJView::OnMWuRenJiYunSuDaoMuBiaoDian()
{
// TODO: Add your command handler code here
m_BJ = WRJYSDMBD;//标记是匀速到目标点
KillTimer(WRJALX);//将按路线时钟关闭
}
void CJQRXJView::OnMWuRenJiAnLuXianXingShi()
{
// TODO: Add your command handler code here
m_BJ = WRJALX;//标记是按路线
m_WRJ.m_nLX = 0;//开始一条新路线,将旧路线清零
KillTimer(WRJYSDMBD);//将匀速到目标点时钟关闭
}
13、最后在OnTimer里添加如下代码。
14、此时,编译运行结果如下图所示。
四、煤气泄漏类
假设某一位置煤气管道阀门发生了泄露,将泄露状态以及在空气中蔓延的状态模拟出来,然后让机器人(小车、无人机)完全自动地去找泄漏点。
(1)当某一位置发生泄露时,随着粒子的扩散,空气中的浓度分布会有所不同,用鼠标点击任意位置,会感应该点的浓度值。
(2)当机器人沿着某一指定路线行驶时,也会自动感应该路线上浓度值的变化情况。
(3)让机器人全自动的查找泄漏点。
1、新建一个煤气泄漏类,如下图所示。
2、添加变量,并进行初始化,如下图所示。
3、煤气泄露出来是由一个个粒子构成,可用圆形代替,粒子的产生是随机的,首先我们就要创建粒子,如下图所示。
代码如下:
//创建粒子(在OnTimer中调用)
void CMeiQiXieLou::CreateLiZi(float deltat)
{
int i;
for (i=0;i<100;i++)//假设每次产生100个粒子
{
m_LZ[m_nLZ].x = m_x;
m_LZ[m_nLZ].y = m_y;
m_LZ[m_nLZ].vx = rand()%100 - 50;//-50—50之间,50-(-50)=100,对100求余就是0—100,再加个-50
m_LZ[m_nLZ].vy = rand()%100 - 50;
m_LZ[m_nLZ].size = rand()%2 + 1;//粒子大小在1—3之间
m_LZ[m_nLZ].color = rand()%120 + 100;
m_LZ[m_nLZ].scT = rand()%10 + 20;//20—30
m_LZ[m_nLZ].czT = 0;
m_nLZ++;
}
}
4、创建好粒子后,就将它画出来,如下图所示。
代码如下:
//画粒子(在OnDraw里调用)
void CMeiQiXieLou::Draw(CDC *p)
{
int i;
int x,y,r;
pDC = p;//不加这行画不出来
for (i=0;i<m_nLZ;i++)
{
x = m_YD.x + m_LZ[i].x * m_kx;
y = m_YD.y + m_LZ[i].y * m_ky;
r = m_LZ[i].size * m_kx;
CBrush brush;
brush.CreateSolidBrush(RGB(m_LZ[i].color,m_LZ[i].color,m_LZ[i].color));
pDC->SelectObject(&brush);
pDC->BeginPath();
pDC->Ellipse(x-r,y-r,x+r,y+r);
pDC->EndPath();
pDC->FillPath();
}
}
5、粒子画好后要运动,所以添加Move(float deltat)函数,该函数中,如果第n个粒子的当前存在时间超过了它的总的生存期,就删除该粒子,也就是在条件判断的时候调用DeleteLiZi(int n)函数,如下图所示。
代码如下:
//移动粒子(在OnTimer中调用)
void CMeiQiXieLou::Move(float deltat)
{
int i;
for (i=0;i<m_nLZ;i++)
{
m_LZ[i].x += m_LZ[i].vx * deltat;
m_LZ[i].y += m_LZ[i].vy * deltat;
m_LZ[i].czT += deltat;
if(m_LZ[i].czT >= m_LZ[i].scT)
DeleteLiZi(i);//如果第i个粒子的当前存在时间超过了它的总的生存期,就删除该粒子
}
}
6、删除粒子如下图所示。
代码如下:
void CMeiQiXieLou::DeleteLiZi(int n)
{
m_LZ[n] = m_LZ[m_nLZ - 1];
m_nLZ--;
}
7、添加煤气泄漏菜单,并建立类向导,如下图所示。
8、在CJQRXJView类里嵌入煤气泄漏头文件,在枚举里添加“LIZI”标记,如下图所示。
9、在OnDraw里调用画粒子函数,如下图所示。
10、添加菜单响应代码,如下。
void CJQRXJView::OnMXieLouKaiShi()
{
// TODO: Add your command handler code here
m_BJ = LIZI;
SetTimer(LIZI,100,NULL);
}
void CJQRXJView::OnMXieLouZanTing()
{
// TODO: Add your command handler code here
KillTimer(LIZI);
}
11、然后在OnTimer中调用CreateLiZi和Move函数,如下图所示。
12、编译运行后能产生大量的运动的粒子,但是发现了还有一个问题,就是缩放和平移时,粒子位置没有跟着变化,原因是还没有在鼠标滚轮和鼠标移动(OnMouseWheel、OnMouseMove)中将场景类的原点和比例尺的值赋给煤气泄漏类。如下图所示。
13、接下来就是测煤气泄露的浓度。设置两个重载,一个参数单位是“像素”,一个参数是单位是“米”,如下图所示。
代码如下:
int CMeiQiXieLou::NongDu(CPoint point)
{
float x,y;
x = (point.x - m_YD.x)/m_kx;
y = (point.y - m_YD.y)/m_ky;
return NongDu(x,y);
}
int CMeiQiXieLou::NongDu(float x, float y)
{
int i;
int nd = 0;//浓度
float d;//距离
for (i=0;i<m_nLZ;i++)
{
d = sqrt( (x - m_LZ[i].x)*(x - m_LZ[i].x) + (y - m_LZ[i].y)*(y - m_LZ[i].y));
if(d < 20)
nd++;
}
return nd;
}
14、在CJQRXJView类里添加一个变量,如下图所示。
15、在OnDraw里添加代码输出煤气浓度,如下图所示。
16、当点击“煤气泄漏”的“开始”菜单时,煤气开始泄露。当鼠标点击某一位置,要求能够在屏幕左上角显示该点的浓度值。我们在OnLButtonDown里添加代码,如下图所示。
17、编译运行后的静态效果如下图所示。
18、下面是实现小车、无人机沿指定路线行驶过程中感应各点煤气浓度的变化值,由于已经在OnDraw里显示了m_NDstr,所以只用在OnTimer里输出即可。每动一下,获取该点的浓度。添加的代码如下图所示。
19、编译运行,例如采用无人机检测行驶路线上各点浓度的变化,如下图所示。
五、机器人全自动查找泄漏点
全自动查找采取的方法是画圆,让机器人沿着圆运动一圈,记录下圈上浓度最大的点和浓度最小的点,把坐标记下,然后连接最大、最小浓度点的坐标画一条直线。机器人走完一圈后,再找一个点,这个点可以是当前圆的半径加上一个直径,让机器人第一次的起点移动到下一个圆的半径位置,重复上述操作。
1、由于机器人包括小车和无人机,而无人机是从小车类继承过来的,所以主要在小车类里添加代码即可。
2、在CCar类里添加结构体和成员变量,并在构造函数里初始化,如下图所示。
3、添加“查找泄漏点”菜单,并建立类向导,如下图所示(无人机的类似)。
4、添加移动查找泄露(MoveChaZhaoXieLou(float deltat,int nd))函数,如下图所示。
代码如下:
void CCar::MoveChaZhaoXieLou(float deltat, int nd)
{
if (m_CirGJ.n < 30)//机器人未走完一圈
{
if (m_CirGJ.ndMin > nd)//找浓度最小点位置
{
m_CirGJ.ndMin = nd;
m_CirGJ.ndMinPosx = m_x;
m_CirGJ.ndMinPosy = m_y;
}
if (m_CirGJ.ndMax < nd)//找浓度最大点位置
{
m_CirGJ.ndMax = nd;
m_CirGJ.ndMaxPosx = m_x;
m_CirGJ.ndMaxPosy = m_y;
}
MoveChaZhaoXieLouCircle(deltat);
m_CirGJ.n++;
if (m_CirGJ.n == 30)
{
float d;
m_CirGJ.x -= (m_CirGJ.ndMinPosx - m_CirGJ.ndMaxPosx)/2;
m_CirGJ.y -= (m_CirGJ.ndMinPosy - m_CirGJ.ndMaxPosy)/2; //一圈走完了,求新的圆心
m_CirGJ.r *= 0.9;//每画一个圆,半径是上个圆半径的0.9倍
m_CirGJ.ndMin = 10000;
m_CirGJ.ndMax = -1;
m_MBDx = m_CirGJ.x - m_CirGJ.r;
m_MBDy = m_CirGJ.y;
d = sqrt( (m_x - m_MBDx)*(m_x - m_MBDx) + (m_y - m_MBDy)*(m_y - m_MBDy) );
m_vx = m_v * (m_MBDx - m_x)/d;
m_vy = m_v * (m_MBDy - m_y)/d;
}
}
else
{
if(Move(deltat))
{
m_CirGJ.n = 0;
}
}
}
5、添加转圈(MoveChaZhaoXieLouCircle(deltat))函数,如下图所示。
代码如下:
(记得定义宏#define PI 3.141593)
void CCar::MoveChaZhaoXieLouCircle(float deltat)
{
float jd;//角度
jd = m_CirGJ.n * 12 + 180; //由于将圆分成了30个点,所以就时360°/30 = 12
m_x = m_CirGJ.x + cos(jd/180*PI)*m_CirGJ.r;
m_y = m_CirGJ.y + sin(jd/180*PI)*m_CirGJ.r;
}
6、为了更直观地显示出圆上浓度最大、最小值位置,当经过最大、最小值点时,可以画两个圆来表示,在Draw(CDC *p)函数里添加代码,如下。
void CCar::Draw(CDC *p)
{
CString str;
CFont ft;
CBrush brush,*pOldBrush;
int x,y,r;
pDC = p;
DrawWaiXing();
DrawLuXian();
ft.CreatePointFont(300,_T("隶书"),NULL);
pDC->SelectObject(&ft);
pDC->SetTextColor(RGB(0,0,0));
str.Format("(车)最小浓度:%d 最大浓度:%d",m_CirGJ.ndMin,m_CirGJ.ndMax);
pDC->TextOut(30,750,str);
brush.CreateSolidBrush(RGB(255,140,0));
pOldBrush = pDC->SelectObject(&brush);
x = m_YD.x + m_CirGJ.ndMinPosx*m_kx;
y = m_YD.y + m_CirGJ.ndMinPosy*m_ky;
r = 5;
pDC->Ellipse(x-r,y-r,x+r,y+r);//浓度最小值处的圆
x = m_YD.x + m_CirGJ.ndMaxPosx*m_kx;
y = m_YD.y + m_CirGJ.ndMaxPosy*m_ky;
r = 10;
pDC->Ellipse(x-r,y-r,x+r,y+r) ;//浓度最大值处的圆
brush.DeleteObject();
pDC->SelectObject(pOldBrush);
}
7、由于无人机类从小车类继承,所以无人机Draw(CDC *p)中的代码类似小车类Draw(CDC *p)中的代码,如下。
void CWuRenJi::Draw(CDC *p)
{
CString str;
CFont ft;
CBrush brush,*pOldBrush;
int x,y,r;
pDC = p;
DrawWaiXing();
DrawLuXian();//从小车类中继承
ft.CreatePointFont(300,_T("隶书"),NULL);
pDC->SelectObject(&ft);
pDC->SetTextColor(RGB(0,0,0));
str.Format("(无人机)最小浓度:%d 最大浓度:%d",m_CirGJ.ndMin,m_CirGJ.ndMax);
pDC->TextOut(30,850,str);
brush.CreateSolidBrush(RGB(255,140,0));
pOldBrush = pDC->SelectObject(&brush);
x = m_YD.x + m_CirGJ.ndMinPosx*m_kx;
y = m_YD.y + m_CirGJ.ndMinPosy*m_ky;
r = 5;
pDC->Ellipse(x-r,y-r,x+r,y+r);//浓度最小值处的圆
x = m_YD.x + m_CirGJ.ndMaxPosx*m_kx;
y = m_YD.y + m_CirGJ.ndMaxPosy*m_ky;
r = 10;
pDC->Ellipse(x-r,y-r,x+r,y+r);//浓度最大值处的圆
brush.DeleteObject();
pDC->SelectObject(pOldBrush);
}
8、在枚举里加上CarCZXLD和WRJCZXLD,如下图所示。
9、添加菜单响应代码,如下。
void CJQRXJView::OnMCarChaZhaoXieLouDian()
{
// TODO: Add your command handler code here
m_Car.m_CirGJ.x = m_Car.m_x + m_Car.m_CirGJ.r;
m_Car.m_CirGJ.y = m_Car.m_y;
SetTimer(CarCZXLD,100,NULL);
}
void CJQRXJView::OnMWuRenJiChaZhaoXieLouDian()
{
// TODO: Add your command handler code here
m_WRJ.m_CirGJ.x = m_WRJ.m_x + m_WRJ.m_CirGJ.r;
m_WRJ.m_CirGJ.y = m_WRJ.m_y;
SetTimer(WRJCZXLD,100,NULL);
}
10、最后在OnTimer中添加代码,如下图所示。
11、编译运行结果如下图所示。