热门标签 | HotTags
当前位置:  开发笔记 > IOS > 正文

C/C++实现图形学扫描线填充算法

这篇文章主要介绍了CC++实现图形学扫描线填充算法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

在上图形学课的时候,学习了扫描线填充算法。不过在完成实验的时候在真正理解了该算法,在此记录一下,如果有表达上的错误,欢迎指正!

扫描线填充算法通过在与图形相交的第(1,2)、(3,4)... 边之间划线不断不断填充图形。因此,在扫描时就需要确定什么时候与图形的某条边相交、划线的时候x的范围是多少以及划线时是从哪个交点画至另一个交点。

结构体如下所示:

为了节省存储的空间,边表项也使用链表结构,将图形中ymin值相同的边链接在同一个边表项后,这样在扫描的时候方便添加。

具体的流程如下:

一、初始化活动边表

1. 统计并初始化表项

2. 将每条边分别链接在表项后

二、 绘制与填充

1. 取出当前与扫描线相交的边

① 取出ymin 大于当前扫描线的y值的边

② 删除ymax 小于等于当前扫描线的边(①②过程可以排除掉与扫描线平行的边)

2. 将取出的边按照左右顺序排序(根据边的最低点的坐标与直线的斜率判断)

3. 划线并直接在原结构上修改边的x值(因为是在一个函数内,修改保存的值仅限于函数内,并不影响main函数中的值)

