本文详细的介绍了具有纽带作用的二维表Point2Table,该类是整个算法三个核心类之一,文中用图示的方式一步一步展示了它工作的过程。
终于,我们进入了算法的核心地带。
在《第二回:漫谈新思路,是我们自己干的时候了》中我们说到整个算法的处理过程包括三个大块:寻找辅助线、寻找交点、寻找三角形。交点的寻找及其结果的组织,是能否找到三角形的关键,因而本回会用较长的篇幅把这个过程阐述清楚,为后文打下坚实的基础。
由于内容实在过多,为了避免冗长造成的疲倦,我将本回分成两个部分。本文的主题是介绍寻找交点和寻找三角形两个大块的纽带——二维表Point2Table,还记得《第二回:漫谈新思路,是我们自己干的时候了》“二、寻找相交点”小节中提到的二维表吗,没错,就是它!
一、 类图和代码
这里是整个类的代码,行数逾百,后面会进行逐段的分析。
Point2Table
1Public Class Point2TableClass Point2Table
2
3 Private Class PointIndexWithTClass PointIndexWithT
4 Implements IComparable
5
6 Private _index As Integer '在VertexBuffer中的索引
7 Private t As Decimal
8
9 Public Sub New()Sub New(ByVal ind As Integer, ByVal t As Decimal)
10 Me._index = ind
11 Me.t = t
12 End Sub
13
14 Public ReadOnly Property Index()Property Index() As Integer
15 Get
16 Return Me._index
17 End Get
18 End Property
19
20 Public Function CompareTo()Function CompareTo(ByVal obj As Object) As Integer Implements System.IComparable.CompareTo
21 If Not (TypeOf obj Is PointIndexWithT) Then
22 MsgBox("PointWithU中传入参数有误")
23 Return 0
24 End If
25 Dim pwu As PointIndexWithT = CType(obj, PointIndexWithT)
26 Return Me.t.CompareTo(pwu.t)
27 End Function
28 End Class
29
30 Private rowsForSort As New List(Of List(Of PointIndexWithT))
31 Private rows As New List(Of List(Of Integer))
32 Private vb As New VertexBuffer
33 Private ib As IndexBuffer = Nothing
34
35 Public ReadOnly Property IndexBuffer()Property IndexBuffer() As IndexBuffer
36 Get
37 If (ib Is Nothing) Then
38 ib = New IndexBuffer
39 Dim qs As New QuadrangleSeeker(Me)
40 Dim quadrangles As List(Of Integer) = Nothing
41 Me.Sort() '对表进行排序
42 quadrangles = qs.getQuadrangles()
43 For i As Integer = 0 To quadrangles.Count - 1 Step 4
44 If (quadrangles(i + 2) <> quadrangles(i + 3)) Then
45 ib.add(quadrangles(i))
46 ib.add(quadrangles(i + 2))
47 ib.add(quadrangles(i + 3))
48 End If
49 If (quadrangles(i) <> quadrangles(i + 1)) Then
50 ib.add(quadrangles(i + 3))
51 ib.add(quadrangles(i + 1))
52 ib.add(quadrangles(i))
53 End If
54 Next
55 End If
56 Return Me.ib
57 End Get
58 End Property
59
60 Public ReadOnly Property VertexBuffer()Property VertexBuffer() As VertexBuffer
61 Get
62 Return Me.vb
63 End Get
64 End Property
65
66 Default Public ReadOnly Property Item()Property Item(ByVal i As Integer) As List(Of Integer)
67 Get
68 If (i < rowsForSort.Count AndAlso i >= 0) Then
69 Return rows(i)
70 Else
71 MsgBox("数组越界")
72 Return Nothing
73 End If
74 End Get
75 End Property
76
77 Public ReadOnly Property Count()Property Count() As Integer
78 Get
79 Return Me.rowsForSort.Count
80 End Get
81 End Property
82
83 Public Sub addRow()Sub addRow()
84 rowsForSort.Add(New List(Of PointIndexWithT))
85 End Sub
86
87 Public Sub addPoint()Sub addPoint(ByVal point As Point2, ByVal t As Decimal, ByVal u As Decimal)
88 If (u < 0 OrElse u > 1) Then '没有和线段相交
89 Return
90 End If
91 Dim index As Integer = Me.vb.add(point)
92 rowsForSort(rowsForSort.Count - 1).Add(New PointIndexWithT(index, t))
93 End Sub
94
95 Public Sub removePoint()Sub removePoint(ByVal rowIndex As Integer, ByVal columnIndex As Integer)
96 Me.rowsForSort(rowIndex).RemoveAt(columnIndex)
97 End Sub
98
99 Public Sub Sort()Sub Sort()
100 Me.rows = New List(Of List(Of Integer))
101 For Each row As List(Of PointIndexWithT) In rowsForSort
102 row.Sort() '按照t进行排序
103 Dim list As New List(Of Integer) '实例化一行
104 For Each piwt As PointIndexWithT In row '遍历行中每一个带T的点索引
105 list.Add(piwt.Index) '将索引值添加到列表中
106 Next
107 Me.rows.Add(list) '将列表添加进行中
108 Next
109 End Sub
110End Class
111
二、 两个缓冲的宿主
容易看出属性中的IndexBuffer和VertexBuffer正是上篇文章中设计的数据结构,客户代码可以直接调用这两个只读属性获得处理完毕的两个缓冲,进而加以利用,所以说Point2Table除了是寻找交点和寻找三角形的纽带外,还有另一重身份,那就是整个算法和客户代码的纽带,这里是算法的出口。在调用IndexBuffer属性时,程序会去调用寻找三角形的逻辑,这部分内容我们将在下一回中详细介绍。在调用VertexBuffer属性时,程序直接返回顶点缓冲vb,这个缓冲是用户在寻找交点的过程中填充的,这部分内容我们将在本回的下一部分中详细介绍。
三、嵌套类PointIndexWithT
在上面的类图中,有一个可爱的棒糖,棒糖下面是一个名字古怪的私有嵌套类PointIndexWithT,翻译过来就是带着T的点索引,索引就是上文VertexBuffer中的索引,T就是《第四回:掌握数学工具,没个好帮手怎么行》“三、用向量描述直线”小节中提到的T。下图可以直观的看出Index和T的含义:
该类的引入只有一个目的,将某条扫描线上的所有交点从左至右进行排序,注意一点,这里参与排序的是T,而排序的实体(我们要的结果)是点的索引。
1 Public Function CompareTo()Function CompareTo(ByVal obj As Object) As Integer Implements System.IComparable.CompareTo
2 If Not (TypeOf obj Is PointIndexWithT) Then
3 MsgBox("PointWithU中传入参数有误")
4 Return 0
5 End If
6 Dim pwu As PointIndexWithT = CType(obj, PointIndexWithT)
7 Return Me.t.CompareTo(pwu.t)
8 End Function
上面的这段代码实现了IComparable接口的CompateTo方法,目的是让PointIndexWithT的数组可以使用框架提供的Sort函数。代码很简单,只是简单的比较了一下T,我们已经说过了,T就是为了排序而生。如果你还不是太清楚,没关系,下面我就用一个例子来说明。
四、例说排序
下图是三角剖分一个凹多边形的过程,图中的VB代表VertexBuffer,也就是顶点缓冲。每一行后面会有几个矩形框,每个矩形框代表一个加入到Point2Table的点,框中上面一个值代表Index,下面的值为该点的T。
上图中的T只是一个示意值,请不要用尺子丈量。
五、排序的实现
有了上面直观的了解,相信下面的内容很容易理解。
Private rowsForSort As New List(Of List(Of PointIndexWithT))
Private rows As New List(Of List(Of Integer))
1Public Sub Sort()Sub Sort()
2 Me.rows = New List(Of List(Of Integer))
3 For Each row As List(Of PointIndexWithT) In rowsForSort
4 row.Sort() '按照t进行排序
5 Dim list As New List(Of Integer) '实例化一行
6 For Each piwt As PointIndexWithT In row '遍历行中每一个带T的点索引
7 list.Add(piwt.Index) '将索引值添加到列表中
8 Next
9 Me.rows.Add(list) '将列表添加进行中
10 Next
11 End Sub
顾名思义,rowsForSort就是为了排序而建立的行集合,这里的一行指的是一条扫描线,从代码中可以看到该字段实际上是一个PointIndexWithT的参差二维数组(二维表)。你一定发现了rows的存在,它和rowsForSort的结构完全相同,从Sort方法中可以看到rows中保存的是排好了序后rowsForSort中的所有索引,换句话说,它将用于排序的T去除了。引入这样一个字段显然浪费了一些空间,因为rowsForSort完全可以担当起rows的职责,但是由于在寻找三角形的过程中需要对rows进行一些处理(删除某个点),因此如果直接将rowsForSort传过去将会造成不可逆转的删除,因而这里选择了克隆一份新数据的变通方法。
1Public Sub addRow()Sub addRow()
2 rowsForSort.Add(New List(Of PointIndexWithT))
3End Sub
4
5Public Sub addPoint()Sub addPoint(ByVal point As Point2, ByVal t As Decimal, ByVal u As Decimal)
6 If (u < 0 OrElse u > 1) Then '没有和线段相交
7 Return
8 End If
9 Dim index As Integer = Me.vb.add(point)
10 rowsForSort(rowsForSort.Count - 1).Add(New PointIndexWithT(index, t))
11End Sub
addRow和addPoint是操纵rowsForSort的两个方法。其中addRow是为二维表添加一行,addPoint则是在新添加的行中添加一个交点,注意addPoint的三个参数,第一个是一个二维点对象,第二个是上文提到的用于排序的T,第三个是《第四回:掌握数学工具,没个好帮手怎么行》 “四、射线与线段的交点”小节中提到的U,我们已经知道,如果U没有落在[0,1]的区间内,该点是不在线段上的,因而也是不会被添加到二维表中的。
二维表是一个容纳交点的数据结构,同时它可以根据每个交点的T来对表中每行上的交点进行排序以供寻找三角形之用,因而该类是寻找交点和寻找三角形两个大块的纽带,又由于该类提供了算法的出口(两个缓冲),故而该类还是整个算法和用户代码之间的纽带。上面的种种奠定了该类在整个算法中的地位,是整个算法三个核心类之一。下一篇文章,也就是本回的第二部分,将介绍另一个核心类RegionToTriangle,正是该类的辛勤工作填满了Point2Table。