私は SVG パスを a として正確に表現しようとしていますがUIBezierPath
、残念ながらaddArc
onUIBezierPath
は楕円を考慮せず、円のみを考慮しています (半径の値は 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
しかし、可能性のある円弧を解析するので、楕円を分割する方法と、各ベジエ曲線の制御点を計算する方法を知る必要があります。
この方法で計算された 3 次曲線を示すさまざまなリソースを見てきました... http://www.spaceroots.org/documents/ellipse/node12.html
しかし、これをコードで表現する方法がわかりません
これは私がこれまでに持っているものです....
a
SVG のパスの値
radiusX radiusY rotationOfArcX isLarge isSweep destinationX destinationY
編集
@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
}