Lab 059 – Extruding a Mesh from a Shape

I was so excited to see this added at WWDC 2024. Too bad it took me a year to try it!

Maybe this year we’ll get some new mesh creation options. I’d love a Lathe.

We can convert a 2D path into an extruded mesh that we can use on a Model Component or Model Entity.

Basics:

let somePath // assume this is set to a Path

// Create some options
var options = MeshResource.ShapeExtrusionOptions()
options.extrusionMethod = .linear(depth: 0.5)

// Create the mesh resource
guard let mesh = try? await MeshResource(extruding: somePath, extrusionOptions: options) else { return }

// Use the mesh to create an Model Entity
let subject = ModelEntity(mesh: mesh, materials: [material])

Full Lab Code

struct Lab059: View {
    @State private var extrusionDepth: Float = 0.01

    var body: some View {
        RealityView { content in
            // Create the 3D extruded shape
            var options = MeshResource.ShapeExtrusionOptions()
            options.extrusionMethod = .linear(depth: extrusionDepth)
            guard let mesh = try? await MeshResource(extruding: simplePath(), extrusionOptions: options) else { return }

            // Set up an entity and add it to the scene.
            var material = PhysicallyBasedMaterial()
            material.baseColor = .init(tint: .stepGreen)

            let subject = ModelEntity(mesh: mesh, materials: [material])
            subject.name = "Subject"
            subject.orientation = .init(angle: .pi / 4, axis: [0, 1, 0])

            content.add(subject)
            
        } update: { content in
            // Update the extrusion depth when it changes
            if let subject = content.entities.first?.findEntity(named: "Subject") {
                var options = MeshResource.ShapeExtrusionOptions()
                options.extrusionMethod = .linear(depth: extrusionDepth)
                
                Task {
                    // Get the existing model component from the subject and replace the mesh with a new one using the
                    if let newMesh = try? await MeshResource(extruding: simplePath(), extrusionOptions: options) {
                        if var modelComponent = subject.components[ModelComponent.self] {
                            modelComponent.mesh = newMesh
                            subject.components.set(modelComponent)
                        }
                    }
                }
            }
        }
        .task {
            // Animate the value of extrusionDepth over time in a sequence
            // Note: it would be great if we could animate values with Timeline in Reality Composer Pro
            try? await Task.sleep(nanoseconds: 1_000_000_000) // 1 second
            
            let startTime = Date()
            let duration: TimeInterval = 2.0
            let min: Float  = 0.01
            let max: Float = 0.5
            while true {
                let elapsed = Date().timeIntervalSince(startTime)
                let cycleTime = elapsed.truncatingRemainder(dividingBy: 7.0) // 7 second cycle
                
                if cycleTime < 1.0 {
                    extrusionDepth = min
                } else if cycleTime < 3.0 {
                    let progress = (cycleTime - 1.0) / duration
                    extrusionDepth = min + Float(progress) * (max - 0.01)
                } else if cycleTime < 4.0 {
                    extrusionDepth = max
                } else if cycleTime < 6.0 {
                    let progress = (cycleTime - 4.0) / duration
                    extrusionDepth = max - Float(progress) * (max - 0.01)
                } else {
                    extrusionDepth = min
                }
                
                try? await Task.sleep(nanoseconds: 1_000_000_000 / 60) // 60 FPS
            }
        }
    }

    func simplePath() -> Path {
        let rect = CGRect(x: -0.1, y: -0.1, width: 0.2, height: 0.2)
        var path = Path()
        
        path.move(to: CGPoint(x: rect.midX, y: rect.maxY))
        path.addLine(to: CGPoint(x: rect.minX, y: rect.minY))
        path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
        path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY))
        
        return path
    }
}

Support our work so we can continue to bring you new examples and articles.

Download the Xcode project with this and many more examples from Step Into Vision.
Some examples are provided as standalone Xcode projects. You can find those here.

Questions or feedback?