当前位置: 首页 > 面试题库 >

在三次贝塞尔曲线上找到点的切线

申浩广
2023-03-14
问题内容

对于三次贝塞尔曲线,通常具有四个点a,b,c和d,

对于给定的值t

如何最优雅地找到 切线


问题答案:

这是经过完全测试的代码,可以复制和粘贴:

它沿着曲线绘制 近似 点, 绘制切线。

bezierInterpolation 找到要点

bezierTangent 找到切线

两个版本bezierInterpolation下面提供:

bezierInterpolation 完美地工作。

altBezierInterpolation完全相同,但以扩展,清晰,解释性的方式编写。它使算法更容易理解。

使用这两个例程之一:结果相同。

在这两种情况下,bezierTangent都可用于查找切线。

drawRect:还包括如何使用with的完整示例。

// MBBezierView.m    original BY MICHAL stackoverflow #4058979

#import "MBBezierView.h"



CGFloat bezierInterpolation(
    CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d) {
// see also below for another way to do this, that follows the 'coefficients'
// idea, and is a little clearer
    CGFloat t2 = t * t;
    CGFloat t3 = t2 * t;
    return a + (-a * 3 + t * (3 * a - a * t)) * t
    + (3 * b + t * (-6 * b + b * 3 * t)) * t
    + (c * 3 - c * 3 * t) * t2
    + d * t3;
}

CGFloat altBezierInterpolation(
   CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d)
    {
// here's an alternative to Michal's bezierInterpolation above.
// the result is absolutely identical.
// of course, you could calculate the four 'coefficients' only once for
// both this and the slope calculation, if desired.
    CGFloat C1 = ( d - (3.0 * c) + (3.0 * b) - a );
    CGFloat C2 = ( (3.0 * c) - (6.0 * b) + (3.0 * a) );
    CGFloat C3 = ( (3.0 * b) - (3.0 * a) );
    CGFloat C4 = ( a );

    // it's now easy to calculate the point, using those coefficients:
    return ( C1*t*t*t + C2*t*t + C3*t + C4  );
    }







CGFloat bezierTangent(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d)
 {
    // note that abcd are aka x0 x1 x2 x3

/*  the four coefficients ..
    A = x3 - 3 * x2 + 3 * x1 - x0
    B = 3 * x2 - 6 * x1 + 3 * x0
    C = 3 * x1 - 3 * x0
    D = x0

    and then...
    Vx = 3At2 + 2Bt + C         */

    // first calcuate what are usually know as the coeffients,
    // they are trivial based on the four control points:

    CGFloat C1 = ( d - (3.0 * c) + (3.0 * b) - a );
    CGFloat C2 = ( (3.0 * c) - (6.0 * b) + (3.0 * a) );
    CGFloat C3 = ( (3.0 * b) - (3.0 * a) );
    CGFloat C4 = ( a );  // (not needed for this calculation)

    // finally it is easy to calculate the slope element,
    // using those coefficients:

    return ( ( 3.0 * C1 * t* t ) + ( 2.0 * C2 * t ) + C3 );

    // note that this routine works for both the x and y side;
    // simply run this routine twice, once for x once for y
    // note that there are sometimes said to be 8 (not 4) coefficients,
    // these are simply the four for x and four for y,
    // calculated as above in each case.
 }







@implementation MBBezierView

- (void)drawRect:(CGRect)rect {
    CGPoint p1, p2, p3, p4;

    p1 = CGPointMake(30, rect.size.height * 0.33);
    p2 = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
    p3 = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
    p4 = CGPointMake(-30 + CGRectGetMaxX(rect), rect.size.height * 0.66);

    [[UIColor blackColor] set];
    [[UIBezierPath bezierPathWithRect:rect] fill];
    [[UIColor redColor] setStroke];
    UIBezierPath *bezierPath = [[[UIBezierPath alloc] init] autorelease];   
    [bezierPath moveToPoint:p1];
    [bezierPath addCurveToPoint:p4 controlPoint1:p2 controlPoint2:p3];
    [bezierPath stroke];

    [[UIColor brownColor] setStroke];

 // now mark in points along the bezier!

    for (CGFloat t = 0.0; t <= 1.00001; t += 0.05) {
  [[UIColor brownColor] setStroke];

        CGPoint point = CGPointMake(
            bezierInterpolation(t, p1.x, p2.x, p3.x, p4.x),
            bezierInterpolation(t, p1.y, p2.y, p3.y, p4.y));

            // there, use either bezierInterpolation or altBezierInterpolation,
            // identical results for the position

        // just draw that point to indicate it...
        UIBezierPath *pointPath =
           [UIBezierPath bezierPathWithArcCenter:point
             radius:5 startAngle:0 endAngle:2*M_PI clockwise:YES];
        [pointPath stroke];

        // now find the tangent if someone on stackoverflow knows how
        CGPoint vel = CGPointMake(
            bezierTangent(t, p1.x, p2.x, p3.x, p4.x),
            bezierTangent(t, p1.y, p2.y, p3.y, p4.y));

        // the following code simply draws an indication of the tangent
        CGPoint demo = CGPointMake( point.x + (vel.x*0.3),
                                      point.y + (vel.y*0.33) );
        // (the only reason for the .3 is to make the pointers shorter)
        [[UIColor whiteColor] setStroke];
        UIBezierPath *vp = [UIBezierPath bezierPath];
        [vp moveToPoint:point];
        [vp addLineToPoint:demo];
        [vp stroke];
    }   
}

