扫描线填充多边形

xiaoxiao2021-02-28  10

扫描线填充多边形java程序设计 

多边形的两种表示方法: 

1、定点表示 

如上图所示,可以实用点(P1,P3,P10,P8,P11,P12)这些点来表示。 

2、点阵表示 

也可以使用点(P1~P13)来表示图形。

多边形的扫描转换: 把多边形的顶点表示转换为点阵表示,这种转换称为多边形的扫描转换。

如果Yk+1=Yk+1;

数据结构: 

本文定义边的数据结构为Edge,其包含的数据部分如下所示

一条边的记录包含了三个数据部分: 

(1)该边y的最大值ymax;  (2)该边底端的x坐标x;  (3)该边的斜率的倒数deltax; 

(4)以及下一个边记录地址的指针Edge;

同时由于java没有独立的指针,我们不能直接调用*p来指我们的下一条结构体数据,但是我们可以调用其默认的指针并同时让其为空来解决这个问题。如图所示:

举例: ]

对于下面的多边形为其建立边表: 

建立边表的方法: 

(1)与x轴平行的边不计入;  (2)多边形的顶点分为两大类:一类是局部极值点,如上图的P1和P3。另外一类是非极值点,如上图的P2、P4、P5。当扫面线与第一类顶点相交时,应看作两个点;而当扫描线与第二类顶点相交时,应视为一个点;  对于极值点则要记录两条边;  (3)扫描线按照y轴从低到高顺次记录;  (4)一条边按照y轴的高低记录;  (5)多条边以x轴递增顺序记录;  上图说明:  (1)扫描线y=1,穿过了P3点,此点为极值点,则有两条边。P3P4和P3P2。注意不是P4P3和P2P3。也不是P3P2和P3P4。因为对于一条边来说,P4的y大于P3的y,所以是P3P4。同理,P3P2也是这么得到的。又因为P2点的x轴坐标大于P4点的坐标,所以P3P4在P3P2的前面。  (2)扫描线y=2,和多边形的两条边相交,P3P4和P3P2,但是这两条边已经被扫描线1占用了,也就不算做新的边,故扫描线2的边表为空。  (3)扫描线y=3,穿越了P4P5,P3P4,P3P2,这里只有P4P5是新的边,所以加入扫描线3的边表。  (4)扫描线y=4,与多边形相交的边有P4P5和P3P2,而这两条边都已经在边表中,所以扫描线4的边表为空。  (5)扫描线y=5,与多边形相交的边有P4P5、P3P2、P2P1,其中只有线段P2P1是新边,所以扫描线y=5的边表中有一个新边。  (6)扫描线y=6,与多边形相交的边有P4P5、P5P1、P2P1,其中只有线段P5P1是新边,所以扫描线y=6的边表中有一个新边。  (7)扫描线y=7,与多边形相交的边有P5P1、P2P1,这些边都不是新边,所以不需要加入边表中去。  边表的数据结构:  (Ymax,Xi,1/m,指针)  其中Ymax为边的两个端点的y值大的那一个y。而Xi为较低点的x,m为斜率,这里取个倒数。

算法过程: (1)开始y=1,将边表中的y=1节点AET中,什么是AET呢?active edge table,俗称活化边表。这里要保证AET链表中记录按照x递增排列,如下图所示: 

(2)因为上图中P3P4和P3P2两个xi是相同的都是6,中间没有任何点需要着色,所以无需处理。 

(3)然后因为P3P4和P3P2的两个y值一个是3,一个是5,而此时处理的是y=1,所以不需要删除。为什么不删除呢?因为此时y=1还没有到达y=3和y=5。  (4)对保留下来的每个记录,用Xi+1/m代替Xi,比如: 

(5)使yi+1,以便进入下一轮循环。当y=2时,ET表为空,所以不需要ET表加入AET表。此时上图中x=4和x=6.5,将他们之间的点填上像素。由于y=2,此时没有达到y=3和y=5,所以不必删除节点。 

(6)再让Xi+1/m代替Xi,得到: 

使yi=3,此时他的边表不为空,所以将其ET表加入,得到: 

注意按照xi的升序排列。 

将Xi=2,至Xi=7之间填上颜色。  (7)由于此时yi=3,而第一个节点的yi=3,所以去掉此节点。 

(8)再用Xi+1/m代替Xi,得到: 

(9)使yi=4,重复继续。

下面我们来运行一下我们根据上述算法编写的程序,测试一下我们的的边的分类表(ET)是否正确:

首先在命令行界面输入相应的顶点坐标,如图所示:

p1(6,7) p2(8,5) p3(6,1) p4(2,3) p5(2,6) 

测试结果如图所示,与上图我们手工建的边的分类表相同,由此证明我们的边的分类表(AT)建立的正确性。

如图所示接下来我们来尝试建立边的活性表(AET)

图中活性表自动对x进行由小到大的进行排序,与上面我们手工分析的结果相同。

在此附上相应的java代码:

关于边的分类表:

