MFC鼠标绘制直线段并使用编码裁剪算法

时间:2024-05-22 15:27:17

聪明的你通过本文可以学会在MFC中

  • 初始化时绘制自定义矩形框
  • 使用鼠标来实时绘制你想要的直线段
  • 实现编码裁剪算法裁去直线段在自定义矩形框以外的部分

完成效果如下

  • 进入运行界面
    MFC鼠标绘制直线段并使用编码裁剪算法
  • 鼠标绘制直线
    MFC鼠标绘制直线段并使用编码裁剪算法
  • 编码算法裁剪
    MFC鼠标绘制直线段并使用编码裁剪算法

接下来让我们共同打败这三个boss吧!难度也是和闯关类游戏一样依次递增呢!一步一步的跟着做哦,很容易就学会了

这篇博客主要讲思路,所以要求你已经基本会使用MFC建立项目了,如果还不会的话,没关系,我已经为你写好了一篇MFC入门教程手把手教你建立绘制圆形的MFC项目,你保准一学就会,如果具备基本条件了,那还等什么,快开始吧


首先建立【MFC AppWizard (exe)】项目,我将其命名为【LineCut】
其次,将项目调成【Release】版本,这个很重要,否则该项目运行不了 具体步骤在向上数三行的链接中可以找到

1. 初始化时绘制自定义矩形框

  • 达到的效果:初次运行程序,每次更新或重绘屏幕都会显示该矩形框
  1. 先在【LineCutView.h】添加变量
protected:
	double wxl,wxr,wyt,wyb;//矩形左上角,右下角坐标或是看成上下左右四个边界直线坐标
  1. 在【LineCutView.cpp】的构造函数中初始化变量
	//初始化矩形大小
	wxl=100;wxr=500;
	wyt=250;wyb=100;
  1. 在【LineCutView.cpp】的OnDraw(CDC* pDC)函数中绘制矩形
	//初始化时绘制矩形
	
	CClientDC dc(this);//获取画布
	CPen pen(PS_SOLID,3,RGB(0,0,225));//获取画笔
	dc.SelectObject(&pen);//将画笔移动到画布上
	dc.Rectangle(wxl,wyt,wxr,wyb);//在画布上绘制矩形
  1. 添加完这三部分代码,运行项目,看看自己的成果,如果效果如下,那么恭喜,你已经通过第一关了,若是没有这种效果就得回去检查检查是哪里出了问题
    MFC鼠标绘制直线段并使用编码裁剪算法

2.鼠标实时绘制直线段

  • 预期效果:在客户区内鼠标左键单击触发直线绘制,单击点成为直线段起点=》按住鼠标左键同时移动光标=》移动过程中光标实际位置与起点总是有线段相连=》松开鼠标左键=》直线段固定=》绘制完成
  • 步骤分析:根据效果可知,在此鼠标有三个状态/动作,左键按下,光标移动,左键弹起,所以接下来要给这三个动作添加响应函数,也就是消息函数,此三种消息函数在MFC ClassWizard 中的Messages中以存在,接下来为具体步骤:选择【Class name:】下的【CLineCutView】=》单击【Messages:】下的【WM_RBUTTONDOWN】=》单击右上方的【Edit Code】=》进入【CLineCutView.cpp】编辑该响应函数。

MFC鼠标绘制直线段并使用编码裁剪算法

  • 变量准备,直线至少需要两个顶点,并添加一些鼠标左键按下的标志
  • 在【LineCutView.h】中添加变量
protected:
	BOOL m_Draw;//记录鼠标左键状态
	float x1,y1,x0,y0;//起止点坐标
  • 在构造函数中初始化变量
	x0=0;y0=0;x1=0;y1=0;
	m_Draw=FALSE;

1. 鼠标左键按下的绑定函数OnLButtonDown

void CLineCutView::OnLButtonDown(UINT nFlags, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default

//以下4行是自己添加的,其他均为自动生成
	CClientDC dc(this);
	m_Draw=TRUE;//标记鼠标左键按下
	x0=point.x;//记录下起点的 
	y0=point.y;

	CView::OnLButtonDown(nFlags, point);
}

