我编写了一个图表编辑器,并绘制了一些曲线链接,这些链接完全适用于二次贝塞尔曲线(见图):
我搜索绘制“尖峰”曲线链接的最佳方式(如果可能的话)。大致像这样(蓝色):
我不知道从哪里开始,我读了几篇关于画笔或“如何绘制弯曲文本”的文章,但这似乎不是我需要的......
感谢您的帮助或建议!:)
只是为了完成一些评论,贝塞尔曲线是用 quadrametricBezier 类从 3 点开始制作的。感谢大家 !
我编写了一个图表编辑器,并绘制了一些曲线链接,这些链接完全适用于二次贝塞尔曲线(见图):
我搜索绘制“尖峰”曲线链接的最佳方式(如果可能的话)。大致像这样(蓝色):
我不知道从哪里开始,我读了几篇关于画笔或“如何绘制弯曲文本”的文章,但这似乎不是我需要的......
感谢您的帮助或建议!:)
只是为了完成一些评论,贝塞尔曲线是用 quadrametricBezier 类从 3 点开始制作的。感谢大家 !
我认为 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
。
因此,如果您想例如找到t
第三个等距点的值,您可以计算s(3) = TotalLength / n * 3
. 然后,您将遍历采样点集,将距离相加,直到到达沿曲线的总距离 > s(3) 的点。现在您知道要查找的点之前和之后的点,您可以使用三规则来计算两者之间的 t。
现在您有一组沿曲线相隔相同距离的点:
这是最简单的部分:在每个等距点,计算法线(使用上面的导数公式)。将该法线除以其长度得到单位法线。然后添加到每个偶数点+d * UnitNormal
和每个奇数点-d * UnitNormal
,其中d
是尖峰的“深度”,即尖端到曲线的距离。
假设您已经计算出贝塞尔曲线,所需曲线是三角波乘以贝塞尔曲线与贝塞尔曲线的法向量的总和。您应该考虑的唯一一件事是,贝塞尔曲线是参数t
在 [0, 1] 中的参数曲线。然后你需要 Bezier 曲线长度函数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
。
对于那些可能对 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>