Commits

martinwinter  committed be5299b

Edit a few copyright notices. Move code shared between platforms to separate folder.

  • Participants
  • Parent commits ef2a8b6
  • Branches cgpath

Comments (0)

Files changed (51)

File Shared/CGPath_Boolean.h

+//
+//  CGPath_Boolean.h
+//  VectorBoolean
+//
+//  Created by Martin Winter on 02.08.12.
+//  Based on work by Andrew Finnell of Fortunate Bear, LLC.
+//  Copyright (c) 2012 Martin Winter. All rights reserved.
+//
+
+#ifndef VectorBoolean_CGPath_Boolean_h
+#define VectorBoolean_CGPath_Boolean_h
+
+#if TARGET_OS_IPHONE
+#import <CoreGraphics/CoreGraphics.h>
+#else
+#import <ApplicationServices/ApplicationServices.h>
+#endif
+
+
+CGPathRef CGPath_FBCreateUnion (
+    CGPathRef path,
+    CGPathRef otherPath
+);
+
+CGPathRef CGPath_FBCreateIntersect (
+    CGPathRef path,
+    CGPathRef otherPath
+);
+
+CGPathRef CGPath_FBCreateDifference (
+    CGPathRef path,
+    CGPathRef otherPath
+);
+
+CGPathRef CGPath_FBCreateXOR (
+    CGPathRef path,
+    CGPathRef otherPath
+);
+
+
+#endif

File Shared/CGPath_Boolean.m

+//
+//  CGPath_Boolean.c
+//  VectorBoolean
+//
+//  Created by Martin Winter on 02.08.12.
+//  Based on work by Andrew Finnell of Fortunate Bear, LLC.
+//  Copyright (c) 2012 Martin Winter. All rights reserved.
+//
+
+#include "CGPath_Boolean.h"
+#include "FBBezierGraph.h"
+
+
+CGPathRef CGPath_FBCreateUnion( CGPathRef path, CGPathRef otherPath )
+{
+    FBBezierGraph *thisGraph = [FBBezierGraph bezierGraphWithBezierPath:path];
+    FBBezierGraph *otherGraph = [FBBezierGraph bezierGraphWithBezierPath:otherPath];
+    CGPathRef result = [[thisGraph unionWithBezierGraph:otherGraph] newBezierPath];
+    // TODO: attributes are property of CGContext, not CGPath; or use UIBezierPath
+    return result;
+}
+
+
+CGPathRef CGPath_FBCreateIntersect( CGPathRef path, CGPathRef otherPath )
+{
+    FBBezierGraph *thisGraph = [FBBezierGraph bezierGraphWithBezierPath:path];
+    FBBezierGraph *otherGraph = [FBBezierGraph bezierGraphWithBezierPath:otherPath];
+    CGPathRef result = [[thisGraph intersectWithBezierGraph:otherGraph] newBezierPath];
+    // TODO: attributes are property of CGContext, not CGPath; or use UIBezierPath
+    return result;
+}
+
+
+CGPathRef CGPath_FBCreateDifference( CGPathRef path, CGPathRef otherPath )
+{
+    FBBezierGraph *thisGraph = [FBBezierGraph bezierGraphWithBezierPath:path];
+    FBBezierGraph *otherGraph = [FBBezierGraph bezierGraphWithBezierPath:otherPath];
+    CGPathRef result = [[thisGraph differenceWithBezierGraph:otherGraph] newBezierPath];
+    // TODO: attributes are property of CGContext, not CGPath; or use UIBezierPath
+    return result;
+}
+
+
+CGPathRef CGPath_FBCreateXOR( CGPathRef path, CGPathRef otherPath )
+{
+    FBBezierGraph *thisGraph = [FBBezierGraph bezierGraphWithBezierPath:path];
+    FBBezierGraph *otherGraph = [FBBezierGraph bezierGraphWithBezierPath:otherPath];
+    CGPathRef result = [[thisGraph xorWithBezierGraph:otherGraph] newBezierPath];
+    // TODO: attributes are property of CGContext, not CGPath; or use UIBezierPath
+    return result;
+}

File Shared/CGPath_Utilities.h

+//
+//  CGPath_Utilities.h
+//  VectorBoolean
+//
+//  Created by Martin Winter on 02.08.12.
+//  Based on work by Andrew Finnell of Fortunate Bear, LLC.
+//  Copyright (c) 2012 Martin Winter. All rights reserved.
+//
+
+#ifndef VectorBoolean_CGPath_Utilities_h
+#define VectorBoolean_CGPath_Utilities_h
+
+#if TARGET_OS_IPHONE
+#import <CoreGraphics/CoreGraphics.h>
+#else
+#import <ApplicationServices/ApplicationServices.h>
+#endif
+
+
+typedef struct {
+    CGPathElementType kind;
+    CGPoint point;
+    CGPoint controlPoints[2];
+} FBBezierElement;
+
+
+CGPoint CGPath_FBPointAtIndex (
+    CGPathRef path,
+    NSUInteger index
+);
+
+FBBezierElement CGPath_FBElementAtIndex (
+    CGPathRef path,
+    NSUInteger index
+);
+
+
+CGPathRef CGPath_FBCreateSubpathWithRange (
+    CGPathRef path,
+    NSRange range
+);
+
+
+void CGPath_FBAppendPath (
+    CGMutablePathRef path,
+    CGPathRef otherPath
+);
+
+void CGPath_FBAppendElement (
+    CGMutablePathRef path,
+    FBBezierElement element
+);
+
+
+NSUInteger CGPath_MWElementCount (
+    CGPathRef path
+);
+
+NSString * CGPath_MWLog (
+    CGPathRef path
+);
+
+
+#endif

File Shared/CGPath_Utilities.m

+//
+//  CGPath_Utilities.m
+//  VectorBoolean
+//
+//  Created by Martin Winter on 02.08.12.
+//  Based on work by Andrew Finnell of Fortunate Bear, LLC.
+//  Copyright (c) 2012 Martin Winter. All rights reserved.
+//
+
+#include "CGPath_Utilities.h"
+
+
+void CGPath_MWElementAtIndex_ApplierFunction ( void *info, const CGPathElement *element );
+void CGPath_MWElementCount_ApplierFunction ( void *info, const CGPathElement *element );
+void CGPath_MWLog_ApplierFunction ( void *info, const CGPathElement *element );
+
+
+CGPoint CGPath_FBPointAtIndex ( CGPathRef path, NSUInteger index )
+{
+    return CGPath_FBElementAtIndex(path, index).point;
+}
+
+
+FBBezierElement CGPath_FBElementAtIndex ( CGPathRef path, NSUInteger index )
+{
+    FBBezierElement element = {};
+    
+    // Use CGPathApply in lieu of -[NSBezierPath elementAtIndex:associatedPoints:].
+    NSMutableDictionary *dictionary = [@{ @"currentIndex" : @( 0 ), @"targetIndex" : @( index ) } mutableCopy];
+    CGPathApply(path, (__bridge void *)(dictionary), CGPath_MWElementAtIndex_ApplierFunction);
+    
+    NSNumber *typeNumber = dictionary[@"type"];
+    if (!typeNumber)
+    {
+        return element;
+    }
+    
+    element.kind = (CGPathElementType)[typeNumber intValue];
+    CGPoint *points = (CGPoint *)[(NSData *)dictionary[@"points"] bytes];
+
+    switch (element.kind) {
+        case kCGPathElementMoveToPoint:
+        case kCGPathElementAddLineToPoint:
+        case kCGPathElementCloseSubpath:
+            element.point = points[0];
+            break;
+            
+        case kCGPathElementAddCurveToPoint:
+            element.controlPoints[0] = points[0];
+            element.controlPoints[1] = points[1];
+            element.point = points[2];
+            break;
+        
+        case kCGPathElementAddQuadCurveToPoint:
+        default:
+            NSLog(@"%s  Encountered unhandled element type (quad curve)", __PRETTY_FUNCTION__);
+            break;
+    }
+    return element;
+}
+
+
+CGPathRef CGPath_FBCreateSubpathWithRange ( CGPathRef path, NSRange range )
+{
+    CGMutablePathRef subpath = CGPathCreateMutable();
+    for (NSUInteger i = 0; i < range.length; i++) {
+        FBBezierElement element = CGPath_FBElementAtIndex(path, range.location + i);
+        if ( i == 0 )
+            CGPathMoveToPoint(subpath, NULL, element.point.x, element.point.y);
+        else
+            CGPath_FBAppendElement(subpath, element);
+    }
+    return subpath;
+}
+
+
+void CGPath_FBAppendPath ( CGMutablePathRef path, CGPathRef otherPath )
+{
+    NSUInteger elementCount = CGPath_MWElementCount(path);
+    FBBezierElement previousElement = (elementCount > 0
+                                       ? CGPath_FBElementAtIndex(path, elementCount - 1)
+                                       : (FBBezierElement){});
+    
+    NSUInteger otherElementCount = CGPath_MWElementCount(otherPath);
+    for (NSUInteger i = 0; i < otherElementCount; i++) {
+        FBBezierElement element = CGPath_FBElementAtIndex(otherPath, i);
+        
+        // If the first element is a move to where we left off, skip it
+        if ( element.kind == kCGPathElementMoveToPoint ) {
+            if ( CGPointEqualToPoint(element.point, previousElement.point) )
+                continue;
+            else
+                element.kind = kCGPathElementAddLineToPoint; // change it to a line to
+        }
+        
+        CGPath_FBAppendElement(path, element);
+        previousElement = element;
+    }
+}
+
+
+void CGPath_FBAppendElement ( CGMutablePathRef path, FBBezierElement element )
+{
+    switch (element.kind) {
+        case kCGPathElementMoveToPoint:
+            CGPathMoveToPoint(path, NULL, element.point.x, element.point.y);
+            break;
+        case kCGPathElementAddLineToPoint:
+            CGPathAddLineToPoint(path, NULL, element.point.x, element.point.y);
+            break;
+        case kCGPathElementAddCurveToPoint:
+            CGPathAddCurveToPoint(path, NULL,
+                                  element.controlPoints[0].x, element.controlPoints[0].y,
+                                  element.controlPoints[1].x, element.controlPoints[1].y,
+                                  element.point.x, element.point.y);
+            break;
+        case kCGPathElementCloseSubpath:
+            CGPathCloseSubpath(path);
+            break;
+        case kCGPathElementAddQuadCurveToPoint:
+        default:
+            break;
+    }
+}
+
+
+#pragma mark -
+
+
+void CGPath_MWElementAtIndex_ApplierFunction ( void *info, const CGPathElement *element )
+{
+    NSMutableDictionary *dictionary = (__bridge NSMutableDictionary *)info;
+    NSUInteger currentIndex = [dictionary[@"currentIndex"] unsignedIntegerValue];
+    NSUInteger targetIndex = [dictionary[@"targetIndex"] unsignedIntegerValue];
+
+    if (targetIndex != currentIndex)
+    {
+        dictionary[@"currentIndex"] = @( currentIndex + 1 );
+        return;
+    }
+    
+    CGPathElementType type = element->type;
+    CGPoint *points = element->points;
+    
+    // Determine number of points for element type.
+    NSUInteger pointCount = 0;
+    switch (type)
+    {
+        case kCGPathElementMoveToPoint:
+            pointCount = 1;
+            break;
+            
+        case kCGPathElementAddLineToPoint:
+            pointCount = 1;
+            break;
+            
+        case kCGPathElementAddQuadCurveToPoint:
+            pointCount = 2;
+            break;
+            
+        case kCGPathElementAddCurveToPoint:
+            pointCount = 3;
+            break;
+            
+        case kCGPathElementCloseSubpath:
+            pointCount = 1; // This _must_ be one (as opposed to when logging)!
+            break;
+            
+        default:
+            return;
+    }
+    
+    // Serialize points.
+    NSData *pointsData = [NSData dataWithBytes:points
+                                        length:(pointCount * sizeof(CGPoint))];
+    
+    // Package type and points.
+    dictionary[@"type"]   = @( type );
+    dictionary[@"points"] = pointsData;
+
+    dictionary[@"currentIndex"] = @( currentIndex + 1 );
+}
+
+
+NSUInteger CGPath_MWElementCount ( CGPathRef path )
+{
+    NSUInteger count = 0;
+    CGPathApply(path, &count, CGPath_MWElementCount_ApplierFunction);
+    return count;
+}
+
+
+void CGPath_MWElementCount_ApplierFunction ( void *info, const CGPathElement *element )
+{
+    NSUInteger *count = (NSUInteger *)info;
+    (*count)++;
+}
+
+
+NSString * CGPath_MWLog ( CGPathRef path )
+{
+    NSMutableString *string = [NSMutableString stringWithFormat:@"CGPath <%#lx>", (NSUInteger)path];
+    
+    CGRect bounds = CGPathGetPathBoundingBox(path);
+    [string appendFormat:@"\n  Bounds: {{%f, %f}, {%f, %f}}",
+     bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height];
+    
+    CGRect controlBounds = CGPathGetBoundingBox(path);
+    [string appendFormat:@"\n  Control point bounds: {{%f, %f}, {%f, %f}}",
+     controlBounds.origin.x, controlBounds.origin.y, controlBounds.size.width, controlBounds.size.height];
+    
+    CGPathApply(path, (__bridge void *)(string), CGPath_MWLog_ApplierFunction);
+    return string;
+}
+
+
+void CGPath_MWLog_ApplierFunction ( void *info, const CGPathElement *element )
+{
+    NSMutableString *string = (__bridge NSMutableString *)info;
+    CGPathElementType type = element->type;
+    CGPoint *points = element->points;
+    
+    NSUInteger pointCount = 0;
+    NSString *command = @"";
+    switch (type)
+    {
+        case kCGPathElementMoveToPoint:
+            pointCount = 1;
+            command = @"moveto";
+            break;
+            
+        case kCGPathElementAddLineToPoint:
+            pointCount = 1;
+            command = @"lineto";
+            break;
+            
+        case kCGPathElementAddQuadCurveToPoint:
+            pointCount = 2;
+            command = @"quadcurveto";
+            break;
+            
+        case kCGPathElementAddCurveToPoint:
+            pointCount = 3;
+            command = @"curveto";
+            break;
+            
+        case kCGPathElementCloseSubpath:
+            pointCount = 0;
+            command = @"closepath";
+            break;
+            
+        default:
+            return;
+    }
+    
+    [string appendString:@"\n    "];
+    
+    for (NSUInteger pointIndex = 0; pointIndex < pointCount; pointIndex++)
+    {
+        [string appendFormat:@"%f %f ", points[pointIndex].x, points[pointIndex].y];
+    }
+    
+    [string appendString:command];
+}