2.鼠标移动的绑定函数OnMouseMove

//鼠标移动:实时更新当前位置到鼠标左键单击位置的直线,动态效果,便于用户判断直线段位置与长短
//反复使用两点确定直线绘制法+不断更新的动画制作原理=》从而产生动态效果
void CLineCutView::OnMouseMove(UINT nFlags, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default
	CClientDC dc(this);	
	x1=point.x;//记录当前光标位置
	y1=point.y;
	
	if(m_Draw==TRUE)//若是没这句,松开鼠标左键后直线就消失
		RedrawWindow();//重新调用OnDraw函数绘制直线,具体步骤稍后补上

	CView::OnMouseMove(nFlags, point);
}

3. 鼠标左键弹起绑定函数

void CLineCutView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default
	m_Draw=FALSE;//标记鼠标左键弹起
	RedrawWindow();
	
	CView::OnLButtonUp(nFlags, point);
}

4. 想OnDraw函数添加直线绘制代码

在操作前先在【LineCutView.cpp】中添加四舍五入宏定义

#define ROUND(a) (int)(a+0.5)
//编码宏定义后面千万别带分号,不然代入式会把分号一同带入,造成语法错误
	//设置直线画笔
	CPen pen2(PS_SOLID,1,RGB(0,0,225));
	dc.SelectObject(&pen2);

	//将绘制直线封装到Draw中,这样每次更新即可重新绘制直线,便于其他部分使用
	//鼠标左键点击后才开始传递鼠标移动信息
	if(m_Draw==TRUE)
	{	
			dc.MoveTo(ROUND(x0),ROUND(y0));
			dc.LineTo(ROUND(x1),ROUND(y1));
	}

5. 检验时刻: 完成了前四步,运行项目试试,若有如下效果,那么,恭喜你,成功地闯过第二关,你离目标只有1/3了,若没有如下效果,则请回头检查
MFC鼠标绘制直线段并使用编码裁剪算法

编码裁剪算法实现

  • 编码函数
  1. 添加变量和编码函数
protected:
unsigned int RC,RC0,RC1;//变量
public:
unsigned int enCode(float x,float y);//函数
  1. 完善编码函数
    先添加四条边界的宏定义
//编码宏定义后面千万别带分号,不然代入式会把分号一同带入,造成语法错误
#define LEFT 1//0001
#define RIGHT 2//0010
#define BOTTOM 4//0100
#define TOP 8//1000

具体实现

unsigned int CLineCutView::enCode(float xx,float yy )
{
	RC=0;//初始化

	if(xx<wxl)//当前点在左边界以左
		RC=RC|LEFT;//利用或运算将相应位置的数置为1

	if(xx>wxr)
		RC=RC|RIGHT;

	if(yy<wyb)
		RC=RC|BOTTOM;

	if(yy>wyt)
		RC=RC|TOP;
	
	return RC;
}

  • cohen()裁剪函数

一路破关斩将的你是不是很轻松地到了这里,那么,接下来,有个博主尚未搞清原因的问题,等着聪明的你解答
问题描述
【LineCutView】类中的变量x0,y0,x1,y1中的x1,y1在为对其进行任何运算时在鼠标动作绑定的函数与在其他函数中的值竟然不同,而x0,y0,却没有这个问题。
初步猜测
x1,y1是已经被使用的变量名,于是我将二者换为ttx,tty,问题依旧。
解决方案
定义变量ttx,tty,先把终点坐标记录下来,在使用x1,y1时再赋给二者

  1. 添加变量与裁剪标志和相应函数
protected:
	BOOL m_clip;//记录裁剪状态
	float k,ttx,tty;//k为斜率,ttx,tty是为了解决上面的问题而添加的辅助变量
public:
	void cohen();//裁剪函数

  1. 在构造函数初始化m_clip=FLASE;
  2. 在OnLButtonUp函数中添加以下代码,
