如何在最接近某个任意点p的(x,y)坐标的闭合2D复合Beziér曲线上找到点q的(x,y)坐标?

时间:2016-07-21 19:25:36

标签: algorithm bezier closest-points

我有一组 2D 笛卡尔点[b],它从头开始顺时针运行并形成一个闭合的形状。其中每一个都有自己的伴随2D笛卡尔点q0q1,它们定义了点周围的Beziér曲线(以及前一点和后一点)。所有这些点共同定义了一个封闭的2D composite Beziér curve

我有一个单独的点p,它是同一平面上的任意2D笛卡尔点。 是否有一个简单的算法来查找新的2D笛卡尔点(x, y)的{​​{1}}坐标,这是q路径上的最近点?

Four blue points labeled b[0] through b[4], each with two child green points labeled b[n].q0 and b[n].q1 connected to their blue parent by grey lines, forming a red path whose basic shape is dictated by the positions of the blue points, and curvature by the green ones. Above the curve there lies an orange point p, connected by a grey line to a purple point q, which lies on the red path at the closest point to p.

如图所示,我有点pb[0]及其句柄b[3]b[n].q0,我有任意点b[n].q1。我正在尝试计算点p,而不是沿着曲线的浮点位置,而是作为一对q坐标。

我尝试搜索此内容,但有些内容似乎只有for a very small curve,而其他内容则是abstract mathematicsscientific research papers

非常感谢任何帮助我学习算法解决方案的帮助,特别是如果它可以翻译成类C语言而不是上述SO答案中的纯数学。

1 个答案:

答案 0 :(得分:0)

通过调整the algorithm posted by Tatarize,我在Swift中提出了这个解决方案,它应该可以翻译成其他语言:

struct BezierPoint {
    let q0: CGPoint
    let point: CGPoint
    let q1: CGPoint
}

struct SimpleBezierCurve {
    let left: BezierPoint
    let right: BezierPoint
}

class BezierPath {
    var pathPoints = [BezierPoint]()

    func findClosestPoint(to targetPoint: CGPoint) -> CGPoint {
        let segments = allSegments()
        guard segments.count > 0 else { return targetPoint }
        var closestPoint = (distance: CGFloat.infinity, point: CGPoint(x: CGFloat.infinity, y: CGFloat.infinity))
        segments.forEach{ curve in
            let thisPoint = BezierPath.findClosestPoint(to: targetPoint, along: curve)
            let distance = findDistance(from: targetPoint, to: thisPoint)

            if (distance < closestPoint.distance) {
                closestPoint = (distance: distance, point: thisPoint)
            }
        }
        return closestPoint.point
    }

    func allSegments() -> [SimpleBezierCurve] {
        guard pathPoints.count > 0 else { return [] }
        var segments = [SimpleBezierCurve]()
        var prevPoint = pathPoints[0]
        for i in 1 ..< pathPoints.count {
            let thisPoint = pathPoints[i]
            segments.append(SimpleBezierCurve(left: prevPoint, right: thisPoint))
            prevPoint = thisPoint
        }
        segments.append(SimpleBezierCurve(left: prevPoint, right: pathPoints[0]))
        return segments
    }

    static func findClosestPoint(to point: CGPoint, along curve: SimpleBezierCurve) -> CGPoint {
        return findClosestPointToCubicBezier(to: point, slices: 10, iterations: 10, along: curve)
    }

    // Adapted from https://stackoverflow.com/a/34520607/3939277
    static func findClosestPointToCubicBezier(to target: CGPoint, slices: Int, iterations: Int, along curve: SimpleBezierCurve) -> CGPoint {
        return findClosestPointToCubicBezier(iterations: iterations, to: target, start: 0, end: 1, slices: slices, along: curve)
    }

    // Adapted from https://stackoverflow.com/a/34520607/3939277
    private static func findClosestPointToCubicBezier(iterations iterations: Int, to: CGPoint, start: CGFloat, end: CGFloat, slices: Int, along curve: SimpleBezierCurve) -> CGPoint {
        if iterations <= 0 {
            let position = (start + end) / 2
            let point = self.point(for: position, along: curve)
            return point
        }
        let tick = (end - start) / slices
        var best = CGFloat(0)
        var bestDistance = CGFloat.infinity
        var currentDistance: CGFloat
        var t = start

        while (t <= end) {
            //B(t) = (1-t)**3 p0 + 3(1 - t)**2 t P1 + 3(1-t)t**2 P2 + t**3 P3
            let point = self.point(for: t, along: curve)

            var dx = point.x - to.x;
            var dy = point.y - to.y;
            dx *= dx;
            dy *= dy;
            currentDistance = dx + dy;
            if (currentDistance < bestDistance) {
                bestDistance = currentDistance;
                best = t;
            }
            t += tick;
        }
        return findClosestPointToCubicBezier(iterations: iterations - 1, to: to, start: max(best - tick, 0.0), end: min(best + tick, 1.0), slices: slices, along: curve);
    }

    static func point(for t: CGFloat, along curve: SimpleBezierCurve) -> CGPoint {
        // This had to be broken up to avoid the "Expression too complex" error

        let x0 = curve.left.point.x
        let x1 = curve.left.q1.x
        let x2 = curve.right.q0.x
        let x3 = curve.right.point.x

        let y0 = curve.left.point.y
        let y1 = curve.left.q1.y
        let y2 = curve.right.q0.y
        let y3 = curve.right.point.y

        let x = (1 - t) * (1 - t) * (1 - t) * x0 + 3 * (1 - t) * (1 - t) * t * x1 + 3 * (1 - t) * t * t * x2 + t * t * t * x3
        let y = (1 - t) * (1 - t) * (1 - t) * y0 + 3 * (1 - t) * (1 - t) * t * y1 + 3 * (1 - t) * t * t * y2 + t * t * t * y3

        return CGPoint(x: x, y: y)
    }
}

// Possibly in another file
func findDistance(from a: CGPoint, to b: CGPoint) -> CGFloat {
    return sqrt(pow(a.x - b.x, 2) + pow(a.y - b.y, 2));
}

GitHub Gist