Frage

Für eine kubische Bézier-Kurve mit den üblichen vier Punkten a, b, c und d,

für einen bestimmten Wert t,

, wie man am elegantesten finden Sie die Tangens an diesem Punkt?

War es hilfreich?

Lösung

Die Tangente einer Kurve ist einfach sein Derivat. Die Parametergleichung, dass Michal verwendet:

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

sollte ein Derivat von

haben
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 

, die durch die Art und Weise, erscheint in Ihrer früheren Frage falsch. Ich glaube, dass Sie die Steigung dort für eine quadratische Bézierkurve verwenden, nicht kubisch.

Von dort sollte es trivial sein, eine C-Funktion zu implementieren, dass führt diese Berechnung, wie Michal bereits für die Kurve zur Verfügung gestellt hat selbst.

Andere Tipps

Hier ist Code vollständig getestet zu kopieren und einzufügen:

Es zieht approxidistant Punkte entlang der Kurve, und es die Tangenten zieht.

bezierInterpolation findet die Punkte

bezierTangent findet die Tangenten

Es gibt zwei Versionen von bezierInterpolation geliefert unter:

bezierInterpolation funktioniert perfekt.

altBezierInterpolation ist genau das gleiche, aber es ist in einem erweiterten, klar, erklärend geschrieben. Es macht die arithmetischen viel leichter zu verstehen.

Verwenden Sie eine dieser beiden Routinen: die Ergebnisse sind identisch

.

In beiden Fällen verwenden bezierTangent die Tangenten zu finden. (Anmerkung:. Michal fabelhafte Code-Basis href="https://stackoverflow.com/questions/4058979/">)

Ein vollständiges Beispiel dafür, wie mit drawRect: zu verwenden, ist ebenfalls enthalten.

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

Hier sind die beiden Routinen zu berechnen etwa gleich weit Punkte, und die Tangenten von denen , entlang einer Bezier kubisch.

Aus Gründen der Klarheit und Zuverlässigkeit sind diese Routinen im einfachsten geschrieben, die meisten erklärend, Art und Weise möglich.

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

Die vier vorberechnete Werte, C1 C2 C3 C4, sind manchmal die Koeffizienten der Bézier bezeichnet. (Daran erinnern, dass a b c d sind in der Regel die vier Kontrollpunkte genannt ).

Natürlich t läuft von 0 bis 1, zum Beispiel alle 0,05.

Rufen Sie einfach diese Routinen einmal für X, und dann einmal separat für Y.

Hope es hilft jemand!


Wichtige Fakten:

(1) Es ist eine absolute Tatsache , dass. Leider, auf jeden Fall, keine Methode, von Apple, um Extrakt Punkte von einem UIBezierPath

(2) Vergessen Sie nicht, es ist so einfach wie überzeugend zu animieren etwas zusammen ein UIBezierPath. Google viele Beispiele .

(3) Viele fragen, "Can not CGPathApply verwendet werden, um die Punkte von einem UIBezierPath zu extrahieren?" Nein, CGPathApply völlig unabhängig ist : es gibt Ihnen einfach eine Liste Ihrer Anweisungen in jeden Pfad zu machen (so, „Start hier“, „eine gerade Linie zu diesem Punkt ziehen“, etc etc.)


Für Spieleprogrammierer - als @Engineer Punkte aus man gut die Normale der Tangente wollen kann, zum Glück hat Apple Vektor Mathematik eingebaut:

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

Ich fand es zu fehleranfällig die mitgelieferten Gleichungen zu verwenden. Zu einfach einem subtilen t oder fehl am Platz Klammer zu verpassen.

Im Gegensatz dazu liefert Wikipedia eine viel klarere, sauberere, Ableitung IMHO:

... die Geräte leicht in Code wie:

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

(vorausgesetzt, Sie haben Vektor-Minus in der Sprache Ihrer Wahl konfiguriert, Frage nicht als ObjC speziell markiert ist, und iOS hat jetzt mehrere langs verfügbar)

Hier geht meine Swift Umsetzung.

versucht, den ich mein Bestes, um Geschwindigkeit optimieren, indem alle redundanten mathematische Operationen zu beseitigen. das heißt macht die minimale Anzahl der Anrufe an mathematischen Operationen. Und verwenden Sie eine möglichst geringe Anzahl von Multiplikationen (die sind viel teurer als Summen).

Es gibt 0 Multiplikationen die Bezier zu erstellen. Dann wurden 3 Multiplikationen einen Punkt Bezier zu bekommen. Und 2 Multiplikationen eine Tangente an den Bezier zu erhalten.

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

Verwenden Sie wie:

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)

Ich kann nichts davon an der Arbeit, bis ich für parametrische Gleichungen realisiert, dass (dy / dt) / (dx / dt) = dy / dx

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top