如何在iOS中简化单个复杂的UIBezierPath多边形

时间:2014-05-10 08:20:33

标签: ios objective-c uibezierpath polygons

问题:

我有一个用户生成的多边形(通过在屏幕上注册用户的触摸),它可以是简单的或复杂的(复杂意味着具有未知数量的交叉点),并且我希望有一个由原始多边形上的相同点产生的简单多边形,如果你愿意的话,就像轮廓或轮廓。

image Courtesy of unknown user on stack overflow

可能的解决方案:

我找到了this,但它是一个JavaScript解决方案,this完全说明了我需要的东西,但是在ActionScript中!我不需要Path本身,这些点就足够了。 你会怎么解决这个问题?

更新

当我进一步观察时,我看到一些人建议解决方案是在点上使用 Convex Hull 算法,但是,凸壳不是答案在这里,因为如果我是对的,结果将如下:

convex hull reuslt

3 个答案:

答案 0 :(得分:12)

您所描述的操作是联合。通常,计算一组多边形的并集有点复杂。有效和准确地这样做更复杂。 iOS不为此操作提供公共API。

我建议您考虑使用现有的C或C ++库来为您完成。以下是一些:

您可以从这些网页的链接或使用您最喜爱的搜索引擎找到其他内容。有用的搜索词包括“polygon”,“geometry”,“union”和“clipping”。

更新

我知道你只是绘制一个多边形。然而,至少在Clipper库的情况下,union操作可以满足您的要求:

star union

白色背景上的多边形是我通过点击创建的星形。它与自身相交。我使用Clipper的union操作在绿色背景上创建多边形。我将抽出的多边形作为主题传递,没有剪辑多边形:

- (UIBezierPath *)pathWithManyPoints:(CGPoint const *)points count:(NSUInteger)count {
    Path subject;
    for (NSUInteger i = 0; i < count; ++i) {
        subject << IntPoint(points[i].x, points[i].y);
    }

    Clipper clipper;
    clipper.AddPath(subject, ptSubject, true);
    Paths solutions;
    clipper.Execute(ctUnion, solutions, pftNonZero, pftNonZero);

    UIBezierPath *path = [UIBezierPath bezierPath];
    for (size_t i = 0; i < solutions.size(); ++i) {
        Path& solution = solutions[i];
        if (solution.size() > 0) {
            [path moveToPoint:cgPointWithIntPoint(solution[0])];
            for (size_t j = 1; j < solution.size(); ++j) {
                [path addLineToPoint:cgPointWithIntPoint(solution[j])];
            }
            [path closePath];
        }
    }

    return path;
}

另一方面,给定一个更复杂的输入多边形,有几种方法可能需要修改它:

hole union

简单(单个)联合会返回一个带有孔的多边形。如果你想在输出中没有空洞,你需要获取初始联合输出的各个循环(子路径),以相同的方式定向它们,然后采用所有定向循环的第二个并集。这就是我在橙色背景上计算“深度结合”多边形的方法:

- (UIBezierPath *)pathWithManyPoints:(CGPoint const *)points count:(NSUInteger)count {
    Path subject;
    for (NSUInteger i = 0; i < count; ++i) {
        subject << IntPoint(points[i].x, points[i].y);
    }

    Clipper clipper;
    clipper.AddPath(subject, ptSubject, true);
    Paths intermediateSolutions;
    clipper.Execute(ctUnion, intermediateSolutions, pftNonZero, pftNonZero);

    clipper.Clear();
    for (size_t i = 0; i < intermediateSolutions.size(); ++i) {
        if (Orientation(intermediateSolutions[i])) {
            reverse(intermediateSolutions[i]);
        }
    }
    clipper.AddPaths(intermediateSolutions, ptSubject, true);
    Paths solutions;
    clipper.Execute(ctUnion, solutions, pftNonZero, pftNonZero);

    UIBezierPath *path = [UIBezierPath bezierPath];
    for (size_t i = 0; i < solutions.size(); ++i) {
        Path& solution = solutions[i];
        if (solution.size() > 0) {
            [path moveToPoint:cgPointWithIntPoint(solution[0])];
            for (size_t j = 1; j < solution.size(); ++j) {
                [path addLineToPoint:cgPointWithIntPoint(solution[j])];
            }
            [path closePath];
        }
    }

    return path;
}

Clipper还有一个SimplifyPolygon函数,可以为明星示例生成相同的结果,可能代码较少。我不知道它为第二个例子(内部孔)产生了什么。我没试过。

您可以从this github repository下载我自包含的测试应用。

答案 1 :(得分:1)

要使用JavaScript代码,您可以查看this answer:

  

介绍了JavascriptCore框架中的Objective-C接口   iOS 7允许从Javascript调用Objective-C方法。为一个   有关这些功能的详细介绍,请查看2013 WWDC简介   Apple开发人员将“将JavaScript集成到Native Apps”会话中   网络:https://developer.apple.com/videos/wwdc/2013/?id=615

它提到了JS-&gt; Objective-C,但JSCore也是相反的。

否则,正如有人建议的那样,“翻译”JS代码应该相当容易,因为它只是数学计算。

答案 2 :(得分:0)

稍作思考:

  1. 找到最左边的顶点。它绝对是在边界上。从那里开始;
  2. 沿着当前边缘向下一个列出的顶点看;
  3. 检查是否有任何其他边缘与您正在查看的边缘相交;
  4. 如果是,那么找到当前位置前面的第一个顶点并移动到它;
  5. 从(2)重复您刚转换到的边缘;
  6. 继续,直到你回到(1)处的顶点。
  7. (并看到评论重新:我之前就UIBezierPath推出路径而做出的错误评论