swiftarkitrealitykit

How to spawn an entity in Zero Gravity with RealityKit?


I'm building a RealityKit application where I want to spawn a floating object into my scene, in dynamic mode, which is not affected by gravity. It needs to collide with other objects, have a mass of 1, and be affected by an impulse, which is why I've made it dynamic.

Currently the object spawns into the scene at the correct point, but immedately falls to the floor due to a gravitational force being applied. I've been stuck on this for a while now, and can't find anything on how to remove this gravitational force.

Here's how I'm creating and spawning the object;

// Create the object entity
let object = ModelEntity(mesh: MeshResource.generateBox(size: [0.07, 0.01, 0.14]), materials: [SimpleMaterial(color: .white, isMetallic: false)])

// Define object shape
let objectShape = [ShapeResource.generateBox(size: [0.07, 0.01, 0.14]]

// Create the object PhysicsBody
var objectPhysicsBody = PhysicsBodyComponent(shapes: objectShape, mass: 1, material: .default, mode: .dynamic)

// Create the object CollisionComponent
let objectCollision = CollisionComponent(shapes: objectShape)

// Set components to the object
object.components.set(objectPhysicsBody)
object.components.set(objectCollision)

// Attach the object to the anchor
anchor.addChild(object)

One thing I have tried is applying a constant negating force to the standard gravitational force, however this didn't work as intended. It does apply an opposite force, but it is increasing so gradually ends up sending the object flying upwards

This is what I tried;

var displayLink: CADisplayLink?

    // Main view loaded function
    override func viewDidLoad() {
        
        super.viewDidLoad()
        
        // Repeatedly call the update function
        displayLink = CADisplayLink(target: self, selector: #selector(update))
        displayLink?.add(to: .current, forMode: .common)
        
... 

    }


…

    // Repeatedly call applyAntiGravity
    @objc private func update() {
        
        applyAntiGravityForce()
        
    }
    
    private func applyAntiGravityForce() {
        
        guard let ballPhysicsBody = object.physicsBody else { return }
        let gravity: SIMD3<Float> = [0, -9.8, 0]
        let antiGravityForce = -gravity
        ball.addForce(antiGravityForce, relativeTo: nil)
        
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        
        super.viewWillDisappear(animated)
        displayLink?.invalidate()
        
    }

Solution

  • Spawning an Entity in Zero Gravity with RealityKit

    To add custom gravity to entities in a RealityKit scene, you can utilize the Entity Component System (ECS) to create a custom gravity Component. The following approach allow you to create custom gravity behavior for entities in your scene by applying linear impulse to an Entity with physicsBody based on mass and gravity properties.

    To spawn an entity in zero gravity, you can add this component to the entity and set its gravity property to 0. Here's an example implementation of a simple GravityComponent:

    class GravityComponent: Component {
        var gravity: Float
        var mass: Float 
        
        init(gravity:Float = 9.81, mass:Float) {
            self.gravity = gravity 
            self.mass = mass
        }
        
        func update(entity: Entity, with deltaTime: TimeInterval, at physicsOrigin: Entity) {
            // Check that gravity is not default
            guard gravity != 9.81 else { return }
            // Check that entity indeed has physicsBody
            guard let physicsBody = entity as? HasPhysicsBody else { return }       
            
            // Calculate gravity correction
            let newG: Float = 9.81 - gravity
            
            // Apply linear impulse to the physicsBody
            let impulse = mass * newG * Float(deltaTime)
            physicsBody.applyLinearImpulse([0.0, impulse, 0.0], relativeTo: physicsOrigin)
        }
    }
    

    The update method applies a linear impulse to the entity's physics body based on the mass and gravity properties of the GravityComponent. It also compensates for FPS changes by considering the time interval (deltaTime) between frames in the update loop. Note that we use applyLinearImpulse to apply the gravity effect, which is preferred over addForce, as explained by Apple in this article on designing scene hierarchies for efficient physics simulation.

    You can add the component to your entity like this:

    let gravity = GravityComponent(gravity: 0.0, mass: 1.0)
    entity.components.set(gravity)
    

    To use the GravityComponent you can call the update method inside your update-loop of choice on entities with the component. Here's an example of how to do this with SceneEvents:

    func updateScene(on event: SceneEvents.Update) {
        for entity in sceneEntities {
            if let gravity = entity.components[GravityComponent.self] as? GravityComponent {
                gravity.update(entity: entity, with: event.deltaTime, at: physicsOrigin)
            }
        }
    }
    

    In this example, we call the update method on each entity that has a GravityComponent. Note that we pass in the delta time and a physicsOrigin entity, which is also recommended by Apple for improved physics simulation in scenes with entities of varying sizes. Apply the impulse relative to nil to skip this recommendation.

    Finally, remember to register your component (once!) before using it:

    GravityComponent.registerComponent()
    

    Limitations

    Even though the GravityComponent compensates for delta time, the implementation is still sensitive to some FPS changes. If the frame rate drops significantly, the impulse becomes unpredictable and might add some disturbance to the entities -- which is especially visible in the case of simulating zero-g.

    This solution might not work well for use-cases that requires high precision and predicability, but should be sufficient for simple cases and as a starting point for further improvements.

    Zero gravity demo (Vimeo)


    Update iOS 18

    Starting with iOS 18, you can set custom gravity in RealityKit natively by modifying the gravity property of a PhysicsSimulationComponent.

    Gravity

    The gravity vector of PhysicsSimulationComponent defines the gravitational force applied to entities within the simulation. You can customize this vector to simulate different gravitational conditions or orientations. The default value is [0.0, -9.81, 0.0]

    To apply a custom gravity vector to a specific part of your scene, you can add a PhysicsSimulationComponent to an entity and set its gravity property:

    let entity = Entity()
    var physicsSimulation = PhysicsSimulationComponent()
    physicsSimulation.gravity = [0.0, 0.0, 0.0] // Zero gravity
    entity.components.set(physicsSimulation)
    

    This setup applies the specified gravity to the entity and its descendants.