Namespace Infragistics.Samples.Common
Public Class BezierCurveBuilder
''' <summary>
''' Returns PathFigure representing a Bézier curve that passes through specified points
''' </summary>
Public Shared Function GetBezierSegments(points As PointCollection, tension As Double, Optional isClosed As Boolean = False) As PathFigure
Dim ret As New PathFigure()
ret.Segments.Clear()
ret.IsClosed = False
If isClosed Then
Dim first As Point = points(0)
Dim last As Point = points(points.Count - 1)
If first.X <> last.X OrElse first.Y <> last.Y Then
points.Add(first)
End If
End If
Dim bzPoints = GetBezierPoints(points, tension)
' First point is the starting point.
ret.StartPoint = bzPoints(0)
For i As Integer = 1 To bzPoints.Count - 1 Step 3
' B1 control point.
' B2 control point.
' P2 start / end point.
ret.Segments.Add(New BezierSegment() With { _
.Point1 = bzPoints(i), _
.Point2 = bzPoints(i + 1), _
.Point3 = bzPoints(i + 2) _
})
Next
Return ret
End Function
#Region "Bezier Methods"
' some of the logic for calculating is based an article on:
' http://www.codeproject.com/KB/silverlight/MapBezier.aspx
''' <summary>
''' Returns points of a Bézier curve passing through specified points
''' </summary>
''' <param name="points"></param>
''' <param name="tension"></param>
''' <returns></returns>
Public Shared Function GetBezierPoints(points As PointCollection, tension As Double) As PointCollection
Dim ret As New PointCollection()
For i As Integer = 0 To points.Count - 1
' for first point append as is.
If i = 0 Then
ret.Add(points(0))
Continue For
End If
' for each point except first and last get B1, B2. next point.
' Last point do not have a next point.
ret.Add(GetBezierControlPoint1(points, i - 1, tension))
ret.Add(GetBezierControlPoint2(points, i - 1, tension))
ret.Add(points(i))
Next
Return ret
End Function
''' <summary>
''' Returns first control point of a Bézier curve.
''' </summary>
''' <param name="points">Points on the curve.</param>
''' <param name="i">Point no to calculate control point for.</param>
''' <param name="tension">Tension</param>
''' <returns></returns>
''' <remarks>Formula: B1i = Pi + Pi' / 3</remarks>
Public Shared Function GetBezierControlPoint1(points As PointCollection, i As Integer, tension As Double) As Point
Dim drv = GetBezierDerivative(points, i, tension)
Return New Point(points(i).X + drv.X / 3, points(i).Y + drv.Y / 3)
End Function
''' <summary>
''' Returns second control point of a Bézier curve.
''' </summary>
''' <param name="points">Points on the curve.</param>
''' <param name="i">Point no to calculate control point for.</param>
''' <param name="tension">Tension</param>
''' <returns></returns>
''' <remarks>Formula: B2i = P[i + 1] - P'[i + 1] / 3</remarks>
Public Shared Function GetBezierControlPoint2(points As PointCollection, i As Integer, tension As Double) As Point
Dim drv = GetBezierDerivative(points, i + 1, tension)
Return New Point(points(i + 1).X - drv.X / 3, points(i + 1).Y - drv.Y / 3)
End Function
''' <summary>
''' Returns scaled derivative of a point in a point collection.
''' </summary>
''' <param name="points">Points on the curve.</param>
''' <param name="i">Point no to calculate control point for.</param>
''' <param name="tension">Tension</param>
''' <returns></returns>
Public Shared Function GetBezierDerivative(points As PointCollection, i As Integer, tension As Double) As Point
If points.Count < 2 Then
Throw New System.ArgumentOutOfRangeException("points", "PointCollection must contain at least two points.")
End If
Dim x As Double, y As Double
If i = 0 Then
' First point.
x = (points(1).X - points(0).X) / tension
y = (points(1).Y - points(0).Y) / tension
Return New Point(x, y)
End If
If i Is points.Count - 1 Then
' Last point.
x = (points(i).X - points(i - 1).X) / tension
y = (points(i).Y - points(i - 1).Y) / tension
Return New Point(x, y)
End If
x = (points(i + 1).X - points(i - 1).X) / tension
y = (points(i + 1).Y - points(i - 1).Y) / tension
Return New Point(x, y)
End Function
#End Region
''' <summary>
''' Returns string representing Path.Data for a Bézier curve.
''' This is intended for inspecting curves in Blend. Not used in the code.
''' </summary>
''' <returns>Path.Data that can be used in XAML.</returns>
Public Shared Function GetBezierPathDataString(points As PointCollection, tension As Double) As String
Dim bzPoints = GetBezierPoints(points, tension)
Dim sbRet As New System.Text.StringBuilder()
' M 0,0 C 1.66, 1.66 6.66,11.66 10,10
For i As Integer = 0 To bzPoints.Count - 1
If i = 0 Then
sbRet.AppendFormat("M {0},{1} ", bzPoints(i).X, bzPoints(i).Y)
Continue For
End If
If i Mod 3 = 1 Then
sbRet.Append("C ")
End If
sbRet.AppendFormat("{0},{1} ", bzPoints(i).X, bzPoints(i).Y)
Next
Return sbRet.ToString()
End Function
End Class
End Namespace