如何获得路径的长度? [英] How to get length of path?

查看:243
本文介绍了如何获得路径的长度?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想知道路径的长度

例如,如果我有一条直线我可以用它的开始 x y 计算长度,并结束 x y 值。但如果我使用 QuadCurves CubicCurves ,它会很快变得非常棘手。

For example, if I have a straight line I can just compute the length with its start x,y and end x,y values. But it gets quickly very tricky if I use QuadCurves or CubicCurves.

有没有办法得到路径的长度或近似值?

Is there any way to get the length or an approximation of the length of a Path?

例如,以下路径:

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);


推荐答案

我最近也需要这个。我在网上找不到任何解决方案,但它发生在我身上 PathTransition 必须计算它。确实如此,参见 PathTransition.recomputeSegment ,其中计算了 totalLength

I needed this recently as well. I couldn't find any solutions online, but it occurred to me PathTransition must be calculating it. It does, see PathTransition.recomputeSegment, where totalLength is calculated.

不幸的是,它在 Node PathElement 路径转换为 java.awt.geom.Path2D 。我提取了这些方法并用 java.awt 替换了 com.sun 类的其他用法,然后拉出了部分与计算长度 PathTransition.recomputeSegments 相关。

Unfortunately, it uses many internal APIs in Node and the PathElement to convert the Path to a java.awt.geom.Path2D. I extracted these methods out and replaced other usages of com.sun classes with java.awt ones, then pulled the parts relevant to calculating length out of PathTransition.recomputeSegments.

结果代码如下。它在Kotlin而不是Java,但它应该很容易将其转换回Java。我还没有对它进行过广泛的测试,但它似乎正在研究我测试过的相当复杂的路径。我将结果与 PathTransition 计算的长度进行了比较,它们非常接近,我认为差异是由于我的代码使用 Path2D.Double 其中 Path2D.Float PathElement.impl_addTo 使用。

The resulting code is below. It is in Kotlin not Java, but it should be easy to convert it back to Java. I have not yet tested it extensively but it seems to be working on the fairly complex paths I have tested it against. I've compared my results to the length calculated by PathTransition and they are very close, I believe the discrepancies are due to my code using Path2D.Double where as Path2D.Float is used by PathElement.impl_addTo.

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)
}

这篇关于如何获得路径的长度?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