Is it true that without code, Reality Composer Pro cannot make a vertical door that swings with gravity?

I tested all variations. The checkboxes in Reality Composer Pro 2 (beta 4) in the Physics Body component:

are absolute and not parent-relative. Also, regardless of what I set the center of mass to:

it always rotates around the center of mass despite the local rotation being correctly at the center of origin (imported from Blender). Thus I can get the door to turn but never to swing because it always rotates around its center of mass.

Tell me if this is expected behaviour or if there is a simple way to make this work.

Answered by Vision Pro Engineer in 799534022

Hi @a.p.

The key to getting dynamic physics bodies to work in this approach is to use addForce instead of manually setting their positions. This ensures the physics bodies interact with other objects in the physics simulation in a natural way.

I spent some time modifying your code to get this to work and here's what I came up with:

First, create a component and a system responsible for adding force to the hand joints to move them toward a target position. This system will be used to move the hand joints to their tracked real world positions using forces instead of directly setting their positions.

/// Component that stores the current target position of a hand joint.
struct DynamicTargetComponent: Component {
    var targetPosition: SIMD3<Float>
}

/// System that adds force to the hand joints' dynamic physics bodies to move them towards their target positions.
struct DynamicTargetSystem: System {
    let query = EntityQuery(where: .has(DynamicTargetComponent.self))
    
    public init(scene: RealityKit.Scene) { }
    
    public func update(context: SceneUpdateContext) {
        let handJointEntities = context.entities(matching: self.query, updatingSystemWhen: .rendering)
        
        for handJointEntity in handJointEntities {
            guard let handJointModelEntity = handJointEntity as? ModelEntity,
                  let physicsBody = handJointModelEntity.components[PhysicsBodyComponent.self],
                  let dynamicHandJointComponent = handJointModelEntity.components[DynamicTargetComponent.self] else { return }
            
            // Get the vector pointing in the direction of the target position.
            let toTarget = dynamicHandJointComponent.targetPosition - handJointModelEntity.position(relativeTo: nil)
            
            // Add force to the hand joint in that direction, multiplying by mass so that this
            // works consistently no matter what the mass of the hand joint itself is.
            let forceFactor: Float = 5000 * physicsBody.massProperties.mass
            handJointModelEntity.addForce(toTarget * forceFactor, relativeTo: nil)
        }
    }
}

Then, be sure to register the DynamicTargetSystem. I do this at the top of the RealityView.

RealityView { content in 
    DynamicTargetSystem.registerSystem()

    //...
}

Finally, replace the for loop iterating over every joint in the handSkeleton in your code with the following snippet.

for joint in handSkeleton.allJoints.filter({ $0.isTracked }) {
    let partID = "\(joint.name)-\(anchor.chirality)"
    var part = parts[partID]
    
    // Get the transform matrix for the current joint.
    let transform = anchor.originFromAnchorTransform * joint.anchorFromJointTransform
    // Extract its position from the transform matrix.
    let jointPosition = simd_make_float3(transform.columns.3)
    
    if part == nil {
        part = ModelEntity(mesh: .generateSphere(radius: 0.01), materials: [mat3])
        part!.generateCollisionShapes(recursive: false)
        part!.components.set(OpacityComponent(opacity: 0.5))
        
        // Add a dynamic physics body component not affected by gravity.
        var physicsBody = PhysicsBodyComponent(mode: .dynamic)
        physicsBody.isAffectedByGravity = false
        physicsBody.massProperties.mass = 10  // Increase mass to prevent the door from moving the hand too much.
        physicsBody.linearDamping = 50  // Use absurdly high linear damping to reduce overshoot and springiness.
        part!.components.set(physicsBody)
        
        // Prevent the hand joints from being able to collide with one another using a collision filter.
        let handJointGroup = CollisionGroup(rawValue: 1 << 1)
        let allButHandJointGroup = CollisionGroup.all.subtracting(handJointGroup)
        let allButHandJointFilter = CollisionFilter(group: handJointGroup, mask: allButHandJointGroup)
        part!.components[CollisionComponent.self]?.filter = allButHandJointFilter
        
        // Add a `DynamicTargetComponent` to the joint.
        part!.components.set(DynamicTargetComponent(targetPosition: jointPosition))
        // Set the starting position of the joint.
        part!.position = jointPosition
        
        handRoot.addChild(part!)
        parts[partID] = part
    }
    
    // Set the joint's target position to be its current tracked position.
    // The `DynamicTargetSystem` will then try to move the joint toward this position every frame using forces.
    part?.components[DynamicTargetComponent.self]?.targetPosition = jointPosition
}

You should now be able to use your hands to interact with the door. You can even pinch the door to rotate it around!

This approach isn't perfect. Colliders can get caught on the door and temporarily lag behind the hands as I mentioned in my previous response. That being said, there are a number of variables you can tweak to adjust this approach to your liking:

  1. mass: Increase this value to increase the rigidity of the hands and improve their ability to boss around the door and other physics objects in the scene. Decrease this value to allow the door and other physics objects to move the hands and behave more naturally when colliding.
  2. linearDamping: Increase this value to increase the rate at which the hand joints come to rest. This is useful for preventing them from overshooting their targets, but can cause them to move too slowly if increased too high.
  3. forceFactor: Increase this value to increase the speed at which the hand joints reach their target positions—but be careful—if you increase this value too high the hand joints will overshoot and oscillate around their target positions.

Here is a test project I made: https://www.transfernow.net/dl/20240804cujYO3mm Link is valid until 11 Aug

Hi @a.p.

The best approach for creating a swinging door is to use the Physics joints and pins API, which Reality Composer Pro does not currently support. If this is a feature you'd like to see Reality Composer Pro support in the future, please file a feedback request at https://feedbackassistant.apple.com and post the FB number here so I can take a look or forward it to the relevant engineers. Thanks!

That being said, if you are up for a bit of coding, feel free to build off of the following code snippet which creates a vertically swinging door by using a PhysicsRevoluteJoint.

RealityView { content in
    // Create a door entity with a collider and a dynamic physics body
    let doorEntity = ModelEntity(mesh: .generateBox(size: [1, 0.1, 1]), materials: [SimpleMaterial(color: .brown, isMetallic: false)])
    doorEntity.generateCollisionShapes(recursive: false)
    doorEntity.components.set(PhysicsBodyComponent(mode: .dynamic))
    
    // Add a pin to the door at its far end
    let doorPin = doorEntity.pins.set(named: "Door", position: [0, 0, -0.5])
    
    // Create a hinge entity with a collider and a kinematic physics body
    let hingeEntity = ModelEntity(mesh: .generateBox(size: [1, 0.05, 0.05]),
                                    materials: [SimpleMaterial(color: .red, isMetallic: false)])
    hingeEntity.generateCollisionShapes(recursive: false)
    hingeEntity.components.set(PhysicsBodyComponent(mode: .kinematic))
    
    // Add a pin to the hinge
    let hingePin = hingeEntity.pins.set(named: "Hinge")
    
    // Position the hinge and the door so that they are connected
    doorEntity.position = [0, 1, -1]
    hingeEntity.position = [0, 1, -1.5]
    
    // Create a joint connecting the hinge to the door
    let doorHingeJoint = PhysicsRevoluteJoint(pin0: hingePin, pin1: doorPin)
    
    // Try to add the joint to the physics simulation
    do {
        try doorHingeJoint.addToSimulation()
    } catch {
        print("Failed to add hinge joint to simulation: \(error)")
    }
    
    // Add the door and hinge to the scene
    content.add(doorEntity)
    content.add(hingeEntity)
}

Ok, I will file a feedback request. I do have a problem with the code though. My own code is very similar (and works in general) but as soon as I have another kinematic entity (like a fingertip or a sphere I move via code) the door CAN be moved and not only rotated.

I tried with a PhysicsRevoluteJoint and a custom joint with fixed position. It will move away for a moment when pushed and then gets back to its position when it can and I have not yet found out to actually fix it in place so it only rotates.

Is there a way to prevent this?

Hi @a.p.

I tried to recreate the issue you mentioned by adding a sphere with kinematic physics body to the scene created in the example above, and moving it so that it collides with the door. However, I'm not seeing the issue you described, as the door appears to respond correctly to the physics interaction by rotating around its hinge without separating from it. The following code should be all you need to test this for yourself along with the previously provided example code.

let sphereEntity = ModelEntity(mesh: .generateSphere(radius: 0.1), materials: [SimpleMaterial()])
sphereEntity.generateCollisionShapes(recursive: false)
sphereEntity.components.set(PhysicsBodyComponent(mode: .kinematic))
sphereEntity.position = [0, 0.5, -5]
sphereEntity.components.set(PhysicsMotionComponent(linearVelocity: [0,0,10]))
content.add(sphereEntity)

If you are still running into issues please let me know, and feel free to share a snippet of the offending code!

Hi, @a.p.

I was able to recreate the behavior you described using the sample app you provided. This behavior appears to be the result of using kinematic physics bodies to represent the hands. Whenever one of these kinematic physics bodies overlaps with the door the physics simulation must find a way to resolve this collision and stop the two physics bodies from overlapping. Since the kinematic physics bodies will not be moved as a result of the collision by definition, the door must move instead to bring the physics simulation out of the colliding state. This can result in the door leaving its hinge in order to resolve the collision.

There are a couple of approaches you could take to remedy this issue. One approach is to use dynamic physics bodies to represent the hands, where you provide a restoring force every frame to try to keep them up to date with the user's actual hand positions. This would allow the user's hands to have correct physics interactions with the door, but could lead to situations where the virtual hands lag behind the user's actual hand positions, especially during a collision. Another approach is to represent the hands using TriggerVolumes or, more directly, collision components set to the trigger mode. This way whenever a collision occurs between the hands and the door you can provide a procedural force to the door to simulate it being interacted with.

Thanks for looking into this @Vision Pro Engineer

While waiting I actually tried making the hand bodies dynamic and used a System to update their positions that hand tracking producted. This works fine for a while and seems like an easy workaround because the door behaves perfect. However, after a while and to my surprise, the spheres that are dynamic and represent the fingers end up at different positions than the hand tracking data (some kind of exploded offset which is relatively stable) and never return back despite my code setting their positions to tracking data the same way as at the beginning where it worked. This effect mainly starts to occur when dynamic entities overlap but strangely keeps on going despite me moving the hands away and collision free.

I have therefore tried to create a DTS (Case-ID: 8614262) and pointed it to this thread because I have a reproducible case and a video documenting it but that DTS has not yet been handled at all.

For me, the DTS / this thread stand for a “recommended / documented way for hand-object interactions” which in my opinion is definitely a common case but is not well communicated/documented.

I also briefly tried to work with forces/impulses instead but I could never get rid of heavy oscillations despite my best efforts to dampen things. I can’t be that you need to be an expert in this topic (if I would, I would write my own physics engine).

Accepted Answer

Hi @a.p.

The key to getting dynamic physics bodies to work in this approach is to use addForce instead of manually setting their positions. This ensures the physics bodies interact with other objects in the physics simulation in a natural way.

I spent some time modifying your code to get this to work and here's what I came up with:

First, create a component and a system responsible for adding force to the hand joints to move them toward a target position. This system will be used to move the hand joints to their tracked real world positions using forces instead of directly setting their positions.

/// Component that stores the current target position of a hand joint.
struct DynamicTargetComponent: Component {
    var targetPosition: SIMD3<Float>
}

/// System that adds force to the hand joints' dynamic physics bodies to move them towards their target positions.
struct DynamicTargetSystem: System {
    let query = EntityQuery(where: .has(DynamicTargetComponent.self))
    
    public init(scene: RealityKit.Scene) { }
    
    public func update(context: SceneUpdateContext) {
        let handJointEntities = context.entities(matching: self.query, updatingSystemWhen: .rendering)
        
        for handJointEntity in handJointEntities {
            guard let handJointModelEntity = handJointEntity as? ModelEntity,
                  let physicsBody = handJointModelEntity.components[PhysicsBodyComponent.self],
                  let dynamicHandJointComponent = handJointModelEntity.components[DynamicTargetComponent.self] else { return }
            
            // Get the vector pointing in the direction of the target position.
            let toTarget = dynamicHandJointComponent.targetPosition - handJointModelEntity.position(relativeTo: nil)
            
            // Add force to the hand joint in that direction, multiplying by mass so that this
            // works consistently no matter what the mass of the hand joint itself is.
            let forceFactor: Float = 5000 * physicsBody.massProperties.mass
            handJointModelEntity.addForce(toTarget * forceFactor, relativeTo: nil)
        }
    }
}

Then, be sure to register the DynamicTargetSystem. I do this at the top of the RealityView.

RealityView { content in 
    DynamicTargetSystem.registerSystem()

    //...
}

Finally, replace the for loop iterating over every joint in the handSkeleton in your code with the following snippet.

for joint in handSkeleton.allJoints.filter({ $0.isTracked }) {
    let partID = "\(joint.name)-\(anchor.chirality)"
    var part = parts[partID]
    
    // Get the transform matrix for the current joint.
    let transform = anchor.originFromAnchorTransform * joint.anchorFromJointTransform
    // Extract its position from the transform matrix.
    let jointPosition = simd_make_float3(transform.columns.3)
    
    if part == nil {
        part = ModelEntity(mesh: .generateSphere(radius: 0.01), materials: [mat3])
        part!.generateCollisionShapes(recursive: false)
        part!.components.set(OpacityComponent(opacity: 0.5))
        
        // Add a dynamic physics body component not affected by gravity.
        var physicsBody = PhysicsBodyComponent(mode: .dynamic)
        physicsBody.isAffectedByGravity = false
        physicsBody.massProperties.mass = 10  // Increase mass to prevent the door from moving the hand too much.
        physicsBody.linearDamping = 50  // Use absurdly high linear damping to reduce overshoot and springiness.
        part!.components.set(physicsBody)
        
        // Prevent the hand joints from being able to collide with one another using a collision filter.
        let handJointGroup = CollisionGroup(rawValue: 1 << 1)
        let allButHandJointGroup = CollisionGroup.all.subtracting(handJointGroup)
        let allButHandJointFilter = CollisionFilter(group: handJointGroup, mask: allButHandJointGroup)
        part!.components[CollisionComponent.self]?.filter = allButHandJointFilter
        
        // Add a `DynamicTargetComponent` to the joint.
        part!.components.set(DynamicTargetComponent(targetPosition: jointPosition))
        // Set the starting position of the joint.
        part!.position = jointPosition
        
        handRoot.addChild(part!)
        parts[partID] = part
    }
    
    // Set the joint's target position to be its current tracked position.
    // The `DynamicTargetSystem` will then try to move the joint toward this position every frame using forces.
    part?.components[DynamicTargetComponent.self]?.targetPosition = jointPosition
}

You should now be able to use your hands to interact with the door. You can even pinch the door to rotate it around!

This approach isn't perfect. Colliders can get caught on the door and temporarily lag behind the hands as I mentioned in my previous response. That being said, there are a number of variables you can tweak to adjust this approach to your liking:

  1. mass: Increase this value to increase the rigidity of the hands and improve their ability to boss around the door and other physics objects in the scene. Decrease this value to allow the door and other physics objects to move the hands and behave more naturally when colliding.
  2. linearDamping: Increase this value to increase the rate at which the hand joints come to rest. This is useful for preventing them from overshooting their targets, but can cause them to move too slowly if increased too high.
  3. forceFactor: Increase this value to increase the speed at which the hand joints reach their target positions—but be careful—if you increase this value too high the hand joints will overshoot and oscillate around their target positions.

Thanks for the extended reply @Vision Pro Engineer. I actually approached this in the same way but you added some crucial extra details that I missed. I will add them to my own code and verify.

I’ll get back with results and maybe if I get some time make this into a swift package for everyone to use.

Is it true that without code, Reality Composer Pro cannot make a vertical door that swings with gravity?
 
 
Q