使用8立方贝塞尔曲线创建(近似)圆的控制点是哪些

cod*_*rut 4 math cocoa geometry bezier uibezierpath

处所

我有一个动画成由8个贝塞尔曲线组成的形状的圆。为了使过渡平滑,我还需要用8立方贝塞尔曲线制成圆。这是我到目前为止的内容:

代码

- (UIBezierPath*)pathBubbleLeft {
    UIBezierPath *path = [UIBezierPath new];
    [path moveToPoint:p(sqlx, sqlMidy)];

    CGFloat r = sqlW/2;
    CGFloat sin45 = 0.7071 * r;
    CGFloat cos45 = 0.7071 * r;

    [path addRelativeCurveToPoint:point(sqlMidx - cos45, sqlMidy - sin45) control1:vector(0, 0.4) control2:vector(0.5, 0.8)];
    [path addRelativeCurveToPoint:point(sqlMidx, sqly) control1:vector(0.2, 0.5) control2:vector(0.4, 1)];

    [path addRelativeCurveToPoint:point(sqlMidx + cos45, sqlMidy - sin45) control1:vector(0.6, 0) control2:vector(0.8, 0.5)];
    [path addRelativeCurveToPoint:point(sqlMaxx, sqlMidy) control1:vector(0.5, 0.2) control2:vector(1, 0.5)];

    [path addRelativeCurveToPoint:point(sqlMidx + cos45, sqlMidy + sin45) control1:vector(0, 0.4) control2:vector(0.5, 0.8)];
    [path addRelativeCurveToPoint:point(sqlMidx, sqlMaxy) control1:vector(0.2, 0.5) control2:vector(0.4, 1)];

    [path addRelativeCurveToPoint:point(sqlMidx - cos45, sqlMidy + sin45) control1:vector(0.6, 0) control2:vector(0.8, 0.5)];
    [path addRelativeCurveToPoint:point(sqlx, sqlMidy) control1:vector(0.5, 0.2) control2:vector(1, 0.5)];

    return path;
}
Run Code Online (Sandbox Code Playgroud)

路径从左开始并顺时针旋转(从pi到pi / 2、0、3pi / 4,pi)

pointvector是短裤CGPointMake和CGVectorMake

“SQL”中sqlxsqlysqlMidxsqlMidYsqlMaxxsqlMaxy代表“squareLeft”,圆的边界矩形。这些都是CGFloats。

addRelativeCurveToPoint用于定义相对于起点/终点的控制点。(0,0)是开始,(1,1)是结束。更容易阅读。

- (void)addRelativeCurveToPoint:(CGPoint)endPoint control1:(CGVector)controlPoint1 control2:(CGVector)controlPoint2 {
    CGPoint start = self.currentPoint;
    CGPoint end = endPoint;
    CGFloat x1 = start.x + controlPoint1.dx*(end.x - start.x);
    CGFloat x2 = start.x + controlPoint2.dx*(end.x - start.x);
    CGFloat y1 = start.y + controlPoint1.dy*(end.y - start.y);
    CGFloat y2 = start.y + controlPoint2.dy*(end.y - start.y);
    [self addCurveToPoint:endPoint controlPoint1:CGPointMake(x1, y1) controlPoint2:CGPointMake(x2, y2)];
}
Run Code Online (Sandbox Code Playgroud)

到目前为止的结果: 静态的

红色圆圈有点波浪。这就是我要解决的问题。

在下面,左圆使用上面的代码,右圆由4条曲线组成,顶部有2个零长度插入,底部有2个零长度插入([path addLineToPoint:path.currentPoint];)。

在此处输入图片说明

从左边的一个过渡到花生中段是可以的,但是从中间的向右过渡很奇怪

Mik*_*ans 5

使用四个线段,使用三次贝塞尔曲线的圆逼近不能比@fang在评论中提供的0.55228 [...]值更圆:这只是数学上唯一三次贝塞尔曲线最佳的值近似一个圆。在无限精度表示中,它实际上是您从中获得的价值:

     4        angle       4                     sqrt(2) - 1
k =  - * tan(-------)  =  - * tan(pi/8)  =  4 * -----------
     3          2         3                          3
Run Code Online (Sandbox Code Playgroud)

是0.5522847498307933984022516322796 [...]。这将为您提供4个线段的最佳近似值,因此,如果您需要使用8个线段,我们需要一个不同的值,这意味着我们需要使用为pi / 2(四分之一圆)的角度提供k的导数。,看看它对pi / 4(第八个圆)的作用。因此:我们将角度pi/4插入到Bezier CurvePrimer上有关用三次曲线逼近圆的部分中概述的函数中,得到:

start = {
  x: 1,
  y: 0
}

c1 = {
  x: 1,
  y: 4/3 * tan(pi/16)
}

c2 = {
  x: cos(pi/4) + 4/3 * tan(pi/16) * sin(pi/4)
  y: sin(pi/4) - 4/3 * tan(pi/16) * cos(pi/4)
}

e = {
  x: cos(pi/4)
  y: sin(pi/4)
}
Run Code Online (Sandbox Code Playgroud)

这为我们提供了这些(非常有用的)近似坐标:

s  = (1,           0)
c1 = (1,           0.265216...)
c2 = (0.894643..., 0.51957...)
e  = (0.7071...,   0.7071...)
Run Code Online (Sandbox Code Playgroud)

那将是第1段,然后其余的段简单地通过对称性得出,第2段为:

s  = (0.7071...,   0.7071...)
c1 = (0.51957...,  0.894643...)
c2 = (0.265216..., 1)
e  = (0,           1)
Run Code Online (Sandbox Code Playgroud)

此处显示了覆盖四分之一圆的这些坐标的示例:http : //jsbin.com/ridedahixu/edit?html,输出

其余是(+,-),(-,+)和(-,-)象限中的明显对称性。

这些是最好的近似值,因此:如果bezierPathWithOvalInRect(...)做其他事情,它的准确性不如我们几十年前得出的值=)