15

I have many UIBezierPath that I am animating with CALyerAnimation @"StrokeStart" and @"strokeEnd".

I wish that the animation will have the same speed for all the paths so I thougt I might use the length of the path in :

DISTANCE / TIME = SPEED

Is there a way to calculate the path "length" ?

Thanks

Shani

shannoga
  • 19,649
  • 20
  • 104
  • 169
  • 4
    +1, I'm honestly surprised this hasn't been asked before. A four year old [Apple Mailing List entry](http://lists.apple.com/archives/quartz-dev/2008/Sep/msg00038.html) is all there is. – CodaFi Aug 19 '12 at 06:30

1 Answers1

9

While you can calculate Bezier path's length by integrating it numerically, it can be done much easier by dividing path into linear segments. If the segments are small enough the approximation error should be neglectable, especially that you are just trying to animate it.

I'll show you function for quad curves, but you can easily incorporate the solution for cubic curves as well:

- (float) bezierCurveLengthFromStartPoint: (CGPoint) start toEndPoint: (CGPoint) end withControlPoint: (CGPoint) control
{
    const int kSubdivisions = 50;
    const float step = 1.0f/(float)kSubdivisions;

    float totalLength = 0.0f;
    CGPoint prevPoint = start;

    // starting from i = 1, since for i = 0 calulated point is equal to start point
    for (int i = 1; i <= kSubdivisions; i++)
    {
        float t = i*step;

        float x = (1.0 - t)*(1.0 - t)*start.x + 2.0*(1.0 - t)*t*control.x + t*t*end.x;
        float y = (1.0 - t)*(1.0 - t)*start.y + 2.0*(1.0 - t)*t*control.y + t*t*end.y;

        CGPoint diff = CGPointMake(x - prevPoint.x, y - prevPoint.y);

        totalLength += sqrtf(diff.x*diff.x + diff.y*diff.y); // Pythagorean

        prevPoint = CGPointMake(x, y);
    }

    return totalLength;
}

EDIT

If you don't have access to path control points (say you created path using arcs) you can always access underlying Bezier curves using CGPathApply function:

- (void) testPath
{
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointZero];
    [path addQuadCurveToPoint:CGPointMake(1, 1) controlPoint:CGPointMake(-2, 2)];

    CGPathRef p = path.CGPath;

    CGPathApply(p, nil, pathFunction);
}

void pathFunction(void *info, const CGPathElement *element)
{
    if (element->type == kCGPathElementAddQuadCurveToPoint)
    {
        CGPoint p;
        p = element->points[0]; // control point
        NSLog(@"%lg %lg", p.x, p.y);

        p = element->points[1]; // end point
        NSLog(@"%lg %lg", p.x, p.y);
    }
    // check other cases as well!
}

Note that it doesn't provide the path's start point, but it's easy to keep track of it on your own.

Bartosz Ciechanowski
  • 10,293
  • 5
  • 45
  • 60
  • Thanks, I will change it to my needs Now I just can not figure out hew to get the start and end points. Do I have to store them when creating the path? I don't see any property for that in the reference. – shannoga Aug 19 '12 at 08:11
  • You can either keep track of it when creating paths, or I've just edited my answer showing how to query `CGPathRef` for this details – Bartosz Ciechanowski Aug 19 '12 at 08:48
  • @BartoszCiechanowski Hi, Bartosz. Nice work. Quick question: I have a `CGPathAddArcToPoint` which does not exist in `element->type`, is there other way around this? Thanks in advance. Regards. – Unheilig Oct 19 '13 at 10:19
  • @Unheilig As far as I remember, arcs are actually added as curves, so they should fall into `kCGPathElementAddCurveToPoint` case – Bartosz Ciechanowski Oct 19 '13 at 12:28
  • @BartoszCiechanowski I tried, it didn't work. If you have more information about it or another way to get round it in the future, please feel free to post back. Dziekuje. – Unheilig Oct 19 '13 at 17:49
  • How would your first function work if there were two control points for the curve such as when creating a uibezierpath with "addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2" – Travis Beck Apr 19 '15 at 18:39