我编写了一个图表编辑器,并绘制了一些曲线链接,这些链接完全适用于二次贝塞尔曲线段(见图):
我搜索最好的方式(如果可能的话)绘制“尖峰”曲线链接。大约像这样(蓝色):
我不知道从哪里开始,我读了很少关于画笔的文章或“如何画出弯曲的文字”,但它似乎不是我需要的......
感谢您的帮助或建议! :)
只是为了完成一些评论,bezier是使用quadrametricBezier类从3点开始的。谢谢你们所有人!
答案 0 :(得分:5)
我认为WPF中没有内置的方法可以做到这一点。您必须自己计算坐标并自己绘制线条(例如使用DrawingVisual)。
要计算坐标,您必须:
具有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的符号)给出法线方向。
然而,这些点并不等距:
因此,如果你直接使用这些点,你会得到一条曲线,其中一些“峰值”比其他点短:
您现在有一个沿曲线的点列表。您可以计算每个点与下一个点之间的欧氏距离。将所有这些距离相加得出曲线的总长度。
假设你想要(大约)10像素宽的尖峰。那你需要n=round(TotalLength / 10)
分。这些点位于s(i) = TotalLength / n * i
。
所以如果你想要,例如找到第3个等距点的t
值,您需要计算s(3) = TotalLength / n * 3
。然后你迭代一组采样点,总结你去的距离,直到你到达一个沿着曲线的总距离的点> S(3)。现在您知道要查找的点之前和之后的点,并且您可以使用三的规则来计算它们之间的t。
现在你有一组沿着曲线相同距离的点:
这是最简单的部分:在每个等距点,计算法线(使用上面的导数公式)。将该法线除以其长度以使单位正常。然后添加到每个偶数点+d * UnitNormal
和每个奇数点-d * UnitNormal
,其中d
是尖峰的“深度”,即尖端到曲线的距离。
答案 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解决方案感兴趣的人,我最终根据QuadraticBezierSegment
和PathGeometry
类对此进行编码(不是非常优化)。
非常感谢大家。 :)
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>