Получить общую длину CGPath

У меня много UIBezierPath, что я анимация с CALyerAnimation @ "StrokeStart" и @ "strokeEnd".

Я хочу, чтобы анимация имела одинаковую скорость для всех путей, поэтому я мог бы использовать длину пути в:

РАССТОЯНИЕ/ВРЕМЯ = СКОРОСТЬ

Есть ли способ вычисления длины пути?

Спасибо

Шани

Ответ 1

Пока вы можете рассчитать длину пути Безье, интегрируя его численно, это можно сделать намного проще, разделив путь на линейные сегменты. Если сегменты достаточно малы, ошибка аппроксимации должна быть пренебрежимой, особенно если вы просто пытаетесь ее оживить.

Я покажу вам функцию для четырехмерных кривых, но вы также можете легко включить решение для кубических кривых:

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

ИЗМЕНИТЬ

Если у вас нет доступа к контрольным точкам маршрута (например, вы создали путь с использованием дуг), вы всегда можете получить доступ к кривым Безье, используя функцию CGPathApply:

- (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!
}

Обратите внимание, что он не предоставляет начальную точку пути, но легко отслеживать ее самостоятельно.

Ответ 2

Недавно у меня была такая же проблема, но в этом случае мне нужна полная длина пути, независимо от задействованных элементов. например, линии, кривые, дуги и вышеприведенное решение не сработали для меня, я придумал общее решение.

Здесь полный код, написанный быстрым UIBezierPath.length

Это расширение, которое содержит свойство length.

extension UIBezierPath{
    var length: CGFloat{
        var pathLength:CGFloat = 0.0
        var current = CGPointZero
        var first   = CGPointZero

        self.CGPath.forEach{ element in            
           pathLength += element.distance(to: current, startPoint: first)            

           if element.type == .MoveToPoint{
              first = element.point
           }            
           if element.type != .CloseSubpath{
              current = element.point
           }
       }
      return pathLength
    }
}


// e.g
    let path = UIBezierPath()
    path.lineWidth = 10
    path.moveToPoint(CGPoint(x: 10, y: 10))

    path.addArcWithCenter(
        CGPoint(x: 100,y: 50),
        radius: 50,
        startAngle: CGFloat(140.0.degrees),
        endAngle: CGFloat(40.0.degrees),
        clockwise: false
    )

    path.addLineToPoint(CGPoint(x: 150, y: 30))
    path.closePath()
    print( path.length )