具体的代码如下所示,使用的库是EasyX(可以在http://www.easyx.cn/下载):

#include "graphics.h"
#include "stdio.h"
#include "conio.h"
#include 
#include 
#include 
#include 
 
using namespace std;
#define MAX_VOL 20
//多边形的边的数据结构
typedef struct Edge
{
 int y_max, y_min; //该有向边的y坐标的最大值与最小值
 double x, deltax; //该有向边的x的最小值以及x的变化的量(1/斜率)
 struct Edge* next; //指向下一条边的指针
 
}Edge;
//活动边表表项
typedef struct TableItem
{
 int curr_y;  //该表项的y坐标值 ymin
 Edge *firstNode; //该表项的首个节点,如果没有,NULL
 struct TableItem *next; //指向下一个活动边表表项的指针
}TableItem;
//活动边表结构体
typedef struct Table
{
 TableItem *itemHeader; //活动边表的表项header
 int item_count; //活动边表表项的个数
}ET;
 
class Point
{
private:
 int x1, x2, y1, y2;
public:
 Point(int x1, int y1, int x2, int y2)
 {
 this->x1 = x1;
 this->x2 = x2;
 this->y1 = y1;
 this->y2 = y2;
 }
 //返回两个点之中的ymax
 int YMax()
 {
 return (y1 > y2 ? y1 : y2);
 }
 //返回ymin
 int YMin()
 {
 return (y1 curr_y = ymins[0];
 T.itemHeader->firstNode = NULL;
 T.itemHeader->next = NULL;
 TableItem *p = T.itemHeader; //指向头结点
 for (int i = 1; icurr_y = ymins[i];
  e->firstNode = NULL;
  e->next = NULL;
  p->next = e;
  p = e;
 }
 
 //按照用于初始化的边数组初始化活动边表
 p = T.itemHeader;
 for (int j = 0; j AppendNode(T, edges[j].y_min, edges[j]);
 }
 //方法结束
 ////////测试区////////////
 //cout <<"遍历表项。。。。。" <curr_y <firstNode;
 // while (ele != NULL) {
 // cout <<"表项中的边: x = " <x <<" y_min = " <y_min <<" y_max = " <y_max <<
 //  "deltax = " <deltax <next;
 // }
 
 // p = p->next;
 //}
 ////////测试删除结点////////
 //p = T.itemHeader;
 //int yMax = 0;
 //while (yMax <24) {
 // p = T.itemHeader;
 // cout <<"-------------------------------" <DeleteNode(T, yMax);
 // while (p != NULL) {
 // cout <<"当前表项y : " <curr_y <firstNode;
 // while (ele != NULL) {
 //  cout <<"表项中的边: x = " <x <<" y_min = " <y_min <<" y_max = " <y_max <<
 //  "deltax = " <deltax <next;
 // }
 // p = p->next;
 // }
 // yMax++;
 //}
 
 /////////////////////////
 
 
 }
 
 
 
 //用于根据边数组计算需要多少个表项
 //表项的个数取决于边的ymin的个数
 //返回值 ymin 数组
 //返回 item_num 表项的个数
 int TableItemCount(Edge *edges, int edge_num, int* ymins)
 {
 int count = 0;
 for (int i = 0; ix = (double)point.x();
  newEdge->y_max = point.YMax();
  newEdge->y_min = point.YMin();
  newEdge->deltax = 1.0 / point.KOfLine(); // 斜率分之一
  newEdge->next = NULL;
  newEdges[j++] = *(newEdge);
 }
 return newEdges;
 }
 //删除所有的小于ymax 的节点
 //参数 curr_ymax 当前扫描线的y值
 void DeleteNode(ET &T, int curr_ymax)
 {
 TableItem *p = T.itemHeader; //指向表项的指针
 while (p != NULL) {
  Edge *item = p->firstNode; //指向表项的邻接链表的指针
  Edge *itempre = p->firstNode;   //指向前一个边结点的指针
  while (item != NULL) {
  if (item->y_max <= curr_ymax) { //删除该结点
   T.item_count--; //当前活动边表中的边的个数-1
   //判断该结点是否是该链表的头结点
   if (item == p->firstNode) {
   p->firstNode = (Edge*)malloc(sizeof(Edge));
   p->firstNode = item->next;
   free(item);  //释放该结点
   item = p->firstNode; //重新指向firstnode结点
   itempre = p->firstNode;
   }
   else {
   itempre->next = item->next; //修改前一个结点的next的值
   free(item);   //删除该指针
   item = itempre->next; //继续向后遍历
   }
  }//if (item->y_max next;
  }
  }//while (item != NULL)
  p = p->next;
 }//while (p != NULL)
 
 
 }
 
 //将指定y值的节点添加到该表项, 该方法插入的顺序取决于调用该方法传入参数的顺序
 //该方法将新节点插入到对应表项的邻接链表的末尾
 void AppendNode(ET &T, int place_y, Edge &e)
 {
 ////////测试区//////////
 //cout <<"In Append , place_y = " <curr_y == e.y_min)
  break;
  p = p->next;
 }
 //将边插入到该表项的邻接链表中
 Edge *egp = p->firstNode; //egp 指向该表项的首个邻接节点
 if (egp == NULL) { //如果该表项还没有节点,直接插入
  egp = (Edge*)malloc(sizeof(Edge));
  *(egp) = e;
  egp->next = NULL;
  p->firstNode = egp;
 }
 else {
  Edge *pre = egp;
  while (egp != NULL) {
  pre = egp;
  egp = egp->next;
  }
  Edge *newedge = (Edge*)malloc(sizeof(Edge));
  *(newedge) = e;
  pre->next = newedge;
  newedge->next = NULL;
 }
 
 
 }
 
 //绘图的方法
 void Draw(ET T) {
 //首先取出ymin 值小于当前扫描线y 的边
 //按照顺序配对
 int curr_y = 0, curr_edge_num = 0, curr_gy = graphy(curr_y); //图形坐标的扫描线的y坐标
 Edge *currEdges = (Edge*)malloc(sizeof(Edge) * 20); //用于存放指针的数组
 //将每条边的记录的x 化为图形上的坐标
 TableItem *p = T.itemHeader;
 while (p != NULL) {
  Edge *q = p->firstNode;
  while (q != NULL) {
  q->x = graphx(q->x);
  q = q->next;
  }
  p = p->next;
 }
 
 
 
 for (; curr_y <30; curr_gy--, curr_y = realy(curr_gy)) {
  this->DeleteNode(T, curr_y); //删除当前扫描过的边(ymax 小于 curr_y)
  currEdges = this->GetCurrEdges(T, curr_y, curr_edge_num); //获取当前与扫描线相交的边
  //对获取到的边进行排序、配对
  for (int i = 0; i IsRightTo(currEdges[i], currEdges[j])) {
   Edge tmp = currEdges[i];
   currEdges[i] = currEdges[j];
   currEdges[j] = tmp;
   }
  }
 
  }
 
 
  ////
 // getchar();
 // cout <<"------------------------------" <" <<(int)currEdges[2 * j + 1].x <<" , " <firstNode;
  while (q != NULL) {
  if (q->y_min <= curr_y) { //等于号很重要,否则会在图形中出现空白区
   currEdges[i++] = *q; //将当前边的值取出(不改变原活动表)
  }
  q = q->next;
  }
  p = p->next;
 }
 edge_num = i; //保存取出的边的个数
 return currEdges;
 }
 
 //判断edge1 是否在edge2 的右边的方法
 bool IsRightTo(Edge edge1, Edge edge2) {
 if (edge1.x > edge2.x) //如果edge1最低点的x坐标小于edge2的最低点的x的坐标,则edge1在edge2的右边
  return true;
 else {
  if (edge1.x  x_max2)
  return true;
 }
 return false;
 }
 
};
 
