如何创建一个随机的闭合平滑CGPath?

7
我正在寻找一种创建随机闭合平滑路径(CGPathUIBezierPath)的方法。我已经阅读了关于De Casteljau's算法和大量关于贝塞尔路径的文章,但似乎不适用于我想实现的内容。
我考虑创建一个圆形CGPath。然后,我会使用一个函数将路径乘以扭曲点的位置,例如正弦或余弦。但是,我不知道这是否是正确的方向,因为路径将没有随机形状。
CGMutablePathRef circle = CGPathCreateMutable();
CGPathAddArc(circle, nil, 0.0f, 0.0f, 100.0f, 2 * M_PI, 0.0f, true);
...
CGPathRelease(circle);

希望有人能指导我如何开始实施它。以下是我试图生成的路径示例:

即使是非自交的闭合光滑路径也可能相当“复杂”。您能否指定所需路径的一些限制? - Martin R
从我的经验来看,生成极坐标下的航点可能会有所帮助,而不是笛卡尔坐标系下的。在绕圆圈行走时随机变化半径,然后你的问题就是“仅仅”插值了。为此,请查阅Catmull-Rom样条过程,它将生成一条路径“通过”点,而不是绕着它们。 - jscs
@JoshCaswell,英雄所见略同。你使用极坐标的解释比我的更易于理解。请查看我回答中链接的示例项目。 - Duncan C
好的,谢谢分享,@DuncanC。 - jscs
2个回答

9
你所画的看起来像一个扭曲的圆。
假设这就是你想要的,以下是我会做的:
编写代码,从0到2pi以固定步长逐个角度增加。 (尝试8) 角度变化量应小于± pi/steps。
选择一个基础半径,它略小于包围正方形边长的一半,这样就有空间让你的点在基础半径内或外而不超出边界正方形。 尝试使用边界框长度的3/8。
对于沿着圆的每个略微随机化的角度值,计算一个半径值,该半径值为base radius ± 0到base radius/2之间的随机值。
使用正弦和余弦将您的角度和半径值转换为点的x和y坐标。
将每个点添加到数组中。如果使用这些点创建一个封闭路径,则会得到一个具有8个面的不规则非自交多边形,即扭曲的圆。
现在将这些点用作Catmull-Rom样条的控制点,以将其转换为平滑曲线。

编辑:我在github上创建了一个名为RandomBlobs的项目,它可以实现我上面描述的内容,以及另一种方法:

将正方形区域分成一个3x3的小正方形网格。忽略中心正方形。 按顺时针方向绕着剩下的8个正方形走。对于每个正方形,在正方形内选择一个随机的x/y坐标(但要防止它靠近边缘)。 按顺序创建连接8个点的闭合UIBezierPath。 使用Catmull-Rom平滑将不规则八边形变成平滑曲线。

第三种方法可能更简单:

像上面概述的第一种方法一样使用圆形布局。选择随机控制点。但是,不要使用Catmull-Rom样条曲线,而是将扭曲圆上每对端点之间的角度平分,并添加一个用随机半径值的二次Bezier曲线的控制点。因此,当您沿着圆圈行走时,您会有交替的端点和控制点。您可能需要添加一些约束条件来避免曲线形状出现“折痕”(为了避免折痕,相邻Bezier曲线的控制点需要沿着两条曲线的共享端点通过一条直线。)


这里是RandomBlobs项目的几个示例图片。我上传的图片已经缩小了。该程序可以选择性地显示用于生成每个图像的控制点,但在缩小的图像中无法真正看到控制点。
首先是基于圆形的blob(使用Josh Caswell和我建议的第一种方法): Circle-based blob 在该图片中,起始圆形形状以浅灰色显示:
其次是基于我描述的第二种基于正方形的技术的blob:

Square-based blob

在该图片中,方格网格被用作参考。该形状基于网格中每个点的随机点(不包括中心方格)。

谢谢 Duncan。你的帖子非常有帮助。我会实施它。 - Rafał Sroka
1
@reecon,我不确定为什么,但我决定将您的问题作为示例项目来解决。经过大量的工作,我在https://github.com/DuncanMC/RandomBlobs上创建了一个项目,以两种不同的方式实现了您要求的功能。第一种方法使用了我的答案中描述的圆形切片方法,第二种方法将视图分成9个正方形的网格,跳过中心正方形,在每个正方形中顺时针移动并选择随机点,形成连接8个点的闭合路径。这两种形状有不同的感觉。 - Duncan C
好棒的代码!我本来想在周末解决这个问题,但看起来你已经帮我解决了。这就是为什么我如此热爱StackOverflow。你可以分享、合作并享受编程的乐趣!谢谢你啊。 - Rafał Sroka

2

我尝试构建了你的路径,但它不完美...无论如何,我将分享我的测试;-D 希望这可以帮到你。

//
//  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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接