贝塞尔曲线的三种类型

您所在的位置:网站首页 adas模型三种类型 贝塞尔曲线的三种类型

贝塞尔曲线的三种类型

2023-10-15 13:25| 来源: 网络整理| 查看: 265

贝塞尔曲线的三种类型 项目 07/13/2023

下载示例

了解如何使用 SkiaSharp 呈现立方体、二次和圆锥贝塞尔曲线

贝塞尔曲线是以皮埃尔·贝塞尔 (1910年到1999年命名的,) 是雷诺汽车公司的法国工程师,他用曲线进行汽车车身的计算机辅助设计。

贝塞尔曲线以非常适合交互式设计而闻名:它们表现良好,换句话说,没有奇点会导致曲线变得无限或笨拙,而且它们通常美观:

基于计算机字体的字符轮廓通常使用贝塞尔曲线定义。

有关 贝塞尔曲线 的维基百科文章包含一些有用的背景信息。 术语 贝塞尔曲线 实际上是指一系列类似的曲线。 SkiaSharp 支持三种类型的贝塞尔曲线,称为 三次方曲线、 二次曲线和 圆锥曲线。 圆锥也称为 有理二次。

立方贝塞尔曲线

立方体是大多数开发人员在贝塞尔曲线的主题出现时想到的贝塞尔曲线类型。

可以使用具有三个SKPath参数的 方法或具有单独 x 和 y 参数的CubicTo重载,将三次方贝塞尔曲线添加到对象CubicTo:SKPoint

public void CubicTo (SKPoint point1, SKPoint point2, SKPoint point3) public void CubicTo (Single x1, Single y1, Single x2, Single y2, Single x3, Single y3)

曲线从轮廓的当前点开始。 完整的三次方贝塞尔曲线由四个点定义:

起点:轮廓中的当前点;如果 MoveTo 尚未调用,则 (0, 0) 第一个控制点:point1在调用中CubicTo 第二个控制点:point2在调用中CubicTo 终结点:point3在调用中CubicTo

生成的曲线从起点开始,在终点结束。 曲线一般不会通过两个控制点:相反,控制点的功能非常类似于磁铁,以将曲线拉向它们。

