计算机图形学实验——利用MFC对话框实现多边形绘制与填充(扫描线填充算法)附源码

这篇具有很好参考价值的文章主要介绍了计算机图形学实验——利用MFC对话框实现多边形绘制与填充(扫描线填充算法)附源码。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

内容概括:

  • 利用基于对话框的MFC项目
  • 实现鼠标点击绘制多边形
  • 实现扫描线算法填充多边形

源码见Yushan-Ji/ComputerGraphics: ECNU2023秋 计算机图形学课程实验代码 (github.com)

实验内容

  1. 通过鼠标交互输入多边形
  2. 对各种多边形进行填充,包括边界自交的情况

算法描述

多边形绘制

  1. 利用OnLButtonDownOnRButtonDown函数,实现:
    • 左键点击:开始绘制多边形,并连接上一个顶点和当前点击的顶点
    • 右键点击:结束绘制多边形,并连接上一个顶点和第一个顶点
      其中,绘制顶点间的连线利用了MoveToLineTo函数
  2. 另外,为了避免多边形绘制完毕后,程序仍然对鼠标点击事件进行响应,因此需要添加布尔变量IsCompleted,用来监测当前多边形是否绘制完毕
    因此在OnLButtonDownOnRButtonDown函数的最开始,需要添加一个条件判断语句,若IsCompletedTRUE,则表明多边形已绘制完毕,函数直接返回
  3. 将所有顶点信息保存在vector<CPoint> points中,便于后续计算边信息

扫描线填充

  1. 首先需要根据points中存储的多边形顶点信息,计算出多边形的边信息,得到ET
    • 新建Edge类,存储每条边的信息
          // 边信息
          class Edge 
          {
          public:
          	float x_lower; // 扫描线与边的交点x值(初始值为线段下端点x值)
          	float dx; // 斜率倒数
          	float y_upper; // 线段上端点y值
          	// 构造函数
          	Edge() : x_lower(0.0), dx(0.0), y_upper(0.0) {}
          	Edge(float x_val, float dx_val, float y_val);
          };
      
    • 新建ET表:map<int, vector<Edge>> ET
      • 建立从线段下端点y值y_lower到多边形所有边vector<Edge>的映射,这样有相同下端点的不同边就能够存储在相同键值的map中,便于后续向AET中添加活性边
    • 获取ET表:通过getET()函数实现
      • 需要判断当前边是否为首尾顶点连成的边
      • 计算当前边斜率的倒数dx
        • 若当前边平行于x轴,continue不执行任何操作
        • 若当前边平行于y轴,则dx=0
        • 不是上述两种情况则正常计算
      • 计算x_lowery_upper
  2. 初始化、更新AET
    • 扫描线y初始化为ymin,并将ET表中ymin对应的所有边都添加到AET
    • 对于每一条扫描线,都需要检查AET中是否有非活性边的存在,还要及时添加新的活性边
      • y >= y_upper 时,说明该边需要移除
      • y >= y_lower时,说明该边需要添加到AET
  3. 获取每条扫描线与活性边的交点,存入scanPoints
    • 对于不同边相交的顶点,不存入
    • 对于其他交点,易知交点数量必为偶数,因此成对存入交点
  4. 根据scanPoints中交点的x值升序排列所有交点,并进行填充

核心代码

CPolygonFillDlg类中新添加的public成员:

class CPolygonFillDlg : public CDialogEx
{
  public:
	std::vector<CPoint> points; // 存储多边形的顶点
	std::vector<CPoint> scanPoints; // 存储扫描线与活动边的交点
    
	BOOL IsCompleted; // 标志多边形是否绘制完毕
    
	std::map<int, std::vector<Edge>> ET; // ET表:从线段下端点y值到边信息链表的映射
	std::vector<Edge> AET; // 活动边表
	float YMAX; // 存储多边形最高点
    
	void getET();
	void PolygonFill();
	static bool compareCPoint(const CPoint& a, const CPoint& b);
}

消息函数

PolygonFillDlg.cpp中需要添加按钮点击、鼠标点击的消息关联函数

