Are PhysicsJoints supported?

I am trying to add joints via code in my visionOS app. My scenario requires me to combine models from Reality Composer Pro with entities and components from code to generate the dynamic result.

I am using the latest visionOS beta and Xcode versions and there is no documentation about joints. I tried to add them via the available API but regardless of how I combine pins, joints and various component, my entities will not get restricted or stay fixated like they are when they are in a child/parent relationship.

I am using RealityKit and RealityView in mixed mode. I also searched the whole internet for related information without finding anything.

Any insights or pointers appreciated!

Answered by a.p. in 796757022

I figured it out with the help of an Apple engineer. He basically confirmed that my initial (much simpler) idea was right and so I made a small demo immersive view that shows how to construct physical joints from code:

import SwiftUI
import RealityKit
import RealityKitContent

extension Entity {
    func makePhysical(
        _ mesh: MeshResource,
        mode: PhysicsBodyMode,
        _ linDamp: Float,
        _ angDamp: Float,
        _ color: UIColor,
        _ position: simd_float3,
        relative: Entity? = nil) -> ModelEntity {
            
        let color = SimpleMaterial(color: color, isMetallic: true)
        let entity = ModelEntity(mesh: mesh, materials: [color])
        entity.setPosition(position, relativeTo: relative)
        self.addChild(entity)
        
        let shape = ShapeResource.generateConvex(from: mesh)
        let material = PhysicsMaterialResource.generate(staticFriction: 100000, dynamicFriction: 100000, restitution: 1)
        var physicsBody = PhysicsBodyComponent(shapes: [shape], mass: 0.1, material: material, mode: mode)
        physicsBody.linearDamping = linDamp
        physicsBody.angularDamping = angDamp
        entity.components.set(physicsBody)
        entity.components.set(CollisionComponent(shapes: [shape], isStatic: false))
        
        return entity
    }
}

extension PhysicsCustomJoint {
    static func addFixedJoint(_ entity1: Entity, _ entity2: Entity, _ pos1: simd_float3, _ pos2: simd_float3) {
        let joint = PhysicsFixedJoint(
            pin0: entity1.pins.set(named: UUID().uuidString, position: pos1),
            pin1: entity2.pins.set(named: UUID().uuidString, position: pos2)
        )
        try! joint.addToSimulation()
    }
    
    static func addBallJoint(
        _ entity1: Entity,
        _ entity2: Entity,
        _ pos1: simd_float3,
        _ pos2: simd_float3) {
            
        var joint = PhysicsCustomJoint(
            pin0: entity1.pins.set(named: UUID().uuidString, position: pos1),
            pin1: entity2.pins.set(named: UUID().uuidString, position: pos2)
        )
        joint.angularMotionAroundX = .unlimited
        joint.angularMotionAroundY = .unlimited
        joint.angularMotionAroundZ = .unlimited
        joint.linearMotionAlongX = .fixed
        joint.linearMotionAlongY = .fixed
        joint.linearMotionAlongZ = .fixed
        try! joint.addToSimulation()
    }
}

