How to Configure angularLimitInYZ for PhysicsSphericalJoint in RealityKit (Pendulum/Swing Behavior)

Hello RealityKit developers,

I'm currently working on physics simulations in my visionOS app and am trying to adapt the concepts from the official sample Simulating physics joints in your RealityKit app.

In the sample, a sphere is connected to the ceiling using a PhysicsRevoluteJoint to create a hinge-like simulation. I've successfully modified this setup to use a PhysicsSphericalJoint instead.

The basic replacement works as expected: pin1 (attached to the sphere) rotates freely around pin0 (attached to the ceiling), much like a ball-and-socket joint should, removing all translational degrees of freedom.

My challenge lies with the PhysicsSphericalJoint's angularLimitInYZ property. The documentation mentions that this property allows limiting the rotation around the Y and Z axes, defining an "elliptical cone shape around the x-axis of pin0." However, I'm struggling to understand how to specify these values to achieve a desired rotational limit.

If I have a sphere that is currently capable of rotating 360 degrees around pin0 (like a free-spinning ball on a string), how would I use angularLimitInYZ to restrict its rotation to a certain height or angular range, preventing it from completing a full circle?

Specifically, I'm trying to achieve a "swing" like behavior where the sphere oscillates back and forth but cannot rotate completely overhead or underfoot. What values or approach should I use for the angularLimitInYZ tuple to define such a restricted pendulum-like motion?

Any insights, code examples, or explanations on how to properly configure angularLimitInYZ for this kind of behavior would be incredibly helpful!

The following code is modified from the sample.

extension MainView {

    func addPinsTo(ballEntity: Entity, attachmentEntity: Entity) throws {
        let hingeOrientation = simd_quatf(from: [1, 0, 0], to: [0, 0, 1])

        let attachmentPin = attachmentEntity.pins.set(
            named: "attachment_hinge",
            position: .zero,
            orientation: hingeOrientation
        )

        let relativeJointLocation = attachmentEntity.position(
            relativeTo: ballEntity
        )

        let ballPin = ballEntity.pins.set(
            named: "ball_hinge",
            position: relativeJointLocation,
            orientation: hingeOrientation
        )

        // Create a PhysicsSphericalJoint between the two pins.
        let revoluteJoint = PhysicsSphericalJoint(pin0: attachmentPin, pin1: ballPin)
        
        try revoluteJoint.addToSimulation()
    }
}

The following image is a screenshot of the operation when changing to PhysicsSphericalJoint.

Thank you in advance for your assistance.

Answered by Vision Pro Engineer in 849176022

Hi @u_kudo

One key to configuring the angularLimitInYZ property of a PhysicsSphericalJoint is to ensure the local space x-axis of your joint is oriented along your desired world space axis. For example, you can prevent a spherical joint from rotating overhead by orienting the pins of the joint so that they face along the y-axis, and then adding sufficient angular limits to prevent the joint from rotating too far away from the y-axis:

func createAngleLimitedPendulum(length: Float = 0.5) -> Entity {
    let pendulumRoot = Entity()
    
    // Create a static sphere to represent the joint.
    let jointEntity = ModelEntity(mesh: .generateSphere(radius: 0.1), materials: [SimpleMaterial(color: .yellow, isMetallic: false)])
    jointEntity.generateCollisionShapes(recursive: false)
    jointEntity.components.set(PhysicsBodyComponent(mode: .static))
    pendulumRoot.addChild(jointEntity)
    
    // Create a dynamic sphere to represent the pendulum.
    let pendulumEntity = ModelEntity(mesh: .generateSphere(radius: 0.1), materials: [SimpleMaterial(color: .green, isMetallic: false)])
    pendulumEntity.generateCollisionShapes(recursive: false)
    pendulumEntity.components.set(PhysicsBodyComponent(mode: .dynamic))
    pendulumEntity.position = [0, -length, 0]
    pendulumRoot.addChild(pendulumEntity)

    // Orient the joint so that it is facing along the y-axis.
    let jointOrientation = simd_quatf(from: [1, 0, 0], to: [0, 1, 0])
    // Set up the pins.
    let jointPin = jointEntity.pins.set(named: "Sphere", position: .zero, orientation: jointOrientation)
    let pendulumPin = pendulumEntity.pins.set(named: "Pendulum", position: [0, length, 0], orientation: jointOrientation)
    // Create the spherical joint with angular limits that prevent it from rotating overhead
    // (by limiting its y and z rotation to a maximum of 135 degrees away from hanging straight down vertically).
    let pendulumSphericalJoint = PhysicsSphericalJoint(pin0: jointPin, pin1: pendulumPin, angularLimitInYZ: (3 * .pi/4, 3 * .pi/4))
    do {
        try pendulumSphericalJoint.addToSimulation()
    } catch {
        print("Failed to add joint to simulation: \(error)")
    }
    
    // Return the pendulum root entity.
    return pendulumRoot
}

