Ollin

Tensegrity

Three tensegrities dropped onto a floor: a three-strut prism, the six-strut icosahedron, and a mast of stacked prisms. Each lands, bounces on its own cables, and stands, because every strut pushes as hard as the cables around it pull. Drag a strut and the whole form follows and rights itself. Shows Tensegrity, addTensegrity, the .cable joint, and prestress.

Run
swift run --package-path Examples Example-3D-Physics-Tensegrity
Imports
OllinPhysics
Source
Examples/3D/Physics/Tensegrity/Sketch.swift
import Foundation
import Ollin
import OllinPhysics

/// Three tensegrities dropped onto a floor: a three-strut prism, the six-strut
/// icosahedron, and a mast of stacked prisms. Each one lands, bounces on its
/// own cables, and stands, because every strut pushes exactly as hard as the
/// cables around it pull. Not one strut touches another in the first two; the
/// mast shares a node where its levels meet.
///
/// **Drag** any strut and let go: the whole form follows, stretches, and
/// rights itself. **Space** drops them again. `prestress` is how much shorter
/// than drawn each cable is made, which is what tightens a real one; take it
/// to zero and a landing can leave a cable loose.
@main
final class TensegritySketch: Sketch {
    let world = World3D()
    var forms: [Tensegrity3D] = []

    /// How much shorter than its drawn length each cable is cut, as a fraction.
    @Param(0 ... 0.08, icon: "arrow.left.and.right") var prestress = 0.02

    let strutColors = [Color(hex: 0xE8632F), Color(hex: 0xF2B84B), Color(hex: 0x72BFB2)]
    let cableColor = Color(hex: 0xE6E9EE)

    override func setup() {
        world.ground = 0
        world.restitution = 0.15
        drop()
    }

    /// Build the three forms a little above the floor and tipped, so each one
    /// lands on an edge and has to find its feet.
    func drop() {
        for form in forms { world.remove(form) }
        forms.removeAll()
        // The prism and the ball are tipped so they land on an edge and have
        // to find their feet; the mast is set down straight, since a tall thin
        // thing dropped on a lean falls over, tensegrity or not.
        let tip = Rotation3D(angle: 0.3, axis: Vector3(0.4, 0, 1).normalized)
        let shapes: [(Tensegrity, Double, Double)] = [
            (Tensegrity.prism(struts: 3, radius: 0.62, height: 1.15).rotated(by: tip), -2.25, 0.9),
            (Tensegrity.icosahedron(strutLength: 1.75).rotated(by: tip), 0, 0.9),
            (Tensegrity.tower(levels: 3, struts: 3, radius: 0.46, levelHeight: 0.95), 2.25, 0.4),
        ]
        for (index, (shape, x, lift)) in shapes.enumerated() {
            guard let built = try? world.addTensegrity(shape,
                                                  at: Vector3(x, lift - shape.bottom, 0),
                                                  strutRadius: 0.038,
                                                  prestress: prestress,
                                                  friction: 0.7)
            else { continue }
            for strut in built.struts { strut.userData = strutColors[index] }
            forms.append(built)
        }
    }

    override func keyPressed() {
        if key == " " { drop() }
    }

    override func draw() {
        background(Color(hex: 0x10141B))
        environment(.sky(turbidity: 3, sunElevation: 0.55).lightingOnly())
        lightingPreset(.studio)
        castShadows()
        perspective(eye: Vector3(0.5, 2.0, 6.4), target: Vector3(0, 1.15, 0), fieldOfView: .pi / 3.4)

        dragBodies(in: world)
        world.advance(by: deltaTime)

        fill(Color(hex: 0x1C2230))
        material(.dielectric(roughness: 0.9))
        drawGround(size: 30)

        // Struts in their own color, cables in pale steel, so the two kinds of
        // member read apart: the struts never meet, the cables meet everywhere.
        for form in forms {
            material(.dielectric(roughness: 0.5))
            for strut in form.struts {
                fill(strut.userData as? Color ?? .white)
                drawBody(strut)
            }
            fill(cableColor)
            material(.metal(roughness: 0.3))
            let nodes = form.nodes
            for cable in form.source.cables {
                drawCapsule(from: nodes[cable.a], to: nodes[cable.b], radius: 0.009,
                            segments: 8, rings: 3)
            }
        }

        drawCaption("struts push, cables pull, nothing touches      drag a strut, space to drop them again")
    }
}

↑↓ move ↩ open