我正在构建一个具有一些图形操作的应用程序。我将形状存储为UIBezierPaths,我希望允许用户沿着线触摸点以创建保存的位置。使用wonderful answer to this question,更具体地说,this project,我可以在一条线上放置一个点,知道点所依赖的长度百分比。这是我问题的一半。
我想要一种方法在路径上取点,并得出其长度的百分比。
我的数学非常弱。我studied bezier curves但我根本无法理解它。
我会谦卑地提出“回去学习几何和三角学”是一个正确的答案,但遗憾的是我目前没有时间。我需要的是一种填写这种方法的方法:
- (CGFloat)percentOfLengthAtPoint:(CGPoint)point onPath:(UIBezierPath*)path
任何帮助表示赞赏!
答案 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")