@end

to draw that class...
MBBezierView *mm = [[MBBezierView alloc]
                     initWithFrame:CGRectMake(400,20, 600,700)];
[mm setNeedsDisplay];
[self addSubview:mm];

这是两个用于计算 近似等距点以及 沿贝塞尔三次方 的切线的 例程。

为了清楚和可靠,这些例程以最简单,最具解释性的方式编写。

CGFloat bezierPoint(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d)
    {
    CGFloat C1 = ( d - (3.0 * c) + (3.0 * b) - a );
    CGFloat C2 = ( (3.0 * c) - (6.0 * b) + (3.0 * a) );
    CGFloat C3 = ( (3.0 * b) - (3.0 * a) );
    CGFloat C4 = ( a );

    return ( C1*t*t*t + C2*t*t + C3*t + C4  );
    }

CGFloat bezierTangent(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d)
    {
    CGFloat C1 = ( d - (3.0 * c) + (3.0 * b) - a );
    CGFloat C2 = ( (3.0 * c) - (6.0 * b) + (3.0 * a) );
    CGFloat C3 = ( (3.0 * b) - (3.0 * a) );
    CGFloat C4 = ( a );

    return ( ( 3.0 * C1 * t* t ) + ( 2.0 * C2 * t ) + C3 );
    }

四个预先计算的值C1 C2 C3 C4有时被称为贝塞尔 系数 。(回想一下abcd通常称为四个 控制点 。)

当然,t从0到1,例如每0.05。

只需 对X一次 调用这些例程 ,然后对Y分别 调用 一次。

希望它能对某人有所帮助!

重要事实:

(1) 绝对的事实 是:不幸的是,Apple确实没有提供任何方法来从UIBezierPath中提取点。截至2019年为准。

(2)不要忘记, 沿着 UIBezierPath
进行动画设置就像馅饼一样容易。Google的例子很多。

(3)许多人问: “不能使用CGPathApply从UIBezierPath中提取点吗?” 不, CGPathApply是完全不相关的
:它只是为您提供“进行任何路径的说明”的列表(因此,“从此处开始”,“到此处画一条直线”等)。名称令人困惑,但CGPathApply与贝塞尔曲线完全无关。

对于游戏程序员-正如@Engineer指出的那样,您可能想要切线的法线,幸运的是Apple内置了矢量数学:

https://developer.apple.com/documentation/accelerate/simd/working_with_vectors


https://developer.apple.com/documentation/simd/2896658-simd_normalize



 类似资料:
  • bezierCurveTo()方法 绘制三次贝塞尔曲线代码如下。 context.bezierCurveTo(cp1x,cp1y,cp2x,cp2y,x,y); 这个方法可谓是绘制波浪线的神器。根据之前的结论,n阶贝塞尔曲线就有n-1个控制点,所以三次贝塞尔曲线有1个起始点、1个终止点、2个控制点。因此传入的6个参数分别为控制点cp1 (cp1x, cp1y),控制点cp2 (cp2x, cp2

  • 创建一条平滑的三维 三次贝塞尔曲线, 由起点、终点和两个控制点所定义。 代码示例 const curve = new THREE.CubicBezierCurve3( new THREE.Vector3( -10, 0, 0 ), new THREE.Vector3( -5, 15, 0 ), new THREE.Vector3( 20, 15, 0 ), new THREE.Ve

  • 创建一条平滑的三维 二次贝塞尔曲线, 由起点、终点和一个控制点所定义。 代码示例 const curve = new THREE.QuadraticBezierCurve3( new THREE.Vector3( -10, 0, 0 ), new THREE.Vector3( 20, 15, 0 ), new THREE.Vector3( 10, 0, 0 ) ); const poi

  • 创建一条平滑的二维 三次贝塞尔曲线, 由起点、终点和两个控制点所定义。 代码示例 const curve = new THREE.CubicBezierCurve( new THREE.Vector2( -10, 0 ), new THREE.Vector2( -5, 15 ), new THREE.Vector2( 20, 15 ), new THREE.Vector2( 10,

  • 贝塞尔曲线 Bézier curve(贝塞尔曲线)是应用于二维图形应用程序的数学曲线。 曲线定义:起始点、终止点、控制点。通过调整控制点,贝塞尔曲线的形状会发生变化。 1962年,法国数学家Pierre Bézier第一个研究了这种矢量绘制曲线的方法,并给出了详细的计算公式,因此按照这样的公式绘制出来的曲线就用他的姓氏来命名,称为贝塞尔曲线。 这里我们不介绍计算公式,只要知道贝塞尔曲线是一条由起始

  • 我需要得到一条三次(2D)bezier曲线B(t)的点Q,其中从点Q到另一个给定点P的直线与bezier曲线垂直相交。 我知道:P,B(t) 我寻找:Q(基本上我想要g的斜率,但当我知道Q时,我可以很容易地计算出来,但g的斜率已经足够了) 注意,我认为这个ansatz是错误的。这只是为了完整性而包括的。 其中B(x)是笛卡尔坐标系下的bezier曲线,B'(x)是(笛卡尔坐标系下的)导数,k是与y