File Shared/Canvas.h

+//
+//  Canvas.h
+//  VectorBoolean
+//
+//  Created by Andrew Finnell on 5/31/11.
+//  Copyright 2011 Fortunate Bear, LLC. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+
+@interface Canvas : NSObject {
+    NSMutableArray *_paths;
+    BOOL _showPoints;
+    BOOL _showIntersections;
+}
+
+- (void) addPath:(CGPathRef)path withColor:(CGColorRef)color;
+- (void) clear;
+
+- (NSUInteger) numberOfPaths;
+- (CGPathRef) pathAtIndex:(NSUInteger)index;
+
+- (void) drawRect:(CGRect)dirtyRect;
+
+@property BOOL showPoints;
+@property BOOL showIntersections;
+
+@end

File Shared/Canvas.m

+//
+//  Canvas.m
+//  VectorBoolean
+//
+//  Created by Andrew Finnell on 5/31/11.
+//  Copyright 2011 Fortunate Bear, LLC. All rights reserved.
+//
+
+#import "Canvas.h"
+#import "CGPath_Utilities.h"
+#import "FBBezierCurve.h"
+#import "FBBezierIntersection.h"
+
+static CGRect BoxFrame(CGPoint point)
+{
+    return CGRectMake(floorf(point.x - 2) - 0.5, floorf(point.y - 2) - 0.5, 5, 5);
+}
+
+@implementation Canvas
+
+@synthesize showPoints=_showPoints;
+@synthesize showIntersections=_showIntersections;
+
+- (id)init
+{
+    self = [super init];
+    if (self) {
+        _paths = [[NSMutableArray alloc] initWithCapacity:3];
+        _showPoints = YES;
+        _showIntersections = YES;
+    }
+    
+    return self;
+}
+
+- (void) addPath:(CGPathRef)path withColor:(CGColorRef)color
+{
+    NSDictionary *object = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(path), @"path", color, @"color", nil];
+    [_paths addObject:object];
+}
+
+- (NSUInteger) numberOfPaths
+{
+    return [_paths count];
+}
+
+- (CGPathRef) pathAtIndex:(NSUInteger)index
+{
+    NSDictionary *object = [_paths objectAtIndex:index];
+    return (__bridge CGPathRef)([object objectForKey:@"path"]);
+}
+
+- (void) clear
+{
+    [_paths removeAllObjects];
+}
+
+- (void) drawRect:(CGRect)dirtyRect
+{
+    // TODO: get current context depending on platform!
+    
+    //CGRect cgRect = NSRectToCGRect(dirtyRect);
+    CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
+    
+    // Draw on a background
+    [[NSColor whiteColor] set];
+    CGContextFillRect(context, dirtyRect);
+    
+    // Draw on the objects
+    for (NSDictionary *object in _paths) {
+        CGColorRef color = (__bridge CGColorRef)([object objectForKey:@"color"]);
+        CGPathRef path = (__bridge CGPathRef)([object objectForKey:@"path"]);
+        CGContextSetFillColorWithColor(context, color);
+        CGContextAddPath(context, path);
+        CGContextFillPath(context);
+    }    
+    
+    // Draw on the end and control points
+    if ( _showPoints ) {
+        CGContextSetLineWidth(context, 1.0);
+        CGContextSetLineCap(context, kCGLineCapButt);
+        CGContextSetLineJoin(context, kCGLineJoinMiter);
+
+        for (NSDictionary *object in _paths) {
+            CGPathRef path = (__bridge CGPathRef)([object objectForKey:@"path"]);
+            
+            NSUInteger elementCount = CGPath_MWElementCount(path);
+            for (NSInteger i = 0; i < elementCount; i++) {
+                FBBezierElement element = CGPath_FBElementAtIndex(path, i);
+                [[NSColor orangeColor] set];
+                CGContextStrokeRect(context, BoxFrame(element.point));
+                if ( element.kind == kCGPathElementAddCurveToPoint ) {
+                    [[NSColor blackColor] set];
+                    CGContextStrokeRect(context, BoxFrame(element.controlPoints[0]));
+                    CGContextStrokeRect(context, BoxFrame(element.controlPoints[1]));
+                }
+            }
+        }
+    }
+
+    // If we have exactly two objects, show where they intersect
+    if ( _showIntersections && [_paths count] == 2 ) {
+        CGPathRef path1 = (__bridge CGPathRef)([[_paths objectAtIndex:0] objectForKey:@"path"]);
+        CGPathRef path2 = (__bridge CGPathRef)([[_paths objectAtIndex:1] objectForKey:@"path"]);
+        NSArray *curves1 = [FBBezierCurve bezierCurvesFromBezierPath:path1];
+        NSArray *curves2 = [FBBezierCurve bezierCurvesFromBezierPath:path2];
+        
+        for (FBBezierCurve *curve1 in curves1) {
+            for (FBBezierCurve *curve2 in curves2) {
+                NSArray *intersections = [curve1 intersectionsWithBezierCurve:curve2];
+                for (FBBezierIntersection *intersection in intersections) {
+                    if ( intersection.isTangent )
+                        [[NSColor purpleColor] set];
+                    else
+                        [[NSColor greenColor] set];
+                    CGPathRef circle = CGPathCreateWithEllipseInRect(BoxFrame(intersection.location), NULL);
+                    CGContextAddPath(context, circle);
+                    CGContextStrokePath(context);
+                    CGPathRelease(circle);
+                }
+            }
+        }
+    }
+}
+
+@end

File Shared/FBBezierContour.h

+//
+//  FBBezierContour.h
+//  VectorBoolean
+//
+//  Created by Andrew Finnell on 6/15/11.
+//  Copyright 2011 Fortunate Bear, LLC. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@class FBBezierCurve;
+@class FBEdgeCrossing;
+
+typedef enum FBContourInside {
+    FBContourInsideFilled,
+    FBContourInsideHole
+} FBContourInside;
+
+// FBBezierContour represents a closed path of bezier curves (aka edges). Contours
+//  can be filled or represent a hole in another contour.
+@interface FBBezierContour : NSObject<NSCopying> {
+    NSMutableArray *_edges;
+    CGRect _bounds;
+    FBContourInside _inside;
+}
+
+// Methods for building up the contour. The reverse forms flip points in the bezier curve before adding them
+//  to the contour. The crossing to crossing methods assuming the crossings are on the same edge. One of
+//  crossings can be nil, but not both.
+- (void) addCurve:(FBBezierCurve *)curve;
+- (void) addCurveFrom:(FBEdgeCrossing *)startCrossing to:(FBEdgeCrossing *)endCrossing;
+- (void) addReverseCurve:(FBBezierCurve *)curve;
+- (void) addReverseCurveFrom:(FBEdgeCrossing *)startCrossing to:(FBEdgeCrossing *)endCrossing;
+
+- (BOOL) containsPoint:(CGPoint)point;
+- (void) markCrossingsAsEntryOrExitWithContour:(FBBezierContour *)otherContour markInside:(BOOL)markInside;
+
+- (void) round;
+
+@property (readonly) NSArray *edges;
+@property (readonly) CGRect bounds;
+@property (readonly) CGPoint firstPoint;
+@property FBContourInside inside;
+@property (readonly) NSArray *intersectingContours;
+
+@end

File Shared/FBBezierContour.m

