Using a Bezier Curve to Draw a Spiral

This is for an iPad application, but it's essentially a math question.

I need to draw a circular arc of varying (monotonically increasing) line width. At the beginning of the curve, it would have an initial thickness (let it be 2pts), and then the thickness would gradually increase to the end of the arc, where it would have the greatest thickness (say, 12pts).

I believe the best way to do this is to create a UIBezierPath and fill out the form. My first attempt was to use two circular arcs (with offset centers) that worked normally up to 90 °, but the arc was often between 90 ° and 180 °, so the approach did not shorten it.

example of 90 degree arc with increasing thickness

My current approach is to make a small spiral (one grows slightly from the arc of a circle and decrease slightly) using square or cubic bezier curves. Question: where do I put control points so that the deviation from the arc of a circle (the so-called "thickness") is the value I want.

Limitations:

  • The form should be able to begin and end at any angle (within 180 ° from each other)
  • The "thickness" of the form (deviation from the circle) should begin and end with the set values
  • "Thickness" should increase monotonously (it cannot become more and then less).
  • It should look smooth, there can be no sharp bends

I am also open to other solutions.

+6
source share
2 answers

My approach only builds 2 circular arcs and fills the area between them. The hard bit calculates the centers and radii of these arcs. It looks good if the thickness is not too large. (Cut and paste and decide for yourself if it suits your needs.) Perhaps it can be improved with 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); } 
+5
source

One solution might be to create a polyline manually. It's simple, but it has the disadvantage that you will have to increase the number of points that you create if the control is displayed in high resolution. I don't know enough about iOS to give you a sample of iOS / ObjC code, but here is some python-ish pseudo-code:

 # 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 
+1
source

Source: https://habr.com/ru/post/919945/


All Articles