【发布时间】:2019-12-11 16:33:24
【问题描述】:
我试图准确地将 SVG 路径表示为 UIBezierPath,但遗憾的是,UIBezierPath 上的 addArc 不考虑椭圆,只考虑圆(半径只有 1 个值)。
bezierPath.addArc(withCenter:CGPoint radius:CGFloat startAngle:CGFloat endAngle:CGFloat clockwise:Bool)
我的想法是将弧线分解为 svg 曲线,但我不确定如何计算它。
如果我知道我想要制作的形状,我可以说,右上角的圆弧
a150,150 0 1,0 150,-150变成曲线c82.84,0,150,44.77,150,100
但由于我将解析任何可能的弧线,我需要知道如何分解任何椭圆并计算每条贝塞尔曲线的控制点。
我一直在查看各种显示以这种方式计算的三次曲线的资源... http://www.spaceroots.org/documents/ellipse/node12.html
但我不确定如何在代码中表达这一点
这是我目前所拥有的......
SVG 中 a 路径的值
半径X 半径Y 圆弧旋转 大 扫一扫 目的地X 目的地Y
编辑
@Spektre 当我渲染出一些简单的路径时,您的答案看起来很棒,但路径正在移动,具体取决于大 + 扫描组合。
例如
小扫/大不扫
M 180.0 80.0 a50,50 0 0,1 50,50 z
M 180.0 80.0 a50,50 0 1,0 50,50 z
X 已翻译 +100
M 180.0 80.0
M 280.0 80.0
C 280.0 73.62 278.63 66.76 276.19 60.87
C 273.75 54.97 269.87 49.15 265.36 44.64
C 260.85 40.13 255.03 36.25 249.13 33.81
C 243.24 31.37 236.38 30.0 230.0 30.0
z
^^ 小扫一扫示例
小不扫/大扫
M 180.0 80.0 a50,50 0 0,0 50,50 z
M 180.0 80.0 a50,50 0 1,1 50,50 z
Y已翻译+100
M 180.0 80.0
M 180.0 180.0
C 186.38 180.0 193.24 178.63 199.13 176.19
C 205.03 173.75 210.85 169.87 215.36 165.36
C 219.87 160.85 223.75 155.03 226.19 149.13
C 228.63 143.24 230.0 136.38 230.0 130.0
C 230.0 123.62 228.63 116.76 226.19 110.87
C 223.75 104.97 219.87 99.15 215.36 94.64
C 210.85 90.13 205.03 86.25 199.13 83.81
C 193.24 81.37 186.38 80.0 180.0 80.0
C 173.62 80.0 166.76 81.37 160.87 83.81
C 154.97 86.25 149.15 90.13 144.64 94.64
C 140.13 99.15 136.25 104.97 133.81 110.87
C 131.37 116.76 130.0 123.62 130.0 130.0
z
^^大扫除示例
你的弧的我的代码版本
M 10 70 a 133.591805 50 12.97728 0 0 70 -50 z
M 10.0 70.0
M 65.33 62.67
C 53.75 67.15 35.85 69.91 17.44 70.06
C -0.97 70.2 -24.36 67.78 -45.14 63.57
C -65.92 59.36 -89.13 52.34 -107.24 44.79
z
我的代码版本
private func arcAsCurves(x0: CGFloat, y0: CGFloat, a: CGFloat, b: CGFloat, angle: CGFloat, large: Bool, sweep: Bool, x1: CGFloat, y1: CGFloat) -> String {
//return "L\(x1) \(y1)"
var localSweep = sweep
if large { localSweep = !localSweep }
let pi = CGFloat.pi
let pi2 = pi*2
let ang = pi-(angle*pi/180.0) // [deg] -> [rad] and offset to match my coordinate system
let e = a/b
var c = cos(+ang)
var s = ang == pi ? 0.0 : sin(+ang)
let ax = x0*c-y0*s // (ax,ay) = unrotated (x0,y0)
var ay = x0*s+y0*c
let bx = x1*c-y1*s // (bx,by) = unrotated (x1,y1)
var by = x1*s+y1*c
ay *= e // transform ellipse to circle by scaling y axis
by *= e
// rotated centre by angle
let axd = ax+bx
let ayd = ay+by
var sx = 0.5 * axd // mid point between A,B
var sy = 0.5 * ayd
var vx = ay-by // perpendicular direction vector to AB of size |AB|
var vy = bx-ax
var l = (a*a / (vx*vx + vy*vy)) - 0.25 // compute distance of center to (sx,sy) from pythagoras
//l=divide(a*a,(vx*vx)+(vy*vy))-0.25
if l < 0 { // handle if start/end points out of range (not on ellipse) center is in mid of the line
l = 0
}
l = sqrt(l)
vx *= l // rescale v to distance from id point to center
vy *= l
if localSweep { // pick the center side
sx += vx
sy += vy
} else {
sx -= vx
sy -= vy
}
// sx += localSweep ? vx : -vx
// sy += localSweep ? vy : -vy
var a0 = atan2(ax-sx, ay-sy) // compute unrotated angle range
var a1 = atan2(bx-sx, by-sy)
// a0 = atanxy(ax-sx,ay-sy);
// a1 = atanxy(bx-sx,by-sy);
ay /= e
by /= e
sy /= e // scale center back to ellipse
// pick angle range
var da = a1-a0
let zeroAng = 0.000001 * pi/180.0
if abs(abs(da)-pi) <= zeroAng { // half arc is without larc and sweep is not working instead change a0,a1
var db = (0.5 * (a0+a1)) - atan2(bx-ax,by-ay)
while (db < -pi) { db += pi2 } // db<0 CCW ... sweep=1
while (db > pi) { db -= pi2 } // db>0 CW ... sweep=0
if (db < 0.0 && !sweep) || (db > 0.0 && sweep) {
if da >= 0.0 { a1 -= pi2 }
if da < 0.0 { a0 -= pi2 }
}
}
else if large {
if da < pi && da >= 0.0 { a1 -= pi2 }
if da > -pi && da < 0.0 { a0 -= pi2 }
}
else {
if da > pi { a1 -= pi2 }
if da < -pi { a0 -= pi2 }
}
da = a1-a0
c = cos(-ang)
s = sin(-ang)
// var cx = sx*c-sy*s // don't need this
// var cy = sx*s+sy*c
var n: Int = 0
let maxCount: Int = 16
var dt: CGFloat = 0.0
var px = [CGFloat]()
var py = [CGFloat]()
n = Int(abs((CGFloat(maxCount) * da)/pi2))
if n < 1 { n = 1 }
else if n > maxCount { n = maxCount }
dt = da / CGFloat(n)
// get n+3 points on ellipse (with edges uniformly outside a0,a1)
let t = a0 - dt
for i in 0..<n+3 {
// point on axis aligned ellipse
let tt = t + (dt*CGFloat(i))
let xx = sx+a*cos(tt)
let yy = sy+b*sin(tt)
// rotate by ang
let c: CGFloat = cos(-ang)
let s: CGFloat = sin(-ang)
px.append(xx*c-yy*s)
py.append(xx*s+yy*c)
}
let m: CGFloat = 1/6
var string = ""
for i in 0..<n
{
// convert to interpolation cubic control points to BEZIER
let x0 = px[i+1]; let y0 = py[i+1];
let x1 = px[i+1]-(px[i+0]-px[i+2])*m; let y1 = py[i+1]-(py[i+0]-py[i+2])*m;
let x2 = px[i+2]+(px[i+1]-px[i+3])*m; let y2 = py[i+2]+(py[i+1]-py[i+3])*m;
let x3 = px[i+2]; let y3 = py[i+2];
if i == 0 {
let mString = String(format: "M%.2f %.2f", x0, y0)
string.append(mString)
}
let cString = String(format: "C%.2f %.2f %.2f %.2f %.2f %.2f", x1, y1, x2, y2, x3, y3)
string.append(cString)
}
return string
}
【问题讨论】:
-
我有使用贝塞尔曲线逼近椭圆弧(由椭圆中心、半轴、旋转、起始角和扫角定义)的 Delphi 代码。对你有用吗?
-
不会受伤。这只是关于如何将圆弧变成曲线。完成后,我可以做剩下的事情了
标签: svg geometry cubic-bezier