Graphics 贝塞尔曲线上的等距点
目前,我正在尝试使多个贝塞尔曲线具有等距点。我目前正在使用三次插值来寻找点,但由于贝塞尔的工作方式,一些区域比其他区域更密集,并且由于可变距离,证明纹理贴图的粗糙性有没有办法通过距离而不是百分比来查找贝塞尔曲线上的点?此外,是否有可能将其扩展到多条连接曲线?p_0和p_3之间的距离(立方形式),是的,但我想你知道,这是直接的 曲线上的距离仅为弧长: 其中: 很可能,你会有t_0=0,t_1=1.0,dz(t)=0(二维平面)。这就是所谓的“弧长”参数化。几年前我写了一篇关于这一点的论文:Graphics 贝塞尔曲线上的等距点,graphics,interpolation,bezier,curve,Graphics,Interpolation,Bezier,Curve,目前,我正在尝试使多个贝塞尔曲线具有等距点。我目前正在使用三次插值来寻找点,但由于贝塞尔的工作方式,一些区域比其他区域更密集,并且由于可变距离,证明纹理贴图的粗糙性有没有办法通过距离而不是百分比来查找贝塞尔曲线上的点?此外,是否有可能将其扩展到多条连接曲线?p_0和p_3之间的距离(立方形式),是的,但我想你知道,这是直接的 曲线上的距离仅为弧长: 其中: 很可能,你会有t_0=0,t_1=1.0,dz(t)=0(二维平面)。这就是所谓的“弧长”参数化。几年前我写了一篇关于这一点的论文:
这个想法是预先计算一条“参数化”曲线,并通过它来评估曲线。我知道这是一个老问题,但我最近遇到了这个问题,并创建了一个
UIBezierPath
扩展来解决给定Y
坐标的X
坐标,反之亦然。用swift写的
扩展UIBezierPath{
函数解算运算(开始:CGPoint,点1:CGPoint,点2:CGPoint,结束:CGPoint,y:CGFloat)->[CGPoint]{
//贝塞尔控制点
设C0=start.y-y
设C1=点1.y-y
设C2=点2.y-y
设C3=end.y-y
//三次多项式系数,使得Bez(t)=A*t^3+B*t^2+C*t+D
设A=C3-3.0*C2+3.0*C1-C0
设B=3.0*C2-6.0*C1+3.0*C0
设C=3.0*C1-3.0*C0
设D=C0
设根=三次方(A,b:b,c:c,d:d)
var结果=[CGPoint]()
扎根{
如果(根>=0&&root[CGPoint]{
//贝塞尔控制点
设C0=start.x-x
设C1=point1.x-x
设C2=point2.x-x
设C3=end.x-x
//三次多项式系数,使得Bez(t)=A*t^3+B*t^2+C*t+D
设A=C3-3.0*C2+3.0*C1-C0
设B=3.0*C2-6.0*C1+3.0*C0
设C=3.0*C1-3.0*C0
设D=C0
设根=三次方(A,b:b,c:c,d:d)
var结果=[CGPoint]()
扎根{
if(root>=0&&root[CGFloat]{
如果(a==nil){
回报率(b,b:c,c:d)
}
b/=a!
c/=a!
d/=a!
设p=(3*c-b*b)/3
设q=(2*b*b*b-9*b*c+27*d)/27
如果(p==0){
返回[pow(-q,1/3)]
}else如果(q==0){
返回[sqrt(-p),-sqrt(-p)]
}否则{
设判别式=pow(q/2,2)+pow(p/3,3)
if(判别式==0){
返回[pow(q/2,1/3)-b/3]
}else if(判别式>0){
设x=crt(-(q/2)+sqrt(判别式))
设z=crt((q/2)+sqrt(判别式))
返回[x-z-b/3]
}否则{
设r=sqrt(功率(-(p/3),3))
设phi=acos(-(q/(2*sqrt)(pow(-(p/3),3‘‘‘)'))
设s=2*pow(r,1/3)
返回[
s*cos(φ/3)-b/3,
s*cos((phi+CGFloat(2)*CGFloat(M_PI))/3)-b/3,
s*cos((phi+CGFloat(4)*CGFloat(M_PI))/3)-b/3
]
}
}
}
func(a:CGFloat,b:CGFloat,c:CGFloat)->[CGFloat]{
设判别式=b*b-4*a*c;
if(判别式<0){
返回[]
}否则{
返回[
(-b+sqrt(判别式))/(2*a),
(-b-sqrt(判别式))/(2*a)
]
}
}
专用func crt(v:CGFloat)->CGFloat{
if(cGV)点{
//贝塞尔控制点
让C0=开始
设C1=1点
设C2=点2
设C3=结束
//三次多项式系数,使得Bez(t)=A*t^3+B*t^2+C*t+D
设A=CGPointMake(C3.x-3.0*C2.x+3.0*C1.x-C0.x,C3.y-3.0*C2.y+3.0*C1.y-C0.y)
设B=CGPointMake(3.0*C2.x-6.0*C1.x+3.0*C0.x,3.0*C2.y-6.0*C1.y+3.0*C0.y)
设C=CGPointMake(3.0*C1.x-3.0*C0.x,3.0*C1.y-3.0*C0.y)
设D=C0
返回CGPointMake(((A.x*t+B.x)*t+C.x)*t+D.x,((A.y*t+B.y)*t+C.y)*t+D.y)
}
//TODO:-未来实施
专用函数切线角度(起点:CGPoint,点1:CGPoint,点2:CGPoint,终点:CGPoint,t:CGFloat)->CGFloat{
//贝塞尔控制点
让C0=开始
设C1=1点
设C2=点2
设C3=结束
//三次多项式系数,使得Bez(t)=A*t^3+B*t^2+C*t+D
设A=CGPointMake(C3.x-3.0*C2.x+3.0*C1.x-C0.x,C3.y-3.0*C2.y+3.0*C1.y-C0.y)
设B=CGPointMake(3.0*C2.x-6.0*C1.x+3.0*C0.x,3.0*C2.y-6.0*C1.y+3.0*C0.y)
设C=CGPointMake(3.0*C1.x-3.0*C0.x,3.0*C1.y-3.0*C0.y)
返回atan2(3.0*A.y*t*t+2.0*B.y*t+C.y,3.0*A.x*t*t+2.0*B.x*t+C.x)
}
}
这就是给定参数时求弧长的方法,但求等距点需要与此函数相反。从一个点到另一个点并不容易。@Christian Romo:你是怎么做到的?我的意思是,你可以只使用二进制搜索,但这会非常慢(无论如何,对于我正在尝试的工作而言).我还没有完全阅读这篇论文。但我想问一下,是否有更好的方法来定义不需要“转换”的曲线首先。例如,你知道如果我使用NURBS定义所有路径/曲线,它是否支持更快的等距弧长参数化?或者其他方式?编辑:更快,我指的是使用CPU或GPU。使用NURBS没有帮助,基本问题是相同的。文末展示了一种使用这将生成一条带弧长参数化的新曲线,但如果曲线的阶数较高,则计算速度较慢。另请参见。
extension UIBezierPath {
func solveBezerAtY(start: CGPoint, point1: CGPoint, point2: CGPoint, end: CGPoint, y: CGFloat) -> [CGPoint] {
// bezier control points
let C0 = start.y - y
let C1 = point1.y - y
let C2 = point2.y - y
let C3 = end.y - y
// The cubic polynomial coefficients such that Bez(t) = A*t^3 + B*t^2 + C*t + D
let A = C3 - 3.0*C2 + 3.0*C1 - C0
let B = 3.0*C2 - 6.0*C1 + 3.0*C0
let C = 3.0*C1 - 3.0*C0
let D = C0
let roots = solveCubic(A, b: B, c: C, d: D)
var result = [CGPoint]()
for root in roots {
if (root >= 0 && root <= 1) {
result.append(bezierOutputAtT(start, point1: point1, point2: point2, end: end, t: root))
}
}
return result
}
func solveBezerAtX(start: CGPoint, point1: CGPoint, point2: CGPoint, end: CGPoint, x: CGFloat) -> [CGPoint] {
// bezier control points
let C0 = start.x - x
let C1 = point1.x - x
let C2 = point2.x - x
let C3 = end.x - x
// The cubic polynomial coefficients such that Bez(t) = A*t^3 + B*t^2 + C*t + D
let A = C3 - 3.0*C2 + 3.0*C1 - C0
let B = 3.0*C2 - 6.0*C1 + 3.0*C0
let C = 3.0*C1 - 3.0*C0
let D = C0
let roots = solveCubic(A, b: B, c: C, d: D)
var result = [CGPoint]()
for root in roots {
if (root >= 0 && root <= 1) {
result.append(bezierOutputAtT(start, point1: point1, point2: point2, end: end, t: root))
}
}
return result
}
func solveCubic(a: CGFloat?, var b: CGFloat, var c: CGFloat, var d: CGFloat) -> [CGFloat] {
if (a == nil) {
return solveQuadratic(b, b: c, c: d)
}
b /= a!
c /= a!
d /= a!
let p = (3 * c - b * b) / 3
let q = (2 * b * b * b - 9 * b * c + 27 * d) / 27
if (p == 0) {
return [pow(-q, 1 / 3)]
} else if (q == 0) {
return [sqrt(-p), -sqrt(-p)]
} else {
let discriminant = pow(q / 2, 2) + pow(p / 3, 3)
if (discriminant == 0) {
return [pow(q / 2, 1 / 3) - b / 3]
} else if (discriminant > 0) {
let x = crt(-(q / 2) + sqrt(discriminant))
let z = crt((q / 2) + sqrt(discriminant))
return [x - z - b / 3]
} else {
let r = sqrt(pow(-(p/3), 3))
let phi = acos(-(q / (2 * sqrt(pow(-(p / 3), 3)))))
let s = 2 * pow(r, 1/3)
return [
s * cos(phi / 3) - b / 3,
s * cos((phi + CGFloat(2) * CGFloat(M_PI)) / 3) - b / 3,
s * cos((phi + CGFloat(4) * CGFloat(M_PI)) / 3) - b / 3
]
}
}
}
func solveQuadratic(a: CGFloat, b: CGFloat, c: CGFloat) -> [CGFloat] {
let discriminant = b * b - 4 * a * c;
if (discriminant < 0) {
return []
} else {
return [
(-b + sqrt(discriminant)) / (2 * a),
(-b - sqrt(discriminant)) / (2 * a)
]
}
}
private func crt(v: CGFloat) -> CGFloat {
if (v<0) {
return -pow(-v, 1/3)
}
return pow(v, 1/3)
}
private func bezierOutputAtT(start: CGPoint, point1: CGPoint, point2: CGPoint, end: CGPoint, t: CGFloat) -> CGPoint {
// bezier control points
let C0 = start
let C1 = point1
let C2 = point2
let C3 = end
// The cubic polynomial coefficients such that Bez(t) = A*t^3 + B*t^2 + C*t + D
let A = CGPointMake(C3.x - 3.0*C2.x + 3.0*C1.x - C0.x, C3.y - 3.0*C2.y + 3.0*C1.y - C0.y)
let B = CGPointMake(3.0*C2.x - 6.0*C1.x + 3.0*C0.x, 3.0*C2.y - 6.0*C1.y + 3.0*C0.y)
let C = CGPointMake(3.0*C1.x - 3.0*C0.x, 3.0*C1.y - 3.0*C0.y)
let D = C0
return CGPointMake(((A.x*t+B.x)*t+C.x)*t+D.x, ((A.y*t+B.y)*t+C.y)*t+D.y)
}
// TODO: - future implementation
private func tangentAngleAtT(start: CGPoint, point1: CGPoint, point2: CGPoint, end: CGPoint, t: CGFloat) -> CGFloat {
// bezier control points
let C0 = start
let C1 = point1
let C2 = point2
let C3 = end
// The cubic polynomial coefficients such that Bez(t) = A*t^3 + B*t^2 + C*t + D
let A = CGPointMake(C3.x - 3.0*C2.x + 3.0*C1.x - C0.x, C3.y - 3.0*C2.y + 3.0*C1.y - C0.y)
let B = CGPointMake(3.0*C2.x - 6.0*C1.x + 3.0*C0.x, 3.0*C2.y - 6.0*C1.y + 3.0*C0.y)
let C = CGPointMake(3.0*C1.x - 3.0*C0.x, 3.0*C1.y - 3.0*C0.y)
return atan2(3.0*A.y*t*t + 2.0*B.y*t + C.y, 3.0*A.x*t*t + 2.0*B.x*t + C.x)
}
}