// 消息关联
BEGIN_MESSAGE_MAP(CPolygonFillDlg, CDialogEx)
	...
	ON_WM_LBUTTONDOWN() // 关联鼠标左键点击事件
	ON_WM_RBUTTONDOWN() // 关联鼠标右键点击事件
	ON_BN_CLICKED(IDC_BUTTON_FILL, &PolygonFill) // 关联按钮点击事件
END_MESSAGE_MAP()

PolygonFillDlg.h中需要添加鼠标点击函数的声明

class CPolygonFillDlg : public CDialogEx
{
    ...
  protected:
    ...
    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
    afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
    ...
}

多边形绘制

// 鼠标左键点击事件(绘制多边形)
void CPolygonFillDlg::OnLButtonDown(UINT nFlags, CPoint point) 
{
	// 若多边形绘制完毕,忽略该函数,不执行操作
	if (IsCompleted == TRUE) 
	{
		return;
	}
	else 
	{
		// 如果是第一个点,存入points数组中
		if (points.empty()) 
		{
			points.push_back(point);
		}
		// 若不是第一个点,则连接当前点和上一个点
		else 
		{
			CStatic* CANVAS = (CStatic*)GetDlgItem(IDC_STATIC_CANVAS);
			CDC* pDC = CANVAS->GetDC();

			// 获取points数组的最后一个point(就是上一个点),并将当前绘图位置移动到该点
			pDC->MoveTo(points[points.size() - 1]);
			// 连接上一个点和当前点
			pDC->LineTo(point);
			points.push_back(point); // 将当前点加入数组
			ReleaseDC(pDC);
		}
	}
}

// 鼠标右键点击事件(多边形绘制完毕)
void CPolygonFillDlg::OnRButtonDown(UINT nFlags, CPoint point) 
{
	if(IsCompleted == TRUE) 
	{
		return;
	}

	else 
	{
		// 如果前面少于3个点,则无法连接成为多边形,弹出错误信息
		if (points.size() < 3) {
			CString message = _T("顶点数量少于3,无法绘制多边形");
			CString caption = _T("多边形绘制错误");
			MessageBox(message, caption, MB_ICONERROR);
		}

		// 若points已存储有2个以上的顶点,则连接上一个点和第一个点
		else 
		{
			CStatic* CANVAS = (CStatic*)GetDlgItem(IDC_STATIC_CANVAS);
			CDC* pDC = CANVAS->GetDC();
			// 连接第一个点和当前点
			pDC->MoveTo(points[points.size() - 1]);
			pDC->LineTo(points[0]);
			ReleaseDC(pDC);

			// 成功消息
			CString message = _T("多边形绘制完毕");
			CString caption = _T("多边形绘制完毕");
			MessageBox(message, caption, MB_OK);

			IsCompleted = TRUE;
		}
	}
}

扫描线填充

由于扫描线填充涉及到的代码较长,这里不全部展示,仅列出关键的几个步骤

获取边表

void CPolygonFillDlg::getET() 
{
	for (int i = 1; i <= points.size() ; i++) 
	{
		float dx;
		float x_lower, y_lower, y_upper;

		// points中最后一个点和第一个点的连线
		if (i == points.size())
		{
			if (points[i - 1].x == points[0].x)
			{
				continue; // 边平行于x轴,不处理
			}
			else if (points[i - 1].y == points[0].y)
			{
				dx = 0.0;
			}
			else
			{
				dx = (points[i - 1].x - points[0].x) * 1.0 / (points[i - 1].y - points[0].y);
			}

			if (points[0].y <= points[i - 1].y)
			{
				y_lower = points[0].y;
				y_upper = points[i - 1].y;
				x_lower = points[0].x;
			}
			else
			{
				y_lower = points[i - 1].y;
				y_upper = points[0].y;
				x_lower = points[i - 1].x;
			}
			// 插入到Edge边信息中
			Edge e(x_lower, dx, y_upper);
			// 插入到ET表中
			ET[y_lower].push_back(e);
		}

		else 
		{
			// 斜率
			if (points[i - 1].x == points[i].x)
			{
				continue; // 边平行于x轴,不处理
			}
			else if (points[i - 1].y == points[i].y)
			{
				dx = 0;
			}
			else
			{
				dx = (points[i - 1].x - points[i].x) * 1.0 / (points[i - 1].y - points[i].y);
			}

			// 线段上端点的x,y值
			if (points[i - 1].y <= points[i].y)
			{
				y_lower = points[i - 1].y;
				y_upper = points[i].y;
				x_lower = points[i - 1].x;
			}
			else
			{
				y_lower = points[i].y;
				y_upper = points[i - 1].y;
				x_lower = points[i].x;
			}

			// 计算多边形的最高点y值
			if (y_upper >= YMAX)
			{
				YMAX = y_upper;
			}

			// 插入到Edge边信息中
			Edge e(x_lower, dx, y_upper);
			// 插入到ET表中
			ET[y_lower].push_back(e);
		}
	}
}

