2012-07-07 51 views
6

這是一個iPad應用程序,但它本質上是一個數學問題。使用貝塞爾曲線繪製螺旋

我需要繪製一個變化(單調增加)線寬的圓弧。在曲線開始時,它會有一個起始厚度(比如說2pts),然後厚度會平滑增加,直到弧線達到最大厚度(比如12pts)。

我想通過創建一個UIBezierPath並填充形狀的最好方法。我的第一個嘗試是使用兩個圓弧(有偏移中心),並且工作精度可達90°,但弧度通常在90°和180°之間,所以這種方法不會削減它。

example of 90 degree arc with increasing thickness

我目前的做法是使略微螺旋使用貝塞爾四或三次曲線(一個稍微從圓弧生長和一個稍微收縮)。問題是在哪裏我把控制點,以便從圓弧的偏差(又名形狀「厚度」)是我想要的值。

限制條件:

  • 形狀必須能夠開始和結束以任意角度(在180°彼此的)
  • 形狀的「厚度」(從圓偏差)必須以給定值開始和結束
  • 「厚度」必須單調增加(不能再變大再變小)
  • 它必須看起來順暢,不能有任何尖銳的彎曲

我也接受其他解決方案。

回答

5

我的做法只是構建了2個圓弧和填充之間的區域。棘手的是找出這些弧的中心和半徑。看起來相當不錯,只要厚度不太大。 (剪切並粘貼,如果它滿足您的需求,請自行決定。)可以通過使用剪切路徑來改進。

- (void)drawRect:(CGRect)rect 
{ 
    CGContextRef context = UIGraphicsGetCurrentContext(); 

    CGMutablePathRef path = CGPathCreateMutable(); 

    // As appropriate for iOS, the code below assumes a coordinate system with 
    // the x-axis pointing to the right and the y-axis pointing down (flipped from the standard Cartesian convention). 
    // Therefore, 0 degrees = East, 90 degrees = South, 180 degrees = West, 
    // -90 degrees = 270 degrees = North (once again, flipped from the standard Cartesian convention). 
    CGFloat startingAngle = 90.0; // South 
    CGFloat endingAngle = -45.0; // North-East 
    BOOL weGoFromTheStartingAngleToTheEndingAngleInACounterClockwiseDirection = YES; // change this to NO if necessary 

    CGFloat startingThickness = 2.0; 
    CGFloat endingThickness = 12.0; 

    CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)); 
    CGFloat meanRadius = 0.9 * fminf(self.bounds.size.width/2.0, self.bounds.size.height/2.0); 

    // the parameters above should be supplied by the user 
    // the parameters below are derived from the parameters supplied above 

    CGFloat deltaAngle = fabsf(endingAngle - startingAngle); 

    // projectedEndingThickness is the ending thickness we would have if the two arcs 
    // subtended an angle of 180 degrees at their respective centers instead of deltaAngle 
    CGFloat projectedEndingThickness = startingThickness + (endingThickness - startingThickness) * (180.0/deltaAngle); 

    CGFloat centerOffset = (projectedEndingThickness - startingThickness)/4.0; 
    CGPoint centerForInnerArc = CGPointMake(center.x + centerOffset * cos(startingAngle * M_PI/180.0), 
              center.y + centerOffset * sin(startingAngle * M_PI/180.0)); 
    CGPoint centerForOuterArc = CGPointMake(center.x - centerOffset * cos(startingAngle * M_PI/180.0), 
              center.y - centerOffset * sin(startingAngle * M_PI/180.0)); 

    CGFloat radiusForInnerArc = meanRadius - (startingThickness + projectedEndingThickness)/4.0; 
    CGFloat radiusForOuterArc = meanRadius + (startingThickness + projectedEndingThickness)/4.0; 

    CGPathAddArc(path, 
       NULL, 
       centerForInnerArc.x, 
       centerForInnerArc.y, 
       radiusForInnerArc, 
       endingAngle * (M_PI/180.0), 
       startingAngle * (M_PI/180.0), 
       !weGoFromTheStartingAngleToTheEndingAngleInACounterClockwiseDirection 
       ); 

    CGPathAddArc(path, 
       NULL, 
       centerForOuterArc.x, 
       centerForOuterArc.y, 
       radiusForOuterArc, 
       startingAngle * (M_PI/180.0), 
       endingAngle * (M_PI/180.0), 
       weGoFromTheStartingAngleToTheEndingAngleInACounterClockwiseDirection 
       ); 

    CGContextAddPath(context, path); 

    CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor); 
    CGContextFillPath(context); 

    CGPathRelease(path); 
} 
+0

這實際上看起來非常棒!你爲我節省了不少工作。這比我工作的方法簡單得多(求解螺旋的貝塞爾多項式方程)。我得到它的工作90°的倍數,但任意角度將是一個痛苦。這是好得多... – 2012-07-08 08:00:12

+0

@JonHull很高興你喜歡它。我剛剛意識到我已經隱含地認爲'endingThickness> = startingThickness',但你應該能夠很容易地安排你的輸入參數,以便滿足這個條件。如果不是這樣,那麼可能會出現「預計的結果厚度」爲負值的情況,然後我再也不能確定代數了。它可能仍然有效,但我沒有測試過它。 – inwit 2012-07-08 14:29:55

+0

哦偉大的工作兄弟,,,,你是一個真正的救星,,,謝謝 – Dhiru 2017-06-19 11:23:58

1

一種解決方案可能是手動生成折線。這很簡單,但它的缺點是如果控件以高分辨率顯示,則必須放大生成的點的數量。我不知道有足夠的瞭解iOS版給你的iOS/ObjC示例代碼,但這裏的一些Python上下的僞代碼:

# lower: the starting angle 
# upper: the ending angle 
# radius: the radius of the circle 

# we'll fill these with polar coordinates and transform later 
innerSidePoints = [] 
outerSidePoints = [] 

widthStep = maxWidth/(upper - lower) 
width = 0 

# could use a finer step if needed 
for angle in range(lower, upper): 
    innerSidePoints.append(angle, radius - (width/2)) 
    outerSidePoints.append(angle, radius + (width/2)) 
    width += widthStep 

# now we have to flip one of the arrays and join them to make 
# a continuous path. We could have built one of the arrays backwards 
# from the beginning to avoid this. 

outerSidePoints.reverse() 
allPoints = innerSidePoints + outerSidePoints # array concatenation 

xyPoints = polarToRectangular(allPoints) # if needed 
+0

感謝您的僞代碼。如果我找不到使用貝塞爾曲線或弧的解決方案,這將是我的備份。 – 2012-07-07 22:55:06