Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find the tangent of a point on a cubic bezier curve

Tags:

ios

swift

bezier

For a cubic Bézier curve, with the usual four points a, b, c and d,

for a given value t,

how to most elegantly find the tangent at that point?

like image 856
Fattie Avatar asked Nov 03 '10 16:11

Fattie


People also ask

How do you find the tangent of a Bezier curve?

Bézier Curves Are Tangent to Their First and Last Legs Letting u = 0 and u = 1 gives C'(0) = n (P1 - P0) and C'(1) = n (Pn - Pn-1) The first means that the tangent vector at u = 0 is in the direction of P1 - P0 multiplied by n. Therefore, the first leg in the indicated direction is tangent to the Bézier curve.

Which is the equation of the cubic Bezier curve?

In general, a cubic Bezier curve is parametrically defined as(2.68)Pt=1tt2t31000−33003−630−13−31V0V1V2V3,0≤t≤1where V0, V1, V2, V3 are the control points of the Bezier curve.

What is a cubic Bezier curve?

A Cubic Bezier curve is defined by four points P0, P1, P2, and P3. P0 and P3 are the start and the end of the curve and, in CSS these points are fixed as the coordinates are ratios. P0 is (0, 0) and represents the initial time and the initial state, P3 is (1, 1) and represents the final time and the final state.

What is the degree or order n of a cubic Bezier curve?

In general, the two-sided offset curve of a cubic Bézier is a 10th-order algebraic curve and more generally for a Bézier of degree n the two-sided offset curve is an algebraic curve of degree 4n−2.


2 Answers

The tangent of a curve is simply its derivative. The parametric equation that Michal uses:

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

should have a derivative of

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  

Which, by the way, appears to be wrong in your earlier question. I believe you're using the slope for a quadratic Bezier curve there, not cubic.

From there, it should be trivial to implement a C function that performs this calculation, like Michal has already provided for the curve itself.

like image 154
Brad Larson Avatar answered Oct 09 '22 04:10

Brad Larson


Here is fully tested code to copy and paste:

It draws approxidistant points along the curve, and it draws the tangents.

bezierInterpolation finds the points

bezierTangent finds the tangents

There are TWO VERSIONS of bezierInterpolation supplied below:

bezierInterpolation works perfectly.

altBezierInterpolation is exactly the same, BUT it is written in an expanded, clear, explanatory manner. It makes the arithmetic much easier to understand.

Use either of those two routines: the results are identical.

In both cases, use bezierTangent to find the tangents. (Note: Michal's fabulous code base here.)

A full example of how to use with drawRect: is also included.

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

Here are the two routines to calculate approximately equidistant points, and the tangents of those, along a bezier cubic.

For clarity and reliability, these routines are written in the simplest, most explanatory, way possible.

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

The four precalculated values, C1 C2 C3 C4, are sometimes called the coefficients of the bezier. (Recall that a b c d are usually called the four control points.)

Of course, t runs from 0 to 1, for example every 0.05.

Simply call these routines once for X, and then once separately for Y.

Hope it helps someone!


Important facts:

(1) It is an absolute fact that: unfortunately, there is, definitely, NO method, provided by Apple, to extract points from a UIBezierPath. True as of 2019.

(2) Don't forget it's as easy as pie to animate something along a UIBezierPath. Google many examples.

(3) Many ask, "Can't CGPathApply be used to extract the points from a UIBezierPath?" No, CGPathApply is totally unrelated: it simply gives you a list of your "instructions in making any path" (so, "start here", "draw a straight line to this point", etc etc.) The name is confusing but CGPathApply is totally unrelated to bezier paths.


For game programmers - as @Engineer points out you may well want the normal of the tangent, fortunately Apple has vector math built-in:

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

like image 33
Fattie Avatar answered Oct 09 '22 02:10

Fattie