ShapeFactory
The parametric and profile mesh set (Möbius, Klein, superellipsoid, supershape, extrude, lathe), morphing on time.
- Run
swift run --package-path Examples Example-3D-Geometry-ShapeFactory- Source
- Examples/3D/Geometry/ShapeFactory/Sketch.swift
import Ollin
/// Parametric and profile 3D shapes — the generative side of the primitive catalog.
///
/// These shapes are *factories*, not fixed forms: the **supershape** (Gielis
/// superformula) and **superellipsoid** sweep through whole families of shapes from
/// a few numbers (here driven by `time`, so they morph), while **extrude** pushes a
/// flat 2D `Profile` into depth and **lathe** revolves a side profile into a vase.
/// Möbius and Klein round out the set with their famous one-sided surfaces, and
/// **tube** sweeps a solid down any 3D curve. (The closed solids — box, sphere,
/// polyhedra, … — are in `3D/Solids`.)
///
/// The morphing shapes are rebuilt each frame (cheap at one shape apiece); the rest
/// are built once. Each takes a `fill` color shaded by the auto-lit default; labels
/// face the camera via `withBillboard`.
@main
final class ShapeFactory: Sketch {
// A vase silhouette (x = radius from the axis, y = height) for the lathe.
private let vase: [Vector2] = (0...24).map { i in
let t = Double(i) / 24
return Vector2(0.12 + 0.3 * sin(t * .pi * 0.92) + 0.06, (t - 0.5) * 1.4)
}
// A wavy closed loop for the swept tube.
private let loop: [Vector3] = (0..<140).map { i in
let t = Double(i) / 140 * 2 * .pi
let r = 0.75 + 0.16 * sin(t * 5)
return Vector3(r * cos(t), 0.3 * sin(t * 3), r * sin(t))
}
private var fixed: [(name: String, mesh: Mesh)] = []
override func setup() {
fixed = [
("möbius", .mobius(radius: 0.62, width: 0.42)),
("klein", .klein(scale: 0.26)),
("extrude star", .extrude(Profile.star(points: 5, outerRadius: 0.75, innerRadius: 0.33), depth: 0.5)),
("extrude gear", .extrude(Profile.polygon(sides: 8, radius: 0.78), depth: 0.5)),
("lathe vase", .lathe(vase, segments: 44)),
("tube loop", .tube(along: loop, radius: 0.12, sides: 10, closed: true)),
]
}
override func draw() {
background(Color(hex: 0x07080D))
cameraShowcase(.sway(amplitude: 0.16, period: .tau / 0.18), target: .zero, radius: 13,
elevation: 0.18, fieldOfView: .focalLength(24))
// The two morphing shapes, rebuilt from time-driven parameters.
let wobble = unipolar(sin(time * 0.4)) // 0…1
let supershape = Mesh.supershape(radius: 0.75, m: 4 + wobble * 8,
n1: 0.3 + wobble * 0.5, n2: 1.7, n3: 1.7,
segments: 96, rings: 48)
let e = 0.2 + wobble * 1.4 // box → sphere → octahedron-ish
let superellipsoid = Mesh.superellipsoid(radius: 0.8, e1: e, e2: e, segments: 56, rings: 28)
let cells: [(name: String, mesh: Mesh)] =
[("supershape", supershape), ("superellipsoid", superellipsoid)] + fixed
let columns = 4, spacing = 3.1
let rows = (cells.count + columns - 1) / columns
let x0 = -spacing * Double(columns - 1) / 2
let y0 = spacing * Double(rows - 1) / 2
for (i, cell) in cells.enumerated() {
let col = i % columns, row = i / columns
let hue = Double(i) / Double(cells.count)
withState {
translate(x0 + spacing * Double(col), y0 - spacing * Double(row), 0)
withState {
rotateY(time * 0.45 + Double(i) * 0.7)
rotateX(sin(time * 0.3 + Double(i)) * 0.25)
fill(Color(hue: hue, saturation: 0.58, brightness: 0.95))
specular(0.4)
specularSharpness(48)
drawMesh(cell.mesh)
}
withBillboard(at: Vector3(0, 1.5, 0)) {
fill(.white)
textSize(26)
textAlign(.center, .middle)
drawText(cell.name, 0, 0)
}
}
}
drawCaption("Parametric & profile shapes — supershape and superellipsoid morph; extrude & lathe build from 2D")
}
}