ttx=x1;tty=y1;//保留终点坐标
  1. 完善cohen()
void CLineCutView::cohen()
{
	//将之前保留的坐标还原
	x1=ttx;y1=tty;//x1,y1在该类中的不同函数使用值竟然不同,猜测是因为变量名重复,所以使用新的变量来保存
	while(TRUE)
	{
		RC0=enCode(x0,y0);//每次循环更新计算起止点的编码
		RC1=enCode(x1,y1);
		
		if((RC0|RC1)==0)//起止点均在窗口内,简取之"运算优先级,|要加括号!!!!!!!"
		{
			return;
		}
		else if((RC0&RC1)!=0)
		{
			x1=x0;y1=y0;//利用起止点相同,重绘是即没有直线显示,等价于完全舍去
			return;
		}
		else //无法简单取舍
		{
			if(RC0==0)//保证起点为外部点
			{
				unsigned int t;
				t=RC0;RC0=RC1;RC1=t;
				
				int tmp;
				tmp=x0;x0=x1;x1=tmp;
				tmp=y0;y0=y1;y1=tmp;
			}
			//斜率不存在单独考虑
			if(x1-x0==0)
			{
				if((RC0&LEFT)||(RC0&RIGHT))//左右两侧外,完全舍弃
				{
					y0=y1;
					return;
				}
				else
				{
					if(RC1==0)//至少一端在矩形内
					{
						if(RC0&BOTTOM)
							y0=wyb;
						else if(RC0&TOP)
							y0=wyt;
					}
					else //穿过矩形
					{
						y0=wyb;
						y1=wyt;
					}
					return;
				}
			}

			//斜率存在
			k=(y0-y1)/(x0-x1);//计算时类型需一致!!!

			//每次循环只处理一侧
			if(RC0&LEFT)	//左侧之左
			{
				x0=wxl;	//交点横坐标
				y0=y1+k*(x0-x1);//交点纵坐标
			}

			else if(RC0&RIGHT)//右侧之右
			{
				x0=wxr;	//交点横坐标
				y0=y1+k*(x0-x1);//交点纵坐标
			}
			else if(RC0&BOTTOM)//下侧之下
			{
				y0=wyb;	//交点横坐标
				x0=x1+(y0-y1)/k;//交点纵坐标
			}
			else if(RC0&TOP)//上侧之上
			{
				y0=wyt;	//交点横坐标
				x0=x1+(y0-y1)/k;//交点纵坐标
			}
		}

			
	}
}
  1. 添加菜单【直线裁剪】,并添加相应菜单响应函数OnMENUcohen(),(详细步骤间见本文开头的链接)
void CLineCutView::OnMENUcohen() 
{
	// TODO: Add your command handler code her
	cohen();	
	m_clip=TRUE;//裁剪函数已使用
	RedrawWindow();
	
}

  1. 修改OnDraw函数中的直线绘制条件
//只需修改此条件,其他皆不动
//若是不改,则裁剪后的直线无法显示
if(m_Draw==TRUE||m_clip==TRUE)
	{		
			dc.MoveTo(ROUND(x0),ROUND(y0));
			dc.LineTo(ROUND(x1),ROUND(y1));
	}
  1. 到此为止,快快运行你的程序,看看是否能如开头效果所示,若能,那么,恭喜,你通关了,基本掌握了鼠标画图与裁剪算法

避坑指南

  • 线段顶点的类型不一致或者是线段顶点的类型均为int,那么计算斜率k则需强制转换
  • 二进制运算符【|】优先级<【=】,所以用作条件判断时【|】两侧必须加括号,如if((a|b)==0)
  • 编码的宏定义要认真,别把自己搞糊涂
  • 最后一个则是我之前提到的问题,目前还不知道原因,期待你的发现

在学习计算机图形学时遇到了许多困难,花了许多时间才解决,所以希望该文能够给你带来帮助,节约时间,知识的意义在于分享,欢迎广大朋友互相交流,qq:1140132928