+//
+//  FBBezierContour.m
+//  VectorBoolean
+//
+//  Created by Andrew Finnell on 6/15/11.
+//  Copyright 2011 Fortunate Bear, LLC. All rights reserved.
+//
+
+#import "FBBezierContour.h"
+#import "FBBezierCurve.h"
+#import "FBContourEdge.h"
+#import "FBEdgeCrossing.h"
+#import "FBDebug.h"
+#import "FBBezierIntersection.h"
+
+@implementation FBBezierContour
+
+@synthesize edges=_edges;
+@synthesize inside=_inside;
+
+- (id)init
+{
+    self = [super init];
+    if ( self != nil ) {
+        _edges = [[NSMutableArray alloc] initWithCapacity:12];
+    }
+    
+    return self;
+}
+
+- (void) addCurve:(FBBezierCurve *)curve
+{
+    // Add the curve by wrapping it in an edge
+    if ( curve == nil )
+        return;
+    FBContourEdge *edge = [[FBContourEdge alloc] initWithBezierCurve:curve contour:self];
+    edge.index = [_edges count];
+    [_edges addObject:edge];
+    _bounds = CGRectZero; // force the bounds to be recalculated
+}
+
+- (void) addCurveFrom:(FBEdgeCrossing *)startCrossing to:(FBEdgeCrossing *)endCrossing
+{
+    // First construct the curve that we're going to add, by seeing which crossing
+    //  is nil. If the crossing isn't given go to the end of the edge on that side.
+    FBBezierCurve *curve = nil;
+    if ( startCrossing == nil && endCrossing != nil ) {
+        // From start to endCrossing
+        curve = endCrossing.leftCurve;
+    } else if ( startCrossing != nil && endCrossing == nil ) {
+        // From startCrossing to end
+        curve = startCrossing.rightCurve;
+    } else if ( startCrossing != nil && endCrossing != nil ) {
+        // From startCrossing to endCrossing
+        curve = [startCrossing.curve subcurveWithRange:FBRangeMake(startCrossing.parameter, endCrossing.parameter)];
+    }
+    [self addCurve:curve];
+}
+
+- (void) addReverseCurve:(FBBezierCurve *)curve
+{
+    // Just reverse the points on the curve. Need to do this to ensure the end point from one edge, matches the start
+    //  on the next edge.
+    if ( curve == nil )
+        return;
+    FBBezierCurve *reverseCurve = [FBBezierCurve bezierCurveWithEndPoint1:curve.endPoint2 controlPoint1:curve.controlPoint2 controlPoint2:curve.controlPoint1 endPoint2:curve.endPoint1];
+    [self addCurve:reverseCurve];
+}
+
+- (void) addReverseCurveFrom:(FBEdgeCrossing *)startCrossing to:(FBEdgeCrossing *)endCrossing
+{
+    // First construct the curve that we're going to add, by seeing which crossing
+    //  is nil. If the crossing isn't given go to the end of the edge on that side.
+    FBBezierCurve *curve = nil;
+    if ( startCrossing == nil && endCrossing != nil ) {
+        // From start to endCrossing
+        curve = endCrossing.leftCurve;
+    } else if ( startCrossing != nil && endCrossing == nil ) {
+        // From startCrossing to end
+        curve = startCrossing.rightCurve;
+    } else if ( startCrossing != nil && endCrossing != nil ) {
+        // From startCrossing to endCrossing
+        curve = [startCrossing.curve subcurveWithRange:FBRangeMake(startCrossing.parameter, endCrossing.parameter)];
+    }
+    [self addReverseCurve:curve];
+}
+
+- (CGRect) bounds
+{
+    // Cache the bounds to save time
+    if ( !CGRectEqualToRect(_bounds, CGRectZero) )
+        return _bounds;
+    
+    // If no edges, no bounds
+    if ( [_edges count] == 0 )
+        return CGRectZero;
+    
+    // Start with the first point to set the topLeft and bottom right points
+    FBContourEdge *firstEdge = [_edges objectAtIndex:0];
+    CGPoint topLeft = firstEdge.curve.endPoint1;
+    CGPoint bottomRight = topLeft;
+    
+    // All the edges are connected, so only add on based on the second end point
+    for (FBContourEdge *edge in _edges) {
+        CGPoint point = edge.curve.endPoint2;
+        if ( point.x < topLeft.x )
+            topLeft.x = point.x;
+        if ( point.x > bottomRight.x )
+            bottomRight.x = point.x;
+        if ( point.y < topLeft.y )
+            topLeft.y = point.y;
+        if ( point.y > bottomRight.y )
+            bottomRight.y = point.y;
+    }
+    
+    _bounds = CGRectMake(topLeft.x, topLeft.y, bottomRight.x - topLeft.x, bottomRight.y - topLeft.y);
+
+    return _bounds;
+}
+
+- (CGPoint) firstPoint
+{
+    if ( [_edges count] == 0 )
+        return CGPointZero;
+
+    FBContourEdge *edge = [_edges objectAtIndex:0];
+    return edge.curve.endPoint1;
+}
+
+- (BOOL) containsPoint:(CGPoint)testPoint
+{
+    // Create a test line from our point to somewhere outside our graph. We'll see how many times the test
+    //  line intersects edges of the graph. Based on the even/odd rule, if it's an odd number, we're inside
+    //  the graph, if even, outside.
+    CGPoint lineEndPoint = CGPointMake(testPoint.x > CGRectGetMinX(self.bounds) ? CGRectGetMinX(self.bounds) - 10 : CGRectGetMaxX(self.bounds) + 10, testPoint.y); /* just move us outside the bounds of the graph */
+    FBBezierCurve *testCurve = [FBBezierCurve bezierCurveWithLineStartPoint:testPoint endPoint:lineEndPoint];
+    
+    NSUInteger intersectCount = 0;
+    for (FBContourEdge *edge in _edges) {
+        NSArray *intersections = [testCurve intersectionsWithBezierCurve:edge.curve];
+        for (FBBezierIntersection *intersection in intersections) {
+            if ( intersection.isTangent )
+                continue;
+            intersectCount++;
+        }
+    }
+    
+    return (intersectCount % 2) == 1;
+}
+
+- (void) markCrossingsAsEntryOrExitWithContour:(FBBezierContour *)otherContour markInside:(BOOL)markInside
+{
+    // Go through and mark all the crossings with the given contour as "entry" or "exit". This 
+    //  determines what part of ths contour is outputted. 
+    
+    // When marking we need to start at a point that is clearly either inside or outside
+    //  the other graph, otherwise we could mark the crossings exactly opposite of what
+    //  they're supposed to be.
+    FBContourEdge *startEdge = [self.edges objectAtIndex:0];
+    FBContourEdge *stopValue = startEdge;
+    while ( startEdge.isStartShared ) {
+        startEdge = startEdge.next;
+        if ( startEdge == stopValue )
+            break; // for safety. But if we're here, we could be hosed
+    }
+    
+    // Calculate the first entry value. We need to determine if the edge we're starting
+    //  on is inside or outside the otherContour.
+    BOOL contains = [otherContour containsPoint:startEdge.curve.endPoint1];
+    BOOL isEntry = markInside ? !contains : contains;
+    
+    // Walk all the edges in this contour and mark the crossings
+    FBContourEdge *edge = startEdge;
+    do {
+        // Mark all the crossings on this edge
+        for (FBEdgeCrossing *crossing in edge.crossings) {
+            // skip over other contours
+            if ( crossing.counterpart.edge.contour != otherContour )
+                continue;
+            crossing.entry = isEntry;
+            isEntry = !isEntry; // toggle.
+        }
+        
+        edge = edge.next;
+    } while ( edge != startEdge );
+}
+
+- (void) round
+{
+    // Go through and round all the end points to integral value
+    for (FBContourEdge *edge in _edges)
+        [edge round];
+}
+
+- (NSArray *) intersectingContours
+{
+    // Go and find all the unique contours that intersect this specific contour
+    NSMutableArray *contours = [NSMutableArray arrayWithCapacity:3];
+    for (FBContourEdge *edge in _edges) {
+        NSArray *intersectingEdges = edge.intersectingEdges;
+        for (FBContourEdge *intersectingEdge in intersectingEdges) {
+            if ( ![contours containsObject:intersectingEdge.contour] )
+                [contours addObject:intersectingEdge.contour];
+        }
+    }
+    return contours;
+}
+
+- (id)copyWithZone:(NSZone *)zone
+{
+    FBBezierContour *copy = [[FBBezierContour allocWithZone:zone] init];
+    for (FBContourEdge *edge in _edges)
+        [copy addCurve:edge.curve];
+    return copy;
+}
+
+- (NSString *) description
+{
+    return [NSString stringWithFormat:@"<%@: bounds = (%f, %f)(%f, %f) edges = %@>", 
+            NSStringFromClass([self class]),
+            CGRectGetMinX(self.bounds), CGRectGetMinY(self.bounds),
+            CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds),
+            FBArrayDescription(_edges)
+            ];
+}
+@end

File Shared/FBBezierCurve.h

+//
+//  FBBezierCurve.h
+//  VectorBoolean
+//
+//  Created by Andrew Finnell on 6/6/11.
+//  Copyright 2011 Fortunate Bear, LLC. All rights reserved.
+//
+
+// FBRange is a range of parameter (t)
+typedef struct FBRange {
+    CGFloat minimum;
+    CGFloat maximum;
+} FBRange;
+
+extern FBRange FBRangeMake(CGFloat minimum, CGFloat maximum);
+extern BOOL FBRangeHasConverged(FBRange range, NSUInteger places);
+extern CGFloat FBRangeGetSize(FBRange range);
+extern CGFloat FBRangeAverage(FBRange range);
+extern CGFloat FBRangeScaleNormalizedValue(FBRange range, CGFloat value);
+
+// FBBezierCurve is one cubic 2D bezier curve. It represents one segment of a bezier path, and is where
+//  the intersection calculation happens
+@interface FBBezierCurve : NSObject {
+    CGPoint _endPoint1;
+    CGPoint _controlPoint1;
+    CGPoint _controlPoint2;
+    CGPoint _endPoint2;
+}
+
++ (NSArray *) bezierCurvesFromBezierPath:(CGPathRef)path;
+
++ (id) bezierCurveWithLineStartPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint;
++ (id) bezierCurveWithEndPoint1:(CGPoint)endPoint1 controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2 endPoint2:(CGPoint)endPoint2;
+
+- (id) initWithEndPoint1:(CGPoint)endPoint1 controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2 endPoint2:(CGPoint)endPoint2;
+- (id) initWithLineStartPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint;
+
+@property CGPoint endPoint1;
+@property CGPoint controlPoint1;
+@property CGPoint controlPoint2;
+@property CGPoint endPoint2;
+
+- (NSArray *) intersectionsWithBezierCurve:(FBBezierCurve *)curve;
+
+- (CGPoint) pointAtParameter:(CGFloat)parameter leftBezierCurve:(FBBezierCurve **)leftBezierCurve rightBezierCurve:(FBBezierCurve **)rightBezierCurve;
+- (FBBezierCurve *) subcurveWithRange:(FBRange)range;
+
+- (void) round;
+
+@end

File Shared/FBBezierCurve.m

