Booleans
the four Shape set operations (union/intersection/subtracting/symmetricDifference) applied to the same two moving shapes, a turning star and an orbiting disc. Each filled result is real geometry, so it strokes and exports like anything drawn by hand
- Run
swift run --package-path Examples Example-Shapes-Booleans- Source
- Examples/Shapes/Booleans/Sketch.swift
import Ollin
/// Boolean set operations on `Shape`: the same two moving shapes — a slowly
/// turning star and an orbiting disc — combined four ways. The thin gray
/// outlines are the two sources; the filled region is what the operation
/// returns. Each result is real geometry (an ordinary `Shape`), so it strokes,
/// hatches, offsets, and exports like anything drawn by hand:
///
/// ```swift
/// let cut = star.subtracting(disc) // a star with a bite taken out
/// drawShape(cut)
/// ```
@main
final class Booleans_Example: Sketch {
private let labelFont = OutlineFont.system
override func draw() {
background(Color(white: 0.97))
let ops: [(name: String, combine: (Shape, Shape) -> Shape, tint: Color)] = [
("union", { $0.union($1) }, Color(red: 0.18, green: 0.44, blue: 0.84, alpha: 0.85)),
("intersection", { $0.intersection($1) }, Color(red: 0.86, green: 0.39, blue: 0.16, alpha: 0.85)),
("subtracting", { $0.subtracting($1) }, Color(red: 0.17, green: 0.6, blue: 0.4, alpha: 0.85)),
("symmetricDifference", { $0.symmetricDifference($1) }, Color(red: 0.55, green: 0.3, blue: 0.75, alpha: 0.85)),
]
let r = shortSide * 0.15
for (i, op) in ops.enumerated() {
let cell = Vector2((Double(i % 2) + 0.5) * width / 2,
(Double(i / 2) + 0.5) * height / 2 - 40 * scale)
// The sources are *baked* into canvas coordinates (mapPoints, not
// the transform stack): a boolean combines geometry, so both
// shapes have to live in the same space when they meet.
let spin = time * 0.25
let star = Shape(starPoints(outer: r, inner: r * 0.48, points: 7))
.mapPoints { $0.rotated(by: spin) + cell }
let orbit = cell + Vector2(angle: time * 0.7) * r * 0.85
let disc = Shape(circlePoints(radius: r * 0.62)).mapPoints { $0 + orbit }
let result = op.combine(star, disc)
noStroke(); fill(op.tint)
drawShape(result)
noFill(); stroke(Color(white: 0.72)); strokeWeight(1.2 * scale)
drawShape(star); drawShape(disc)
noFill(); stroke(Color(white: 0.1)); strokeWeight(2.2 * scale)
drawShape(result)
// Anchored to the cell's bottom edge, not a radius multiple — the
// bottom row's captions must land inside the canvas.
caption(op.name, at: Vector2(cell.x, (Double(i / 2) + 1) * height / 2 - 30 * scale))
}
}
/// A small label in the system font, drawn unstroked in screen space.
private func caption(_ text: String, at p: Vector2) {
noStroke(); fill(Color(white: 0.45))
textFont(labelFont); textSize(24 * scale); textAlign(.center, .middle)
drawText(text, at: p)
}
// MARK: - Shape outlines (centered on the origin)
private func circlePoints(radius: Double) -> [Vector2] {
let n = max(48, Int(radius.rounded(.up)))
return (0..<n).map { k in
let a = 2 * Double.pi * Double(k) / Double(n)
return Vector2(angle: a) * radius
}
}
private func starPoints(outer: Double, inner: Double, points: Int) -> [Vector2] {
(0..<(2 * points)).map { j in
let r = j % 2 == 0 ? outer : inner
let a = Double.pi * Double(j) / Double(points)
return Vector2(r * sin(a), -r * cos(a))
}
}
}