折线遵循弯曲的路径

时间:2012-08-19 17:59:02

标签: wpf

我编写了一个图表编辑器,并绘制了一些曲线链接,这些链接完全适用于二次贝塞尔曲线段(见图):

BezierLink

我搜索最好的方式(如果可能的话)绘制“尖峰”曲线链接。大约像这样(蓝色):

SpikedLine

我不知道从哪里开始,我读了很少关于画笔的文章或“如何画出弯曲的文字”,但它似乎不是我需要的......

感谢您的帮助或建议! :)

只是为了完成一些评论,bezier是使用quadrametricBezier类从3点开始的。谢谢你们所有人!

3 个答案:

答案 0 :(得分:5)

我认为WPF中没有内置的方法可以做到这一点。您必须自己计算坐标并自己绘制线条(例如使用DrawingVisual)。

要计算坐标,您必须:

步骤1沿贝塞尔曲线的采样点。

具有4个控制点的贝塞尔曲线具有以下公式:

curve(t) = t^3 p1 + 3 t^2 (1-t) p2 + 3 t (1-t)^2 p3 + (1-t)^3 p4

d/dt curve(t) = 3 p3 - 3 p4 + 6 p2 t - 12 p3 t + 6 p4 t + 3 p1 t^2 - 9 p2 t^2 + 9 p3 t^2 - 3 p4 t^2

使用这些公式,您可以计算曲线上的点及其切线方向。将切线方向旋转90°(即交换X / Y并改变Y的符号)给出法线方向。

然而,这些点并不等距:

enter image description here

因此,如果你直接使用这些点,你会得到一条曲线,其中一些“峰值”比其他点短:

enter image description here

步骤2:沿曲线获得等距点

您现在有一个沿曲线的点列表。您可以计算每个点与下一个点之间的欧氏距离。将所有这些距离相加得出曲线的总长度。

假设你想要(大约)10像素宽的尖峰。那你需要n=round(TotalLength / 10)分。这些点位于s(i) = TotalLength / n * i

所以如果你想要,例如找到第3个等距点的t值,您需要计算s(3) = TotalLength / n * 3。然后你迭代一组采样点,总结你去的距离,直到你到达一个沿着曲线的总距离的点> S(3)。现在您知道要查找的点之前和之后的点,并且您可以使用三的规则来计算它们之间的t。

现在你有一组沿着曲线相同距离的点:

enter image description here

第3步:绘制尖峰

这是最简单的部分:在每个等距点,计算法线(使用上面的导数公式)。将该法线除以其长度以使单位正常。然后添加到每个偶数点+d * UnitNormal和每个奇数点-d * UnitNormal,其中d是尖峰的“深度”,即尖端到曲线的距离。

enter image description here

答案 1 :(得分:1)

假设您已经计算了Bezier曲线,则所需曲线是triangle wave与Bezier曲线的Bezier曲线的法向量相乘的总和。您唯一应该考虑的是贝塞尔曲线是参数曲线,参数t在[0,1]中。然后你需要贝塞尔曲线长度函数L(t)并将其插入三角波方程而不是t

三角波也可以通过模运算表达

TW(t) = M * abs(mod(q * L(t), n * 2 - 2) - n + 1) + 1

,其中

M - 波浪的大小,

q - 路径缩放系数

n - 曲线时段,

t - 贝塞尔曲线的参数,

L(t) - 贝塞尔曲线的长度函数。

结果曲线:

C(t) = TW(t) * B_normal(t) + B(t)

其中B_normal(t)是点t处贝塞尔曲线的法线向量。

答案 2 :(得分:1)

对于那些可能对WPF解决方案感兴趣的人,我最终根据QuadraticBezierSegmentPathGeometry类对此进行编码(不是非常优化)。

非常感谢大家。 :)

public partial class MainWindow : Window
{
    int orientation = 1;
    int compt = 0;
    int SpikeWidth = 5;
    int SpikeHeigth = 3;

    public MainWindow()
    {

        InitializeComponent();

        Polyline wave = new Polyline();
        wave.Stroke = Brushes.Blue;
        wave.StrokeThickness = 2;

        PathGeometry pg = BezierPath.Data.GetFlattenedPathGeometry();
        double CurveLenght = GetLength(pg, PathFigure.StartPoint);
        double NbrPoint = (Math.Round(CurveLenght / SpikeWidth));
        for (int i = 0; i <= NbrPoint; i++)
        {
            //Calcul de T
            double t = SpikeWidth * i / CurveLenght;

            Point TangentPoint;
            Point PointToDraw;
            pg.GetPointAtFractionLength(t, out PointToDraw, out TangentPoint);


            // Calcul de l'angle
            double a = Math.Atan2(TangentPoint.Y, TangentPoint.X);
            a += Math.PI / 2;

            //Alterner un point sur deux de chaque coté de la courbe
            if (compt % 2 == 0)
                orientation = 1;
            else
                orientation = -1;

            //Calcul du point et ajout à la polyligne.
            //Point calculation and added to the polyline.
            wave.Points.Add(new Point(Math.Cos(a) * SpikeHeigth * orientation + PointToDraw.X, Math.Sin(a) * SpikeHeigth * orientation + PointToDraw.Y));


            //Compte le nombre de passage pour l'orientation
            compt += 1;
        }
        //Traçage sur la canvas
        cv.Children.Add(wave);
    }           

    private double GetLength(PathGeometry pg, Point startPoint)
    {
        PolyLineSegment pls = pg.Figures[0].Segments[0] as PolyLineSegment;

        double distance = 0;
        foreach (Point pt in pls.Points)
        {
            distance += Math.Sqrt((startPoint.X - pt.X).Pow(2) + (startPoint.Y - pt.Y).Pow(2));
            startPoint = pt;
        }
        return distance;
    }
}
<Canvas x:Name="cv">
    <Path Stroke="Black" x:Name="BezierPath">
        <Path.Data>
            <PathGeometry>
                <PathGeometry.Figures>
                    <PathFigureCollection>
                        <PathFigure x:Name="PathFigure" StartPoint="10,400">
                        <PathFigure.Segments>
                            <PathSegmentCollection>
                                <QuadraticBezierSegment x:Name="BezierSegment" Point1="50,80" Point2="400,400">
                                </QuadraticBezierSegment>
                            </PathSegmentCollection>
                        </PathFigure.Segments>
                    </PathFigure>
                    </PathFigureCollection>
                </PathGeometry.Figures>
            </PathGeometry>                    
        </Path.Data>
    </Path>               
</Canvas>