バージョン

ベジエ曲線ビルダー

BezierCurveBuilder は指定の点を通るベジエ曲線を作成する方法を提供します。このオブジェクトは xamDataChart コントロールのカスタム シリーズの作成トピックで使用されます。

Visual Basic の場合:

Namespace Infragistics.Samples.Common
    Public Class BezierCurveBuilder
        ''' <summary>
        ''' 指定の点を通るベジエ曲線を表す PathFigure を返します。
        ''' </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)
            ' 最初の点が開始ポイントです。
            ret.StartPoint = bzPoints(0)
            For i As Integer = 1 To bzPoints.Count - 1 Step 3
                    ' B1 コントロール ポイント
                    ' B2 コントロール ポイント
                    ' P2 開始 / 終了ポイント
                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"
        ' 計算のためのロジックのいくつかは次の記事に基づいています。
        ' http://www.codeproject.com/KB/silverlight/MapBezier.aspx
        ''' <summary>
        ''' 指定の点を通るベジエ曲線のポイントを返します。
        ''' </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
                ' 最初の点をそのまま付加するため
                If i = 0 Then
                    ret.Add(points(0))
                    Continue For
                End If
                ' 最初と最後の点を除く各ポイントが B1、B2、次のポイントをもつため
                ' 最後の点は次のポイントをもちません。
                ret.Add(GetBezierControlPoint1(points, i - 1, tension))
                ret.Add(GetBezierControlPoint2(points, i - 1, tension))
                ret.Add(points(i))
            Next
            Return ret
        End Function
        ''' <summary>
        ''' ベジエ曲線の最初のコントロール ポイントを返します。
        ''' </summary>
        ''' <param name="points">曲線上のポイント</param>
        ''' <param name="i">コントロール ポイントを計算するためのポイント番号</param>
        ''' <param name="tension">テンション</param>
        ''' <returns></returns>
        ''' <remarks>式: 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>
        ''' ベジエ曲線の 2 番目のコントロール ポイントを返します。
        ''' </summary>
        ''' <param name="points">曲線上のポイント</param>
        ''' <param name="i">コントロール ポイントを計算するためのポイント番号</param>
        ''' <param name="tension">テンション</param>
        ''' <returns></returns>
        ''' <remarks>式: 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>
        ''' ポイント コレクションにポイントのサイズが変わった派生形を返します。
        ''' </summary>
        ''' <param name="points">曲線上のポイント</param>
        ''' <param name="i">コントロール ポイントを計算するためのポイント番号</param>
        ''' <param name="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
                ' 最初のポイント
                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
                ' 最後のポイント
                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>
        ''' ベジエ曲線のために Path.Data を表す文字列を返します。
        ''' Blend 中の曲線を探すためです。このコードでは使用されません。
        ''' </summary>
        ''' <returns>XAML で使用される Path.Data</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

C# の場合:

