2017-11-09 3 views
0

Ich möchte die Länge eines Path wissen.Wie bekomme ich die Weglänge?

Zum Beispiel, wenn ich eine gerade Linie haben kann ich berechnen, nur die Länge mit Start x, y und x, y Werte beenden. Aber es wird schnell sehr schwierig, wenn ich QuadCurves oder CubicCurves verwende.

Gibt es eine Möglichkeit, die Länge oder eine Annäherung der Länge eines Path zu erhalten?

Zum Beispiel der folgende Pfad:

Path path = new Path(); 
MoveTo moveTo = new MoveTo(start.getX(), start.getY()); 
double controlPointX = 50; 
CubicCurveTo cubicCurveTo = new CubicCurveTo(start.getX() + controlPointX, start.getY(), 
         start.getX() + controlPointX, end.getY(), end.getX(), end.getY()); 
path.getElements().addAll(moveTo, cubicCurveTo); 
+0

[Dies] (https://pomax.github.io/bezierinfo/#arclengthapprox) gibt eine Vorstellung darüber, wie die Länge eines 'Bézier Curve' zu ​​bekommen. – Sedrick

Antwort

1

brauchte ich dies auch vor kurzem. Ich konnte keine Lösungen online finden, aber es fiel mir PathTransition muss es berechnen. Es tut, siehe PathTransition.recomputeSegment, wo totalLength berechnet wird.

Leider nutzt es viele interne APIs in Node und die PathElement die Path zu einem java.awt.geom.Path2D zu konvertieren. Ich extrahierte diese Methoden heraus und ersetzte andere Verwendungen von com.sun Klassen mit java.awt Einsen, zog dann die relevanten Teile zur Berechnung der Länge aus PathTransition.recomputeSegments heraus.

Der resultierende Code ist unten. Es ist in Kotlin nicht Java, aber es sollte einfach sein, es zurück in Java zu konvertieren. Ich habe es noch nicht umfassend getestet, aber es scheint auf den ziemlich komplexen Wegen zu arbeiten, gegen die ich es getestet habe. Ich habe meine Ergebnisse mit der von PathTransition berechneten Länge verglichen und sie sind sehr nah, ich glaube, dass die Diskrepanzen auf meinen Code beruhen, der Path2D.Double verwendet, wo Path2D.Float von PathElement.impl_addTo verwendet wird.

fun Transform.toAffineTransform(): AffineTransform { 
     if(!isType2D) throw UnsupportedOperationException("Conversion of 3D transforms is unsupported") 
     return AffineTransform(mxx, myx, mxy, myy, tx, ty) 
    } 

val Path.totalLength: Double 
    get() { 
     var length = 0.0 

     val coords = DoubleArray(6) 
     var pt = 0 // Previous segment type 
     var px = 0.0 // Previous x-coordinate 
     var py = 0.0 // Previous y-coordinate 
     var mx = 0.0 // Last move to x-coordinate 
     var my = 0.0 // Last move to y-coordinate 
     val pit = toPath2D().getPathIterator(localToParentTransform.toAffineTransform(), 1.0) 
     while(!pit.isDone) { 
      val type = pit.currentSegment(coords) 

      val x = coords[0] 
      val y = coords[1] 

      when(type) { 
       PathIterator.SEG_MOVETO -> { 
        mx = x 
        my = y 
       } 

       PathIterator.SEG_LINETO -> { 
        val dx = x - px 
        val dy = y - py 
        val l = sqrt(dx * dx + dy * dy) 
        if(l >= 1 || pt == PathIterator.SEG_MOVETO) length += l 
       } 

       PathIterator.SEG_CLOSE -> { 
        val dx = x - mx 
        val dy = y - my 
        val l = sqrt(dx * dx + dy * dy) 
        if(l >= 1 || pt == PathIterator.SEG_MOVETO) length += l 
       } 
      } 

      pt = type 
      px = x 
      py = y 
      pit.next() 
     } 

     return length 
    } 

fun Path.toPath2D(): Path2D { 
    val path: Path2D = Path2D.Double(if(fillRule == FillRule.EVEN_ODD) Path2D.WIND_EVEN_ODD else Path2D.WIND_NON_ZERO) 

    for(e in elements) { 
     when(e) { 
      is Arc2D -> append(e as ArcTo, path) // Why isn't this smart casted? 
      is ClosePath -> path.closePath() 
      is CubicCurveTo -> append(e, path) 
      is HLineTo -> append(e, path) 
      is LineTo -> append(e, path) 
      is MoveTo -> append(e, path) 
      is QuadCurveTo -> append(e, path) 
      is VLineTo -> append(e, path) 
      else -> throw UnsupportedOperationException("Path contains unknown PathElement type: " + e::class.qualifiedName) 
     } 
    } 

    return path 
} 

private fun append(arcTo: ArcTo, path: Path2D) { 
    val x0 = path.currentPoint.x 
    val y0 = path.currentPoint.y 

    val localX = arcTo.x 
    val localY = arcTo.y 
    val localSweepFlag = arcTo.isSweepFlag 
    val localLargeArcFlag = arcTo.isLargeArcFlag 

    // Determine target "to" position 
    val xto = if(arcTo.isAbsolute) localX else localX + x0 
    val yto = if(arcTo.isAbsolute) localY else localY + y0 
    // Compute the half distance between the current and the final point 
    val dx2 = (x0 - xto)/2.0 
    val dy2 = (y0 - yto)/2.0 
    // Convert angle from degrees to radians 
    val xAxisRotationR = Math.toRadians(arcTo.xAxisRotation) 
    val cosAngle = Math.cos(xAxisRotationR) 
    val sinAngle = Math.sin(xAxisRotationR) 

    // 
    // Step 1 : Compute (x1, y1) 
    // 
    val x1 = cosAngle * dx2 + sinAngle * dy2 
    val y1 = -sinAngle * dx2 + cosAngle * dy2 
    // Ensure radii are large enough 
    var rx = abs(arcTo.radiusX) 
    var ry = abs(arcTo.radiusY) 
    var Prx = rx * rx 
    var Pry = ry * ry 
    val Px1 = x1 * x1 
    val Py1 = y1 * y1 
    // check that radii are large enough 
    val radiiCheck = Px1/Prx + Py1/Pry 
    if (radiiCheck > 1.0) { 
     rx *= sqrt(radiiCheck) 
     ry *= sqrt(radiiCheck) 
     if(rx == rx && ry == ry) {/* not NANs */ } 
     else { 
      path.lineTo(xto, yto) 
      return 
     } 
     Prx = rx * rx 
     Pry = ry * ry 
    } 

    // 
    // Step 2 : Compute (cx1, cy1) 
    // 
    var sign = if (localLargeArcFlag == localSweepFlag) -1.0 else 1.0 
    var sq = (Prx * Pry - Prx * Py1 - Pry * Px1)/(Prx * Py1 + Pry * Px1) 
    sq = if (sq < 0.0) 0.0 else sq 
    val coef = sign * Math.sqrt(sq) 
    val cx1 = coef * (rx * y1/ry) 
    val cy1 = coef * -(ry * x1/rx) 

    // 
    // Step 3 : Compute (cx, cy) from (cx1, cy1) 
    // 
    val sx2 = (x0 + xto)/2.0 
    val sy2 = (y0 + yto)/2.0 
    val cx = sx2 + (cosAngle * cx1 - sinAngle * cy1) 
    val cy = sy2 + (sinAngle * cx1 + cosAngle * cy1) 

    // 
    // Step 4 : Compute the angleStart (angle1) and the angleExtent (dangle) 
    // 
    val ux = (x1 - cx1)/rx 
    val uy = (y1 - cy1)/ry 
    val vx = (-x1 - cx1)/rx 
    val vy = (-y1 - cy1)/ry 
    // Compute the angle start 
    var n = sqrt(ux * ux + uy * uy) 
    var p = ux // (1 * ux) + (0 * uy) 
    sign = if (uy < 0.0) -1.0 else 1.0 
    var angleStart = (sign * Math.acos(p/n)).toDegrees() 

    // Compute the angle extent 
    n = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy)) 
    p = ux * vx + uy * vy 
    sign = if (ux * vy - uy * vx < 0.0) -1.0 else 1.0 
    var angleExtent = Math.toDegrees(sign * Math.acos(p/n)) 
    if(!localSweepFlag && angleExtent > 0) angleExtent -= 360.0 
    else if(localSweepFlag && angleExtent < 0) angleExtent += 360.0 

    angleExtent %= 360 
    angleStart %= 360 

    // 
    // We can now build the resulting Arc2D 
    // 
    val arcX = cx - rx 
    val arcY = cy - ry 
    val arcW = rx * 2.0 
    val arcH = ry * 2.0 
    val arcStart = -angleStart 
    val arcExtent = -angleExtent 

    val arc = Arc2D.Double(OPEN).apply { setArc(arcX, arcY, arcW, arcH, arcStart, arcExtent, OPEN) } 
    val xform: AffineTransform? = when(xAxisRotationR) { 
     0.0 -> null 
     else -> AffineTransform().apply { setToRotation(xAxisRotationR, cx, cy) } 
    } 

    val pi = arc.getPathIterator(xform) 
    // RT-8926, append(true) converts the initial moveTo into a 
    // lineTo which can generate huge miter joins if the segment 
    // is small enough. So, we manually skip it here instead. 
    pi.next() 
    path.append(pi, true) 
} 

