Ollin

MeshGrowth

A surface that grows more than it has room for. Its vertices are pushed apart, faster where the surface is already crowded, until it buckles.

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

/// Mesh growth: a surface that makes more of itself than it has room for.
///
/// The rule is one sentence long. Push the vertices of a mesh apart, faster in
/// some places than others, and keep the triangles a fixed size by splitting the
/// ones that stretch. The surface gains area; the area has nowhere to go; it
/// folds. Everything here comes from that, including the parts that look
/// designed.
///
/// Nothing pushes the surface outward, so it cannot answer new area by inflating
/// like a balloon. It folds every time. What `driver` changes is *where* it
/// folds and how big the folds are. **Even** grows everywhere at once and folds
/// uniformly all over, the brain-coral look, and is the baseline worth starting
/// from. **Curvature** grows what already bulges, so a bulge becomes a bigger
/// bulge and the form breaks into lobes. **Chemistry** runs a reaction-diffusion
/// pattern in the surface and grows where it collects: the pattern decides where
/// to grow and the growth gives the pattern more room to spread, which is the
/// branching coral. **Rim** grows only near the equator, the way a leaf grows at
/// its margin, and ruffles into a skirt.
///
/// `coarseness` is the triangle size, so it sets how fine the folds can be, and
/// it is where the cost lives. `detail` caps the vertex count: growth stops when
/// it is reached, so it decides how far the form develops before it settles.
@main
final class MeshGrowthDemo: Sketch {

    enum Rule: String, CaseIterable, ParamOption {
        case chemistry, curvature, rim, even
    }

    @Param(icon: "leaf") var driver = Rule.chemistry
    @Param(0.05 ... 0.16, icon: "triangle") var coarseness = 0.085
    @Param(2000 ... 14000, icon: "circle.grid.3x3") var detail = 9000.0
    @Param(icon: "sparkles") var polished = false

    private var growth: MeshGrowth?
    private var builtFor: (driver: Rule, coarseness: Double)?

    override func draw() {
        background(Color(hex: 0x0A0B10))
        environment(.courtyard.rotated(time * 0.05).lightingOnly())
        lightingPreset(.studio)
        cameraShowcase(.autoOrbit(period: 34), radius: 4.2, elevation: 0.34)

        // Rebuilt only when a parameter changes the form's whole history. Growth is
        // cumulative, so changing the rule halfway would leave a shape that is
        // neither one thing nor the other.
        if builtFor == nil || builtFor! != (driver, coarseness) {
            growth = makeGrowth()
            builtFor = (driver, coarseness)
        }
        guard let growth else { return }

        growth.maxVertices = Int(detail)
        // A few steps a frame: enough to watch it develop, slow enough to see it
        // happen rather than arriving at the answer.
        growth.step(2)

        fill(polished ? Color(hex: 0xE7EBF2) : Color(hex: 0xE2603A))
        material(polished ? .metal(roughness: 0.22) : .dielectric(roughness: 0.45))
        castShadows()
        drawMesh(growth.mesh)

        drawStatus("\(growth.vertexCount) vertices, step \(growth.stepCount)")
        drawCaption("Mesh growth: a surface folding because it is making more area than it has room for")
    }

    private func makeGrowth() -> MeshGrowth {
        let seedMesh = Mesh.icosphere(radius: 0.75, subdivisions: 3)
        switch driver {
        case .chemistry:
            let growth = MeshGrowth(mesh: seedMesh, driver: .chemical(.coral),
                                    edgeLength: coarseness, seed: variation)
            // The chemistry's pattern covers only part of the surface where the
            // other drivers push everywhere, so it earns a faster rate; the
            // settle hands it a formed pattern to grow from on the first step.
            growth.growthRate = 0.85
            growth.settleSteps = 60
            return growth

        case .curvature:
            return MeshGrowth(mesh: seedMesh, driver: .curvature,
                              edgeLength: coarseness, seed: variation)

        case .rim:
            // Grow only near the equator. The band is soft-edged so the folds
            // start gradually instead of tearing away from a hard line.
            let growth = MeshGrowth(mesh: seedMesh,
                                    driver: .field { position, _ in
                                        1 - smoothstep(0.05, 0.45, abs(position.y))
                                    },
                                    edgeLength: coarseness, seed: variation)
            growth.growthRate = 0.9
            return growth

        case .even:
            return MeshGrowth(mesh: seedMesh, driver: .uniform,
                              edgeLength: coarseness, seed: variation)
        }
    }
}

↑↓ move ↩ open