struct ImmersiveView: View {
    var body: some View {
        RealityView { content in
            let root = Entity()
            content.add(root)
            var simulation = PhysicsSimulationComponent()
            // simulation.gravity = simd_float3(0, -9.8, 0)
            simulation.solverIterations = .init(positionIterations: 2, velocityIterations: 2)
            root.components.set(simulation)
            
            let ball1 = root.makePhysical(.generateSphere(radius: 0.05),                 mode: .kinematic, 0, 0, .red,     [0, 1.75, -0.75])
            let rod1  = root.makePhysical(.generateCylinder(height: 0.4, radius: 0.025), mode: .dynamic, 0.2, 0, .green,   [0, -0.2, 0], relative: ball1)
            let ball2 = root.makePhysical(.generateSphere(radius: 0.05),                 mode: .dynamic, 0.2, 0, .blue,    [0, 0.2, 0], relative: rod1)
            let rod2  = root.makePhysical(.generateCylinder(height: 0.4, radius: 0.025), mode: .dynamic, 0.2, 0, .magenta, [0, -0.2, 0], relative: ball2)
            let ball3 = root.makePhysical(.generateSphere(radius: 0.05),                 mode: .dynamic, 0.2, 0, .yellow,  [0, 0.2, 0], relative: rod2)
            let rod3  = root.makePhysical(.generateCylinder(height: 0.4, radius: 0.025), mode: .dynamic, 0.2, 0, .cyan,    [0, -0.2, 0], relative: ball3)
            let ball4 = root.makePhysical(.generateSphere(radius: 0.05),                 mode: .dynamic, 0.2, 0, .black,   [0, 0.2, 0], relative: rod3)
            
            let block = root.makePhysical(.generateCylinder(height: 2, radius: 0.025), mode: .kinematic, 0, 0, .white, .zero, relative: nil)
            
            PhysicsCustomJoint.addBallJoint(ball1, rod1, .zero, [0, 0.2, 0])
            PhysicsCustomJoint.addFixedJoint(rod1, ball2, [0, -0.2, 0], .zero)
            PhysicsCustomJoint.addBallJoint(ball2, rod2, .zero, [0, 0.2, 0])
            PhysicsCustomJoint.addFixedJoint(rod2, ball3, [0, -0.2, 0], .zero)
            PhysicsCustomJoint.addBallJoint(ball3, rod3, .zero, [0, 0.2, 0])
            PhysicsCustomJoint.addFixedJoint(rod3, ball4, [0, -0.2, 0], .zero)
            
            Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { _ in
                Task {
                    let f = Date().timeIntervalSince1970 * 4
                    let y = await ball1.position.y
                    var x = Float(sin(f) / 5.0)
                    var z = Float(cos(f) / 5.0)
                    await ball1.setPosition([x, y, z - 0.75], relativeTo: nil)
                    
                    x = Float(sin(-f * 0.97341) / 3.0)
                    z = Float(cos(-f * 0.97341) / 3.0)
                    await block.setPosition([x, y - 0.3, z - 0.75], relativeTo: nil)
                }
            }
        }
    }
}
Accepted Answer

I figured it out with the help of an Apple engineer. He basically confirmed that my initial (much simpler) idea was right and so I made a small demo immersive view that shows how to construct physical joints from code:

import SwiftUI
import RealityKit
import RealityKitContent

extension Entity {
    func makePhysical(
        _ mesh: MeshResource,
        mode: PhysicsBodyMode,
        _ linDamp: Float,
        _ angDamp: Float,
        _ color: UIColor,
        _ position: simd_float3,
        relative: Entity? = nil) -> ModelEntity {
            
        let color = SimpleMaterial(color: color, isMetallic: true)
        let entity = ModelEntity(mesh: mesh, materials: [color])
        entity.setPosition(position, relativeTo: relative)
        self.addChild(entity)
        
        let shape = ShapeResource.generateConvex(from: mesh)
        let material = PhysicsMaterialResource.generate(staticFriction: 100000, dynamicFriction: 100000, restitution: 1)
        var physicsBody = PhysicsBodyComponent(shapes: [shape], mass: 0.1, material: material, mode: mode)
        physicsBody.linearDamping = linDamp
        physicsBody.angularDamping = angDamp
        entity.components.set(physicsBody)
        entity.components.set(CollisionComponent(shapes: [shape], isStatic: false))
        
        return entity
    }
}

extension PhysicsCustomJoint {
    static func addFixedJoint(_ entity1: Entity, _ entity2: Entity, _ pos1: simd_float3, _ pos2: simd_float3) {
        let joint = PhysicsFixedJoint(
            pin0: entity1.pins.set(named: UUID().uuidString, position: pos1),
            pin1: entity2.pins.set(named: UUID().uuidString, position: pos2)
        )
        try! joint.addToSimulation()
    }
    
