65.9K
CodeProject 正在变化。 阅读更多。
Home

Silverlight 中的贝塞尔曲线角度计算

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (8投票s)

2011年5月21日

CPOL

4分钟阅读

viewsIcon

43049

downloadIcon

1204

本文演示了如何计算贝塞尔曲线中的各种点,以及如何确定曲线上任意一点的角度。

1.png

引言

贝塞尔曲线是计算机图形学中最常用的曲线之一。更高维度的贝塞尔曲线用于 3D 工作,其中包括贝塞尔三角形。Pierre Bezier,一位雷诺汽车公司的工程师,推导出了这条曲线的计算机制概念,并用它来设计汽车车身部件。通常,贝塞尔方程很简单,但它们是关于变量 t(t 的取值范围从 0 到 1)的参数方程。

我们将用它来做什么?

作为一名软件工程师/程序员,当我们看到一些不规则的数学计算时,我们都会问这个问题。但答案是,我们在几乎所有的图形相关工作中都使用贝塞尔曲线来绘制简单而平滑的曲线。我们在几乎所有的动画中都使用贝塞尔曲线来实现从一个点到另一个点的平滑移动。这在 Adobe Flash 和 Silverlight 的转换中被广泛使用。

有时,当你使用 3D 动画时,贝塞尔曲线用于定义 3D 路径和 2D 曲线以进行关键帧插值。

如果我们想创建扭曲的文本或搞笑的脸部,我们也会使用贝塞尔曲线,因为这需要大量的计算;具体来说,是大量在后台进行的数学计算来执行这些功能。

在本文中,我将尝试定义一个简单的贝塞尔曲线(及其点),并找到贝塞尔曲线上任意给定点的角度。本文将对那些已经拥有贝塞尔曲线并希望对象沿着曲线移动同时保持对象旋转的人非常有帮助。

我是怎么做到的?

我有一个需求,需要在给定的画布上绘制一个对象的路径,并将这些点转换为平滑的曲线。完成此操作后,我需要移动一个对象(如箭头),使其从起始位置到结束位置,同时保持箭头的旋转角度。

我最初认为这是一个棘手的要求,但正如你所知,维基百科几乎包含了所有的定义和计算。作为一个数学爱好者,我接受了挑战,花了一段时间研究贝塞尔曲线,最终得出了一个标准函数。

B(t) = (1-t)2 P0 + 2(1-t) t P1 + t2P2 , t ranges from 0,1

这是二次贝塞尔曲线的函数,其中 P0 是起点,P2 是终点,曲线通过 P1。可以使用给定的函数求解各种其他贝塞尔曲线。

我们将此函数转换为如下所示的 X、Y 坐标

  • B(t)x = (1-t)2 P0x + 2(1-t) t P1x + t2P2x ,t 的范围是 0 到 1(确定曲线的 x 坐标)
  • B(t)y = (1-t)2 P0y + 2(1-t) t P1y + t2P2y ,t 的范围是 0 到 1(确定曲线的 y 坐标)

通过代入 t(0-1)的值,我们可以得到一系列值;为 t 提供更精确的值将为你提供 x、y 坐标的细微值,使其更加平滑。

代码描述

代码分为以下几个块

  • 绘制贝塞尔曲线(Silverlight 已包含此功能)。
  • 查找曲线上每个部分对应的贝塞尔坐标。
  • 确定点之间的角度。
  • 绘制你的箭头。

System.Windows.Media 命名空间已经提供了用于绘制贝塞尔曲线段的各种类。它们是 BezierSegment(用于绘制两个点之间的三次贝塞尔曲线)、PolyBezierSegment(用于绘制多个三次贝塞尔曲线)、QuadraticBezierSegment(用于绘制二次贝塞尔曲线)和 PolyQuadraticBezierSegment(用于绘制一系列二次贝塞尔曲线)。

我们使用如下所示的 QuadraticBezierSegment 类,它将为给定的三个点创建一条贝塞尔曲线。

