' Gambas module file

' Draw a smooth spline through a set of prescribed points. Based on:
' [1] https://www.particleincell.com/2012/bezier-splines/
' [2] https://www.jacos.nl/jacos_html/spline/circular/index.html

Private Const MIN_WEIGHT As Float = 1

Private Sub SameFloat(X As Float, Y As Float) As Boolean
  
  Dim F As Float
  
  F = Max(Abs(X), Abs(Y))
  If F = 0 Then
    Return True
  Else
    Return Abs(X - Y) / F < 1E-6
  Endif
  
End

Public Sub Run(aPoints As PointF[])
  
  Dim N As Integer
  Dim X As Float[]
  Dim Y As Float[]
  Dim I As Integer
  Dim aWeights As Float[]
  Dim PX2 As Float[]
  Dim PX1 As Float[]
  Dim PY2 As Float[]
  Dim PY1 As Float[]
  Dim bClose As Boolean
  
  N = aPoints.Count
  
  X = New Float[]
  Y = New Float[]
  
  For I = 0 To aPoints.Max
    X.Add(aPoints[I].X)
    Y.Add(aPoints[I].Y)
  Next
  
  If SameFloat(X[0], X[N - 1]) And If SameFloat(Y[0], Y[N - 1]) Then
    X.Pop()
    Y.Pop()
    Dec N
    bClose = True
  Endif
  
  aWeights = New Float[N]
  For I = 0 To N - 2
    aWeights[I] = Max(MIN_WEIGHT, Hyp(X[I + 1] - X[I], Y[I + 1] - Y[I]))
  Next
  aWeights[I] = Max(MIN_WEIGHT, Hyp(X[0] - X[I], Y[0] - Y[I]))
  
  ComputeControlPoints(X, aWeights, ByRef PX1, ByRef PX2)
  ComputeControlPoints(Y, aWeights, ByRef PY1, ByRef PY2)
  
  Paint.MoveTo(X[0], Y[0])
  For I = 0 To N - 2
    Paint.CurveTo(PX1[I], PY1[I], PX2[I], PY2[I], X[I + 1], Y[I + 1])
  Next
  
  If bClose Then
    Paint.CurveTo(PX1[I], PY1[I], PX2[I], PY2[I], X[0], Y[0])
  Endif
  
End

Private Sub ComputeControlPoints(aValues As Float[], aWeights As Float[], ByRef P1 As Float[], ByRef P2 As Float[])
  
  Dim W As Float[]
  Dim K As Float[]
  Dim N As Integer
  Dim I As Integer
  Dim FracI As Float
  Dim A As Float[]
  Dim B As Float[]
  Dim C As Float[]
  Dim R As Float[]
  
  W = aWeights.Copy()
  K = aValues.Copy()
  N = K.Count
  
  A = New Float[N]
  B = New Float[N]
  C = New Float[N]
  R = New Float[N]
  
  FracI = W[0] / W[1]
  A[0] = W[0] ^ 2
  B[0] = 2 * W[N - 1] * (W[N - 1] + W[0])
  C[0] = W[N - 1] * W[N - 1] * FracI
  R[0] = (W[N - 1] + W[0]) ^ 2 * K[0] + (W[N - 1] ^ 2) * (1 + FracI) * K[1]
  
  For I = 1 To N - 2
    
    FracI = W[I] / W[I + 1]
    A[I] = W[I] ^ 2
    B[I] = 2 * W[I - 1] * (W[I - 1] + W[I])
    C[I] = (W[I - 1] ^ 2) * FracI
    R[I] = (W[I - 1] + W[I]) ^ 2 * K[I] + (W[I - 1] ^ 2) * (1 + FracI) * K[I + 1]
    
  Next
  
  FracI = W[I] / W[0]
  A[I] = W[I] ^ 2
  B[I] = 2 * W[I - 1] * (W[I - 1] + W[I])
  C[I] = (W[I - 1] ^ 2) * FracI
  R[I] = (W[I - 1] + W[I]) ^ 2 * K[I] + (W[I - 1] ^ 2) * (1 + FracI) * K[0]
  
  P1 = ThomasCircular(R, A, B, C)
  
  P2 = New Float[P1.Count]
  
  For I = 0 To N - 2
    P2[I] = K[I + 1] * (1 + W[I] / W[I + 1]) - P1[I + 1] * (W[I] / W[I + 1])
  Next
  P2[I] = K[0] * (1 + W[I] / W[0]) - P1[0] * (W[I] / W[0])
  
End

Private Sub ThomasCircular(R As Float[], A As Float[], B As Float[], C As Float[]) As Float[]
  
  Dim N As Integer
  Dim LR As Float
  Dim I As Integer
  Dim M As Float
  Dim LC As Float[]
  Dim X As Float[]
  
  R = R.Copy()
  A = A.Copy()
  B = B.Copy()
  C = C.Copy()
  N = R.Count
  
  LC = New Float[N]
  LC[0] = A[0]
  
  LR = C[N - 1]
  
  For I = 0 To N - 4
    M = A[I + 1] / B[I]
    B[I + 1] -= M * C[I]
    R[I + 1] -= M * R[I]
    LC[I + 1] = -M * LC[I]
    
    M = LR / B[I]
    B[N - 1] -= M * LC[I]
    LR = -M * C[I]
    R[N - 1] -= M * R[I]
  Next
  
  M = A[I + 1] / B[I]
  B[I + 1] -= M * C[I]
  R[I + 1] -= M * R[I]
  
  C[I + 1] -= M * LC[I]
  
  M = LR / B[I]
  B[N - 1] -= M * LC[I]
  
  A[N - 1] -= M * C[I]
  
  R[N - 1] -= M * R[I]
  
  I = N - 2
  
  M = A[I + 1] / B[I]
  B[I + 1] -= M * C[I]
  R[I + 1] -= M * R[I]
  
  X = New Float[N]
  
  X[N - 1] = R[N - 1] / B[N - 1]
  LC[N - 2] = 0
  
  For I = N - 2 DownTo 0
    X[I] = (R[I] - C[I] * X[I + 1] - LC[I] * X[N - 1]) / B[I]
  Next
  
  Return X
  
End
