Question Obtenir la longueur totale de CGPath


J'ai beaucoup de UIBezierPath que j'anime avec CALyerAnimation @ "StrokeStart" et @ "strokeEnd".

Je souhaite que l'animation ait la même vitesse pour tous les chemins, donc je pense que je pourrais utiliser la longueur du chemin dans:

DISTANCE / TEMPS = VITESSE

Y a-t-il un moyen de calculer le chemin "longueur"?

Merci

Shani


11
2017-08-19 06:24


origine


Réponses:


Bien que vous puissiez calculer la longueur du chemin de Bézier en l'intégrant numériquement, cela peut être fait beaucoup plus facilement en divisant le chemin en segments linéaires. Si les segments sont suffisamment petits, l'erreur d'approximation devrait être négligeable, surtout que vous essayez juste de l'animer.

Je vais vous montrer la fonction pour les courbes en quad, mais vous pouvez facilement incorporer la solution pour les courbes cubiques:

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

MODIFIER

Si vous n'avez pas accès aux points de contrôle des chemins (par exemple, si vous avez créé un chemin en utilisant des arcs), vous pouvez toujours accéder aux courbes de Bézier sous-jacentes en utilisant CGPathApply fonction:

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

Notez qu'il ne fournit pas le point de départ du chemin, mais il est facile de le suivre par vous-même.


9
2017-08-19 07:37



Récemment, j'ai eu le même problème, mais dans ce cas, j'avais besoin de la longueur de chemin complète, quels que soient les éléments impliqués. Par exemple, les lignes, les courbes, les arcs et la solution ci-dessus n'ont pas fonctionné pour moi, j'ai trouvé une solution générale.

Voici le code complet écrit en swift UIBezierPath.length

C'est l'extension qui contient le length propriété.

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 )

-2
2018-06-13 15:40