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?
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.
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.
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.
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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With