16

Imagine you have a completely normal four-point bezier curve (two points and two control points) created using curveToPoint:controlPoint1:controlPoint2: in your cocoa application:

simple cubic bezier curve example
How do you find points (and the tangents), along the curve?


Later: for a complete, simplified, solution based on Michal's answer below, click to:
Find the tangent of a point on a cubic bezier curve (on an iPhone)

And just copy and paste the code from: https://stackoverflow.com/a/31317254/294884

Community
  • 1
  • 1
Fattie
  • 27,874
  • 70
  • 431
  • 719

4 Answers4

31

There's some simple math behind calculating the positions, you can read about it in every paper discussing Bézier curves, even on wikipedia. Anyway, I can relate to everybody who's in trouble to actually implement it in code, so I wrote this sample UIView as it's probably the easiest way to get you started.

#import "MBBezierView.h"

CGFloat bezierInterpolation(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d) {
    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;
}

@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];
    for (CGFloat t = 0.0; t <= 1.00001; t += 0.05) {
        CGPoint point = CGPointMake(bezierInterpolation(t, p1.x, p2.x, p3.x, p4.x), bezierInterpolation(t, p1.y, p2.y, p3.y, p4.y));
        UIBezierPath *pointPath = [UIBezierPath bezierPathWithArcCenter:point radius:5 startAngle:0 endAngle:2*M_PI clockwise:YES];
        [pointPath stroke];
    }   
}

@end

This is what I get:

alt text

Michal
  • 4,846
  • 3
  • 33
  • 25
  • 3
    @Joe Blow: is this really what you wanted? You said you wanted a point that is a distance D along the curve. Michal, correct me if I'm wrong, but your code just evaluates the curve at different parameter values, which isn't the same as length. As you can see, the points are closer together in the peaks and further apart in the middle of the curve. – CromTheDestroyer Nov 19 '10 at 03:56
  • Hi Crom -- you are quite right, they are only APPROXIMATELY equally spaced along the curve. Apparently there is no really easy way mathematically to get EXACT spacings. This common approximation works fine for game engines and the like. See also 4089443. – Fattie Nov 19 '10 at 06:17
  • hello Michel, i have implements this code but it shows me white screen instead of drawing. Whats wrong as i have called this method in view did load [self drawRect:bgImage.frame]; Whats wrong ? – Sandeep Singh May 25 '11 at 11:59
  • Hi @crom - to reiterate, they are only APPROXIMATELY equally spaced along the curve. It is extremely hard, I mean incredibly hard, to do equidistant spacing. How hard? Here's a typical math paper on the problem! http://www.geometrictools.com/Documentation/MovingAlongCurveSpecifiedSpeed.pdf Particularly note Daniel's amazingly useful link below. (Priceless link Daniel, thanks again.) – Fattie Apr 11 '14 at 06:29
  • Hey @Michael, could you take a look at my question at http://stackoverflow.com/questions/26857850/get-points-from-a-uibezierpath – Maximus S Nov 13 '14 at 05:35
3

The approximation that t is the distance along the curve that Michal is proposing can be troublesome with some curves and for some purposes. Sadly I've searched without luck for quite a while for an Obj-C implementation of the correct solution.

The solution is, however, described in a rather fantastic way by Mike "Pomax" Kamermans in his amazing Primer on Bezier Curves. It even has all the code, written in processing and in public domain. I'm astonished no one has converted this to Obj-C yet. Very tempted, that I am.

Daniel Schlaug
  • 1,504
  • 1
  • 13
  • 17
  • Staggeringly useful link @Daniel - thank you. Magnificent. This is one of those pages ... http://pomax.github.io/bezierinfo ... that is "just too useful!" Heh. Again thanks the link is so useful I'm bountying you! Awesome. – Fattie Apr 11 '14 at 06:31
  • 2
    hahaha, glad the Primer's finding a use. Let me know if you find anything missing or unclear =P – Mike 'Pomax' Kamermans May 15 '14 at 06:36
0

Any Beziér curve can be simply viewed as a polynomial function with vector or complex coefficients. A cubic Beziér curve such as the one in your screenshot, would then be generated by a polynomial function of order 3, and each point on the curve describes the result B(t) of the curve polynomial, evaluated for a particular input value t. If I'm not mistaken, once you know the polynomial used to create the curve, you can simply solve for B(t) = a+bi, where a+bi describes the point on the complex plane you want to find the t value for. Finding roots in polynomials like that is a well-understood problem, and can be solved algebraically for curves of order 2 or lower, and using some method like forward-newton for polynomials of higher degree. If you know the generating polynomial, it should of course also be very simple to find the derivatives. Beziér are usually drawn from "template polynomials" where only the coefficients are changed when a different curve is drawn, so you can probably look it up somewhere in the documentation.

0

Based on Michal's answer, wrote this in Swift for my project. Maybe will be helpful, just leave it here with explanation on original Fattie's screenshot:

let targetPoint = CGPoint(x: bezierInterpolation(t: distance, p1: sP1.point.x, cp1: sP1.nextMarker.x, cp2: sP2.previousMarker.x, p2: sP2.point.x),
                          y: bezierInterpolation(t: distance, p1: sP1.point.y, cp1: sP1.nextMarker.y, cp2: sP2.previousMarker.y, p2: sP2.point.y))

func bezierInterpolation(t: CGFloat, p1: CGFloat, cp1: CGFloat, cp2: CGFloat, p2: CGFloat) -> CGFloat {
    let t2: CGFloat = t * t;
    let t3: CGFloat = t2 * t;
    return p1 + (-p1 * 3 + t * (3 * p1 - p1 * t)) * t
    + (3 * cp1 + t * (-6 * cp1 + cp1 * 3 * t)) * t
    + (cp2 * 3 - cp2 * 3 * t) * t2
    + p2 * t3;
}

enter image description here

bodich
  • 1,708
  • 12
  • 31
  • RIght, there's something to be said for writing the arithmetic in the "long, very clear" method (obviously the result is identical) .. https://stackoverflow.com/a/31317254/294884 – Fattie Jan 07 '21 at 12:01