Collisions & Physics: Generating Collision Shapes
RealityKit provides a few methods to generate complex collision shapes, with one notable omission.
Overview
Collision Shapes are made of ShapeResources. In a previous example, we took a look at generating simple shapes. For example:
.generateSphere(radius: 0.1)
.generateCapsule(height: 0.4, radius: 0.1)
.generateBox(size: .init(x: 0.2, y: 0.2, z: 0.2))What if we need more complex shapes? Let’s use a few models from The Base Mesh. We’ll take a loot at two options we have to generate complex shapes from a mesh.

https://www.thebasemesh.com/asset/bowl-01
https://www.thebasemesh.com/asset/bowling-pin
https://www.thebasemesh.com/asset/corrugated-curved-sheet
Generate convex shapes
We can use .generateConvex(from: mesh) on our bowling pin. We’ll get the mesh from the model component, then try to generate a shape resource. These convex shapes can be used with dynamic, kinematic, and static physics bodies.
if let pin = scene.findEntity(named: "pin") {
if let modelComponent = pin.children.first?.components[ModelComponent.self] {
let mesh = modelComponent.mesh
// Example that throws an error (nonisolated static method)
Task {
do {
let collision = try await CollisionComponent(shapes: [.generateConvex(from: mesh)])
pin.components[CollisionComponent.self] = collision
} catch {
print("Error generating collision mesh: \(error)")
}
}
// Example that uses @MainActor @preconcurrency
Task {
let collision = CollisionComponent(shapes: [.generateConvex(from: mesh)])
pin.components.components.set(collision)
}
}
}This works pretty well, but it is far from perfect. We can knock the pin over and it will roll as we expect. But since the collision covers a gap in the shape of the pin, many interactions won’t look or feel realistic.
Jessy C. pointed out that there are two versions of this method. When possible, use the one that throws an error: generateConvex(from:)
Generate static shapes
For the metal sheet, let’s use .generateStaticMesh(from: mesh). This will give us a much more realistic shape, but with one major limitation. We can only use this with static physic bodies. Essentially, we can only use this option for entities that are not going to move. If we try to pick up and move this entity, the collision shape will not move the ball that is resting on it.
if let sheet = scene.findEntity(named: "sheet") {
if let modelComponent = sheet.children.first?.components[ModelComponent.self] {
let mesh = modelComponent.mesh
Task {
do {
let collision = try await CollisionComponent(shapes: [.generateStaticMesh(from: mesh)])
sheet.components[CollisionComponent.self] = collision
} catch {
print("Error generating collision mesh: \(error)")
}
}
}
}Using both concepts
We can see the difference between these options by using them both on the bowl mesh. The one in the front has a concave (but static) collision shape that can’t be moved. The bowl in the back has a convex collision shape. We can move this bowl around, but notice how the ball rests on top of a false surface that connects the top edges.

I’m not sure why RealityKit doesn’t support dynamic concave collision shapes. I know the math is more complex, but this seems like a pretty important component of any realistic physic system. Hopefully visionOS 3.0 will bring concave collision shapes.
It’s work noting that these methods above work best with simple meshes with few polygons. If your mesh has very complex geometry, you may want to consider providing a decimated version with fewer polygons for the collision shape. Many 3D content tools offer features to decimate a mesh.
Video Demo
Full Example Code
This scene was composed in Reality Composer Pro, where I added the physics bodies and adjusted materials.
struct Example057: View {
// Just a hack to access the scene from the toolbar buttons
@State var sceneContent = Entity()
var body: some View {
RealityView { content in
// Note: These USDZ files have the mesh in a child entity because 🤷🏻♂️
guard let scene = try? await Entity(named: "CollisionLabsCustom", in: realityKitContentBundle) else { return }
content.add(scene)
scene.position.y = -0.4
self.sceneContent = scene
// Example 1: bowling pin with convex collision shape
if let pin = scene.findEntity(named: "pin") {
if let modelComponent = pin.children.first?.components[ModelComponent.self] {
let mesh = modelComponent.mesh
Task {
do {
let collision = try await CollisionComponent(shapes: [.generateConvex(from: mesh)])
pin.components[CollisionComponent.self] = collision
} catch {
print("Error generating collision mesh: \(error)")
}
}
}
}
// Example 2: sheet with concave static collision shape
if let sheet = scene.findEntity(named: "sheet") {
if let modelComponent = sheet.children.first?.components[ModelComponent.self] {
let mesh = modelComponent.mesh
Task {
do {
let collision = try await CollisionComponent(shapes: [.generateStaticMesh(from: mesh)])
sheet.components[CollisionComponent.self] = collision
} catch {
print("Error generating collision mesh: \(error)")
}
}
}
}
// Repeate examples 1 & 2 with the same model
// Example 3: bowl with concave static collision shape
if let bowl = scene.findEntity(named: "bowl") {
if let modelComponent = bowl.children.first?.components[ModelComponent.self] {
let mesh = modelComponent.mesh
Task {
do {
let collision = try await CollisionComponent(shapes: [.generateStaticMesh(from: mesh)])
bowl.components[CollisionComponent.self] = collision
} catch {
print("Error generating collision mesh: \(error)")
}
}
}
}
// Example 4: bowl with convex static collision shape
if let bowl2 = scene.findEntity(named: "bowl_2") {
if let modelComponent = bowl2.children.first?.components[ModelComponent.self] {
let mesh = modelComponent.mesh
Task {
do {
let collision = try await CollisionComponent(shapes: [.generateConvex(from: mesh)])
bowl2.components[CollisionComponent.self] = collision
} catch {
print("Error generating collision mesh: \(error)")
}
}
}
}
}
.modifier(DragGestureImproved())
.toolbar {
ToolbarItem(placement: .bottomOrnament, content: {
HStack {
Button(action: {
if let ball1 = sceneContent.findEntity(named: "ExamplePhysics_1") {
ball1.position.y = 0.3
}
}, label: {
Text("Drop 1")
})
Button(action: {
if let ball2 = sceneContent.findEntity(named: "ExamplePhysics_2") {
ball2.position.y = 0.3
}
}, label: {
Text("Drop 2")
})
Button(action: {
if let ball3 = sceneContent.findEntity(named: "ExamplePhysics_3") {
ball3.position.y = 0.3
}
}, label: {
Text("Drop 3")
})
}
})
}
}
}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.

Follow Step Into Vision