简体   繁体   English

使用贝塞尔曲线绘制螺旋线

[英]Using a Bezier Curve to draw a spiral

This is for an iPad application, but it is essentially a math question. 这适用于iPad应用程序,但它本质上是一个数学问题。

I need to draw a circular arc of varying (monotonically increasing) line width. 我需要绘制一个变化(单调增加)线宽的圆弧。 At the beginning of the curve, it would have a starting thickness (let's say 2pts) and then the thickness would smoothly increase until the end of the arc where it would be at its greatest thickness (let's say 12pts). 在曲线的开始处,它将具有起始厚度(假设为2pts),然后厚度将平滑地增加直到弧的末端,其将处于其最大厚度(假设为12pts)。

I figure the best way to make this is by creating a UIBezierPath and filling the shape. 我认为最好的方法是创建一个UIBezierPath并填充形状。 My first attempt was to use two circular arcs (with offset centers), and that worked fine up to 90°, but the arc will often be between 90° and 180°, so that approach won't cut it. 我的第一次尝试是使用两个圆弧(带有偏移中心),并且工作精细到90°,但弧度通常在90°和180°之间,因此接近不会切割它。

随着厚度增​​加,90度弧的例子

My current approach is to make a slight spiral (one slightly growing from the circular arc and one slightly shrinking) using bezier quad or cubic curves. 我目前的方法是使用贝塞尔四边形或三次曲线制作一个轻微螺旋(一个从圆弧略微增长,一个略微收缩)。 The question is where do I put the control points so that the deviation from the circular arc (aka the shape "thickness") is the value I want. 问题是我在哪里放置控制点,以便与圆弧(也就是形状“厚度”)的偏差是我想要的值。

Constraints: 约束:

  • The shape must be able to start and end at an arbitrary angle (within 180° of each other) 形状必须能够以任意角度开始和结束(彼此相差180°)
  • The "thickness" of the shape (deviation from the circle) must start and end with the given values 形状的“厚度”(与圆的偏差)必须以给定值开始和结束
  • The "thickness" must increase monotonically (it can't get bigger and then smaller again) “厚度”必须单调增加(它不能再变大,然后再变小)
  • It has to look smooth to the eye, there can't be any sharp bends 它必须看起来光滑,不会有任何急剧弯曲

I am open to other solutions as well. 我也对其他解决方案持开放态度。

My approach just constructs 2 circular arcs and fills the region in between. 我的方法只是构造2个圆弧并填充其间的区域。 The tricky bit is figuring out the centers and radii of these arcs. 棘手的一点是弄清楚这些弧的中心和半径。 Looks quite good provided the thicknesses are not too large. 如果厚度不是太大,看起来相当不错。 (Cut and paste and decide for yourself if it meet your needs.) Could possibly be improved by use of a clipping path. (剪切并粘贴并自行决定是否满足您的需求。)可以通过使用剪切路径来改进。

- (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);  
}

One solution could be to generate a polyline manually. 一种解决方案可以是手动生成折线。 This is simple but it has the disadvantage that you'd have to scale up the amount of points you generate if the control is displayed at high resolution. 这很简单,但它的缺点是,如果以高分辨率显示控件,则必须放大生成的点数。 I don't know enough about iOS to give you iOS/ObjC sample code, but here's some python-ish pseudocode: 我不太了解iOS给你iOS / ObjC示例代码,但这里有一些python-ish伪代码:

# 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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM