Drag Gesture on Entity with PhysicsBodyComponent is not behaving properly (glitching)

Hi everyone,

I'm new to Swift and VisionOS development in general, so please go easy on me.

Currently, I'm looking at a sample project from a WWDC23 session that uses RealityKit and ARKit to add a cube entity to a scene via tap gesture. The link to the sample project is here.

Instead of adding a cube, I changed the code to adding a usdz model instead. Here is my code:

    func add3DModel(tapLocation: SIMD3<Float>) {
        let placementLocation = tapLocation + SIMD3<Float>(0, 0.1, 0)
        
        guard let entity = try? Entity.load(named: "cake-usdz", in: realityKitContentBundle)
        else { logger.error("failed to load 3D model")
            return }
        
        // calculate the collision box (the boundaries)
        let entitySize = entity.visualBounds(relativeTo: nil)
        let width = entitySize.max.x - entitySize.min.x
        let height = entitySize.max.y - entitySize.min.y
        let depth = entitySize.max.z - entitySize.min.z
        
        // logger.debug("width: \(width), height: \(height), depth: \(depth)")

        // set collision shape
        let collisionShape = ShapeResource.generateBox(size: SIMD3<Float>(width, height, depth))
        entity.components.set(CollisionComponent(shapes: [collisionShape]))
        
        
        // set the position and input types to indirect
        entity.setPosition(placementLocation, relativeTo: nil)
        entity.components.set(InputTargetComponent(allowedInputTypes: .indirect))
        
        let material = PhysicsMaterialResource.generate(friction: 0.8, restitution: 0.0)
        
        entity.components.set(PhysicsBodyComponent(
            shapes: [collisionShape],
            mass: 1.0,
            material: material,
            mode: .dynamic
        ))
        
        contentEntity.addChild(entity)
    }

This works fine so far.

But when I tried to add a Drag Gesture to drag the added entity around. There are weird glitches happening with the model. The model jumped up and down, and even rotating around it self sometimes.

Below is my code for Drag Gesture. I placed it directly below the code for Spatial Tap Gesture in the sample project.

.gesture(DragGesture().targetedToAnyEntity().onChanged({ value in
     let targetedEntity = value.entity
     targetedEntity.position = value.convert(value.location3D, from: .local, to: .scene)
}))

At first, I thought my code was wrong. But after looking around and removing the PhysicsBodyComponent for the added model, the entity was moving as intended while dragging.

I can't figure out a solution to this. Could anyone help me?

I'm currently on Xcode 16 beta 2, and visionOS 2.0. Because I'm on Beta, I'm unsure if this is a bug or if I just missed something.

Thank you.

Answered by Vision Pro Engineer in 801022022

Hello @yun531

The cause of your issue appears to be that you are setting the position of a .dynamic PhysicsBodyComponent inside your gesture callback. When a PhysicsBodyComponent is configured to be .dynamic, its translation and rotation will be controlled by forces in the physics simulation, just as movement works in the real world. Each frame, the simulation takes into account gravity, the velocity of the body, and any other forces (such as those generated by collisions) and calculates the position and rotation for the next frame. When you directly set the position of a .dynamic body, this interupts the simulation because the body is no longer moving according to forces.

PhysicsBodyComponent can be configured as .dynamic, .kinematic, or .static (the last one is for objects that never move). You can check out the official documentation for more details.

A good rule of thumb is that you should never directly set the position of .dynamic bodies, and if you find yourself wanting to do so, you probably want your body to be .kinematic. My recommendation is to track when the DragGesture begins and ends, set your body to be .kinematic during the drag, then set it back to .dynamic when the drag ends.

Something like this may work:

.gesture(DragGesture().targetedToAnyEntity().onChanged({ value in
    let targetedEntity = value.entity
    var physicsBody = targetedEntity.components[PhysicsBodyComponent.self]!
    if physicsBody.mode != .kinematic {
        physicsBody.mode = .kinematic
        targetedEntity.components.set(physicsBody)
    }
    targetedEntity.position = value.convert(value.location3D, from: .local, to: .scene)
}))

.gesture(DragGesture().targetedToAnyEntity().onEnded({ value in
    let targetedEntity = value.entity
    targetedEntity.components[PhysicsBodyComponent.self]?.mode = .dynamic
}))

Good luck!

Accepted Answer

Hello @yun531

The cause of your issue appears to be that you are setting the position of a .dynamic PhysicsBodyComponent inside your gesture callback. When a PhysicsBodyComponent is configured to be .dynamic, its translation and rotation will be controlled by forces in the physics simulation, just as movement works in the real world. Each frame, the simulation takes into account gravity, the velocity of the body, and any other forces (such as those generated by collisions) and calculates the position and rotation for the next frame. When you directly set the position of a .dynamic body, this interupts the simulation because the body is no longer moving according to forces.

PhysicsBodyComponent can be configured as .dynamic, .kinematic, or .static (the last one is for objects that never move). You can check out the official documentation for more details.

A good rule of thumb is that you should never directly set the position of .dynamic bodies, and if you find yourself wanting to do so, you probably want your body to be .kinematic. My recommendation is to track when the DragGesture begins and ends, set your body to be .kinematic during the drag, then set it back to .dynamic when the drag ends.

Something like this may work:

.gesture(DragGesture().targetedToAnyEntity().onChanged({ value in
    let targetedEntity = value.entity
    var physicsBody = targetedEntity.components[PhysicsBodyComponent.self]!
    if physicsBody.mode != .kinematic {
        physicsBody.mode = .kinematic
        targetedEntity.components.set(physicsBody)
    }
    targetedEntity.position = value.convert(value.location3D, from: .local, to: .scene)
}))

.gesture(DragGesture().targetedToAnyEntity().onEnded({ value in
    let targetedEntity = value.entity
    targetedEntity.components[PhysicsBodyComponent.self]?.mode = .dynamic
}))

Good luck!

Drag Gesture on Entity with PhysicsBodyComponent is not behaving properly (glitching)
 
 
Q