namespace Infragistics.Samples.Common
{
    public class BezierCurveBuilder
    {
        /// <summary>
        /// 指定の点を通るベジエ曲線を表す PathFigure を返します。
        /// </summary>
        public static PathFigure GetBezierSegments(PointCollection points, double tension, bool isClosed = false)
        {
            PathFigure ret = new PathFigure();
            ret.Segments.Clear();
            ret.IsClosed = false;
            if (isClosed)
            {
                Point first = points[0];
                Point last = points[points.Count - 1];
                if (first.X != last.X || first.Y != last.Y)
                {
                    points.Add(first);
                }
            }
            var bzPoints = GetBezierPoints(points, tension);
            // 最初の点が開始ポイントです。
            ret.StartPoint = bzPoints[0];
            for (int i = 1; i < bzPoints.Count; i += 3)
            {
                ret.Segments.Add(new BezierSegment
                {
                    Point1 = bzPoints[i],       // B1 コントロール ポイント
                    Point2 = bzPoints[i + 1],   // B2 コントロール ポイント
                    Point3 = bzPoints[i + 2]    // P2 開始 / 終了ポイント
                });
            }
            return ret;
        }
        #region Bezier Methods
        // 計算のためのロジックのいくつかは次の記事に基づいています。
        // http://www.codeproject.com/KB/silverlight/MapBezier.aspx
        /// <summary>
        /// 指定の点を通るベジエ曲線のポイントを返します。
        /// </summary>
        /// <param name="points"></param>
        /// <param name="tension"></param>
        /// <returns></returns>
        public static PointCollection GetBezierPoints(PointCollection points, double tension)
        {
            PointCollection ret = new PointCollection();
            for (int i = 0; i < points.Count; i++)
            {
                /// 最初の点をそのまま付加するため
                if (i == 0)
                {
                    ret.Add(points[0]);
                    continue;
                }
                /// 最初と最後の点を除く各ポイントが B1、B2、次のポイントをもつため
                /// 最後の点は次のポイントをもちません。
                ret.Add(GetBezierControlPoint1(points, i - 1, tension));
                ret.Add(GetBezierControlPoint2(points, i - 1, tension));
                ret.Add(points[i]);
            }
            return ret;
        }
        /// <summary>
        /// ベジエ曲線の最初のコントロール ポイントを返します。
        /// </summary>
        /// <param name="points">曲線上のポイント</param>
        /// <param name="i">コントロール ポイントを計算するためのポイント番号</param>
        /// <param name="tension">テンション</param>
        /// <returns></returns>
        /// <remarks>式: B1i = Pi + Pi' / 3</remarks>
        public static Point GetBezierControlPoint1(PointCollection points, int i, double tension)
        {
            var drv = GetBezierDerivative(points, i, tension);
            return new Point(points[i].X + drv.X / 3, points[i].Y + drv.Y / 3);
        }
        /// <summary>
        /// ベジエ曲線の 2 番目のコントロール ポイントを返します。
        /// </summary>
        /// <param name="points">曲線上のポイント</param>
        /// <param name="i">コントロール ポイントを計算するためのポイント番号</param>
        /// <param name="tension">テンション</param>
        /// <returns></returns>
        /// <remarks>式: B2i = P[i + 1] - P'[i + 1] / 3</remarks>
        public static Point GetBezierControlPoint2(PointCollection points, int i, double tension)
        {
            var drv = GetBezierDerivative(points, i + 1, tension);
            return new Point(points[i + 1].X - drv.X / 3, points[i + 1].Y - drv.Y / 3);
        }
        /// <summary>
        /// ポイント コレクションにポイントのサイズが変わった派生形を返します。
        /// </summary>
        /// <param name="points">曲線上のポイント</param>
        /// <param name="i">コントロール ポイントを計算するためのポイント番号</param>
        /// <param name="tension">テンション</param>
        /// <returns></returns>
        public static Point GetBezierDerivative(PointCollection points, int i, double tension)
        {
            if (points.Count < 2)
                throw new System.ArgumentOutOfRangeException("points", "PointCollection must contain at least two points.");
            double x, y;
            if (i == 0)
            {
                // 最初のポイント
                x = (points[1].X - points[0].X) / tension;
                y = (points[1].Y - points[0].Y) / tension;
                return new Point(x, y);
            }
            if (i == points.Count - 1)
            {
                // 最後のポイント
                x = (points[i].X - points[i - 1].X) / tension;
                y = (points[i].Y - points[i - 1].Y) / tension;
                return new Point(x, y);
            }
            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);
        }
        #endregion
        /// <summary>
        /// ベジエ曲線のために Path.Data を表す文字列を返します。
        /// Blend 中の曲線を探すためです。このコードでは使用されません。
        /// </summary>
        /// <returns>XAML で使用される Path.Data</returns>
        public static string GetBezierPathDataString(PointCollection points, double tension)
        {
            var bzPoints = GetBezierPoints(points, tension);
            System.Text.StringBuilder sbRet = new System.Text.StringBuilder();
            // M 0,0 C 1.66, 1.66 6.66,11.66 10,10
            for (int i = 0; i < bzPoints.Count; i++)
            {
                if (i == 0)
                {
                    sbRet.AppendFormat("M {0},{1} ", bzPoints[i].X, bzPoints[i].Y);
                    continue;
                }
                if (i % 3 == 1)
                {
                    sbRet.Append("C ");
                }
                sbRet.AppendFormat("{0},{1} ", bzPoints[i].X, bzPoints[i].Y);
            }
            return sbRet.ToString();
        }
    }
}