...

var body: some View {
    RealityView { content in
        let pendulum = createAngleLimitedPendulum(length: 0.5)
        pendulum.position = [0, 1, -0.5]
        content.add(pendulum) 
    }
}

By setting the orientation of the pins to simd_quatf(from: [1, 0, 0], to: [0, 1, 0]), and the angularLimitInYZ to (3 * .pi/4, 3 * .pi/4), the pendulum is prevented from rotating further than 135 degrees away from hanging straight down vertically, thereby ensuring it can't swing all the way around above the joint.

That being said, you also mention wanting to prevent the joint from swinging "underfoot"—could you expand on that a little more to help me further understand the kind of constraints you wish to apply to the joint? Do you not only want to prevent the joint from going overhead, but also prevent it from hanging straight down vertically below the joint? Given that angularLimitInYZ specifies limits in the shape of an elliptical cone, it may be difficult to define a non-elliptical constraint with angular limits alone.

Let me know how I can be of further help!

Hi @u_kudo

One key to configuring the angularLimitInYZ property of a PhysicsSphericalJoint is to ensure the local space x-axis of your joint is oriented along your desired world space axis. For example, you can prevent a spherical joint from rotating overhead by orienting the pins of the joint so that they face along the y-axis, and then adding sufficient angular limits to prevent the joint from rotating too far away from the y-axis:

func createAngleLimitedPendulum(length: Float = 0.5) -> Entity {
    let pendulumRoot = Entity()
    
    // Create a static sphere to represent the joint.
    let jointEntity = ModelEntity(mesh: .generateSphere(radius: 0.1), materials: [SimpleMaterial(color: .yellow, isMetallic: false)])
    jointEntity.generateCollisionShapes(recursive: false)
    jointEntity.components.set(PhysicsBodyComponent(mode: .static))
    pendulumRoot.addChild(jointEntity)
    
    // Create a dynamic sphere to represent the pendulum.
    let pendulumEntity = ModelEntity(mesh: .generateSphere(radius: 0.1), materials: [SimpleMaterial(color: .green, isMetallic: false)])
    pendulumEntity.generateCollisionShapes(recursive: false)
    pendulumEntity.components.set(PhysicsBodyComponent(mode: .dynamic))
    pendulumEntity.position = [0, -length, 0]
    pendulumRoot.addChild(pendulumEntity)

    // Orient the joint so that it is facing along the y-axis.
    let jointOrientation = simd_quatf(from: [1, 0, 0], to: [0, 1, 0])
    // Set up the pins.
    let jointPin = jointEntity.pins.set(named: "Sphere", position: .zero, orientation: jointOrientation)
    let pendulumPin = pendulumEntity.pins.set(named: "Pendulum", position: [0, length, 0], orientation: jointOrientation)
    // Create the spherical joint with angular limits that prevent it from rotating overhead
    // (by limiting its y and z rotation to a maximum of 135 degrees away from hanging straight down vertically).
    let pendulumSphericalJoint = PhysicsSphericalJoint(pin0: jointPin, pin1: pendulumPin, angularLimitInYZ: (3 * .pi/4, 3 * .pi/4))
    do {
        try pendulumSphericalJoint.addToSimulation()
    } catch {
        print("Failed to add joint to simulation: \(error)")
    }
    
    // Return the pendulum root entity.
    return pendulumRoot
}

...

var body: some View {
    RealityView { content in
        let pendulum = createAngleLimitedPendulum(length: 0.5)
        pendulum.position = [0, 1, -0.5]
        content.add(pendulum) 
    }
}

By setting the orientation of the pins to simd_quatf(from: [1, 0, 0], to: [0, 1, 0]), and the angularLimitInYZ to (3 * .pi/4, 3 * .pi/4), the pendulum is prevented from rotating further than 135 degrees away from hanging straight down vertically, thereby ensuring it can't swing all the way around above the joint.

That being said, you also mention wanting to prevent the joint from swinging "underfoot"—could you expand on that a little more to help me further understand the kind of constraints you wish to apply to the joint? Do you not only want to prevent the joint from going overhead, but also prevent it from hanging straight down vertically below the joint? Given that angularLimitInYZ specifies limits in the shape of an elliptical cone, it may be difficult to define a non-elliptical constraint with angular limits alone.

Let me know how I can be of further help!

How to Configure angularLimitInYZ for PhysicsSphericalJoint in RealityKit (Pendulum/Swing Behavior)
 
 
Q