6

Problem:

I have a user generated polygon (via registering user's touches on screen) which can be simple or complex (complex means having unknown number of intersections) and I want to have a simple polygon resulting from the same points on the original polygon, something like an outline or a contour if you will.

image Courtesy of unknown user on stack overflow

Possible solutions:

I have found this, but it is a JavaScript solution and this is a perfect illustration of what I need but in ActionScript! I don't need the Path itself, the points will suffice. How would you approach such problem?

Update:

As I was looking further around, I have seen a few people suggesting that the solution is using a Convex Hull algorithm on the points, but, convex hull is not the answer here because if I'm right the result will be as follows:

convex hull reuslt

Community
  • 1
  • 1
M. Porooshani
  • 1,797
  • 5
  • 34
  • 42
  • Have you tried to translate the JavaScript solution to Objective-C? – Martin R May 10 '14 at 09:21
  • That's one way to go, but I'm not sure I can. I have tried [GPC](https://github.com/jpswensen/GPCPolygon) with no luck. – M. Porooshani May 10 '14 at 09:23
  • Seems like you need an ObjC implementation of a concave (not convex) hull algorithm. – augustzf May 10 '14 at 12:18
  • @Rob, 1- I didn't investigate any of those algorithms. 2- I Need Objective-C solutions for iOS, that sample ActionScript does what I need to do perfectly, and that's why I referred to it. Thanks – M. Porooshani May 10 '14 at 13:14
  • I was about to suggest something like the accepted answer to that other question. My only caveat would be to make sure you start with a point that is not contained within the other paths. I'd suggest you take a stab at that algorithm, and if you hit a stumbling block, post a question with your attempt and we'll see if we can help. – Rob May 10 '14 at 13:37
  • @Rob, I know the order of points, but still, implementing that algorithm needs some extra tools something like UIBezierPath doesn't offer! – M. Porooshani May 10 '14 at 13:39
  • Agreed, `UIBezierPath` is not going to do any of this for you. You'll only use it to render the final path you construct. But you'll need to write quite a few routines yourself: You need a "find intersection between two line segments" routine, then having that, you can write a "pick the nearest intersection from the array of intersections found". You probably need a "calculate angle between three points" algorithm, too. But then, armed with those, you can implement the algorithm. – Rob May 10 '14 at 13:53
  • @Rob I already have all of them, but I'm not too clear on what i should do with them! I was kinda thinking about filling the polygon the non-zero style and then convert it to a new contour, but I'm not sure that's even possible! – M. Porooshani May 10 '14 at 13:58
  • What about filling the polygons, merge them and try to get the new path? – Larme May 12 '14 at 10:24
  • I've already suggested it my self @Larme, but I can't find out how... – M. Porooshani May 12 '14 at 10:27
  • Sorry, didn't see it. Maybe with inspiring you with: http://stackoverflow.com/questions/8859285/uibezierpath-subtract-path – Larme May 12 '14 at 10:29
  • while it is a very good tutorial, it has nothing to do with my problem, I need both points and the resulting shape. – M. Porooshani May 12 '14 at 11:05
  • Is the shape always a single, closed loop of straight lines? – Nikolai Ruhe May 12 '14 at 14:25
  • @NikolaiRuhe, yes it is. – M. Porooshani May 12 '14 at 14:27
  • Obvious question: you definitely need the simple polygon geometry; just drawing the thing isn't sufficient? – Tommy May 13 '14 at 01:53

3 Answers3

13

The operation you are describing is a union. Generally, computing the union of a set of polygons is somewhat complicated. Doing so efficiently and accurately is more complicated. iOS doesn't provide a public API for this operation.

I suggest you look into using an existing C or C++ library to do it for you. Here are some:

There are others you can find from links off of those pages or using your favorite search engine. Useful search terms include “polygon”, “geometry”, “union”, and “clipping”.

UPDATE

I understand that you're just drawing one polygon. Nevertheless, in the case of the Clipper library at least, the union operation does what you're asking for:

star union

The polygon on the white background is the star I created by tapping. It intersects itself. I used Clipper's union operation to create the polygon on the green background. I passed the tapped-out polygon as the subject with no clip polygons:

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

On the other hand, given a more complex input polygon, there are a couple of ways you might want to modify it:

hole union

The simple (single) union gives you back a polygon with a hole in it. If you want no holes in the output, you need to take the individual loops (subpaths) output by the initial union, orient them all the same way, and then take a second union of all the oriented loops. That's how I computed the “deep union” polygon on the orange background:

- (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 also has a SimplifyPolygon function that produces the same result for the star example, perhaps with less code. I don't know what it produces for the second example (with the interior hole). I didn't try it.

You can download my self-contained test app from this github repository.

David James
  • 2,430
  • 1
  • 26
  • 35
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
1

To use the JavaScript code you can check this answer:

The Objective-C interface in the JavascriptCore framework introduced with iOS 7 permits calling Objective-C methods from Javascript. For a great intro to these features, check out the 2013 WWDC introduction "Integrating JavaScript into Native Apps" session on Apple's developer network: https://developer.apple.com/videos/wwdc/2013/?id=615

It mentions JS->Objective-C but JSCore also works the other way around.

Otherwise as someone suggested it should be fairly easy to "translate" the JS code as it would just be mathematical calculations.

Community
  • 1
  • 1
Rivera
  • 10,792
  • 3
  • 58
  • 102
0

With a little thought:

  1. find the leftmost vertex. It's definitely on the boundary. Start there;
  2. look along the current edge towards the next listed vertex;
  3. check if any of the other edges intersect the one you're looking along;
  4. if so then find the first vertex in front of your current position and move to it;
  5. repeat from (2) with the edge you just transitioned onto;
  6. continue until you get back to the vertex at (1).

(and see the comment re: an erroneous earlier comment I'd made about getting the path out of a UIBezierPath)

Tommy
  • 99,986
  • 12
  • 185
  • 204
  • To query the geometry of a `UIBezierPath` you can get it's `CGPath` and use `CGPathApply` to walk the path's elements. – Nikolai Ruhe May 14 '14 at 13:54