+//
+//  FBBezierCurve.m
+//  VectorBoolean
+//
+//  Created by Andrew Finnell on 6/6/11.
+//  Copyright 2011 Fortunate Bear, LLC. All rights reserved.
+//
+
+#import "FBBezierCurve.h"
+#import "CGPath_Utilities.h"
+#import "Geometry.h"
+#import "FBBezierIntersection.h"
+#import "MWPointValue.h"
+
+//////////////////////////////////////////////////////////////////////////////////
+// Normalized lines
+//
+typedef struct FBNormalizedLine {
+    CGFloat a; // * x +
+    CGFloat b; // * y +
+    CGFloat c; // constant
+} FBNormalizedLine;
+
+// Create a normalized line such that computing the distance from it is quick.
+//  See:    http://softsurfer.com/Archive/algorithm_0102/algorithm_0102.htm#Distance%20to%20an%20Infinite%20Line
+//          http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/geometry/basic.html
+//
+static FBNormalizedLine FBNormalizedLineMake(CGPoint point1, CGPoint point2)
+{
+    FBNormalizedLine line = { point1.y - point2.y, point2.x - point1.x, point1.x * point2.y - point2.x * point1.y };
+    CGFloat distance = sqrtf(line.b * line.b + line.a * line.a);
+    line.a /= distance;
+    line.b /= distance;
+    line.c /= distance;
+    return line;
+}
+
+static CGFloat FBNormalizedLineDistanceFromPoint(FBNormalizedLine line, CGPoint point)
+{
+    return line.a * point.x + line.b * point.y + line.c;
+}
+
+//////////////////////////////////////////////////////////////////////////////////
+// Parameter ranges
+//
+FBRange FBRangeMake(CGFloat minimum, CGFloat maximum)
+{
+    FBRange range = { minimum, maximum };
+    return range;
+}
+
+BOOL FBRangeHasConverged(FBRange range, NSUInteger places)
+{
+    CGFloat factor = powf(10.0, places);
+    NSInteger minimum = (NSInteger)(range.minimum * factor);
+    NSInteger maxiumum = (NSInteger)(range.maximum * factor);
+    return minimum == maxiumum;
+}
+
+CGFloat FBRangeGetSize(FBRange range)
+{
+    return range.maximum - range.minimum;
+}
+
+CGFloat FBRangeAverage(FBRange range)
+{
+    return (range.minimum + range.maximum) / 2.0;
+}
+
+CGFloat FBRangeScaleNormalizedValue(FBRange range, CGFloat value)
+{
+    return (range.maximum - range.minimum) * value + range.minimum;
+}
+
+//////////////////////////////////////////////////////////////////////////////////
+// Helper functions
+//
+
+// The three points are a counter-clockwise turn if the return value is greater than 0,
+//  clockwise if less than 0, or colinear if 0.
+static CGFloat CounterClockwiseTurn(CGPoint point1, CGPoint point2, CGPoint point3)
+{
+    // We're calculating the signed area of the triangle formed by the three points. Well,
+    //  almost the area of the triangle -- we'd need to divide by 2. But since we only
+    //  care about the direction (i.e. the sign) dividing by 2 is an unnecessary step.
+    // See http://mathworld.wolfram.com/TriangleArea.html for the signed area of a triangle.
+    return (point2.x - point1.x) * (point3.y - point1.y) - (point2.y - point1.y) * (point3.x - point1.x);
+}
+
+// Calculate if and where the given line intersects the horizontal line at y.
+static BOOL LineIntersectsHorizontalLine(CGPoint startPoint, CGPoint endPoint, CGFloat y, CGPoint *intersectPoint)
+{
+    // Do a quick test to see if y even falls on the startPoint,endPoint line
+    if ( y < MIN(startPoint.y, endPoint.y) || y > MAX(startPoint.y, endPoint.y) )
+        return NO;
+    
+    // There's an intersection here somewhere
+    if ( startPoint.x == endPoint.x )
+        *intersectPoint = CGPointMake(startPoint.x, y);
+    else {
+        CGFloat slope = (endPoint.y - startPoint.y) / (endPoint.x - startPoint.x);
+        *intersectPoint = CGPointMake((y - startPoint.y) / slope + startPoint.x, y);
+    }
+    
+    return YES;
+}
+
+static CGPoint BezierWithPoints(NSUInteger degree, CGPoint *bezierPoints, CGFloat parameter, CGPoint *leftCurve, CGPoint *rightCurve)
+{
+    // Calculate a point on the bezier curve passed in, specifically the point at parameter.
+    //  We're using De Casteljau's algorithm, which not only calculates the point at parameter
+    //  in a numerically stable way, it also computes the two resulting bezier curves that
+    //  would be formed if the original were split at the parameter specified.
+    //
+    // See: http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/de-casteljau.html
+    //  for an explaination of De Casteljau's algorithm.
+    
+    // bezierPoints, leftCurve, rightCurve will have a length of degree + 1. 
+    // degree is the order of the bezier path, which will be cubic (3) most of the time.
+    
+    // With this algorithm we start out with the points in the bezier path. 
+    CGPoint points[4] = {}; // we assume we'll never get more than a cubic bezier
+    for (NSUInteger i = 0; i <= degree; i++)
+        points[i] = bezierPoints[i];
+    
+    // If the caller is asking for the resulting bezier curves, start filling those in
+    if ( leftCurve != nil )
+        leftCurve[0] = points[0];
+    if ( rightCurve != nil )
+        rightCurve[degree] = points[degree];
+        
+    for (NSUInteger k = 1; k <= degree; k++) {
+        for (NSUInteger i = 0; i <= (degree - k); i++) {
+            points[i].x = (1.0 - parameter) * points[i].x + parameter * points[i + 1].x;
+            points[i].y = (1.0 - parameter) * points[i].y + parameter * points[i + 1].y;            
+        }
+        
+        if ( leftCurve != nil )
+            leftCurve[k] = points[0];
+        if ( rightCurve != nil )
+            rightCurve[degree - k] = points[degree - k];
+    }
+    
+    // The point in the curve at parameter ends up in points[0]
+    return points[0];
+}
+
+//////////////////////////////////////////////////////////////////////////////////
+// FBBezierCurve
+//
+// The main purpose of this class is to compute the intersections of two bezier
+//  curves. It does this using the bezier clipping algorithm, described in
+//  "Curve intersection using Bezier clipping" by TW Sederberg and T Nishita.
+//  http://cagd.cs.byu.edu/~tom/papers/bezclip.pdf
+//
+@interface FBBezierCurve ()
+
+- (FBNormalizedLine) regularFatLineBounds:(FBRange *)range;
+- (FBNormalizedLine) perpendicularFatLineBounds:(FBRange *)range;
+
+- (FBRange) clipWithFatLine:(FBNormalizedLine)fatLine bounds:(FBRange)bounds;
+- (NSArray *) splitCurveAtParameter:(CGFloat)t;
+- (NSArray *) convexHull;
+- (FBBezierCurve *) bezierClipWithBezierCurve:(FBBezierCurve *)curve original:(FBBezierCurve *)originalCurve rangeOfOriginal:(FBRange *)originalRange intersects:(BOOL *)intersects;
+- (NSArray *) intersectionsWithBezierCurve:(FBBezierCurve *)curve usRange:(FBRange *)usRange themRange:(FBRange *)themRange originalUs:(FBBezierCurve *)originalUs originalThem:(FBBezierCurve *)originalThem;
+- (CGFloat) refineParameter:(CGFloat)parameter forPoint:(CGPoint)point;
+
+@property (readonly, getter = isPoint) BOOL point;
+
+@end
+
+@implementation FBBezierCurve
+
+@synthesize endPoint1=_endPoint1;
+@synthesize controlPoint1=_controlPoint1;
+@synthesize controlPoint2=_controlPoint2;
+@synthesize endPoint2=_endPoint2;
+
++ (NSArray *) bezierCurvesFromBezierPath:(CGPathRef)path
+{
+    // Helper method to easily convert a bezier path into an array of FBBezierCurves. Very straight forward,
+    //  only lines are a special case.
+    
+    CGPoint lastPoint = CGPointZero;
+    NSUInteger elementCount = CGPath_MWElementCount(path);
+    NSMutableArray *bezierCurves = [NSMutableArray arrayWithCapacity:elementCount];
+    
+    for (NSUInteger i = 0; i < elementCount; i++) {
+        FBBezierElement element = CGPath_FBElementAtIndex(path, i);
+        
+        switch (element.kind) {
+            case kCGPathElementMoveToPoint:
+                lastPoint = element.point;
+                break;
+                
+            case kCGPathElementAddLineToPoint: {
+                // Convert lines to bezier curves as well. Just set control point to be in the line formed
+                //  by the end points
+                [bezierCurves addObject:[FBBezierCurve bezierCurveWithLineStartPoint:lastPoint endPoint:element.point]];
+                
+                lastPoint = element.point;
+                break;
+            }
+                
+            case kCGPathElementAddCurveToPoint:
+                [bezierCurves addObject:[FBBezierCurve bezierCurveWithEndPoint1:lastPoint controlPoint1:element.controlPoints[0] controlPoint2:element.controlPoints[1] endPoint2:element.point]];
+                
+                lastPoint = element.point;
+                break;
+                
+            case kCGPathElementCloseSubpath:
+                lastPoint = CGPointZero;
+                break;
+            
+            case kCGPathElementAddQuadCurveToPoint:
+            default:
+                break;
+        }
+    }
+    
+    return bezierCurves;
+}
+
++ (id) bezierCurveWithLineStartPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint
+{
+    return [[FBBezierCurve alloc] initWithLineStartPoint:startPoint endPoint:endPoint];
+}
+
++ (id) bezierCurveWithEndPoint1:(CGPoint)endPoint1 controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2 endPoint2:(CGPoint)endPoint2
+{
+    return [[FBBezierCurve alloc] initWithEndPoint1:endPoint1 controlPoint1:controlPoint1 controlPoint2:controlPoint2 endPoint2:endPoint2];
+}
+
+- (id) initWithEndPoint1:(CGPoint)endPoint1 controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2 endPoint2:(CGPoint)endPoint2
+{
+    self = [super init];
+    
+    if ( self != nil ) {
+        _endPoint1 = endPoint1;
+        _controlPoint1 = controlPoint1;
+        _controlPoint2 = controlPoint2;
+        _endPoint2 = endPoint2;
+    }
+    
+    return self;
+}
+
+- (id) initWithLineStartPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint
+{
+    self = [super init];
+    
+    if ( self != nil ) {
+        // Convert the line into a bezier curve to keep our intersection algorithm general (i.e. only
+        //  has to deal with curves, not lines). As long as the control points are colinear with the
+        //  end points, it'll be a line. But for consistency sake, we put the control points inside
+        //  the end points, 1/3 of the total distance away from their respective end point.
+        CGFloat distance = FBDistanceBetweenPoints(startPoint, endPoint);
+        CGPoint leftTangent = FBNormalizePoint(FBSubtractPoint(endPoint, startPoint));
+        _controlPoint1 = FBAddPoint(startPoint, FBUnitScalePoint(leftTangent, distance / 3.0));
+        _controlPoint2 = FBAddPoint(startPoint, FBUnitScalePoint(leftTangent, 2.0 * distance / 3.0));
+        _endPoint1 = startPoint;
+        _endPoint2 = endPoint;
+    }
+    
+    return self;
+}
+
+- (NSArray *) intersectionsWithBezierCurve:(FBBezierCurve *)curve
+{
+    FBRange usRange = FBRangeMake(0, 1);
+    FBRange themRange = FBRangeMake(0, 1);
+    return [self intersectionsWithBezierCurve:curve usRange:&usRange themRange:&themRange originalUs:self originalThem:curve];
+}
+
+- (NSArray *) intersectionsWithBezierCurve:(FBBezierCurve *)curve usRange:(FBRange *)usRange themRange:(FBRange *)themRange originalUs:(FBBezierCurve *)originalUs originalThem:(FBBezierCurve *)originalThem
+{
+    // This is the main work loop. At a high level this method sits in a loop and removes sections (ranges) of the two bezier curves that it knows
+    //  don't intersect (how it knows that is covered in the appropriate method). The idea is to whittle the curves down to the point where they
+    //  do intersect. When the range where they intersect converges (i.e. matches to 6 decimal places) or there are more than 500 attempts, the loop
+    //  stops. A special case is when we're not able to remove at least 20% of the curves on a given interation. In that case we assume there are likely
+    //  multiple intersections, so we divide one of curves in half, and recurse on the two halves.
+    
+    static const NSUInteger places = 6; // How many decimals place to calculate the solution out to
+    static const NSUInteger maxIterations = 500; // how many iterations to allow before we just give up
+    static const CGFloat minimumChangeNeeded = 0.20; // how much to clip off for a given iteration minimum before we subdivide the curve
+
+    FBBezierCurve *us = self; // us is self, but clipped down to where the intersection is
+    FBBezierCurve *them = curve; // them is the other curve we're intersecting with, but clipped down to where the intersection is
+    FBBezierCurve *previousUs = us;
+    FBBezierCurve *previousThem = them;
+    
+    // Don't check for convergence until we actually see if we intersect or not. i.e. Make sure we go through at least once, otherwise the results
+    //  don't mean anything. Be sure to stop as soon as either range converges, otherwise calculations for the other range goes funky because one
+    //  curve is essentially a point.
+    NSUInteger iterations = 0;
+    while ( iterations < maxIterations && ((iterations == 0) || (!FBRangeHasConverged(*usRange, places) && !FBRangeHasConverged(*themRange, places))) ) {
+        // Remember what the current range is so we can calculate how much it changed later
+        FBRange previousUsRange = *usRange;
+        FBRange previousThemRange = *themRange;
+        
+        // Remove the range from ourselves that doesn't intersect with them. If the other curve is already a point, use the previous iteration's
+        //  copy of them so calculations still work.
+        BOOL intersects = NO;
+        us = [us bezierClipWithBezierCurve:them.isPoint ? previousThem : them original:originalUs rangeOfOriginal:usRange intersects:&intersects];
+        if ( !intersects )
+            return [NSArray array]; // If they don't intersect at all stop now
+        // Remove the range of them that doesn't intersect with us
+        them = [them bezierClipWithBezierCurve:us.isPoint ? previousUs : us original:originalThem rangeOfOriginal:themRange intersects:&intersects];
+        if ( !intersects )
+            return [NSArray array];  // If they don't intersect at all stop now
+        
+        // If either curve has been reduced to a point, stop now even if the range hasn't properly converged. Once curves become points, the math
+        //  falls apart.
+        if ( us.isPoint || them.isPoint )
+            break;
+        
+        // See if either of curves ranges is reduced by less than 20%.
+        CGFloat percentChangeInUs = (FBRangeGetSize(previousUsRange) - FBRangeGetSize(*usRange)) / FBRangeGetSize(previousUsRange);
+        CGFloat percentChangeInThem = (FBRangeGetSize(previousThemRange) - FBRangeGetSize(*themRange)) / FBRangeGetSize(previousThemRange);
+        if ( percentChangeInUs < minimumChangeNeeded && percentChangeInThem < minimumChangeNeeded ) {
+            // We're not converging fast enough, likely because there are multiple intersections here. So
+            //  divide and conquer. Divide the longer curve in half, and recurse
+            if ( FBRangeGetSize(*usRange) > FBRangeGetSize(*themRange) ) {
+                // Since our remaining range is longer, split the remains of us in half at the midway point
+                FBRange usRange1 = FBRangeMake(usRange->minimum, (usRange->minimum + usRange->maximum) / 2.0);
+                FBBezierCurve *us1 = [originalUs subcurveWithRange:usRange1];
+                FBRange themRangeCopy1 = *themRange; // make a local copy because it'll get modified when we recurse
+
+                FBRange usRange2 = FBRangeMake((usRange->minimum + usRange->maximum) / 2.0, usRange->maximum);
+                FBBezierCurve *us2 = [originalUs subcurveWithRange:usRange2];
+                FBRange themRangeCopy2 = *themRange; // make a local copy because it'll get modified when we recurse
+                
+                // Compute the intersections between the two halves of us and them
+                NSArray *intersections1 = [us1 intersectionsWithBezierCurve:them usRange:&usRange1 themRange:&themRangeCopy1 originalUs:originalUs originalThem:originalThem];
+                NSArray *intersections2 = [us2 intersectionsWithBezierCurve:them usRange:&usRange2 themRange:&themRangeCopy2 originalUs:originalUs originalThem:originalThem];
+                
+                return [intersections1 arrayByAddingObjectsFromArray:intersections2];
+            } else {
+                // Since their remaining range is longer, split the remains of them in half at the midway point
+                FBRange themRange1 = FBRangeMake(themRange->minimum, (themRange->minimum + themRange->maximum) / 2.0);
+                FBBezierCurve *them1 = [originalThem subcurveWithRange:themRange1];
+                FBRange usRangeCopy1 = *usRange;  // make a local copy because it'll get modified when we recurse
+
+                FBRange themRange2 = FBRangeMake((themRange->minimum + themRange->maximum) / 2.0, themRange->maximum);
+                FBBezierCurve *them2 = [originalThem subcurveWithRange:themRange2];
+                FBRange usRangeCopy2 = *usRange;  // make a local copy because it'll get modified when we recurse
+
+                // Compute the intersections between the two halves of them and us
+                NSArray *intersections1 = [us intersectionsWithBezierCurve:them1 usRange:&usRangeCopy1 themRange:&themRange1 originalUs:originalUs originalThem:originalThem];
+                NSArray *intersections2 = [us intersectionsWithBezierCurve:them2 usRange:&usRangeCopy2 themRange:&themRange2 originalUs:originalUs originalThem:originalThem];
+                
+                return [intersections1 arrayByAddingObjectsFromArray:intersections2];
+            }
+        }
+        
+        iterations++;
+        previousUs = us;
+        previousThem = them;
+    }
+    
+    // It's possible that one of the curves has converged, but the other hasn't. Since the math becomes wonky once a curve becomes a point,
+    //  the loop stops as soon as either curve converges. However for our purposes we need _both_ curves to converge; that is we need
+    //  the parameter for each curve where they intersect. Fortunately, since one curve did converge we know the 2D point where they converge,
+    //  plus we have a reasonable approximation for the parameter for the curve that didn't. That means we can use Newton's method to refine
+    //  the parameter of the curve that did't converge.
+    if ( FBRangeHasConverged(*usRange, places) && !FBRangeHasConverged(*themRange, places) ) {
+        // Refine the them range since it didn't converge
+        CGPoint intersectionPoint = [originalUs pointAtParameter:usRange->minimum leftBezierCurve:nil rightBezierCurve:nil];
+        CGFloat refinedParameter = FBRangeAverage(*themRange); // Although the range didn't converge, it should be a reasonable approximation which is all Newton needs
+        for (NSUInteger i = 0; i < 3; i++)
+            refinedParameter = [originalThem refineParameter:refinedParameter forPoint:intersectionPoint];
+        themRange->minimum = refinedParameter;
+        themRange->maximum = refinedParameter;
+    } else if ( !FBRangeHasConverged(*usRange, places) && FBRangeHasConverged(*themRange, places) ) {
+        // Refine the us range since it didn't converge
+        CGPoint intersectionPoint = [originalThem pointAtParameter:themRange->minimum leftBezierCurve:nil rightBezierCurve:nil];
+        CGFloat refinedParameter = FBRangeAverage(*usRange); // Although the range didn't converge, it should be a reasonable approximation which is all Newton needs
+        for (NSUInteger i = 0; i < 3; i++)
+            refinedParameter = [originalUs refineParameter:refinedParameter forPoint:intersectionPoint];
+        usRange->minimum = refinedParameter;
+        usRange->maximum = refinedParameter;        
+    }
+    
+    // Return the final intersection, which we represent by the original curves and the parameters where they intersect. The parameter values are useful
+    //  later in the boolean operations, plus it allows us to do lazy calculations.
+    return [NSArray arrayWithObject:[FBBezierIntersection intersectionWithCurve1:originalUs parameter1:usRange->minimum curve2:originalThem parameter2:themRange->minimum]];
+}
+
+- (FBBezierCurve *) bezierClipWithBezierCurve:(FBBezierCurve *)curve original:(FBBezierCurve *)originalCurve rangeOfOriginal:(FBRange *)originalRange intersects:(BOOL *)intersects
+{
+    // This method does the clipping of self. It removes the parts of self that we can determine don't intersect
+    //  with curve. It'll return the clipped version of self, update originalRange which corresponds to the range
+    //  on the original curve that the return value represents. Finally, it'll set the intersects out parameter
+    //  to yes or no depending on if the curves intersect or not.
+    
+    // Clipping works as follows:
+    //  Draw a line through the two endpoints of the other curve, which we'll call the fat line. Measure the 
+    //  signed distance between the control points on the other curve and the fat line. The distance from the line
+    //  will give us the fat line bounds. Any part of our curve that lies further away from the fat line than the 
+    //  fat line bounds we know can't intersect with the other curve, and thus can be removed.
+    
+    // We actually use two different fat lines. The first one uses the end points of the other curve, and the second
+    //  one is perpendicular to the first. Most of the time, the first fat line will clip off more, but sometimes the
+    //  second proves to be a better fat line in that it clips off more. We use both in order to converge more quickly.
+    
+    // Compute the regular fat line using the end points, then compute the range that could still possibly intersect
+    //  with the other curve
+    FBRange fatLineBounds = {};
+    FBNormalizedLine fatLine = [curve regularFatLineBounds:&fatLineBounds];
+    FBRange regularClippedRange = [self clipWithFatLine:fatLine bounds:fatLineBounds];
+    // A range of [1, 0] is a special sentinel value meaning "they don't intersect". If they don't, bail early to save time
+    if ( regularClippedRange.minimum == 1.0 && regularClippedRange.maximum == 0.0 ) {
+        *intersects = NO;
+        return self;
+    }
+    
+    // Just in case the regular fat line isn't good enough, try the perpendicular one
+    FBRange perpendicularLineBounds = {};
+    FBNormalizedLine perpendicularLine = [curve perpendicularFatLineBounds:&perpendicularLineBounds];
+    FBRange perpendicularClippedRange = [self clipWithFatLine:perpendicularLine bounds:perpendicularLineBounds];
+    if ( perpendicularClippedRange.minimum == 1.0 && perpendicularClippedRange.maximum == 0.0 ) {
+        *intersects = NO;
+        return self;
+    }
+    
+    // Combine to form Voltron. Take the intersection of the regular fat line range and the perpendicular one.
+    FBRange clippedRange = FBRangeMake(MAX(regularClippedRange.minimum, perpendicularClippedRange.minimum), MIN(regularClippedRange.maximum, perpendicularClippedRange.maximum));    
+    
+    // Right now the clipped range is relative to ourself, not the original curve. So map the newly clipped range onto the original range
+    FBRange newRange = FBRangeMake(FBRangeScaleNormalizedValue(*originalRange, clippedRange.minimum), FBRangeScaleNormalizedValue(*originalRange, clippedRange.maximum));    
+    *originalRange = newRange;
+    *intersects = YES;
+    
+    // Actually divide the curve, but be sure to use the original curve. This helps with errors building up.
+    return [originalCurve subcurveWithRange:*originalRange];
+}
+
+- (FBNormalizedLine) regularFatLineBounds:(FBRange *)range
+{
+    // Create the fat line based on the end points
+    FBNormalizedLine line = FBNormalizedLineMake(_endPoint1, _endPoint2);
+    
+    // Compute the bounds of the fat line. The fat line bounds should entirely encompass the
+    //  bezier curve. Since we know the convex hull entirely compasses the curve, just take
+    //  all four points that define this cubic bezier curve. Compute the signed distances of
+    //  each of the end and control points from the fat line, and that will give us the bounds.
+    
+    // In this case, we know that the end points are on the line, thus their distances will be 0.
+    //  So we can skip computing those and just use 0.
+    CGFloat controlPoint1Distance = FBNormalizedLineDistanceFromPoint(line, _controlPoint1);
+    CGFloat controlPoint2Distance = FBNormalizedLineDistanceFromPoint(line, _controlPoint2);    
+    CGFloat min = MIN(controlPoint1Distance, MIN(controlPoint2Distance, 0.0));
+    CGFloat max = MAX(controlPoint1Distance, MAX(controlPoint2Distance, 0.0));
+        
+    *range = FBRangeMake(min, max);
+    
+    return line;
+}
+
+- (FBNormalizedLine) perpendicularFatLineBounds:(FBRange *)range
+{
+    // Create a fat line that's perpendicular to the line created by the two end points.
+    CGPoint normal = FBLineNormal(_endPoint1, _endPoint2);
+    CGPoint startPoint = FBLineMidpoint(_endPoint1, _endPoint2);
+    CGPoint endPoint = FBAddPoint(startPoint, normal);
+    FBNormalizedLine line = FBNormalizedLineMake(startPoint, endPoint);
+    
+    // Compute the bounds of the fat line. The fat line bounds should entirely encompass the
+    //  bezier curve. Since we know the convex hull entirely compasses the curve, just take
+    //  all four points that define this cubic bezier curve. Compute the signed distances of
+    //  each of the end and control points from the fat line, and that will give us the bounds.
+    CGFloat controlPoint1Distance = FBNormalizedLineDistanceFromPoint(line, _controlPoint1);
+    CGFloat controlPoint2Distance = FBNormalizedLineDistanceFromPoint(line, _controlPoint2);
+    CGFloat point1Distance = FBNormalizedLineDistanceFromPoint(line, _endPoint1);
+    CGFloat point2Distance = FBNormalizedLineDistanceFromPoint(line, _endPoint2);
+
+    CGFloat min = MIN(controlPoint1Distance, MIN(controlPoint2Distance, MIN(point1Distance, point2Distance)));
+    CGFloat max = MAX(controlPoint1Distance, MAX(controlPoint2Distance, MAX(point1Distance, point2Distance)));
+    
+    *range = FBRangeMake(min, max);
+    
+    return line;
+}
+
+- (FBRange) clipWithFatLine:(FBNormalizedLine)fatLine bounds:(FBRange)bounds
+{
+    // This method computes the range of self that could possibly intersect with the fat line passed in (and thus with the curve enclosed by the fat line).
+    //  To do that, we first compute the signed distance of all our points (end and control) from the fat line, and map those onto a bezier curve at
+    //  evenly spaced intervals from [0..1]. The parts of the distance bezier that fall inside of the fat line bounds, correspond to the parts of ourself
+    //  that could potentially intersect with the other curve. Ideally, we'd calculate where the distance bezier intersected the horizontal lines representing
+    //  the fat line bounds. However, computing those intersections is hard and costly. So instead we'll compute the convex hull, and intersect those lines
+    //  with the fat line bounds. The intersection with the lowest x coordinate will be the minimum, and the intersection with the highest x coordinate will
+    //  be the maximum.
+    
+    // The convex hull (for cubic beziers) is the four points that define the curve. A useful property of the convex hull is that the entire curve lies
+    //  inside of it.
+    
+    // First calculate bezier curve points distance from the fat line that's clipping us
+    FBBezierCurve *distanceBezier = [FBBezierCurve bezierCurveWithEndPoint1:CGPointMake(0, FBNormalizedLineDistanceFromPoint(fatLine, _endPoint1)) controlPoint1:CGPointMake(1.0/3.0, FBNormalizedLineDistanceFromPoint(fatLine, _controlPoint1)) controlPoint2:CGPointMake(2.0/3.0, FBNormalizedLineDistanceFromPoint(fatLine, _controlPoint2)) endPoint2:CGPointMake(1.0, FBNormalizedLineDistanceFromPoint(fatLine, _endPoint2))];
+    NSArray *convexHull = [distanceBezier convexHull]; // the convex hull can be anywhere from 2 to 4 points.
+    
+    // Find intersections of convex hull with the fat line bounds
+    FBRange range = FBRangeMake(1.0, 0.0);
+    for (NSUInteger i = 0; i < [convexHull count]; i++) {
+        // Pull out the current line on the convex hull
+        NSUInteger indexOfNext = i < ([convexHull count] - 1) ? i + 1 : 0;
+        CGPoint startPoint = [(MWPointValue *)[convexHull objectAtIndex:i] point];
+        CGPoint endPoint = [(MWPointValue *)[convexHull objectAtIndex:indexOfNext] point];
+        CGPoint intersectionPoint = CGPointZero;
+        
+        // See if the segment of the convex hull intersects with the minimum fat line bounds
+        if ( LineIntersectsHorizontalLine(startPoint, endPoint, bounds.minimum, &intersectionPoint) ) {
+            if ( intersectionPoint.x < range.minimum )
+                range.minimum = intersectionPoint.x;
+            if ( intersectionPoint.x > range.maximum )
+                range.maximum = intersectionPoint.x;
+        }
+        // This is a very special case that I really wish I could get rid of. If perfectly horizontal and perfectly vertical lines intersect at both of their end points,
+        //  the convex hull becomes a horizontal line on top of the minimum and maximum lines, which makes the line intersection calculation wonky. At this point, we
+        //  throw our hands up and just say "we don't know where in here they intersect". If we don't do this, we end up saying they don't intersect at all, which could
+        //  be wrong.
+        if ( [convexHull count] == 2 && FBAreValuesClose(startPoint.y, endPoint.y) && FBAreValuesClose(startPoint.y, bounds.minimum) && !FBAreValuesClose(bounds.minimum, bounds.maximum) )
+            range = FBRangeMake(0, 1);
+        
+        // See if this segment of the convex hull intersects with the maximum fat line bounds
+        if ( LineIntersectsHorizontalLine(startPoint, endPoint, bounds.maximum, &intersectionPoint) ) {
+            if ( intersectionPoint.x < range.minimum )
+                range.minimum = intersectionPoint.x;
+            if ( intersectionPoint.x > range.maximum )
+                range.maximum = intersectionPoint.x;
+        }
+        // See the corresponding comment for the minimum intersection
+        if ( [convexHull count] == 2 && FBAreValuesClose(startPoint.y, endPoint.y) && FBAreValuesClose(startPoint.y, bounds.maximum) && !FBAreValuesClose(bounds.minimum, bounds.maximum) )
+            range = FBRangeMake(0, 1);
+        
+        // We want to be able to refine t even if the convex hull lies completely inside the bounds. This
+        //  also allows us to be able to use range of [1..0] as a sentinel value meaning the convex hull
+        //  lies entirely outside of bounds, and the curves don't intersect.
+        if ( startPoint.y < bounds.maximum && startPoint.y > bounds.minimum ) {
+            if ( startPoint.x < range.minimum )
+                range.minimum = startPoint.x;
+            if ( startPoint.x > range.maximum )
+                range.maximum = startPoint.x;
+        }
+    }
+    return range;
+}
+
+- (FBBezierCurve *) subcurveWithRange:(FBRange)range
+{
+    // Return a bezier curve representing the parameter range specified. We do this by splitting
+    //  twice: once on the minimum, the splitting the result of that on the maximum.
+    NSArray *curves1 = [self splitCurveAtParameter:range.minimum];
+    FBBezierCurve *upperCurve = [curves1 objectAtIndex:1];
+    if ( range.minimum == 1.0 )
+        return upperCurve; // avoid the divide by zero below
+    // We need to adjust the maximum parameter to fit on the new curve before we split again
+    CGFloat adjustedMaximum = (range.maximum - range.minimum) / (1.0 - range.minimum);
+    NSArray *curves2 = [upperCurve splitCurveAtParameter:adjustedMaximum];
+    return [curves2 objectAtIndex:0];
+}
+
+- (CGPoint) pointAtParameter:(CGFloat)parameter leftBezierCurve:(FBBezierCurve **)leftBezierCurve rightBezierCurve:(FBBezierCurve **)rightBezierCurve
+{    
+    // This method is a simple wrapper around the BezierWithPoints() helper function. It computes the 2D point at the given parameter,
+    //  and (optionally) the resulting curves that splitting at the parameter would create.
+    
+    CGPoint points[4] = { _endPoint1, _controlPoint1, _controlPoint2, _endPoint2 };
+    CGPoint leftCurve[4] = {};
+    CGPoint rightCurve[4] = {};
+
+    CGPoint point = BezierWithPoints(3, points, parameter, leftCurve, rightCurve);
+    
+    if ( leftBezierCurve != nil )
+        *leftBezierCurve = [FBBezierCurve bezierCurveWithEndPoint1:leftCurve[0] controlPoint1:leftCurve[1] controlPoint2:leftCurve[2] endPoint2:leftCurve[3]];
+    if ( rightBezierCurve != nil )
+        *rightBezierCurve = [FBBezierCurve bezierCurveWithEndPoint1:rightCurve[0] controlPoint1:rightCurve[1] controlPoint2:rightCurve[2] endPoint2:rightCurve[3]];
+    return point;
+}
+
+- (CGFloat) refineParameter:(CGFloat)parameter forPoint:(CGPoint)point
+{
+    // Use Newton's Method to refine our parameter. In general, that formula is:
+    //
+    //  parameter = parameter - f(parameter) / f'(parameter)
+    //
+    // In our case:
+    //
+    //  f(parameter) = (Q(parameter) - point) * Q'(parameter) = 0
+    //
+    // Where Q'(parameter) is tangent to the curve at Q(parameter) and orthogonal to [Q(parameter) - P]
+    //
+    // Taking the derivative gives us:
+    //
+    //  f'(parameter) = (Q(parameter) - point) * Q''(parameter) + Q'(parameter) * Q'(parameter)
+    //
+    
+    CGPoint bezierPoints[4] = {_endPoint1, _controlPoint1, _controlPoint2, _endPoint2};
+    
+    // Compute Q(parameter)
+    CGPoint qAtParameter = BezierWithPoints(3, bezierPoints, parameter, nil, nil);
+    
+    // Compute Q'(parameter)
+    CGPoint qPrimePoints[3] = {};
+    for (NSUInteger i = 0; i < 3; i++) {
+        qPrimePoints[i].x = (bezierPoints[i + 1].x - bezierPoints[i].x) * 3.0;
+        qPrimePoints[i].y = (bezierPoints[i + 1].y - bezierPoints[i].y) * 3.0;
+    }
+    CGPoint qPrimeAtParameter = BezierWithPoints(2, qPrimePoints, parameter, nil, nil);
+    
+    // Compute Q''(parameter)
+    CGPoint qPrimePrimePoints[2] = {};
+    for (NSUInteger i = 0; i < 2; i++) {
+        qPrimePrimePoints[i].x = (qPrimePoints[i + 1].x - qPrimePoints[i].x) * 2.0;
+        qPrimePrimePoints[i].y = (qPrimePoints[i + 1].y - qPrimePoints[i].y) * 2.0;        
+    }
+    CGPoint qPrimePrimeAtParameter = BezierWithPoints(1, qPrimePrimePoints, parameter, nil, nil);
+    
+    // Compute f(parameter) and f'(parameter)
+    CGPoint qMinusPoint = FBSubtractPoint(qAtParameter, point);
+    CGFloat fAtParameter = FBDotMultiplyPoint(qMinusPoint, qPrimeAtParameter);
+    CGFloat fPrimeAtParameter = FBDotMultiplyPoint(qMinusPoint, qPrimePrimeAtParameter) + FBDotMultiplyPoint(qPrimeAtParameter, qPrimeAtParameter);
+    
+    // Newton's method!
+    return parameter - (fAtParameter / fPrimeAtParameter);
+}
+
+- (NSArray *) splitCurveAtParameter:(CGFloat)parameter
+{
+    // Convience method that returns the result of splitting at the given parameter
+    FBBezierCurve *leftCurve = nil;
+    FBBezierCurve *rightCurve = nil;
+    [self pointAtParameter:parameter leftBezierCurve:&leftCurve rightBezierCurve:&rightCurve];
+    return [NSArray arrayWithObjects:leftCurve, rightCurve, nil];
+}
+
+- (NSArray *) convexHull
+{
+    // Compute the convex hull for this bezier curve. The convex hull is made up of the end and control points.
+    //  The hard part is determine the order they go in, and if any are inside or colinear with the convex hull.
+    
+    // We're using the Graham Scan algorithm to determine the convex hull. It finds the points that form the outside
+    //  bounds of the curve.
+    //
+    // See also: http://en.wikipedia.org/wiki/Graham_scan
+    //  and     http://softsurfer.com/Archive/algorithm_0109/algorithm_0109.htm
+    
+    // Start with all the end and control points in any order.
+    NSMutableArray *points = [NSMutableArray arrayWithObjects:
+                              [[MWPointValue alloc] initWithPoint:_endPoint1],
+                              [[MWPointValue alloc] initWithPoint:_controlPoint1],
+                              [[MWPointValue alloc] initWithPoint:_controlPoint2],
+                              [[MWPointValue alloc] initWithPoint:_endPoint2],
+                              nil];
+
+    // First, find the point that is on the bottom right, and move it to the first position in our array.
+    NSUInteger lowestIndex = 0;
+    CGPoint lowestValue = [(MWPointValue *)[points objectAtIndex:0] point];
+    for (NSUInteger i = 0; i < [points count]; i++) {
+        CGPoint point = [(MWPointValue *)[points objectAtIndex:i] point];
+        if ( point.y < lowestValue.y || (point.y == lowestValue.y && point.x > lowestValue.x) ) {
+            lowestIndex = i;
+            lowestValue = point;
+        }
+    }
+    [points exchangeObjectAtIndex:0 withObjectAtIndex:lowestIndex];
+
+    // Sort the points by the angle they form with the horizontal line on the lowest point, ascending.
+    //  Remember any redundant (i.e. colinear) points so we can remove them later.
+    NSMutableArray *pointsToDelete = [NSMutableArray arrayWithCapacity:4];
+    [points sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
+        CGPoint point1 = [(MWPointValue *)obj1 point];
+        CGPoint point2 = [(MWPointValue *)obj2 point];
+        
+        // Special case: Our pivot value (lowestValue, at index 0) should stay at the lowest
+        if ( CGPointEqualToPoint(lowestValue, point1) )
+            return NSOrderedAscending;
+        if ( CGPointEqualToPoint(lowestValue, point2) )
+            return NSOrderedDescending;
+        
+        // We don't care about the actual angle value, just their values relative to each other.
+        //  Compute the signed area of the triangle the points form, as a quick estimate of
+        //  where the points lie relative to each other.
+        CGFloat area = CounterClockwiseTurn(lowestValue, point1, point2);
+        if ( FBAreValuesClose(area, 0.0) ) {
+            // Ugh, the points are colinear. That means at least one of the points is going
+            //  to be redundant, specifically the one closest to the pivot point. Remember
+            //  the redundant point so it can be deleted later.
+            CGFloat distance1 = FBDistanceBetweenPoints(point1, lowestValue);
+            CGFloat distance2 = FBDistanceBetweenPoints(point2, lowestValue);
+            // The three points are colinear, so base it on distance instead
+            if ( distance1 < distance2 ) {
+                [pointsToDelete addObject:obj1];
+                return NSOrderedAscending;
+            } else if ( distance1 > distance2 ) {
+                [pointsToDelete addObject:obj2];
+                return NSOrderedDescending;
+            }
+            [pointsToDelete addObject:obj1];            
+            return NSOrderedSame;
+        } else if ( area < 0.0 )
+            // point2 is to the right of the line formed by lowestValue, point1
+            return NSOrderedDescending;
+        //else if ( area > 0.0 )
+        // point2 is left of the line formed by lowestValue, point1
+        return NSOrderedAscending;                    
+    }];
+    // Remove any colinear points
+    for (NSValue *value in pointsToDelete)
+        [points removeObject:value];
+    
+    // We want to create an array of points where we only ever turn left.
+    // Push the first two points onto the top of the results stack. Consider the point at i
+    //  in the points array. If it causes the results array to turn left (counter clock wise),
+    //  then add it to the results, then move on to consider the next point in points array.
+    //  If it causes the results array to turn right, then remove the top of the results stack
+    //  and try the point at i again.
+    NSMutableArray *results = [NSMutableArray arrayWithCapacity:4];
+    [results addObject:[points objectAtIndex:0]];
+    [results addObject:[points objectAtIndex:1]];
+    NSUInteger i = 2;
+    while ( i < [points count] ) {
+        CGPoint lastPoint = [(MWPointValue *)[results lastObject] point];
+        CGPoint nextToLastPoint = [(MWPointValue *)[results objectAtIndex:[results count] - 2] point];
+        CGPoint pointUnderConsideration = [(MWPointValue *)[points objectAtIndex:i] point];
+        CGFloat area = CounterClockwiseTurn(nextToLastPoint, lastPoint, pointUnderConsideration);
+        if ( area > 0.0 ) {
+            // Turning left is good, so keep going
+            [results addObject:[points objectAtIndex:i]];
+            i++;
+        } else 
+            // Turning right is bad, so remove the top point
+            [results removeLastObject];
+    }
+    
+    return results;    
+}
+
+- (BOOL) isPoint
+{
+    // If the two end points are close together, then we're a point. Ignore the control
+    //  points.
+    return FBArePointsClose(_endPoint1, _endPoint2);
+}
+
+- (void) round
+{
+    // Round the end and control points to the nearest integral value.
+    _endPoint1 = FBRoundPoint(_endPoint1);
+    _controlPoint1 = FBRoundPoint(_controlPoint1);
+    _controlPoint2 = FBRoundPoint(_controlPoint2);
+    _endPoint2 = FBRoundPoint(_endPoint2);
+}
+
+- (NSString *) description
+{
+    return [NSString stringWithFormat:@"<%@ (%f, %f)-[%f, %f] curve to [%f, %f]-(%f, %f)>", 
+            NSStringFromClass([self class]), 
+            _endPoint1.x, _endPoint1.y, _controlPoint1.x, _controlPoint1.y,
+            _controlPoint2.x, _controlPoint2.y, _endPoint2.x, _endPoint2.y];
+}
+
+@end

