Ollin

Solids

The closed-solid catalog (box, sphere, cylinder, torus, the Platonics, and more), drawn as colorful clay and shaded by the auto-lit default.

Run
swift run --package-path Examples Example-3D-Geometry-Solids
Source
Examples/3D/Geometry/Solids/Sketch.swift
import Ollin

/// Solid 3D primitives — the built-in closed shapes, drawn through a `Camera3D`.
///
/// Each is a `Mesh`, built once in `setup()` and drawn every frame with `drawMesh`
/// (the recommended pattern — generating a sphere or swept knot each frame is
/// wasted work when the geometry never changes; `drawBox`/`drawSphere`/… are the
/// build-it-each-frame convenience). They sit on a facing grid, each spinning while
/// the camera sways. The parametric and profile generators (supershape, Möbius,
/// extrude, lathe, …) have their own example in `3D/ShapeFactory`.
///
/// Each solid takes a `fill` color and is shaded by the auto-lit default — a solid
/// looks 3D out of the box, no lights to set up — with a soft specular highlight.
/// A 2D label sits above each shape via `withBillboard`.
@main
final class Solids3D: Sketch {
    private var shapes: [(name: String, mesh: Mesh)] = []

    override func setup() {
        shapes = [
            ("cube",         .box(size: 1.2)),
            ("sphere",       .sphere(radius: 0.75)),
            ("icosphere",    .icosphere(radius: 0.75, subdivisions: 2)),
            ("cylinder",     .cylinder(radius: 0.58, height: 1.4)),
            ("cone",         .cone(radius: 0.72, height: 1.5)),
            ("capsule",      .capsule(radius: 0.36, height: 0.8)),
            ("rounded box",  .roundedBox(size: 1.2, radius: 0.3)),
            ("torus",        .torus(radius: 0.58, tube: 0.25)),
            ("tetrahedron",  .tetrahedron(radius: 0.82)),
            ("octahedron",   .octahedron(radius: 0.82)),
            ("icosahedron",  .icosahedron(radius: 0.82)),
            ("dodecahedron", .dodecahedron(radius: 0.82)),
            ("pyramid",      .pyramid(width: 1.3, depth: 1.3, height: 1.5)),
            ("helix",        .helix(radius: 0.5, tube: 0.11, turns: 3, height: 1.5, segments: 160, sides: 10)),
            ("torus knot",   .torusKnot(p: 2, q: 3, radius: 0.62, tube: 0.17, segments: 150, sides: 12)),
            ("plane",        .plane(width: 1.4, depth: 1.4)),
        ]
    }

    override func draw() {
        background(Color(hex: 0x07080D))

        // A catalog wall: shapes on an x–y grid facing a gently swaying camera, so
        // the rows don't stack front-to-back and every label sits above its shape.
        cameraShowcase(.sway(amplitude: 0.14, period: .tau / 0.18), target: .zero, radius: 15,
                    elevation: 0.12, fieldOfView: .pi / 3.4)

        let columns = 4, spacing = 3.0
        let rows = (shapes.count + columns - 1) / columns
        let x0 = -spacing * Double(columns - 1) / 2
        let y0 = spacing * Double(rows - 1) / 2

        for (i, shape) in shapes.enumerated() {
            let col = i % columns, row = i / columns
            // A hue per shape, lit by the default rig with a gentle highlight.
            let hue = Double(i) / Double(shapes.count)
            withState {
                translate(x0 + spacing * Double(col), y0 - spacing * Double(row), 0)
                withState {
                    rotateY(time * 0.5 + Double(i) * 0.6)
                    rotateX(sin(time * 0.35 + Double(i)) * 0.3)
                    fill(Color(hue: hue, saturation: 0.6, brightness: 0.95))
                    specular(0.4)
                    specularSharpness(48)
                    drawMesh(shape.mesh)
                }
                withBillboard(at: Vector3(0, 1.35, 0)) {
                    fill(.white)
                    textSize(24)
                    textAlign(.center, .middle)
                    drawText(shape.name, 0, 0)
                }
            }
        }

        drawCaption("Solid 3D primitives — sixteen shapes, shaded by the auto-lit default")
    }
}

↑↓ move ↩ open