private fun append(cubicCurveTo: CubicCurveTo, path: Path2D) { 
    if(cubicCurveTo.isAbsolute) { 
     path.curveTo(cubicCurveTo.controlX1, cubicCurveTo.controlY1, 
        cubicCurveTo.controlX2, cubicCurveTo.controlY2, 
        cubicCurveTo.x, cubicCurveTo.y) 
    } 
    else { 
     val dx = path.currentPoint.x 
     val dy = path.currentPoint.y 
     path.curveTo(cubicCurveTo.controlX1 + dx, cubicCurveTo.controlY1 + dy, 
       cubicCurveTo.controlX2 + dx, cubicCurveTo.controlY2 + dy, 
       cubicCurveTo.x + dx, cubicCurveTo.y + dy) 
    } 
} 

private fun append(hLineTo: HLineTo, path: Path2D) { 
    if(hLineTo.isAbsolute) path.lineTo(hLineTo.x, path.currentPoint.y) 
    else path.lineTo(path.currentPoint.x + hLineTo.x, path.currentPoint.y) 
} 

private fun append(lineTo: LineTo, path: Path2D) { 
    if(lineTo.isAbsolute) path.lineTo(lineTo.x, lineTo.y) 
    else path.lineTo(path.currentPoint.x + lineTo.x, path.currentPoint.y + lineTo.y) 
} 

private fun append(moveTo: MoveTo, path: Path2D) { 
    if(moveTo.isAbsolute) path.moveTo(moveTo.x, moveTo.y) 
    else path.moveTo((path.currentPoint.x + moveTo.x), path.currentPoint.y + moveTo.y) 
} 

private fun append(quadCurveTo: QuadCurveTo, path: Path2D) { 
    if(quadCurveTo.isAbsolute) { 
     path.quadTo(quadCurveTo.controlX, quadCurveTo.controlY, 
        quadCurveTo.x, quadCurveTo.y) 
    } 
    else { 
     val dx = path.currentPoint.x 
     val dy = path.currentPoint.y 
     path.quadTo(quadCurveTo.controlX + dx, quadCurveTo.controlY + dy, 
       quadCurveTo.x + dx, quadCurveTo.y + dy) 
    } 
} 

private fun append(vLineTo: VLineTo, path: Path2D) { 
    if(vLineTo.isAbsolute) path.lineTo(path.currentPoint.x, vLineTo.y) 
    else path.lineTo(path.currentPoint.x, path.currentPoint.y + vLineTo.y) 
}