UIBezierPath点长度百分比

时间:2015-11-08 17:54:09

标签: ios core-graphics uibezierpath

我正在构建一个具有一些图形操作的应用程序。我将形状存储为UIBezierPaths,我希望允许用户沿着线触摸点以创建保存的位置。使用wonderful answer to this question,更具体地说,this project,我可以在一条线上放置一个点,知道点所依赖的长度百分比。这是我问题的一半。

我想要一种方法在路径上取点,并得出其长度的百分比。

我的数学非常弱。我studied bezier curves但我根本无法理解它。

我会谦卑地提出“回去学习几何和三角学”是一个正确的答案,但遗憾的是我目前没有时间。我需要的是一种填写这种方法的方法:

- (CGFloat)percentOfLengthAtPoint:(CGPoint)point onPath:(UIBezierPath*)path

任何帮助表示赞赏!

2 个答案:

答案 0 :(得分:1)

我有工作代码解决了我的问题。我并不为此感到特别自豪;整体技术本质上是对UIBezierPath的暴力攻击,如果你想一想,这有点好玩。 (请不要考虑它。)

正如我所提到的,我可以访问allows me to get a point from a given percentage of a line的方法。我利用这种力量通过运行1000个百分比值来找到与给定点最接近的百分比。即便:

从CGPoint开始,表示用户触摸的位置。

let pointA = // the incoming CGPoint

运行数千的0-1范围。这是我们要蛮力的百分比,看看我们是否匹配。对于每个,我们从上面的链接项目运行pointAtPercentOfLength

var pointArray:[[String:Any]] = []
for (var i:Int = 0; i <= 1000; i++) {
    let value = CGFloat(round((CGFloat(i) / CGFloat(1000)) * 1000) / 1000)
    let testPoint = path.pointAtPercentOfLength(value)

    let pointB = CGPoint(x: floor(testPoint.x), y: floor(testPoint.y))
    pointArray.append(["point" : pointB, "percent" : value])
}

那是困难的部分。现在我们获取返回值并计算每个点与触摸点之间的距离。最接近的是我们的赢家。

// sort the damned array by distance so we find the closest
var distanceArray:[[String:Any]] = []
for point in pointArray {
    distanceArray.append([
        "distance" : self.distanceFrom(point["point"] as! CGPoint, point2: pointA),
        "point" : point["point"],
        "percent" : point["percent"] as! CGFloat
        ])
}

如果你有兴趣,这是排序功能:

func distanceFrom(point1:CGPoint, point2:CGPoint) -> CGFloat {
    let xDist = (point2.x - point1.x);
    let yDist = (point2.y - point1.y);
    return sqrt((xDist * xDist) + (yDist * yDist));
}

最后,我按照值的距离对数组进行排序,并选出胜者作为我们最接近的百分比。

let ordered = distanceArray.sort { return CGFloat($0["distance"] as! CGFloat) < CGFloat($1["distance"] as! CGFloat) }

ordered是一个包含percent的小字典,是一行长度百分比的正确值。

我知道,这不是漂亮的代码。 我知道。但它完成了工作并且似乎没有计算成本。

作为后记,我应该指出看似合适的资源。在我的研究期间,我读了DavidRönnqvist的this beautiful article,其中包括计算沿路径的百分比距离的等式:

start⋅(1-t)3 + 3⋅c1⋅t(1-t)2 + 3⋅c2⋅t2(1-t) + end⋅t3

在我最终解决方案发生之前,我正准备实施。 数学,伙计。我甚至无法想脑。但如果你比我更野心勃勃,并希望用五行代替覆盖我的30行代码,那么每个人都会很感激!

答案 1 :(得分:1)

我认为你的做法很合理,但你可以更有效地做到

不是创建两个dicts数组(每个都有一千个元素)然后对数组进行排序 - 只需使用while循环从0.0移动到1.0,计算到触摸的距离指出并跟踪最小距离。

例如:

    var t:CGFloat = 0.0
    let step:CGFloat = 0.001
    var minDistance:CGFloat = -1.0
    var minPoint:CGPoint = CGPointZero
    var minT:CGFloat = -1;
    while (t<1.0) {

        let point = pointAtPercentOfLength(t)
        let distance:CGFloat = self.distanceFrom(point, point2: pointA)

        if (minDistance == -1.0 || distance < minDistance) {
            minDistance = distance
            minPoint = point
            minT = t
        }
        t += step
    }
    print("minDistance: \(minDistance) minPoint: \(minPoint.x) \(minPoint.y) t\(minT)\n")