I am trying to find a way to create a random closed smooth path (CGPath
or UIBezierPath
). I have read about the De Casteljau's algorithm and tons other articles about Bezier paths but it does not seem to fit to what I try to achieve.
I thought about creating a circle CGPath. Then I would multiplicate the path by a function that would distort the positions of the points say, sine or cosine. However I don't know if this is the right direction to go since the path would not have a random shape.
CGMutablePathRef circle = CGPathCreateMutable();
CGPathAddArc(circle, nil, 0.0f, 0.0f, 100.0f, 2 * M_PI, 0.0f, true);
...
CGPathRelease(circle);
It would be great if anyone could point me in a right direction how to start implementing it. Example of a path I am trying to generate:
What you've drawn looks like a distorted circle.
Assuming that's what you are after, here is what I would do:
Write code that steps an angle from 0 to 2pi by a fixed number of steps. (Try 8) Have the angle vary by some small random amount less than ± pi/steps.
Pick a base radius that is somewhat less than 1/2 the length of a side of the enclosing square, so there is room to make your points go inside or outside the base radius without going outside your bounding square. Try 3/8 of your bounding box length.
For each slightly randomized angle value along the circle, calculate a radius value that is base radius ± a random value from 0 to base radius/2.
Use sine and cosine to convert your angle and radius values into x and y coordinates for a point.
Add each point to an array. If you use those points to create a closed path, it would give you an 8-sided irregular non-selfintersecting polygon that is a distorted circle.
Now use those points as the control points for a Catmull-Rom spline to turn it into a smooth curve.
EDIT: I created a project on github called RandomBlobs that does what I describe above, as well as another approach:
Break the square area into a 3x3 grid of smaller squares. Ignore the center square. Walk around the 8 remaining squares clockwise. For each square, pick a random x/y coorindate inside the square (but prevent it from getting too close to the edges.) Create closed UIBezierPath connecting the 8 points in order. Use Catmull-Rom smoothing to turn the irregular octagon into a smooth curve.
Yet a third approach would probably be even simpler:
Use a circular layout like in the first approach outlined above. Pick random control points. But then instead of using Catmull-Rom splines, bisect the angle between each pair of endpoints on the distorted circle and add a control point for a quadratic Bezier curve, also with a randomized radius value. So as you walk around the circle, you'd have alternating endpoints and control points. You might need to add some constraints to the bezier control points so you don't have "kinks" in your curved shape (In order to avoid kinks, the control points for neighboring Bezier curves need to follow a line through the shared end-point of the two curves.)
Here are a couple of sample images from the RandomBlobs project. The images I've uploaded are scaled down. The program optionally shows the control points it uses to generate each image, but you can't really see the control points in the scaled-down image.
First, a circle-based blob (using the first method that Josh Caswell and I suggested): In that picture, the starting circle shape is shown in light gray:
And second, a blob based on the second square-based technique I described:
And in that picture, the grid of squares is shown for reference. The shape is based on a random point in each of the points in the grid (excluding the center square).
I've try to build your path, but it's not perfect... Anyhow, I'll share my test ;-D Hop this can help.
//
// DrawView.h
// test
//
// Created by Armand DOHM on 03/03/2014.
//
//
#import <UIKit/UIKit.h>
@interface DrawView : UIView
@end
//
// DrawView.m
// test
//
// Created by Armand DOHM on 03/03/2014.
//
//
#import "DrawView.h"
#import <math.h>
@implementation DrawView
- (void)drawRect:(CGRect)rect
{
float r; //radius
float rmax = MIN(rect.size.height,rect.size.width) * .5; //max radius
float rmin = rmax * .1; //min radius
NSMutableArray *points = [[NSMutableArray alloc] init];
/*cut a circle into x pies. for each of this pie take a point at a random radius
link all of this point with quadcurve*/
for (double a=0;a < 2 * M_PI;a += M_PI / 10) {
r = rmin + ((arc4random_uniform((int)(rmax - rmin) * 100)) / 100.0f);
CGPoint p = CGPointMake((rect.size.width / 2) + (r * cos (a)) , (rect.size.height / 2) + (r * sin (a)));
[points addObject:[NSValue valueWithCGPoint:p]];
}
UIBezierPath *myPath=[[UIBezierPath alloc]init];
myPath.lineWidth=2;
[myPath strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
r = rmin + ((arc4random_uniform((int)(rmax - rmin) * 100)) / 100.0f);
[myPath moveToPoint:CGPointMake((rect.size.width / 2) + (r * cos (0)) , (rect.size.height / 2) + (r * sin (0)))];
for (int i = 0; i < points.count; i+=2) {
NSValue *value = [points objectAtIndex:i];
CGPoint p1 = [value CGPointValue];
value = [points objectAtIndex:(i+1)];
CGPoint p2 = [value CGPointValue];
[myPath addQuadCurveToPoint:p2 controlPoint:p1];
}
[myPath closePath];
[myPath stroke];
}
@end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With