File Shared/FBBezierGraph.h

+//
+//  FBBezierGraph.h
+//  VectorBoolean
+//
+//  Created by Andrew Finnell on 6/15/11.
+//  Copyright 2011 Fortunate Bear, LLC. All rights reserved.
+//
+
+// FBBezierGraph is more or less an exploded version of an NSBezierPath, and
+//  the two can be converted between easily. FBBezierGraph allows boolean
+//  operations to be performed by allowing the curves to be annotated with
+//  extra information such as where intersections happen.
+@interface FBBezierGraph : NSObject {
+    NSMutableArray *_contours;
+    CGRect _bounds;
+}
+
++ (id) bezierGraph;
++ (id) bezierGraphWithBezierPath:(CGPathRef)path;
+- (id) initWithBezierPath:(CGPathRef)path;
+
+- (FBBezierGraph *) unionWithBezierGraph:(FBBezierGraph *)graph;
+- (FBBezierGraph *) intersectWithBezierGraph:(FBBezierGraph *)graph;
+- (FBBezierGraph *) differenceWithBezierGraph:(FBBezierGraph *)graph;
+- (FBBezierGraph *) xorWithBezierGraph:(FBBezierGraph *)graph;
+
+- (CGPathRef) newBezierPath;
+
+@end

File Shared/FBBezierGraph.m

