Four Lessons from Circle Intersection

Yesterday, a fairly simple discussion of circle intersection came up: how do you subtract one circle from another to form a “cut out” Bezier path. This allows you to add an affordance or decoration to a circle, but add some space between the primary figure and the secondary bubble.

Exploring this question provided some interesting lessons that I thought I’d share in a quick post.

Embrace Core Graphics Types

When working with basic geometry, redirect your mathematics to Core Graphics. Bezier paths, points, rects, and other core geometric features use CGFloat, not Double. You minimize your pain by extending CGFloat as needed to address common geometric concerns such as exponentiation or mathematical constants rather than switching back and forth between Swift.Double and CGFloat:

/// Exponentiation operator
infix operator **: HighPrecedence

extension CGFloat {
    /// Returns base ^^ exp
    /// - parameter base: the base value
    /// - parameter exp: the exponentiation value
    public static func **(base: CGFloat, exp: CGFloat) -> CGFloat {
        return CGFloat(pow(Double(base), Double(exp)))
    }
    
    /// Tau
    public static var tau = CGFloat(Double.pi * 2.0)
}

Construct utility types

It helps to think about the kinds of types that will support your geometric challenge. For this example, I built two new types: a line segment (2 end points) and a circle (a center and a radius). Bringing these under the Core Graphics umbrella unified my representations with the tools they need to express their geometry:

/// 2-point representation of line segment
public struct Segment {
    public let (p1, p2): (CGPoint, CGPoint)
    var dx: CGFloat { return p2.x - p1.x }
    var dy: CGFloat { return p2.y - p1.y }
    public var length: CGFloat { 
        return sqrt(dx ** 2 + dy ** 2) 
    }
}

/// Center/Radius circle representation
public struct Circle {
    public let center: CGPoint
    public let radius: CGFloat
}

Being Core Graphic native enables you to leverage constructors using the appropriate types. For example, my circle can construct a path for drawing to a UIKit-compatible context:

/// Returns a Bezier path sweeping between two angles
public func path(from startAngle: CGFloat = 0, to endAngle: CGFloat = CGFloat.tau, 
    clockwise: Bool = true) -> UIBezierPath {
    return UIBezierPath(arcCenter: center, radius: radius,
                        startAngle: startAngle, endAngle: endAngle,
                        clockwise: clockwise)
}

This path method allows me to express each circle as a path, hiding the details of a UIBezierPath constructor. When I want to throw up a visual reference point, I just construct a circle at that point and fill it:

let p1 = Circle(center: segment.p1, radius: 2.5)
p1.path().fill()

Very handy.

Let the math be the math

When you’re working with standard algorithms, it helps to step back from your standard Swift key paths and use the terms of art you’re translating into code. For example, it can be convenient to break down some basic Swift into algorithm-specific terms:

let (x1, y1, r1) = (self.center.x, self.center.y, self.radius)
let (x2, y2, r2) = (other.center.x, other.center.y, other.radius)

It may not be “Swifty”, but doing so leads to easier inspection when comparing your implementation to the how-to reference in a book or paper.

Draw It

Playgrounds are great when it comes to testing drawing algorithms because you can use real data and get immediate results to see whether your assumptions and expectations are properly met.

In this example, the first two circles intersect, allowing the construction of the “chunked out” major circle and a condensed minor circle. The original circles are outlined and the two red points indicate the overlap points.

The second pair does not intersect, so it’s left simply as two outlines.

If you want to play around with this rough implementation, I left the code for this over at github. Feel free to expand or improve upon my approach, as it was very much an off-hand “here’s more or less how you can do this” exercise.

3 Comments

  • static let pi = ... works as well, even though it’s in an extension 🙂

  • Also, I believe `CGFloat.pi`, or just `.pi` for short, is already built in.

    • Ah nice catch! public static var pi: CGFloat { get } Don’t know why I was sure that wasn’t there…