Path p = new Path();
p.Stroke= new SolidColorBrush(Color.FromArgb(100,255,100,255));
p.StrokeThickness= 2;
//Setting the path. 
QuadraticBezierSegment qg = new QuadraticBezierSegment();
       
qg.Point1 = new Point(Convert.ToDouble(p1x.Text), Convert.ToDouble(p1y.Text));
//P1

qg.Point2 = new Point(Convert.ToDouble(p2x.Text), Convert.ToDouble(p2y.Text));
//P2

PathFigure pf = new PathFigure();
pf.StartPoint = new Point(Convert.ToDouble(p0x.Text), 
                          Convert.ToDouble(p0y.Text));
//P0

pf.Segments.Add(qg);
PathGeometry pg = new PathGeometry();
pg.Figures.Add(pf); 
p.Data=pg;
LayoutRoot.Children.Add(p); 

我们已经使用上述代码绘制了贝塞尔曲线;曲线绘制完成后,我们将确定曲线中涉及的各种点。就我而言,我只想确定终点的箭头,所以我将 t 的值限制在 0.99 到 1 之间,这样我只会得到两个点(这两个点是确定箭头角度所必需的)。

注意:如果你想找到曲线上所有涉及的点,你应该将起始值从 0.99 更改为 0,如果你想获取更细微的曲线点,请将增量值从 0.01 更改为任何更小的值。

double x = 0, y = 0;
double xold = 0, yold = 0;
double angle = 0;
for (double t = 0.99; t < 1.001; t += 0.01)
{
    x = ((1 - t) * (1 - t) * p0.X) + (2 * t * (1 - t) * p1.X) + (t * t * p2.X);
    //this statement is used to determine the x coordinate of the curve. 

    y = ((1 - t) * (1 - t) * p0.Y) + (2 * t * (1 - t) * p1.Y) + (t * t * p2.Y);
    //this statement is used to determine the y coordinate of the curve. 

    x = Math.Round(x, 3);
    y = Math.Round(y, 3);
    angle = Math.Round(Angle(xold, yold, x, y), 3);
    textBox1.Text += "Angle = " + angle.ToString() + "Degrees :" + 
                     x.ToString() + "," + y.ToString() + "\n";

    xold = x; yold = y;
}

e 可以使用几何函数确定两个不同点之间的角度。Carlos Femmer 提供的链接(谢谢你!)对此进行了精彩的解释:http://www.carlosfemmer.com/post/2006/02/Calculate-Angle-between-2-points-using-C.aspx

我使用了与上述 URL 中提到的相同的函数。

本文的最后一部分是根据从贝塞尔曲线最后一个点确定的角度绘制箭头。我使用了一个 rotateImage 函数,该函数根据角度旋转图像的功能。下面的代码解释了涉及的细节。

//code to be placed in the Bezier Function
Image image = new Image();
Uri uri = new Uri("arrow.jpg", UriKind.Relative);
ImageSource img = new System.Windows.Media.Imaging.BitmapImage(uri);
image.Margin = new Thickness(x, y, 0, 0);

image.SetValue(Image.SourceProperty, img);
image.Width = 36;
image.Height = 32;
image.Stretch = Stretch.Fill;
image.VerticalAlignment = System.Windows.VerticalAlignment.Top;
image.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;

LayoutRoot.Children.Add(image);

private void rotateImage(double angle, Image image1)
{
    WriteableBitmap bm;
    RotateTransform objTran;

    try
    {
        objTran = new RotateTransform();
        objTran.CenterX = 0.5 * image1.Width;
        objTran.CenterY = 0.5 * image1.Height;

        objTran.Angle = angle;

        bm = new WriteableBitmap(image1, objTran);
        image1.Source = (System.Windows.Media.ImageSource)bm;
        bm = null;
        GC.Collect();
    }
    catch (Exception ex)
    {
        //do waht ever you want here :) 
       
    } 

其他变体

我们可以使用下面的公式创建贝塞尔曲线的变体

bn.png

历史

这段代码没有太多历史,我前几天才写好,但我有一个。不在这里写,以免你们的评论把我搞砸。

© . All rights reserved.