int main()
{
 //TODO 测试活动边表初始化
 Solution solution;
 int edges[] = { 4,18,14,14,26,22,26,10,14,2,4,6,4,18 };
 Edge* newEdges = solution.InitEdges(edges, 6);
 ET T;
 solution.Init(T, newEdges, 6); //初始化活动边表
 
 
 
 initgraph(800, 800, SHOWCONSOLE);
 solution.DrawCoordinate(edges, 6);
 solution.Draw(T);
 getchar();
 closegraph();
 return 0;
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • ANSI最全介绍linux终端字体改变颜色等ANSI转义序列维基百科,自由的百科全书由于国内不能访问wiki而且国内关于ANSI的介绍都是简短的不能达到,不够完整所以转wiki到此 ... [详细]
  • C++基础教程:探索随机数生成
    生活充满了不确定性,这些不确定因素使我们的生活更加丰富多彩。本文将探讨如何在C++编程中利用随机数增加程序的趣味性和实用性。 ... [详细]
  • C++中类的内外定义及内联函数详解
    本文详细介绍了C++中的类内定义与类外定义,以及内联函数的使用方法和注意事项。通过实例说明了不同定义方式的优缺点,并探讨了编译器对内联函数的处理机制。 ... [详细]
  • 本文详细介绍了苹果6s设备上实施性能保护的具体步骤,包括减少定位服务使用、控制后台应用刷新、调整通知中心设置等方面。 ... [详细]
  • 本文介绍如何使用 Oracle 数据库的 EXPDP 工具导出特定用户下的所有数据。包括登录系统用户、创建导出目录、授权访问权限及执行导出操作的具体步骤。 ... [详细]
  • Vue 中实现 ECharts 组件的动态刷新与分页
    本文介绍了如何在 Vue 项目中使用 ECharts 组件实现数据的动态刷新和分页显示。通过合理的数据处理和页面逻辑设计,提升用户体验。 ... [详细]
  • 深入理解Redis集群机制
    本文旨在深入探讨Redis集群的工作原理,包括其架构设计、数据分布策略、节点通信及故障恢复机制等方面的内容。 ... [详细]
  • 本文介绍了iOS应用开发的主要框架,包括Foundation、UIKit、CoreData及CoreGraphics等,并探讨了开发iOS应用所需的硬件和软件环境,以及推荐的编程语言。 ... [详细]
  • POJ2226 二分图最小覆盖问题
    在一个大小为n×m的网格中,部分单元格为泥泞状态,其余为干净。目标是使用宽度固定为1但长度可变的木板覆盖所有泥泞单元格,且不覆盖任何干净单元格。木板允许重叠。本问题通过构建二分图并求其最小覆盖来解决。 ... [详细]
  • iOS 面试实战:15 道经典面试题及解析
    本文精选了15道iOS面试题,并提供了详细的解答思路。旨在帮助开发者更好地准备面试,避免因准备不足而导致的紧张和焦虑。 ... [详细]
  • 在今天的C++考试中遇到了一个关于数组的问题,虽然代码在VS2015上能够成功编译,但在运行时却没有任何输出。请求各位前辈给予指导。 ... [详细]
  • Sass 是一种 CSS 的预处理器,通过使用变量、嵌套、继承等高级功能,使得 CSS 的编写更加灵活和高效。本文将介绍 Sass 的基本语法及其安装使用方法。 ... [详细]
  • 电子与正电子的相互作用
    本文探讨了电子与正电子之间的基本物理特性及其在现代物理学中的应用,包括它们的产生、湮灭过程以及在粒子加速器和宇宙射线中的表现。 ... [详细]
  • 本文介绍了EasyRTSPClient这一高效、稳定的RTSP客户端工具库,并详细阐述了其在与大华球机对接过程中遇到的预览问题及解决方法。 ... [详细]
  • 本文探讨了BZOJ4029 [HEOI2015] 定价问题,通过使用贪心算法解决该问题。文章提供了详细的题目解析和代码实现,重点在于如何通过计算十进制下的最低有效位(lowbit)来简化问题。 ... [详细]
author-avatar
泡泡
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有