iOS RealityKit: Collision component contacting other object despite separation

Image showing problematic collision

In an AR app using RealityKit, I am trying to create collisions between a set of objects, notably a "coin", which is a small disk, and a "table" which is a set of cube primitives. The contact is functioning, however, the coin repeatedly bounces about 0.3 meters above the table, rather than directly on top of it. In the image, the .showPhysics debug option has been enabled so we can see the table contact shape highlighted in purple, and the coin in green.

The models have been created in Reality Composer Pro, and exported to .usdz. I have created the addPhysics extension to Entity which first generates a shape from the object mesh, then creates the collider and physics body. The image indicates that the shapes have properly been assigned to the mesh. I expect when I run, that the coin will bounce a few times before settling directly on top of the table. In actuality, the coin settles 0.3 meters above the table.

import SwiftUI
import RealityKit

/// Note, ContentView is simplified as it is not critical to the question
struct ContentView : View {
    var body: some View {
        ARViewContainer()
    }
}

struct ARViewContainer: UIViewRepresentable {
    
    func makeUIView(context: Context) -> ARView {
        
        let arView = ARView(frame: .zero)
        
        arView.debugOptions = [.showFeaturePoints, .showWorldOrigin, .showAnchorOrigins, .showSceneUnderstanding, .showPhysics]

        // Create horizontal plane anchor for the content
        let anchor = AnchorEntity(.plane(.horizontal, classification: .any, minimumBounds: SIMD2<Float>(0.2, 0.2)))

        // Load the La Rana scene
        if let larana = try? Entity.load(named: "TableAndLaRana.usdz") {
            // Append the loaded model to the anchor
            anchor.addChild(larana)

            // Add contact to the coin
            if let coin = larana.findEntity(named: "Coin") {
                // Set up the shape and physics for the coin
                coin.addPhysics(material: Materials.metal, mode: .dynamic)
                
                // TEMP: put the coin above the table so its visible and bounces a few times
                coin.position = SIMD3<Float>(0.1, 5.0, 0.0)
                Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
                    print("coin position = \(coin.position)")
                }
                
            } else {
                print("Coin entity not found.")
            }
            
            // Add contact to the turf sections
            let turfEntities = ["TableMainFront", "TableMainBack", "TableMainLeft", "TableMainRight"]
            addPhysics(to: turfEntities, in: larana, material: Materials.turf, mode: .static)
            
            // Add contact to La Rana
            let metalEntities = ["LaRanaFront", "LaRanaRear", "LaRanaLeft", "LaRanaRight"]
            addPhysics(to: metalEntities, in: larana, material: Materials.metal, mode: .static)
        }
        
        // Add the horizontal plane anchor to the scene
        arView.scene.anchors.append(anchor)

        return arView
        
    }
    
    func updateUIView(_ uiView: ARView, context: Context) {}
    
    // MARK: - Physics
    
    func addPhysics(to listOfEntityNames: [String], in mainEntity: Entity, material: PhysicsMaterialResource, mode: PhysicsBodyMode) {
        for name in listOfEntityNames {
            if let entity = mainEntity.findEntity(named: name) {
                entity.addPhysics(material: material, mode: mode)
            } else {
                print("Entity \(name) not found")
            }
        }
    }
    
    private struct Materials {
        static let metal = PhysicsMaterialResource.generate(friction: 0.3, restitution: 0.99)
        static let turf = PhysicsMaterialResource.generate(friction: 0.7, restitution: 0.5)
        static let wood = PhysicsMaterialResource.generate(friction: 0.5, restitution: 0.7)
    }
}

extension Entity {
    func addPhysics(material: PhysicsMaterialResource, mode: PhysicsBodyMode) {
        if let childWithModel = children.first(
            where: { $0.components[ModelComponent.self] != nil }
        ) {
            if let modelComponent = childWithModel.components[ModelComponent.self] as? ModelComponent {
                
                // Generate collision shapes based on the model's mesh
                let shape = ShapeResource.generateConvex(from: modelComponent.mesh)
                let collisionComponent = CollisionComponent(shapes: [shape])
                components[CollisionComponent.self] = collisionComponent

                // Create and add a PhysicsBodyComponent
                let physicsBody = PhysicsBodyComponent(
                    massProperties: .default,
                    material: material,
                    mode: mode
                )
                components[PhysicsBodyComponent.self] = physicsBody
                
                print("Physics and collision components added to \(self)")
            }
        } else {
            print("No child with a ModelComponent found")
        }
    }
}

Upvotes: 0

Views: 67

Answers (0)

Related Questions