Encuentra la tangente de un punto en una curva bezier cúbica

Para una curva Bézier cúbica con los cuatro puntos habituales a, b, c y d,

para un valor dado t,

cómo encontrar la tangente de la manera más elegante en ese punto?

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

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

debe tener una derivada 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í, debería ser trivial implementar una función C que realice este cálculo, como Michal ya ha proporcionado para la curva en sí.

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

Dibuja puntos aproximadamente distantes a lo largo de la curva, y dibuja las tangentes.

bezierInterpolation encuentra los puntos

bezierTangent encuentra las tangentes

Hay DOS VERSIONES de bezierInterpolation suministradas a continuación:

bezierInterpolation funciona perfectamente

altBezierInterpolation es exactamente lo 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.

Use 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 aquí ).

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

Aquí están las dos rutinas para calcular puntos aproximadamente equidistantes, y las tangentes de aquellos , a lo largo de un bezier cúbico.

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 del bezier. (Recuerde que los abcd generalmente se llaman los cuatro puntos de control ).

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

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

Espero que ayude a alguien!


Hechos importantes:

(1) Es un hecho absoluto que: desafortunadamente, no hay, definitivamente, ningún método, proporcionado por Apple, para extraer puntos de un UIBezierPath.

(2) No olvides que es tan fácil como un pastel animar algo a lo largo de UIBezierPath. Google muchos ejemplos .

(3) Muchos preguntan: "¿No se puede usar CGPathApply para extraer los puntos de UIBezierPath?" No, CGPathApply no tiene ninguna relación : simplemente te da una lista de tus instrucciones al hacer cualquier ruta (por lo tanto, "comienza aquí", "dibuja una línea recta hasta este punto", etc., etc.)

Me pareció demasiado propenso a errores para usar las ecuaciones proporcionadas. Demasiado fácil pasar por alto un soporte sutil o mal colocado.

Por el contrario, Wikipedia proporciona un IMHO mucho más claro, más limpio, derivado:

enter image description here

… que se 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 vector menos configurado en su idioma de elección, la pregunta no está marcada como ObjC específicamente, y iOS ahora tiene varias lenguas disponibles)

No pude hacer nada de esto hasta que me di cuenta de que para ecuaciones paramétricas, (dy / dt) / (dx / dt) = dy / dx

    Intereting Posts