AET表更新

// 移除已经超出扫描线的边
std::vector<Edge> newAET;
for (auto it = AET.begin(); it != AET.end(); ++it) {
	if (it->y_upper > y) {
		newAET.push_back(*it);
	}
}
AET = newAET;

//检查是否有新的边加入
if (it_ET != ET.end() && y >= it_ET->first) {
	AET.insert(AET.begin(), it_ET->second.begin(), it_ET->second.end());
	it_ET++;
}

获取当前扫描线的所有交点

auto it = AET.begin();
while (it != AET.end())
{
	float x1 = it->x_lower;
	it->x_lower += it->dx;
	it++;

	if (it != AET.end())
	{
		float x2 = it->x_lower;
		it->x_lower += it->dx;
		it++;

		// 扫描线遇到两条线相交的顶点时,不记录交点
		if (x1 != x2)
		{
			scanPoints.push_back(CPoint(x1, y));
			scanPoints.push_back(CPoint(x2, y));
		}
	}
}

交点排序并填充

// 比较函数,按照CPoint对象的x值升序排列
bool CPolygonFillDlg::compareCPoint(const CPoint& a, const CPoint& b) {
	return a.x < b.x;
}
// 对扫描到的所有交点进行升序排列
std::sort(scanPoints.begin(), scanPoints.end(), compareCPoint);

// 获取画板
CStatic* CANVAS = (CStatic*)GetDlgItem(IDC_STATIC_CANVAS);
CDC* pDC = CANVAS->GetDC();
// 成对绘制,i每次+2
for (int i = 0; i < scanPoints.size(); i += 2)
{
	pDC->MoveTo(scanPoints[i]);
	pDC->LineTo(scanPoints[i + 1]);
}
ReleaseDC(pDC);

实验结果

mfc绘制多边形,计算机图形学,mfc,c++

如上图,通过左键点击绘制多边形,右键点击结束绘制

绘制成功后会弹出绘制完毕的提示框

如果多边形顶点少于3个,则弹出错误提示框如下:

mfc绘制多边形,计算机图形学,mfc,c++

随后点击Fill Polygon按钮,运行扫描线填充,结果如下:文章来源地址https://www.toymoban.com/news/detail-756112.html

  • 凸多边形
    mfc绘制多边形,计算机图形学,mfc,c++
  • 凹多边形
    mfc绘制多边形,计算机图形学,mfc,c++
  • 边相交
    mfc绘制多边形,计算机图形学,mfc,c++

