2010-11-03 21 views

Respuesta

29

La tangente de una curva es simplemente su derivada. La ecuación paramétrica que utiliza Michal:

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

debe tener un derivado de

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 

Lo cual, por cierto, parece estar mal en su pregunta anterior. Creo que estás usando la pendiente para una curva de Bezier cuadrática allí, no cúbica.

A partir de ahí, debe ser trivial implementar una función C que realiza este cálculo, como Michal ya ha proporcionado para la curva en sí.

7

Aquí está plenamente probado código para copiar y pegar:

Se basa approxidistant puntos a lo largo de la curva, y dibuja las tangentes.

bezierInterpolation encuentran los puntos

bezierTangent encuentra las tangentes

Hay dos versiones de bezierInterpolation suministran a continuación:

bezierInterpolation funciona perfectamente

altBezierInterpolation es exactamente el mismo, pero está escrito de una manera expandida, muy clara y explicativa . Hace que la aritmética sea mucho más fácil de entender.

Utilice cualquiera de esas dos rutinas: los resultados son idénticos.

En ambos casos, use bezierTangent para encontrar las tangentes. (Nota: el código base de Michal fabuloso here.)

También se incluye un ejemplo completo de cómo usar con 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]; 

Estas son las dos rutinas para calcular puntos aproximadamente equidistantes, y las tangentes de los, a lo largo de Bézier cúbica.

Para mayor claridad y confiabilidad, estas rutinas están escritas de la manera más simple y más explicativa posible.

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

Los cuatro valores precalculados, C1 C2 C3 C4, a veces se llaman los coeficientes de la bezier.(Recuerde que a b c d se suele llamar los cuatro puntos de control ).

Por supuesto, t va de 0 a 1, por ejemplo cada 0.05.

simplemente llame a estas rutinas vez para X, y luego una vez separado para Y.

espero que ayude a alguien!


hechos importantes:

(1) Es un hecho absoluto que: por desgracia, no es, sin duda, ningún método, proporcionado por Apple, para extraer puntos de un UIBezierPath.

(2) No olvide que es tan fácil como un pastel animar algo a lo largo de a UIBezierPath. Google many examples.

(3) Muchos preguntan, "¿No se puede usar CGPathApply para extraer los puntos de un UIBezierPath?" No, CGPathApply tiene nada que ver: simplemente le da una lista de sus instrucciones en la toma de cualquier camino (así, "comenzar aquí", "trazar una línea recta a este punto", etc, etc)

1

I también lo encontró propenso a errores al usar las ecuaciones provistas. Demasiado fácil pasar por alto un soporte sutil o mal colocado.

Por el contrario, la Wikipedia proporciona una mucho más claro, más limpio, en mi humilde opinión derivada:

enter image description here

... que implementa fácilmente en código como:

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

(suponiendo que tiene por vectores menos configurado en el idioma de su elección; la pregunta no está marcada como ObjC específicamente, y iOS ahora tiene varias langs disponibles)

+0

Entrada muy elegante Ad am, gracias (Puramente como curiosidad, en el actual Swift-iOS, tienen un f * -up donde las líneas de código que son demasiado largas ... no funcionan. ¡Entonces, uno simplemente dividiría eso en unas pocas partes!) – Fattie

Cuestiones relacionadas