2

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()
        
    }
Andy Jazz
  • 49,178
  • 17
  • 136
  • 220

2 Answers2

1

Zero Gravity

In Reality Composer, you can easily turn the scene's gravity off.

enter image description here

Then use the following code to reproduce your scenario.

import SwiftUI
import RealityKit

struct ContentView: View {
    var body: some View {
        RealityKitView().ignoresSafeArea()
    }
}

struct RealityKitView: UIViewRepresentable {
    
    let arView = ARView(frame: .zero)
    let scene = try! Experience.loadModels()
    
    func makeUIView(context: Context) -> ARView {
        // Kinematic Body
        let red = scene.redBox!.children[0] as! (Entity & HasPhysicsBody)
        red.physicsBody = .init()
        red.physicsBody?.massProperties.mass = 0.1
        red.physicsBody?.mode = .kinematic
        red.generateCollisionShapes(recursive: true)
        arView.installGestures([.translation], for: red)    
        // Dynamic Body
        let green = scene.greenBox!.children[0] as! (Entity & HasPhysicsBody)
        green.physicsBody = .init()
        green.physicsBody?.massProperties.mass = 0.1
        green.physicsBody?.mode = .dynamic
        green.generateCollisionShapes(recursive: true)
        
        arView.scene.anchors.append(scene)
        return arView
    }
    func updateUIView(_ view: ARView, context: Context) { }
}

Additional info

By default, every RealityKit's 3D model has a component that is used for physics simulations of this model entity in accordance with the laws of Newtonian mechanics. To turn it off, use nil or .none values. Other than the above, the absence of Collision Shape also leads to the impossibility of implementing physics.

modelEntity.physicsBody = nil
modelEntity.components[PhysicsBodyComponent.self] = .none

P. S.

Yes, Experience.loadModels() is an enum's method that loads the scene from Reality Composer. It's up to you, whether you load models into the scene from Reality Composer or create those models programmatically. However, the Reality Composer's scene contains an invisible floor (plane) object, which is a static physics body and it's capable of catching contact shadows from models. In RealityKit, you can move RC's floor up or down to conveniently place it for your needs. Also, in RC there's Participates checkmark to activate models' static/dynamic physical interactions. And, as you've seen, there you can turn the gravity off.

Andy Jazz
  • 49,178
  • 17
  • 136
  • 220
  • 1
    The entity does need other physics properties, so I can't turn off all physics unfortunately. I've tried using the technique on the link you've provided, does "let scene = try! Experience.loadModels()" assume that I'm loading my object in from RealityComposer? Instead of creating the object programatically as I've done in this program? – Developer1928 Apr 13 '23 at 10:10
1

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 (in the article mentioned above) 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).

Therefore, 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

For a quick look at this ECS approach to simulate zero-g in action, you can take a look at my recording on vimeo (as I can't embed gifs here at this time).

ejoplex
  • 61
  • 1
  • 3
  • Thank you so much for such a great answer! I've done some messing around and got zero-g working. Appreciate your help :) – Developer1928 Apr 18 '23 at 20:23