+//
+//  FBBezierGraph.m
+//  VectorBoolean
+//
+//  Created by Andrew Finnell on 6/15/11.
+//  Copyright 2011 Fortunate Bear, LLC. All rights reserved.
+//
+
+#import "FBBezierGraph.h"
+#import "FBBezierCurve.h"
+#import "CGPath_Utilities.h"
+#import "FBBezierContour.h"
+#import "FBContourEdge.h"
+#import "FBBezierIntersection.h"
+#import "FBEdgeCrossing.h"
+#import "FBDebug.h"
+#import "Geometry.h"
+#import <math.h>
+
+//////////////////////////////////////////////////////////////////////////
+// Helper methods for angles
+//
+static const CGFloat FB2PI = 2.0 * M_PI;
+
+// Normalize the angle between 0 and 2pi
+static CGFloat NormalizeAngle(CGFloat value)
+{
+    while ( value < 0.0 )
+        value += FB2PI;
+    while ( value >= FB2PI )
+        value -= FB2PI;
+    return value;
+}
+
+// Compute the polar angle from the cartesian point
+static CGFloat PolarAngle(CGPoint point)
+{
+    CGFloat value = 0.0;
+    if ( point.x > 0.0 )
+        value = atanf(point.y / point.x);
+    else if ( point.x < 0.0 ) {
+        if ( point.y >= 0.0 )
+            value = atanf(point.y / point.x) + M_PI;
+        else
+            value = atanf(point.y / point.x) - M_PI;
+    } else {
+        if ( point.y > 0.0 )
+            value =  M_PI_2;
+        else if ( point.y < 0.0 )
+            value =  -M_PI_2;
+        else
+            value = 0.0;
+    }
+    return NormalizeAngle(value);
+}
+
+//////////////////////////////////////////////////////////////////////////
+// Angle Range structure provides a simple way to store angle ranges
+//  and determine if a specific angle falls within. 
+//
+typedef struct FBAngleRange {
+    CGFloat minimum;
+    CGFloat maximum;
+} FBAngleRange;
+
+static FBAngleRange FBAngleRangeMake(CGFloat minimum, CGFloat maximum)
+{
+    FBAngleRange range = { minimum, maximum };
+    return range;
+}
+
+static BOOL FBAngleRangeContainsAngle(FBAngleRange range, CGFloat angle)
+{
+    if ( range.minimum <= range.maximum )
+        return angle > range.minimum && angle < range.maximum;
+    
+    // The range wraps around 0. See if the angle falls in the first half
+    if ( angle > range.minimum && angle <= FB2PI )
+        return YES;
+    
+    return angle >= 0.0 && angle < range.maximum;
+}
+
+//////////////////////////////////////////////////////////////////////////
+// FBBezierGraph
+//
+// The main point of this class is to perform boolean operations. The algorithm
+//  used here is a modified and expanded version of the algorithm presented
+//  in "Efficient clipping of arbitrary polygons" by Günther Greiner and Kai Hormann.
+//  http://www.inf.usi.ch/hormann/papers/Greiner.1998.ECO.pdf
+//  That algorithm assumes polygons, not curves, and only considers one contour intersecting
+//  one other contour. My algorithm uses bezier curves (not polygons) and handles
+//  multiple contours intersecting other contours.
+//
+
+@interface FBBezierGraph ()
+
+- (void) removeDuplicateCrossings;
+- (BOOL) doesEdge:(FBContourEdge *)edge1 crossEdge:(FBContourEdge *)edge2 atIntersection:(FBBezierIntersection *)intersection;
+- (void) insertCrossingsWithBezierGraph:(FBBezierGraph *)other;
+- (FBEdgeCrossing *) firstUnprocessedCrossing;
+- (void) markCrossingsAsEntryOrExitWithBezierGraph:(FBBezierGraph *)otherGraph markInside:(BOOL)markInside;
+- (FBBezierGraph *) bezierGraphFromIntersections;
+- (void) removeCrossings;
+
+- (void) addContour:(FBBezierContour *)contour;
+- (void) round;
+- (FBContourInside) contourInsides:(FBBezierContour *)contour;
+
+- (NSArray *) nonintersectingContours;
+- (BOOL) containsContour:(FBBezierContour *)contour;
+- (FBBezierContour *) containerForContour:(FBBezierContour *)testContour;
+- (BOOL) eliminateContainers:(NSMutableArray *)containers thatDontContainContour:(FBBezierContour *)testContour usingRay:(FBBezierCurve *)ray;
+- (BOOL) findBoundsOfContour:(FBBezierContour *)testContour onRay:(FBBezierCurve *)ray minimum:(CGPoint *)testMinimum maximum:(CGPoint *)testMaximum;
+- (void) removeContoursThatDontContain:(NSMutableArray *)crossings;
+- (BOOL) findCrossingsOnContainers:(NSArray *)containers onRay:(FBBezierCurve *)ray beforeMinimum:(CGPoint)testMinimum afterMaximum:(CGPoint)testMaximum crossingsBefore:(NSMutableArray *)crossingsBeforeMinimum crossingsAfter:(NSMutableArray *)crossingsAfterMaximum;
+- (void) removeCrossings:(NSMutableArray *)crossings forContours:(NSArray *)containersToRemove;
+- (void) removeContourCrossings:(NSMutableArray *)crossings1 thatDontAppearIn:(NSMutableArray *)crossings2;
+- (NSArray *) minimumCrossings:(NSArray *)crossings onRay:(FBBezierCurve *)ray;
+- (NSArray *) maximumCrossings:(NSArray *)crossings onRay:(FBBezierCurve *)ray;
+- (NSArray *) contoursFromCrossings:(NSArray *)crossings;
+- (NSUInteger) numberOfTimesContour:(FBBezierContour *)contour appearsInCrossings:(NSArray *)crossings;
+
+@property (readonly) NSArray *contours;
+@property (readonly) CGRect bounds;
+
+@end
+
+@implementation FBBezierGraph
+
+@synthesize contours=_contours;
+
++ (id) bezierGraphWithBezierPath:(CGPathRef)path
+{
+    return [[FBBezierGraph alloc] initWithBezierPath:path];
+}
+
++ (id) bezierGraph
+{
+    return [[FBBezierGraph alloc] init];
+}
+
+- (id) initWithBezierPath:(CGPathRef)path
+{
+    self = [super init];
+    
+    if ( self != nil ) {
+        // A bezier graph is made up of contours, which are closed paths of curves. Anytime we
+        //  see a move to in the NSBezierPath, that's a new contour.
+        CGPoint lastPoint = CGPointZero;
+        _contours = [[NSMutableArray alloc] initWithCapacity:2];
+            
+        FBBezierContour *contour = nil;
+        NSUInteger elementCount = CGPath_MWElementCount(path);
+        for (NSUInteger i = 0; i < elementCount; i++) {
+            FBBezierElement element = CGPath_FBElementAtIndex(path, i);
+            
+            switch (element.kind) {
+                case kCGPathElementMoveToPoint:
+                    // Start a new contour
+                    contour = [[FBBezierContour alloc] init];
+                    [self addContour:contour];
+                    lastPoint = element.point;
+                    break;
+                    
+                case kCGPathElementAddLineToPoint: {
+                    // [MO] skip degenerate line segments
+                    if (!CGPointEqualToPoint(element.point, lastPoint)) {
+                        // Convert lines to bezier curves as well. Just set control point to be in the line formed
+                        //  by the end points
+                        FBBezierCurve *curve = [FBBezierCurve bezierCurveWithLineStartPoint:lastPoint endPoint:element.point];
+                        [contour addCurve:curve];
+                        lastPoint = element.point;
+                    }
+                    break;
+                }
+                    
+                case kCGPathElementAddCurveToPoint:
+                {
+                    FBBezierCurve *curve = [FBBezierCurve bezierCurveWithEndPoint1:lastPoint
+                                                                     controlPoint1:element.controlPoints[0]
+                                                                     controlPoint2:element.controlPoints[1]
+                                                                         endPoint2:element.point];
+                    [contour addCurve:curve];
+                    lastPoint = element.point;
+                    break;
+                }
+                    
+                case kCGPathElementCloseSubpath:
+                    // [MO] attempt to close the bezier contour by
+                    // mapping closepaths to equivalent lineto operations,
+                    // though as with our kCGPathElementAddLineToPoint processing,
+                    // we check so as not to add degenerate line segments which 
+                    // blow up the clipping code.
+                    
+                    if ([[contour edges] count]) {
+                        FBContourEdge *firstEdge = [[contour edges] objectAtIndex:0];
+                        CGPoint        firstPoint = [[firstEdge curve] endPoint1];
+                        
+                        // Skip degenerate line segments
+                        if (!CGPointEqualToPoint(lastPoint, firstPoint)) {
+                            FBBezierCurve *curve = [FBBezierCurve bezierCurveWithLineStartPoint:lastPoint endPoint:firstPoint];
+                            [contour addCurve:curve];
+                        }
+                    }
+                    lastPoint = CGPointZero;
+                    break;
+                
+                case kCGPathElementAddQuadCurveToPoint:
+                default:
+                    NSLog(@"%s  Encountered unhandled element type (quad curve)", __PRETTY_FUNCTION__);
+                    break;
+            }
+        }
+        
+        // Go through and mark each contour if its a hole or filled region
+        for (contour in _contours)
+            contour.inside = [self contourInsides:contour];
+    }
+    
+    return self;
+}
+
+- (id) init
+{
+    self = [super init];
+    
+    if ( self != nil ) {
+        _contours = [[NSMutableArray alloc] initWithCapacity:2];
+    }
+    
+    return self;
+}
+
+////////////////////////////////////////////////////////////////////////
+// Boolean operations
+//
+// The three main boolean operations (union, intersect, difference) follow
+//  much the same algorithm. First, the places where the two graphs cross 
+//  (not just intersect) are marked on the graph with FBEdgeCrossing objects.
+//  Next, we decide which sections of the two graphs should appear in the final
+//  result. (There are only two kind of sections: those inside of the other graph,
+//  and those outside.) We do this by walking all the crossings we created
+//  and marking them as entering a section that should appear in the final result,
+//  or as exiting the final result. We then walk all the crossings again, and
+//  actually output the final result of the graphs that intersect.
+//
+//  The last part of each boolean operation deals with what do with contours
+//  in each graph that don't intersect any other contours.
+//
+// The exclusive or boolean op is implemented in terms of union, intersect,
+//  and difference. More specifically it subtracts the intersection of both
+//  graphs from the union of both graphs.
+//
+
+- (FBBezierGraph *) unionWithBezierGraph:(FBBezierGraph *)graph
+{
+    // First insert FBEdgeCrossings into both graphs where the graphs
+    //  cross.
+    [self insertCrossingsWithBezierGraph:graph];
+    
+    // Handle the parts of the graphs that intersect first. Mark the parts
+    //  of the graphs that are outside the other for the final result.
+    [self markCrossingsAsEntryOrExitWithBezierGraph:graph markInside:NO];
+    [graph markCrossingsAsEntryOrExitWithBezierGraph:self markInside:NO];
+
+    // Walk the crossings and actually compute the final result for the intersecting parts
+    FBBezierGraph *result = [self bezierGraphFromIntersections];
+    [result round]; // decimal values make things messy, so round in case the result is used as input elsewhere, like XOR
+    
+    // Finally, process the contours that don't cross anything else. They're either
+    //  completely contained in another contour, or disjoint.
+    NSArray *ourNonintersectingContours = [self nonintersectingContours];
+    NSArray *theirNonintersectinContours = [graph nonintersectingContours];
+    // Since we're doing a union, assume all the non-crossing contours are in, and remove
+    //  by exception when they're contained by another contour.
+    NSMutableArray *finalNonintersectingContours = [ourNonintersectingContours mutableCopy];
+    [finalNonintersectingContours addObjectsFromArray:theirNonintersectinContours];
+    for (FBBezierContour *ourContour in ourNonintersectingContours) {
+        // If the other graph contains our contour, it's redundant and we can just remove it
+        BOOL clipContainsSubject = [graph containsContour:ourContour];
+        if ( clipContainsSubject )
+            [finalNonintersectingContours removeObject:ourContour];
+    }
+    for (FBBezierContour *theirContour in theirNonintersectinContours) {
+        // If we contain this contour, it's redundant and we can just remove it
+        BOOL subjectContainsClip = [self containsContour:theirContour];
+        if ( subjectContainsClip )
+            [finalNonintersectingContours removeObject:theirContour];
+    }
+
+    // Append the final nonintersecting contours
+    for (FBBezierContour *contour in finalNonintersectingContours)
+        [result addContour:contour];
+    
+    // Clean up crossings so the graphs can be reused, e.g. XOR will reuse graphs.
+    [self removeCrossings];
+    [graph removeCrossings];
+    
+    return result;
+}
+
+- (FBBezierGraph *) intersectWithBezierGraph:(FBBezierGraph *)graph
+{
+    // First insert FBEdgeCrossings into both graphs where the graphs cross.
+    [self insertCrossingsWithBezierGraph:graph];
+    
+    // Handle the parts of the graphs that intersect first. Mark the parts
+    //  of the graphs that are inside the other for the final result.
+    [self markCrossingsAsEntryOrExitWithBezierGraph:graph markInside:YES];
+    [graph markCrossingsAsEntryOrExitWithBezierGraph:self markInside:YES];
+    
+    // Walk the crossings and actually compute the final result for the intersecting parts
+    FBBezierGraph *result = [self bezierGraphFromIntersections];
+    [result round]; // decimal values make things messy, so round in case the result is used as input elsewhere, like XOR
+