感受三次方贝塞尔曲线的最佳方式是通过实验。 这是派生自 InteractivePage的贝塞尔曲线页的用途。 BezierCurvePage.xaml 文件实例化 SKCanvasView 和 TouchEffect。 BezierCurvePage.xaml.cs 代码隐藏文件在其构造函数中创建四个TouchPoint对象。 事件处理程序 PaintSurface 创建 , SKPath 以基于四 TouchPoint 个 对象呈现贝塞尔曲线,并绘制从控制点到终点的虚线正切线:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) { SKImageInfo info = args.Info; SKSurface surface = args.Surface; SKCanvas canvas = surface.Canvas; canvas.Clear(); // Draw path with cubic Bezier curve using (SKPath path = new SKPath()) { path.MoveTo(touchPoints[0].Center); path.CubicTo(touchPoints[1].Center, touchPoints[2].Center, touchPoints[3].Center); canvas.DrawPath(path, strokePaint); } // Draw tangent lines canvas.DrawLine(touchPoints[0].Center.X, touchPoints[0].Center.Y, touchPoints[1].Center.X, touchPoints[1].Center.Y, dottedStrokePaint); canvas.DrawLine(touchPoints[2].Center.X, touchPoints[2].Center.Y, touchPoints[3].Center.X, touchPoints[3].Center.Y, dottedStrokePaint); foreach (TouchPoint touchPoint in touchPoints) { touchPoint.Paint(canvas); } }

此处运行:

从数学上看,曲线是三次方多项式。 曲线与直线相交,最多三点。 在起点上,曲线始终与从起点到第一个控制点的直线相切,并且方向与直线相同。 在终点,曲线始终与从第二个控制点到终点的直线相切,并且方向与直线相同。

三次方贝塞尔曲线始终由连接四个点的凸四边形约束。 这称为 凸壳。 如果控制点位于起点和终点之间的直线上,则贝塞尔曲线呈现为直线。 但曲线也可以交叉自身,如第三个屏幕截图所示。

路径轮廓可以包含多个连接的三次方贝塞尔曲线,但仅当以下三个点为共线性 (即位于直线) 上时,两个三次方贝塞尔曲线之间的连接才会平滑:

第一条曲线的第二个控制点 第一条曲线的终点,这也是第二条曲线的起点 第二条曲线的第一个控制点

在下一篇关于 SVG 路径数据的文章中,你将发现一种简化平滑连接贝塞尔曲线定义的工具。

有时,了解呈现三次方贝塞尔曲线的基础参数方程很有用。 对于范围从 0 到 1 的 t ,参数公式如下所示:

x (t) = (1 – t) ーx₀ + 3t (1 – t) ²x₁ + 3t² (1 – t) xー + tーx₃

y (t) = (1 – t) ーy₀ + 3t (1 – t) ²y₁ + 3t² (1 – t) yー + tーy₃

3 的最大指数确认这些是三次方多项式。 很容易验证当等于 0 时 t ,该点 (x₀,y₀) (即起点),当等于 1 时 t ,该点 (x₃,y₃) (即终点)。 在低) 值的 t 起始点 (附近,第一个控制点 (x₁、y₁) 具有强烈的效果,在终点附近 (高值't') 第二个控制点 (xー,yー) 具有很强的效果。

贝塞尔曲线近似圆弧

有时,使用贝塞尔曲线呈现圆弧线会很方便。三次方贝塞尔曲线可以近似于圆弧,最多四分之一圆,因此四条连接的贝塞尔曲线可以定义整个圆。 这一近似值在 25 年前发表的两篇文章中进行了讨论:

Tor Dokken, et al, “通过Curvature-Continuous贝塞尔曲线对圆的良好近似”, 计算机辅助几何设计 7 (1990) , 33-41.

Michael Goldapp,“立方多项式近似圆弧”, 计算机辅助几何设计8 (1991年) ,227-238。

下图显示了四个标有 pto、 pt1、 pt2和 pt3 的点,这些点定义了贝塞尔曲线 (以红色) 表示,该曲线近似于圆弧:

从起点和终点到控制点的线条与圆和贝塞尔曲线相切,其长度为 L。上面引用的第一篇文章指出,当该长度 L 的计算方式如下时,贝塞尔曲线最接近圆弧:

L = 4 × tan (α / 4) / 3

此图显示的角度为 45 度,因此 L 等于 0.265。 在代码中,该值将乘以圆圈的所需半径。

通过 贝塞尔圆弧 页,可以尝试定义贝塞尔曲线,以近似圆弧,其角度范围最大为 180 度。 BezierCircularArcPage.xaml 文件实例化 SKCanvasView 用于选择角度的 和 Slider 。 PaintSurfaceBezierCircularArgPage.xaml.cs 代码隐藏文件中的事件处理程序使用转换将点 (0,0) 设置为画布的中心。 它绘制一个以该点为中心的圆圈进行比较,然后计算贝塞尔曲线的两个控制点:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) { SKImageInfo info = args.Info; SKSurface surface = args.Surface; SKCanvas canvas = surface.Canvas; canvas.Clear(); // Translate to center canvas.Translate(info.Width / 2, info.Height / 2); // Draw the circle float radius = Math.Min(info.Width, info.Height) / 3; canvas.DrawCircle(0, 0, radius, blackStroke); // Get the value of the Slider float angle = (float)angleSlider.Value; // Calculate length of control point line float length = radius * 4 * (float)Math.Tan(Math.PI * angle / 180 / 4) / 3; // Calculate sin and cosine for half that angle float sin = (float)Math.Sin(Math.PI * angle / 180 / 2); float cos = (float)Math.Cos(Math.PI * angle / 180 / 2); // Find the end points SKPoint point0 = new SKPoint(-radius * sin, radius * cos); SKPoint point3 = new SKPoint(radius * sin, radius * cos); // Find the control points SKPoint point0Normalized = Normalize(point0); SKPoint point1 = point0 + new SKPoint(length * point0Normalized.Y, -length * point0Normalized.X); SKPoint point3Normalized = Normalize(point3); SKPoint point2 = point3 + new SKPoint(-length * point3Normalized.Y, length * point3Normalized.X); // Draw the points canvas.DrawCircle(point0.X, point0.Y, 10, blackFill); canvas.DrawCircle(point1.X, point1.Y, 10, blackFill); canvas.DrawCircle(point2.X, point2.Y, 10, blackFill); canvas.DrawCircle(point3.X, point3.Y, 10, blackFill); // Draw the tangent lines canvas.DrawLine(point0.X, point0.Y, point1.X, point1.Y, dottedStroke); canvas.DrawLine(point3.X, point3.Y, point2.X, point2.Y, dottedStroke); // Draw the Bezier curve using (SKPath path = new SKPath()) { path.MoveTo(point0); path.CubicTo(point1, point2, point3); canvas.DrawPath(path, redStroke); } } // Vector methods SKPoint Normalize(SKPoint v) { float magnitude = Magnitude(v); return new SKPoint(v.X / magnitude, v.Y / magnitude); } float Magnitude(SKPoint v) { return (float)Math.Sqrt(v.X * v.X + v.Y * v.Y); }

(point0 和) 的起点和 point3 终点是根据圆的法线参数方程计算的。 由于圆的中心是 (0, 0) ,因此这些点也可以被视为从圆的中心到圆周的径向矢量。 控制点位于与圆正切的线条上,因此它们与这些径向量呈直角。 与另一个直角的向量只是交换了 X 和 Y 坐标且其中一个坐标为负的原始向量。

下面是以不同角度运行的程序:

仔细查看第三张屏幕截图,你会发现当角度为 180 度时,贝塞尔曲线明显偏离了半圆,但 iOS 屏幕显示,当角度为 90 度时,它似乎适合四分之一圆。

当四分之一圆的方向如下所示时,计算两个控制点的坐标非常简单:

如果圆的半径为 100,则 L 为 55,这是一个容易记住的数字。

“ 圆 ”页面在圆形和正方形之间对图形进行动画处理。 该圆由四条贝塞尔曲线近似,其坐标显示在 类中 SquaringTheCirclePage 此数组定义的第一列中:

public class SquaringTheCirclePage : ContentPage { SKPoint[,] points = { { new SKPoint( 0, 100), new SKPoint( 0, 125), new SKPoint() }, { new SKPoint( 55, 100), new SKPoint( 62.5f, 62.5f), new SKPoint() }, { new SKPoint( 100, 55), new SKPoint( 62.5f, 62.5f), new SKPoint() }, { new SKPoint( 100, 0), new SKPoint( 125, 0), new SKPoint() }, { new SKPoint( 100, -55), new SKPoint( 62.5f, -62.5f), new SKPoint() }, { new SKPoint( 55, -100), new SKPoint( 62.5f, -62.5f), new SKPoint() }, { new SKPoint( 0, -100), new SKPoint( 0, -125), new SKPoint() }, { new SKPoint( -55, -100), new SKPoint(-62.5f, -62.5f), new SKPoint() }, { new SKPoint(-100, -55), new SKPoint(-62.5f, -62.5f), new SKPoint() }, { new SKPoint(-100, 0), new SKPoint( -125, 0), new SKPoint() }, { new SKPoint(-100, 55), new SKPoint(-62.5f, 62.5f), new SKPoint() }, { new SKPoint( -55, 100), new SKPoint(-62.5f, 62.5f), new SKPoint() }, { new SKPoint( 0, 100), new SKPoint( 0, 125), new SKPoint() } }; ... }

第二列包含四条贝塞尔曲线的坐标,这些曲线定义了一个正方形,其面积与圆的面积大致相同。 (绘制具有给定圆的 确切 面积的正方形是典型的无法解决的几何问题,即将 圆相等。) 对于使用贝塞尔曲线呈现正方形,每个曲线的两个控制点相同,并且它们与起点和终点是共线性的,因此贝塞尔曲线呈现为直线。

数组的第三列用于动画的内插值。 页面将计时器设置为 16 毫秒,并 PaintSurface 按该速率调用处理程序:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) { SKImageInfo info = args.Info; SKSurface surface = args.Surface; SKCanvas canvas = surface.Canvas; canvas.Clear(); canvas.Translate(info.Width / 2, info.Height / 2); canvas.Scale(Math.Min(info.Width / 300, info.Height / 300)); // Interpolate TimeSpan timeSpan = new TimeSpan(DateTime.Now.Ticks); float t = (float)(timeSpan.TotalSeconds % 3 / 3); // 0 to 1 every 3 seconds t = (1 + (float)Math.Sin(2 * Math.PI * t)) / 2; // 0 to 1 to 0 sinusoidally for (int i = 0; i < 13; i++) { points[i, 2] = new SKPoint( (1 - t) * points[i, 0].X + t * points[i, 1].X, (1 - t) * points[i, 0].Y + t * points[i, 1].Y); } // Create the path and draw it using (SKPath path = new SKPath()) { path.MoveTo(points[0, 2]); for (int i = 1; i < 13; i += 3) { path.CubicTo(points[i, 2], points[i + 1, 2], points[i + 2, 2]); } path.Close(); canvas.DrawPath(path, cyanFill); canvas.DrawPath(path, blueStroke); } }

这些点基于 的正弦振荡值 t进行内插。 然后,内插点用于构造一系列四条连接的贝塞尔曲线。 下面是正在运行的动画:

如果没有算法灵活性足以呈现为圆弧和直线的曲线,此类动画是不可能的。

贝塞尔无穷大页还利用贝塞尔曲线的功能来近似圆弧。下面是 类中的PaintSurfaceBezierInfinityPage处理程序:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) { SKImageInfo info = args.Info; SKSurface surface = args.Surface; SKCanvas canvas = surface.Canvas; canvas.Clear(); using (SKPath path = new SKPath()) { path.MoveTo(0, 0); // Center path.CubicTo( 50, -50, 95, -100, 150, -100); // To top of right loop path.CubicTo( 205, -100, 250, -55, 250, 0); // To far right of right loop path.CubicTo( 250, 55, 205, 100, 150, 100); // To bottom of right loop path.CubicTo( 95, 100, 50, 50, 0, 0); // Back to center path.CubicTo( -50, -50, -95, -100, -150, -100); // To top of left loop path.CubicTo(-205, -100, -250, -55, -250, 0); // To far left of left loop path.CubicTo(-250, 55, -205, 100, -150, 100); // To bottom of left loop path.CubicTo( -95, 100, -50, 50, 0, 0); // Back to center path.Close(); SKRect pathBounds = path.Bounds; canvas.Translate(info.Width / 2, info.Height / 2); canvas.Scale(0.9f * Math.Min(info.Width / pathBounds.Width, info.Height / pathBounds.Height)); using (SKPaint paint = new SKPaint()) { paint.Style = SKPaintStyle.Stroke; paint.Color = SKColors.Blue; paint.StrokeWidth = 5; canvas.DrawPath(path, paint); } } }

最好在图纸上绘制这些坐标,看看它们之间的关系。 无穷大符号围绕点 (0,0) ,两个循环的中心为 (-150,0) , (150,0) 和半径为 100。 在一系列 CubicTo 命令中,可以看到控制点的 X 坐标取值为 –95 和 –205, (这些值为 –150 加减 55) 、205 和 95 (150 加减 55) ,以及右侧和左侧的 250 和 –250。 唯一的例外是当无穷大符号在中心交叉自身时。 在这种情况下,控制点具有 50 和 –50 的组合坐标,以理顺中心附近的曲线。

下面是无穷大符号:

与绘制弧线的三种方式一文中的 Arc 无穷大页呈现的无限号相比,它向中心更平滑。

二次贝塞尔曲线

二次贝塞尔曲线只有一个控制点,曲线仅由三个点定义:起点、控制点和终点。 参数方程与三次方贝塞尔曲线非常相似,只不过最高指数为 2,因此曲线为二次多项式:

x (t) = (1 – t) ²x₀ + 2t (1 – t) x₁ + t²xー

y (t) = (1 – t) ²y₀ + 2t (1 – t) y₁ + t²y²

若要将二次贝塞尔曲线添加到路径,请使用 QuadTo 方法或具有单独 x 和 y 坐标的QuadTo重载:

public void QuadTo (SKPoint point1, SKPoint point2) public void QuadTo (Single x1, Single y1, Single x2, Single y2)

方法将当前位置 point2 的曲线添加到 ,并将 point1 作为控制点。

可以使用“二次曲线”页来试验 二 次贝塞尔曲线,这与 “贝塞尔曲线 ”页非常相似,只不过它只有三个接触点。 下面是 PaintSurfaceQuadraticCurve.xaml.cs 代码隐藏文件中的处理程序:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) { SKImageInfo info = args.Info; SKSurface surface = args.Surface; SKCanvas canvas = surface.Canvas; canvas.Clear(); // Draw path with quadratic Bezier using (SKPath path = new SKPath()) { path.MoveTo(touchPoints[0].Center); path.QuadTo(touchPoints[1].Center, touchPoints[2].Center); canvas.DrawPath(path, strokePaint); } // Draw tangent lines canvas.DrawLine(touchPoints[0].Center.X, touchPoints[0].Center.Y, touchPoints[1].Center.X, touchPoints[1].Center.Y, dottedStrokePaint); canvas.DrawLine(touchPoints[1].Center.X, touchPoints[1].Center.Y, touchPoints[2].Center.X, touchPoints[2].Center.Y, dottedStrokePaint); foreach (TouchPoint touchPoint in touchPoints) { touchPoint.Paint(canvas); } }

此处运行:

虚线与起点和终点处的曲线相切,并在控制点处相交。

如果需要一个一般形状的曲线,二次贝塞尔是好的,但你更喜欢只有一个控制点而不是两个控制点的便利性。 二次贝塞尔比任何其他曲线的渲染效率更高,这就是为什么它在 Skia 内部用来渲染椭圆弧的原因。

但是,二次贝塞尔曲线的形状不是椭圆形的,这就是为什么需要多个二次贝塞尔来近似椭圆弧。二次贝塞尔是抛物线的一段。

圆锥贝塞尔曲线

圆锥贝塞尔曲线(也称为有理二次贝塞尔曲线)是贝塞尔曲线系列中较新的补充。 与二次贝塞尔曲线一样,有理二次贝塞尔曲线涉及起点、终点和一个控制点。 但有理二次贝塞尔曲线也需要 权重 值。 它被称为 有理 二次,因为参数公式涉及比率。

X 和 Y 的参数方程是共享相同分母的比率。 下面是 t 的分母的公式,范围为 0 到 1,权重值为 w:

d (t) = (1 – t) ² + 2wt (1 – t) + t²

从理论上讲,有理二次可以涉及三个单独的权重值,三个项各一个,但这些值可以简化为中间项的一个权重值。

X 和 Y 坐标的参数方程类似于二次贝塞尔的参数方程,只不过中间项还包括权重值,表达式除以分母:

x (t) = ( (1 – t) ²x₀ + 2wt (1 – t) x₁ + t²xー) ) ÷ d (t)

y (t) = ( (1 – t) ²y₀ + 2wt (1 – t) y₁ + t²yー) ) ÷ d (t)