    static func addBallJoint(
        _ entity1: Entity,
        _ entity2: Entity,
        _ pos1: simd_float3,
        _ pos2: simd_float3) {
            
        var joint = PhysicsCustomJoint(
            pin0: entity1.pins.set(named: UUID().uuidString, position: pos1),
            pin1: entity2.pins.set(named: UUID().uuidString, position: pos2)
        )
        joint.angularMotionAroundX = .unlimited
        joint.angularMotionAroundY = .unlimited
        joint.angularMotionAroundZ = .unlimited
        joint.linearMotionAlongX = .fixed
        joint.linearMotionAlongY = .fixed
        joint.linearMotionAlongZ = .fixed
        try! joint.addToSimulation()
    }
}

struct ImmersiveView: View {
    var body: some View {
        RealityView { content in
            let root = Entity()
            content.add(root)
            var simulation = PhysicsSimulationComponent()
            // simulation.gravity = simd_float3(0, -9.8, 0)
            simulation.solverIterations = .init(positionIterations: 2, velocityIterations: 2)
            root.components.set(simulation)
            
            let ball1 = root.makePhysical(.generateSphere(radius: 0.05),                 mode: .kinematic, 0, 0, .red,     [0, 1.75, -0.75])
            let rod1  = root.makePhysical(.generateCylinder(height: 0.4, radius: 0.025), mode: .dynamic, 0.2, 0, .green,   [0, -0.2, 0], relative: ball1)
            let ball2 = root.makePhysical(.generateSphere(radius: 0.05),                 mode: .dynamic, 0.2, 0, .blue,    [0, 0.2, 0], relative: rod1)
            let rod2  = root.makePhysical(.generateCylinder(height: 0.4, radius: 0.025), mode: .dynamic, 0.2, 0, .magenta, [0, -0.2, 0], relative: ball2)
            let ball3 = root.makePhysical(.generateSphere(radius: 0.05),                 mode: .dynamic, 0.2, 0, .yellow,  [0, 0.2, 0], relative: rod2)
            let rod3  = root.makePhysical(.generateCylinder(height: 0.4, radius: 0.025), mode: .dynamic, 0.2, 0, .cyan,    [0, -0.2, 0], relative: ball3)
            let ball4 = root.makePhysical(.generateSphere(radius: 0.05),                 mode: .dynamic, 0.2, 0, .black,   [0, 0.2, 0], relative: rod3)
            
            let block = root.makePhysical(.generateCylinder(height: 2, radius: 0.025), mode: .kinematic, 0, 0, .white, .zero, relative: nil)
            
            PhysicsCustomJoint.addBallJoint(ball1, rod1, .zero, [0, 0.2, 0])
            PhysicsCustomJoint.addFixedJoint(rod1, ball2, [0, -0.2, 0], .zero)
            PhysicsCustomJoint.addBallJoint(ball2, rod2, .zero, [0, 0.2, 0])
            PhysicsCustomJoint.addFixedJoint(rod2, ball3, [0, -0.2, 0], .zero)
            PhysicsCustomJoint.addBallJoint(ball3, rod3, .zero, [0, 0.2, 0])
            PhysicsCustomJoint.addFixedJoint(rod3, ball4, [0, -0.2, 0], .zero)
            
            Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { _ in
                Task {
                    let f = Date().timeIntervalSince1970 * 4
                    let y = await ball1.position.y
                    var x = Float(sin(f) / 5.0)
                    var z = Float(cos(f) / 5.0)
                    await ball1.setPosition([x, y, z - 0.75], relativeTo: nil)
                    
                    x = Float(sin(-f * 0.97341) / 3.0)
                    z = Float(cos(-f * 0.97341) / 3.0)
                    await block.setPosition([x, y - 0.3, z - 0.75], relativeTo: nil)
                }
            }
        }
    }
}
Are PhysicsJoints supported?
 
 
Q