26

通常の4つの点a、b、c、dを持つ3次ベジェ曲線の場合、

与えられた値tに対して、

その点で最もエレガントに接線を見つける方法は?

4

5 に答える 5

45

曲線の接線は単にその導関数です。Michalが使用するパラメトリック方程式:

P(t) = (1 - t)^3 * P0 + 3t(1-t)^2 * P1 + 3t^2 (1-t) * P2 + t^3 * P3

の導関数が必要です

dP(t) / dt =  -3(1-t)^2 * P0 + 3(1-t)^2 * P1 - 6t(1-t) * P1 - 3t^2 * P2 + 6t(1-t) * P2 + 3t^2 * P3 

ちなみに、これはあなたの以前の質問では間違っているようです。ここでは、3次ではなく、2次ベジェ曲線の勾配を使用していると思います。

そこから、Michalがすでに曲線自体に提供しているように、この計算を実行するC関数を実装するのは簡単なはずです。

于 2010-11-03T20:35:07.283 に答える
9

コピーして貼り付けるための完全にテストされたコードは次のとおりです。

曲線に沿って近似点を描画し、接線を描画します。

bezierInterpolationポイントを見つける

bezierTangent接線を見つけます

以下に提供される2つのバージョンがあります。bezierInterpolation

bezierInterpolation完璧に動作します。

altBezierInterpolationまったく同じですが、拡張された明確な説明的な方法で書かれています。これにより、算術がはるかに理解しやすくなります。

これらの2つのルーチンのいずれかを使用してください。結果は同じです。

どちらの場合も、を使用bezierTangentして接線を見つけます。(注:Michalのすばらしいコードベースはここにあります。)

での使用方法の完全な例drawRect:も含まれています。

// 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];

ベジェ3次曲線に沿って、ほぼ等距離の点とそれらの接線を計算する2つのルーチンを次に示します。

明確さと信頼性のために、これらのルーチンは可能な限り最も単純で、最も説明的な方法で書かれています。

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 );
    }

事前に計算された4つの値C1C2C3 C4は、ベジェの係数と呼ばれることもあります。(abcdは通常4つのコントロールポイントと呼ばれることを思い出してください。)

もちろん、tは0から1まで、たとえば0.05ごとに実行されます。

これらのルーチンをXに対して1回呼び出し、次にYに対して別々に1回呼び出すだけです。

それが誰かを助けることを願っています!


重要な事実:

(1)それは絶対的な事実です:残念ながら、UIBezierPathからポイントを抽出するためのAppleが提供する方法は間違いなくありません。2019年現在。

(2) UIBezierPathに沿って何かをアニメーション化するのはパイと同じくらい簡単であることを忘れないでください。グーグル多くの例

(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

于 2015-07-09T12:17:11.147 に答える
3

提供された方程式を使用するのはエラーが発生しやすいことがわかりました。微妙なtや置き忘れたブラケットを見逃しがちです。

対照的に、ウィキペディアは、はるかに明確で、よりクリーンで、派生的なIMHOを提供します。

ここに画像の説明を入力してください

...これは次のようにコードに簡単に実装されます:

3f * oneMinusT * oneMinusT * (p1 - p0)
+ 6f * t * oneMinusT * (p2 - p1)
+ 3f * t * t * (p3 - p2)

(選択した言語でベクトルマイナスが構成されていると仮定します。質問は特にObjCとしてマークされておらず、iOSでは現在いくつかの言語が利用可能です)

于 2018-01-14T23:20:19.413 に答える
3

これが私のSwiftの実装です。

冗長な数学演算をすべて排除することで、速度を最適化するために最善を尽くしました。つまり、数学演算の呼び出しを最小限に抑えます。そして、可能な限り少ない数の乗算を使用します(これは合計よりもはるかに高価です)。

ベジェを作成するための乗算は0です。次に、ベジェ点を取得するために3回乗算します。そして、ベジェへの接線を取得するための2つの乗算。

struct CubicBezier {

    private typealias Me = CubicBezier
    typealias Vector = CGVector
    typealias Point = CGPoint
    typealias Num = CGFloat
    typealias Coeficients = (C: Num, S: Num, M: Num, L: Num)

    let xCoeficients: Coeficients
    let yCoeficients: Coeficients

    static func coeficientsOfCurve(from c0: Num, through c1: Num, andThrough c2: Num, to c3: Num) -> Coeficients
    {
        let _3c0 = c0 + c0 + c0
        let _3c1 = c1 + c1 + c1
        let _3c2 = c2 + c2 + c2
        let _6c1 = _3c1 + _3c1

        let C = c3 - _3c2 + _3c1 - c0
        let S = _3c2 - _6c1 + _3c0
        let M = _3c1 - _3c0
        let L = c0

        return (C, S, M, L)
    }

    static func xOrYofCurveWith(coeficients coefs: Coeficients, at t: Num) -> Num
    {
        let (C, S, M, L) = coefs
        return ((C * t + S) * t + M) * t + L
    }

    static func xOrYofTangentToCurveWith(coeficients coefs: Coeficients, at t: Num) -> Num
    {
        let (C, S, M, _) = coefs
        return ((C + C + C) * t + S + S) * t + M
    }

    init(from start: Point, through c1: Point, andThrough c2: Point, to end: Point)
    {
        xCoeficients = Me.coeficientsOfCurve(from: start.x, through: c1.x, andThrough: c2.x, to: end.x)
        yCoeficients = Me.coeficientsOfCurve(from: start.y, through: c1.y, andThrough: c2.y, to: end.y)
    }

    func x(at t: Num) -> Num {
        return Me.xOrYofCurveWith(coeficients: xCoeficients, at: t)
    }

    func y(at t: Num) -> Num {
        return Me.xOrYofCurveWith(coeficients: yCoeficients, at: t)
    }

    func dx(at t: Num) -> Num {
        return Me.xOrYofTangentToCurveWith(coeficients: xCoeficients, at: t)
    }

    func dy(at t: Num) -> Num {
        return Me.xOrYofTangentToCurveWith(coeficients: yCoeficients, at: t)
    }

    func point(at t: Num) -> Point {
        return .init(x: x(at: t), y: y(at: t))
    }

    func tangent(at t: Num) -> Vector {
        return .init(dx: dx(at: t), dy: dy(at: t))
    }
}

次のように使用します:

let bezier = CubicBezier.init(from: .zero, through: .zero, andThrough: .zero, to: .zero)

let point02 = bezier.point(at: 0.2)
let point07 = bezier.point(at: 0.7)

let tangent01 = bezier.tangent(at: 0.1)
let tangent05 = bezier.tangent(at: 0.5)
于 2018-12-07T19:25:36.943 に答える
1

パラメトリック方程式の場合、(dy / dt)/(dx / dt)= dy / dxであることに気付くまで、これを機能させることはできませんでした。

于 2018-04-19T20:49:56.740 に答える