PhysicsJoint with RotationMotor and Some Initial Rotation Causes Unexpected 360-Degree Spin

I encountered a problem while building an active ragdoll using the Unity Physics package, where most joints unexpectedly spin 360 degrees at the game start. To isolate the issue, I created a simple setup:

  1. Two rigid bodies. Body A has a joint connected to body B. Body B is kinematic (optionally).
  2. A PhysicsJoint connecting them with BallAndSocket and MotorTwist constraints.
  3. The body B has a rotation of 90+ degrees on any axis.
  4. The body A has a non-zero rotation on another axis.


Result: The body A spins 360 degrees around BodyAFromJoint.Axis at the start of the game. This seems to be caused by interpolation between the initial body quaternion and its negative counterpart, resulting in the 360-degree spin. Why the body goes toward the negative quaternion and how to resolve this issue remains unclear to me. My Unity version is 2022.3.44f1. My Unity Physics package version is 1.2.4.

RotationMotorBug

This bug makes it nearly impossible to create stable, complex joint structures like ragdolls.

Below is my joint authoring code, which follows the documentation for creating BodyFrames. I’ve confirmed through testing that the BodyFrames are correct, but the spin persists.

using Unity.Entities;
using Unity.Physics;
using UnityEngine;
using Unity.Mathematics;

public class RotationMotorAuthoring : MonoBehaviour {
    [SerializeField] Transform connectedBody;

    public class Baker : Baker<RotationMotorAuthoring> {
        public override void Bake(RotationMotorAuthoring authoring) {
            DependsOn(authoring.connectedBody);

            if (!authoring.connectedBody) return;

            Constraint ballAndSocket = Constraint.BallAndSocket(1f, 1f);
            Constraint motorTwist = Constraint.MotorTwist(0f, 0.1f, 1f, 1f);

            PhysicsJoint joint = CreateJoint(authoring);
            joint.SetConstraints(new() { motorTwist, ballAndSocket });

            Entity jointEntity = CreateJointEntity(authoring);
            AddComponent(jointEntity, joint);
        }

        public Entity CreateJointEntity(RotationMotorAuthoring authoring) {
            Entity entity = GetEntity(TransformUsageFlags.Dynamic);
            Entity connectedEntity = GetEntity(authoring.connectedBody.gameObject, TransformUsageFlags.Dynamic);
            Entity jointEntity = CreateAdditionalEntity(TransformUsageFlags.Dynamic, false, "Joint");

            PhysicsConstrainedBodyPair bodyPair = new(entity, connectedEntity, false);
            AddComponent(jointEntity, bodyPair);

            PhysicsWorldIndex physicsWorldIndex = default;
            AddSharedComponent(jointEntity, physicsWorldIndex);

            return jointEntity;
        }

        public PhysicsJoint CreateJoint(RotationMotorAuthoring authoring) {
            float3 position = float3.zero;
            float3 axis = new(1, 0, 0);
            float3 secondaryAxis = new(0, 1, 0);

            RigidTransform worldFromA = Unity.Physics.Math.DecomposeRigidBodyTransform(authoring.transform.localToWorldMatrix);
            RigidTransform worldFromB = Unity.Physics.Math.DecomposeRigidBodyTransform(authoring.connectedBody.localToWorldMatrix);

            RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
            float3 connectedPosition = math.transform(bFromA, position);
            float3 connectedAxis = math.normalize(math.mul(bFromA.rot, axis));
            float3 connectedSecondaryAxis1 = math.normalize(math.mul(bFromA.rot, secondaryAxis));
            float3 connectedThirdAxis = math.normalize(math.cross(connectedAxis, connectedSecondaryAxis1));
            float3 connectedSecondaryAxis = math.normalize(math.cross(connectedThirdAxis, connectedAxis));

            BodyFrame bodyAFromJoint = new() {
                Position = position,
                Axis = axis,
                PerpendicularAxis = secondaryAxis,
            };

            BodyFrame bodyBFromJoint = new() {
                Position = connectedPosition,
                Axis = connectedAxis,
                PerpendicularAxis = connectedSecondaryAxis,
            };


            PhysicsJoint physicsJoint = default;
            physicsJoint.BodyAFromJoint = bodyAFromJoint;
            physicsJoint.BodyBFromJoint = bodyBFromJoint;
            physicsJoint.JointType = JointType.Custom;

            return physicsJoint;
        }
    }
}

Here is the repository with a reproduction of this bug:

Thanks for reporting this.

Can you please confirm the Unity Physics package version you are using?
We have made significant improvements to joints and continue to do so.

Note that we provide ragdoll demos, both with ragdolls created in code (4d. Ragdolls.unity) and with a ragdoll created using GameObject physics authoring components (4e. Single Ragdoll.unity). See the Ragdoll scenes here:

Finally, the project above also provides sample scenes for joints and motors:

  • 4c1. All Motors Parade.unity
  • 4c2. Position Motor.unity
  • 4c3. Linear Velocity Motor.unity
  • 4c4. Angular Velocity Motor.unity
  • 4c5. Rotational Motor.unity

All of these use the joint baking code from the custom physics authoring components which is provided as a Unity Physics package sample, which you could have a look at. These authoring components do not suffer from the issues you describe.
I suggest you take inspiration from these bakers for your own joint authoring.

Hi there! Thanks for the response.

I’m using Unity Physics version 1.2.4.

I’ve already checked out the sample projects. Unfortunately, the ragdoll joint isn’t a fit for my setup since I need to use motor constraints—and that’s where the bug pops up. The issue only arises with motor constraints, so using a ragdoll joint wouldn’t solve the problem.

You’re right that the motor demos work without any issues, but that’s because they don’t have the initial rotation conditions that lead to the bug in my case. So, for now, I still don’t have a solution, which is why I went ahead and reported it.

Thanks again for looking into this!