使用贝塞尔曲线绘制螺旋
这是一个iPad应用程序,但它本质上是一个数学问题。使用贝塞尔曲线绘制螺旋
我需要绘制一个变化(单调增加)线宽的圆弧。在曲线开始时,它会有一个起始厚度(比如说2pts),然后厚度会平滑增加,直到弧线达到最大厚度(比如12pts)。
我想通过创建一个UIBezierPath并填充形状的最好方法。我的第一个尝试是使用两个圆弧(有偏移中心),并且工作精度可达90°,但弧度通常在90°和180°之间,所以这种方法不会削减它。
我目前的做法是使略微螺旋使用贝塞尔四或三次曲线(一个稍微从圆弧生长和一个稍微收缩)。问题是在哪里我把控制点,以便从圆弧的偏差(又名形状“厚度”)是我想要的值。
限制条件:
- 形状必须能够开始和结束以任意角度(在180°彼此的)
- 形状的“厚度”(从圆偏差)必须以给定值开始和结束
- “厚度”必须单调增加(不能再变大再变小)
- 它必须看起来顺畅,不能有任何尖锐的弯曲
我也接受其他解决方案。
我的做法只是构建了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);
}
一种解决方案可能是手动生成折线。这很简单,但它的缺点是如果控件以高分辨率显示,则必须放大生成的点的数量。我不知道有足够的了解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
感谢您的伪代码。如果我找不到使用贝塞尔曲线或弧的解决方案,这将是我的备份。 – 2012-07-07 22:55:06
这实际上看起来非常棒!你为我节省了不少工作。这比我工作的方法简单得多(求解螺旋的贝塞尔多项式方程)。我得到它的工作90°的倍数,但任意角度将是一个痛苦。这是好得多... – 2012-07-08 08:00:12
@JonHull很高兴你喜欢它。我刚刚意识到我已经隐含地认为'endingThickness> = startingThickness',但你应该能够很容易地安排你的输入参数,以便满足这个条件。如果不是这样,那么可能会出现“预计的结果厚度”为负值的情况,然后我再也不能确定代数了。它可能仍然有效,但我没有测试过它。 – inwit 2012-07-08 14:29:55
哦伟大的工作兄弟,,,,你是一个真正的救星,,,谢谢 – Dhiru 2017-06-19 11:23:58