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.

Questions or feedback?