有理二次贝塞尔曲线也称为 圆锥 ,因为它们可以准确表示任何圆锥部分的段,例如双曲、抛物线、椭圆和圆。

若要将有理二次贝塞尔曲线添加到路径,请使用 ConicTo 方法或具有单独x和y坐标的ConicTo重载:

public void ConicTo (SKPoint point1, SKPoint point2, Single weight) public void ConicTo (Single x1, Single y1, Single x2, Single y2, Single weight)

请注意最后一个 weight 参数。

使用 “圆锥曲线 ”页可以试验这些曲线。 ConicCurvePage 类从 InteractivePage 派生。 ConicCurvePage.xaml 文件实例化 ,Slider以选择介于 –2 和 2 之间的权重值。 ConicCurvePage.xaml.cs 代码隐藏文件创建三个TouchPoint对象,处理程序PaintSurface只是将结果曲线与切线呈现到控制点:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) { SKImageInfo info = args.Info; SKSurface surface = args.Surface; SKCanvas canvas = surface.Canvas; canvas.Clear(); // Draw path with conic curve using (SKPath path = new SKPath()) { path.MoveTo(touchPoints[0].Center); path.ConicTo(touchPoints[1].Center, touchPoints[2].Center, (float)weightSlider.Value); canvas.DrawPath(path, strokePaint); } // Draw tangent lines canvas.DrawLine(touchPoints[0].Center.X, touchPoints[0].Center.Y, touchPoints[1].Center.X, touchPoints[1].Center.Y, dottedStrokePaint); canvas.DrawLine(touchPoints[1].Center.X, touchPoints[1].Center.Y, touchPoints[2].Center.X, touchPoints[2].Center.Y, dottedStrokePaint); foreach (TouchPoint touchPoint in touchPoints) { touchPoint.Paint(canvas); } }

此处运行:

如你所看到的,当权重较高时,控制点似乎向它拉得更多。 当权重为零时,曲线将成为从起点到终点的直线。

理论上,允许负权重,并导致曲线从控制点 弯曲 。 但是,权重为 –1 或更低会导致参数方程中的分母对于 t 的特定值变为负值。 可能出于此原因,在 方法中 ConicTo 忽略负权重。 圆锥曲线程序允许设置负权重,但从试验中可以看出,负权重与权重为零的效果相同,并导致呈现直线。

很容易派生控制点和权重,以便使用 ConicTo 方法绘制一个圆弧, (但不包括) 半圆。 在下图中,起点和终点的正切线在控制点相交。

可以使用三角法来确定控制点与圆中心之间的距离:它是圆的半径除以角α一半的余弦值。 若要在起点和终点之间绘制圆弧,请将权重设置为角度的一半的余弦值。 请注意,如果角度为 180 度,则切线永远不会满足,并且权重为零。 但对于角度小于 180 度,数学工作正常。

“ 圆锥圆弧 ”页演示了这一点。 ConicCircularArc.xaml 文件实例化 用于Slider选择角度。 PaintSurfaceConicCircularArc.xaml.cs 代码隐藏文件中的处理程序计算控制点和权重:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) { SKImageInfo info = args.Info; SKSurface surface = args.Surface; SKCanvas canvas = surface.Canvas; canvas.Clear(); // Translate to center canvas.Translate(info.Width / 2, info.Height / 2); // Draw the circle float radius = Math.Min(info.Width, info.Height) / 4; canvas.DrawCircle(0, 0, radius, blackStroke); // Get the value of the Slider float angle = (float)angleSlider.Value; // Calculate sin and cosine for half that angle float sin = (float)Math.Sin(Math.PI * angle / 180 / 2); float cos = (float)Math.Cos(Math.PI * angle / 180 / 2); // Find the points and weight SKPoint point0 = new SKPoint(-radius * sin, radius * cos); SKPoint point1 = new SKPoint(0, radius / cos); SKPoint point2 = new SKPoint(radius * sin, radius * cos); float weight = cos; // Draw the points canvas.DrawCircle(point0.X, point0.Y, 10, blackFill); canvas.DrawCircle(point1.X, point1.Y, 10, blackFill); canvas.DrawCircle(point2.X, point2.Y, 10, blackFill); // Draw the tangent lines canvas.DrawLine(point0.X, point0.Y, point1.X, point1.Y, dottedStroke); canvas.DrawLine(point2.X, point2.Y, point1.X, point1.Y, dottedStroke); // Draw the conic using (SKPath path = new SKPath()) { path.MoveTo(point0); path.ConicTo(point1, point2, weight); canvas.DrawPath(path, redStroke); } }

如你所看到的,以红色显示的路径与显示的基础圆圈之间 ConicTo 没有视觉差异,以供参考:

但将角度设置为 180 度,数学失败。

遗憾的是,这种情况下 ConicTo 不支持负权重,因为理论上 (基于参数方程) ,圆可以通过对 具有相同点但权重为负值的另一次调用 ConicTo 来完成。 这将允许基于 (之间的任意角度创建只有两 ConicTo 条曲线的整个圆,但不包括) 零度和 180 度。

相关链接 SkiaSharp API SkiaSharpFormsDemos (示例)


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3