Ollin

Inked

The cartoon look: toon solids with an ink line around every shape (outline(width:color:)), under a three-point rig that follows the camera (relativeTo(.camera)), a world sun, or a headlight(), switched by a parameter.

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

/// Ink on the edges, and a light that follows the camera.
///
/// A still life in the toon material with an ink line around every shape
/// (`outline(width:color:)`), the cartoon look. The line is drawn as an
/// inverted hull, the mesh once more pushed out along its normals with the
/// faces toward the eye culled, so only the rim past the silhouette shows; it
/// holds its width in pixels at any distance, like a pen, and a nearer shape
/// hides a farther one's line.
///
/// The `rig` parameter is the other half. A world-space rig turns the cel bands
/// with the world as the camera orbits, the way a sun would; the same rig read
/// `relativeTo(.camera)` keeps its key, fill, and rim placed around the eye, so
/// the bands stay put on each shape as the view goes round; and a `headlight()`
/// alone is the flat, even look of a flash. Drag to orbit, scroll to dolly.
@main
final class Inked: Sketch {

    enum Rig: CaseIterable, ParamOption {
        case worldSun, cameraRig, headlight
    }

    @Param(0 ... 8, group: "Ink") var lineWidth = 3.0
    @Param(group: "Ink") var ink = Color(white: 0.08)
    @Param(1 ... 8, group: "Ink") var bands = 3
    @Param(group: "Light") var rig: Rig = .cameraRig

    override func draw() {
        background(Color(hex: 0xF2EBDD))
        cameraShowcase(.autoOrbit(period: .tau / 0.18), target: Vector3(0, -0.2, 0), radius: 9,
                       elevation: 0.3, fieldOfView: .pi / 4)

        switch rig {
        case .worldSun:
            lightingPreset(.threePoint)
        case .cameraRig:
            lightingPreset(.threePoint.relativeTo(.camera))
        case .headlight:
            ambientLight(Color(white: 0.2))
            headlight(intensity: 0.9)
        }

        var toon = Material.toon
        toon.toonBands = Double(bands)
        material(toon)
        outline(width: lineWidth, color: ink)

        withState {
            translate(-2.4, -0.3, 0.3)
            fill(Color(hex: 0xE8553F))
            drawSphere(radius: 1.05, segments: 64, rings: 40)
        }
        withState {
            translate(0.1, 0, -0.4)
            rotateY(time * 0.25); rotateX(0.4)
            fill(Color(hex: 0x3F9BD9))
            drawBox(size: 1.5)
        }
        withState {
            translate(2.5, -0.4, 0.5)
            rotateX(1.05); rotateY(time * 0.35)
            fill(Color(hex: 0xC85BB5))
            drawTorus(radius: 0.75, tube: 0.32, segments: 64, sides: 32)
        }
        withState {
            translate(0.2, 1.7, 0.2)
            rotateY(time * 0.2); rotateZ(0.5)
            fill(Color(hex: 0xF0B429))
            drawTorusKnot(radius: 0.55, tube: 0.16)
        }
        withState {
            // The floor takes a matte finish and no line: a toon floor would
            // band, and the rig's highlight would land on it between the shapes.
            translate(0, -1.75, 0)
            material(.matte)
            noOutline()
            fill(Color(hex: 0xD9D0BC))
            drawPlane(width: 16, depth: 16)
        }
    }
}

↑↓ move ↩ open