到了这里,关于计算机图形学实验——利用MFC对话框实现多边形绘制与填充(扫描线填充算法)附源码的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 计算机图形学实验四 OpenGL的鼠标交互绘制

    一、实验目的 1.掌握OpenGL的鼠标按钮响应函数。 2.掌握OpenGL的鼠标移动响应函数。 3.进一步巩固OpenGL的基本图元绘制基础。 二、实验内容 1.鼠标画草图——实现鼠标点到哪,线就画到哪。 思路: 1)在主程序注册鼠标响应和鼠标移动子函数: glutMouseFunc(mymouse); glutMotionFunc(mym

    2023年04月20日
    浏览(121)
  • 【计算机图形学】【实验报告】太阳系绘制、B样条曲线绘制(附代码)

    实 验 报 告 一、实验目的 掌握三维图形的显示原理和方法,掌握三维观察的原理和方法; 掌握OpenGL中矩阵堆栈函数的使用,会使用堆栈函数进行复杂场景的组装。 掌握OpenGL中三维观察变换常用的函数的使用方法,了解三维模型的贴图方法; 掌握自由曲线的生成方法,熟练

    2024年02月10日
    浏览(43)
  • 【HUST】网安|计算机网络安全实验|实验二 DNS协议漏洞利用实验

    写在最前: 这是我个人的实验记录,实现方式有很多种,多台虚拟机更容易做netwox。 认真整理和记录了一下容易出问题的地方。 代码仓库开了。 HUST计算机网络安全实验_Gitee Github docker使用 建立实验环境 普通用户: seed 密码:dees 超级用户:root 密码:seedubuntu Network(bridge):

    2024年02月04日
    浏览(52)
  • 【HUST】网安|计算机网络安全实验|实验一 TCP协议漏洞及利用

    写在最前: 实验指导书已经写得非常好了 ,这是我个人的实验记录, 并没有认真整理和记录容易出问题的地方 。只是免得以后忘了什么是netwox还得翻学习通。 HUST计算机网络安全实验_Gitee Github 普通用户: seed 密码:dees 超级用户:root 密码:seedubuntu Network(bridge):172.17.0.0/1

    2023年04月21日
    浏览(91)
  • MFC 通用对话框之文件对话框

    CFileDialog 类 封装了Windows通用文件对话框,Windows通用文件对话框提供了轻松实现与Windows标准一致的打开文件、保存文件、另存文件对话框的方法。 当我们用CFileDialog类的构造函数生成一个对象后就修改 m_ofn 结构体对象里的值, m_ofn 的类型为 OPENFILENAME。 CFileDialog类构造函数的

    2024年02月05日
    浏览(66)
  • MFC 通用对话框之颜色对话框

    CColorDialog类封装了颜色对话框,此类允许您将颜色选择对话框合并到应用程序中。颜色对话框就像画家的调色板一样,可显示系统定义的颜色列表,用户可以从列表中选择或创建特定颜色。构造一个CColorDialog类对象后,可用 DoModal( ) 函数来显示颜色对话框。 CColorDialog 的构造

    2024年02月06日
    浏览(56)
  • MFC(十三)多个对话框

    1.打开类视图,右键项目---类向导--添加Cpropsheet类,命名为mypropsheet CPropertySheet 是 MFC 的一个类,用于创建包含多个属性页的对话框。它可以使用内置的向导模式,向用户显示“下一步”和“上一步”按钮 2.在Cmypropsheet.h中,声明三个属性类的对象  3.在构造函数里,把三个对

    2024年02月08日
    浏览(48)
  • MFC(十二)多个对话框

    我们来制定多个对话框,每个对话框都有不同的功能,单击下一步,即可跳转到下一个对话框 1.新建一个启动按钮 2.在资源视图,Dialog里面,右键--添加资源----dialog====选择IDD PROPPAGE_SMALL新建 属性页,改名为PROP01 3.打开工具箱,添加group box,group box控件的作用是建立了一个分

    2024年02月07日
    浏览(50)
  • MFC为对话框资源添加类

    VC6新建一个对话框类型的工程; 建立之后资源中默认有2个对话框,一个是主对话框,About这个是默认建立的关于版权信息的; 然后主对话框有对应的.h和.cpp文件;可以在其中进行编程;   默认建立的有一个 关于 对话框;   在资源中新插入一个对话框,IDD_DIALOG1是对话框I

    2024年02月01日
    浏览(47)
  • MFC 对话框与控件的使用

    1、 熟悉 Windows 对话框应用程序开发的基本过程; 2、 学习标准控件的使用; 3、 学些 CommonDialog ActiveX 控件的使用; 4、 练习较复杂的交互式操作的控制流程; 5、 练习文件输入/输出流的使用。 实现一个具有一定实用价值的通讯录程序设计。界面如图所示 ①界面的基本设计

    2024年02月05日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包