for (int i = min; i < max; i++) { ET[i] = null; for (int j = 0; j < num - 1; j++) { if (a[j][1] == i) { if (j < num - 2 && j > 0) { if (a[j + 1][1] > i) { if (ET[i] == null) { ET[i] = new Edge(); ET[i].ymax = Math.max(a[j + 1][1], a[j][1]); ET[i].setX(a[j][0]); ET[i].deltax = (double) (a[j + 1][0] - a[j][0]) / (a[j + 1][1] - a[j][1]); ET[i].nextEdge = null; } else { Edge sptr = ET[i]; Edge newEdge = new Edge(); while (sptr.nextEdge != null) { sptr = sptr.nextEdge; } newEdge.ymax = Math.max(a[j + 1][1], a[j][1]); newEdge.setX(a[j][0]); newEdge.deltax = (double) (a[j + 1][0] - a[j][0]) / (a[j + 1][1] - a[j][1]); newEdge.nextEdge = null; sptr.nextEdge = newEdge; } } if (a[j - 1][1] > i) { if (ET[i] == null) { ET[i] = new Edge(); ET[i].ymax = Math.max(a[j - 1][1], a[j][1]); ET[i].setX(a[j][0]); ET[i].deltax = (double) (a[j - 1][0] - a[j][0]) / (a[j - 1][1] - a[j][1]); ET[i].nextEdge = null; } else { Edge sptr = ET[i]; Edge newEdge = new Edge(); while (sptr.nextEdge != null) { sptr = sptr.nextEdge; } newEdge.ymax = Math.max(a[j - 1][1], a[j][1]); newEdge.setX(a[j][0]); newEdge.deltax = (double) (a[j - 1][0] - a[j][0]) / (a[j - 1][1] - a[j][1]); newEdge.nextEdge = null; sptr.nextEdge = newEdge; } } } else if (j == num - 2) { if (a[0][1] > i) { if (ET[i] == null) { ET[i] = new Edge(); ET[i].ymax = Math.max(a[0][1], a[j][1]); ET[i].setX(a[j][0]); ET[i].deltax = (double) (a[0][0] - a[j][0]) / (a[0][1] - a[j][1]); ET[i].nextEdge = null; } else { Edge sptr = ET[i]; Edge newEdge = new Edge(); while (sptr.nextEdge != null) { sptr = sptr.nextEdge; } newEdge.ymax = Math.max(a[j + 1][1], a[j][1]); newEdge.setX(a[j][0]); newEdge.deltax = (double) (a[0][0] - a[j][0]) / (a[0][1] - a[j][1]); newEdge.nextEdge = null; sptr.nextEdge = newEdge; } } if (a[j - 1][1] > i) { if (ET[i] == null) { ET[i] = new Edge(); ET[i].ymax = Math.max(a[j - 1][1], a[j][1]); ET[i].setX(a[j][0]); ET[i].deltax = (double) (a[j - 1][0] - a[j][0]) / (a[j - 1][1] - a[j][1]); ET[i].nextEdge = null; } else { Edge sptr = ET[i]; Edge newEdge = new Edge(); while (sptr.nextEdge != null) { sptr = sptr.nextEdge; } newEdge.ymax = Math.max(a[j - 1][1], a[j][1]); newEdge.setX(a[j][0]); newEdge.deltax = (double) (a[j - 1][0] - a[j][0]) / (a[j - 1][1] - a[j][1]); newEdge.nextEdge = null; sptr.nextEdge = newEdge; } } } else if (j == 0) { if (a[j + 1][1] > i) { if (ET[i] == null) { ET[i] = new Edge(); ET[i].ymax = Math.max(a[j + 1][1], a[j][1]); ET[i].setX(a[j][0]); ET[i].deltax = (double) (a[j + 1][0] - a[j][0]) / (a[j + 1][1] - a[j][1]); ET[i].nextEdge = null; } else { Edge sptr = ET[i]; Edge newEdge = new Edge(); while (sptr.nextEdge != null) { sptr = sptr.nextEdge; } newEdge.ymax = Math.max(a[j + 1][1], a[j][1]); newEdge.setX(a[j][0]); newEdge.deltax = (double) (a[j + 1][0] - a[j][0]) / (a[j + 1][1] - a[j][1]); newEdge.nextEdge = null; sptr.nextEdge = newEdge; } } if (a[num - 2][1] > i) { if (ET[i] == null) { ET[i] = new Edge(); ET[i].ymax = Math.max(a[num - 2][1], a[j][1]); ET[i].setX(a[j][0]); ET[i].deltax = (double) (a[num - 2][0] - a[j][0]) / (a[num - 2][1] - a[j][1]); ET[i].nextEdge = null; } else { Edge sptr = ET[i]; Edge newEdge = new Edge(); while (sptr.nextEdge != null) { sptr = sptr.nextEdge; } newEdge.ymax = Math.max(a[num - 2][1], a[j][1]); newEdge.setX(a[j][0]); newEdge.deltax = (double) (a[num - 2][0] - a[j][0]) / (a[num - 2][1] - a[j][1]); newEdge.nextEdge = null; sptr.nextEdge = newEdge; } } } } } }

下面我们在实际的画图中采用扫描线填充的算法并进行自交多边形与非自交多边形的填充测试:

如图所示,非自交多边形填充:

由于采用自己写的画线函数可以清晰的看到该多边形边界具有清晰的锯齿状,由此证明我调用的是自己写的填充函数而不是由java系统提供的。

如图所示,自交多边形填充:

1.沙漏⏳图形填充:

2.五角星的填充

最后放一张,自己专门为计算机图形学用java语言设计有关画图的图形(GUI)界面框架---画图,并已经为其编写了相应的函数,从中点画线算法,到bresenham画线,绘制矩形,绘制多边形,中点算法绘制椭圆和圆,到线宽控制,线型控制,再到填充矩形,扫描线填充多边形,种子扫描线填充圆,再到加权区域采样消除锯齿。

转载请注明原文地址: https://www.6miu.com/read-1950320.html

最新回复(0)