VC6.0 MFC 单文档 机器人巡检

VC6.0 MFC 专栏收录该内容
21 篇文章 5 订阅

机器人巡检

一、整体框架

主要包括三个类:场景类(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、编译运行结果如下图所示。
在这里插入图片描述

  • 3
    点赞
  • 1
    评论
  • 4
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

评论 1 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:黑客帝国 设计师:我叫白小胖 返回首页

打赏作者

青哥哥

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值