Public Class PointsList

  Public pointslist(100) As PointF
  Public pointslistflag(100) As Boolean
  Public pointsselectedflag(100) As Boolean
  Public maxpoint As Integer = 0

  Sub ResetPointslistflags()
    'flags all points as unused
    For i As Integer = 0 To pointslistflag.Length - 1
      pointslistflag(i) = False
      pointsselectedflag(i) = False
    Next
  End Sub

  Sub SetPointslistflags(ByVal p As Polygon)
    'flags those points used by the polygon
    For i As Integer = 0 To p.NPOINTS - 1
      pointslistflag(p.POINTS(i).Point) = True
    Next
  End Sub

  Sub ResetMaxpoint()
    'sets maxpoint to the index+1 of the largest used point
    maxpoint = 0
    For i As Integer = 0 To pointslistflag.Length - 1
      If pointslistflag(i) <> False Then maxpoint = i + 1
    Next
  End Sub

  Sub flip()
    For i As Integer = 0 To pointslistflag.Length - 1
      pointslist(i).Y = -pointslist(i).Y
    Next
  End Sub

  Sub Scale(ByVal a As Double)
    For i As Integer = 0 To pointslistflag.Length - 1
      pointslist(i).X *= a
      pointslist(i).Y *= a
    Next
  End Sub

  Sub rotate(ByVal a)
    Dim s, c As Double
    s = Math.Sin(Math.PI * a / 180)
    c = Math.Cos(Math.PI * a / 180)
    Dim p As PointF
    Dim selected As Boolean = False
    For i As Integer = 0 To pointslistflag.Length - 1
      If pointsselectedflag(i) Then selected = True
    Next
    For i As Integer = 0 To pointslistflag.Length - 1
      If pointsselectedflag(i) Or Not selected Then
        p = pointslist(i)
        pointslist(i).X = c * p.X - s * p.Y
        pointslist(i).Y = s * p.X + c * p.Y
      End If
    Next
  End Sub

  Function add(ByVal p As PointF, ByVal opt As String) As Integer
    Dim n As Integer = -1
    If opt = "Search" Then n = Search(p)
    If n = -1 Then
      If maxpoint > pointslist.Length - 1 Then
        ReDim Preserve pointslist(maxpoint + 100)
        ReDim Preserve pointslistflag(maxpoint + 100)
        ReDim Preserve pointsselectedflag(maxpoint + 100)
      End If
      pointslist(maxpoint) = p
      pointslistflag(maxpoint) = True
      maxpoint += 1
      Return maxpoint - 1
    End If
    Return n
  End Function

  Public Function GetPoint(ByVal index As Integer) As PointF
    Return pointslist(index)
  End Function

  Function GetTopLeft() As PointF
    Dim p As New PointF(10000, 10000)
    For i As Integer = 0 To pointslist.Length - 1
      If pointslistflag(i) Then
        If pointslist(i).X < p.X Then p.X = pointslist(i).X
        If pointslist(i).Y < p.Y Then p.Y = pointslist(i).Y
      End If
    Next
    Return p
  End Function

  Function GetBotRight() As PointF
    Dim p As New PointF(-10000, -100000)
    For i As Integer = 0 To pointslist.Length - 1
      If pointslistflag(i) Then
        If pointslist(i).X > p.X Then p.X = pointslist(i).X
        If pointslist(i).Y > p.Y Then p.Y = pointslist(i).Y
      End If
    Next
    Return p
  End Function

  Function Search(ByVal p As PointF) As Integer
    Dim i As Integer = 0
    While i < maxpoint
      If pointslistflag(i) Then
        If (pointslist(i).X - p.X) ^ 2 + (pointslist(i).Y - p.Y) ^ 2 < 100 Then Return i
      End If
      i += 1
    End While
    Return -1
  End Function

  Function GetMaxPoint() As Integer
    Return maxpoint
  End Function

  Sub nearest(ByVal p0 As PointF, ByRef p1 As Integer, ByRef p2 As Integer, ByRef dist As Double, ByRef angle As Double)
    Dim p, p3 As Integer
    Dim a, b, c, d As Double
    d = 1000000 : c = d : b = d
    'find the three nearest points
    For p = 0 To maxpoint - 1
      If pointslistflag(p) Then
        a = distance(p0, pointslist(p))
        If a < b Then
          p3 = p2 : p2 = p1 : p1 = p
          d = c : c = b : b = a
        ElseIf a < c Then
          p3 = p2 : p2 = p : d = c : c = a
        ElseIf a < d Then
          p3 = p : d = a
        End If
      End If
    Next
    'a = distance2line(p0, pointslist(p1), pointslist(p2))
    'b = distance2line(p0, pointslist(p2), pointslist(p3))
    'c = distance2line(p0, pointslist(p3), pointslist(p1))
    'If b < a And b < c Then p1 = p3
    'If c < b And c < a Then p2 = p3
    If threepointalgorithm(pointslist(p1), pointslist(p2), p0) = "Clockwise" Then swap(p1, p2)
    dist = distance(pointslist(p1), pointslist(p2))
    angle = arctan(pointslist(p1), pointslist(p2))
    p0 = pointslist(p1)
  End Sub

  Private Function distance2line(ByVal p0, ByVal p1, ByVal p2)
    'returns the square of the distance between a point p0 and a line p1-p2
    'also checks to see that the perpendicular falls between p1 and p2
    'if it doesn't it returns a massive number
    Dim A, B, C, D As Double
    'calculate the squares of the sides of the triangle
    A = (p1.X - p2.X) ^ 2 + (p1.Y - p2.Y) ^ 2
    B = (p0.X - p1.X) ^ 2 + (p0.Y - p1.Y) ^ 2
    C = (p0.X - p2.X) ^ 2 + (p0.Y - p2.Y) ^ 2
    D = A + B - C
    If D < 0 Then Return 1000000
    If D > 2 * A Then Return 1000000
    Return (2 * (A * B + A * C + B * C) - (A * A + B * B + C * C)) / (4 * A)
  End Function

  Shared Function distance(ByVal p1, ByVal p2) As Double
    Return Math.Sqrt((p1.X - p2.X) ^ 2 + (p1.Y - p2.Y) ^ 2)
  End Function

  Shared Function arctan(ByVal p1, ByVal p2) As Double
    Dim negX As Boolean
    Dim angle As Double
    negX = (p2.X < p1.X)
    If negX Then p2.X = 2 * p1.X - p2.X
    angle = Math.Atan((p2.Y - p1.Y) / (p2.X - p1.X))
    If negX Then angle = Math.PI - angle
    Restore(angle)
    Return angle
  End Function

  Shared Sub Restore(ByRef angle As Double)
    'returns an angle between -pi and +pi
    Dim pi As Double = Math.PI
    If angle > pi Then angle -= 2 * pi
    If angle < -pi Then angle += 2 * pi
  End Sub

  Public Function threepointalgorithm(ByVal p1 As PointF, ByVal p2 As PointF, ByVal p3 As PointF) As String
    Dim a, b, c, d, r As Double
    Dim result As String
    a = p2.X - p1.X : b = p2.Y - p1.Y
    c = p3.X - p1.X : d = p3.Y - p1.Y
    r = a * d - b * c
    Select Case True
      Case r < 0
        result = "Clockwise"
      Case r > 0
        result = "Anticlockwise"
      Case Else
        result = "straight"
    End Select
    Return result
  End Function

  Private Sub swap(ByRef p1, ByRef p2)
    Dim p3 As Integer
    p3 = p2 : p2 = p1 : p1 = p3
  End Sub

End Class
