Skip to content

Instantly share code, notes, and snippets.

@mstevenson
Last active September 9, 2024 17:33
Show Gist options
  • Save mstevenson/4958837 to your computer and use it in GitHub Desktop.
Save mstevenson/4958837 to your computer and use it in GitHub Desktop.
Unity extension methods for computing a ConfigurableJoint.TargetRotation value from a given local or world rotation.
using UnityEngine;
public static class ConfigurableJointExtensions {
/// <summary>
/// Sets a joint's targetRotation to match a given local rotation.
/// The joint transform's local rotation must be cached on Start and passed into this method.
/// </summary>
public static void SetTargetRotationLocal (this ConfigurableJoint joint, Quaternion targetLocalRotation, Quaternion startLocalRotation)
{
if (joint.configuredInWorldSpace) {
Debug.LogError ("SetTargetRotationLocal should not be used with joints that are configured in world space. For world space joints, use SetTargetRotation.", joint);
}
SetTargetRotationInternal (joint, targetLocalRotation, startLocalRotation, Space.Self);
}
/// <summary>
/// Sets a joint's targetRotation to match a given world rotation.
/// The joint transform's world rotation must be cached on Start and passed into this method.
/// </summary>
public static void SetTargetRotation (this ConfigurableJoint joint, Quaternion targetWorldRotation, Quaternion startWorldRotation)
{
if (!joint.configuredInWorldSpace) {
Debug.LogError ("SetTargetRotation must be used with joints that are configured in world space. For local space joints, use SetTargetRotationLocal.", joint);
}
SetTargetRotationInternal (joint, targetWorldRotation, startWorldRotation, Space.World);
}
static void SetTargetRotationInternal (ConfigurableJoint joint, Quaternion targetRotation, Quaternion startRotation, Space space)
{
// Calculate the rotation expressed by the joint's axis and secondary axis
var right = joint.axis;
var forward = Vector3.Cross (joint.axis, joint.secondaryAxis).normalized;
var up = Vector3.Cross (forward, right).normalized;
Quaternion worldToJointSpace = Quaternion.LookRotation (forward, up);
// Transform into world space
Quaternion resultRotation = Quaternion.Inverse (worldToJointSpace);
// Counter-rotate and apply the new local rotation.
// Joint space is the inverse of world space, so we need to invert our value
if (space == Space.World) {
resultRotation *= startRotation * Quaternion.Inverse (targetRotation);
} else {
resultRotation *= Quaternion.Inverse (targetRotation) * startRotation;
}
// Transform back into joint space
resultRotation *= worldToJointSpace;
// Set target rotation to our newly calculated rotation
joint.targetRotation = resultRotation;
}
}
@CustomPhase
Copy link

CustomPhase commented Feb 15, 2019

Nevermind, i was using connectedBody rotation, instead of direct parent. This works perfectly fine for setting world target on local joints:

public static void SetTargetRotation(this ConfigurableJoint cj, Quaternion startRot, Quaternion target, Space space)
{
	Vector3 right = cj.axis;
	Vector3 forward = Vector3.Cross(cj.axis, cj.secondaryAxis).normalized;
	Vector3 up = Vector3.Cross(forward, right).normalized;
	Quaternion localToJointSpace = Quaternion.LookRotation(forward, up);
	if (space == Space.World)
	{
		Quaternion worldToLocal = Quaternion.Inverse(cj.transform.parent.rotation);
		target = worldToLocal * target;
	}
	cj.targetRotation = Quaternion.Inverse(localToJointSpace) * Quaternion.Inverse(target) * startRot * localToJointSpace;
}

@dunky11
Copy link

dunky11 commented Oct 14, 2019

Your script works fine for performing a single rotation, but i always have trouble implementing it in FixedUpdate(). I get the error:
"Skipped updating the transform of this Rigidbody because its components are infinite. Could you have applied infinite forces, acceleration or set huge velocity?"

void Awake() {
        Quaternion startRotation = arm.transform.rotation;
        joint = arm.GetComponent<ConfigurableJoint>();
}

void FixedUpdate() {
        joint.SetTargetRotation(Quaternion.Euler(0, 0.01f, 0), startRotation);
}

I just want to rotate the joint consistently along its joints y-axis. Im currently getting headaches over the simplest tasks using ConfigurableJoints :))

Thanks for any help in advance!

@dunky11
Copy link

dunky11 commented Oct 14, 2019

Nevermind, was my mistake. The problem was that i redefined "startRotation" to be a quaternion in the Awake() call, so always go the default Quaternion value for "startRotation" in FixedUpdate()

@531387937
Copy link

That's so coool!!!!

@iconnary
Copy link

iconnary commented Mar 30, 2020

Can I request that you add an additional method here to return the Local/World space rotation of the joint's targetRotation? I'm trying to Slerp a joint's targetRotation through to a new orientation but I need to know it's current orientation first.

public static Quaternion GetTargetRotation(ConfigurableJoint joint, Quaternion startRotation, Space space) { ... }

Thanks

@TrueBeef
Copy link

TrueBeef commented Apr 6, 2020

Hey @mstevenson, Quick question with the "StartRotation" quaternion. Is this the local rotation (Global, if not a child to anything) of the transform that the joint is created on at the time of joint-creation? Is this ever updated if connected bodies, anchors, etc change?

@jared-cone
Copy link

jared-cone commented Apr 26, 2023

Thanks for this, it works great. I've been trying to wrap my head around what each step is doing so I've paraphrased it below for clarity. I still don't understand why Quaternion.Inverse (targetLocalRotation) * startLocalRotation is required for it to work. It doesn't quite produce the local-space delta rotation.

Anyways here's my understanding of how this is working (for local space):

// Calculate how far the target's local rotation has moved from its initial local rotation.
// The calculation should be `deltaRotation = targetLocalRotation * Quaternion.Inverse(startLocalRotation)`,
//   but for some reason the order of operations has to be changed for this to actually work.
// Note that in order to achieve a desired delta rotation on the body, the opposite of that rotation
//   has to be applied to the joint, and it has to be applied in joint space.
Quaternion deltaRotation = Quaternion.Inverse(targetLocalRotation) * startLocalRotation;

// Calculate rotations for converting from joint space to local space and back.
Vector3 jointX = joint.axis;
Vector3 jointZ = Vector3.Cross(jointX, joint.secondaryAxis).normalized;
Vector3 jointY = Vector3.Cross(jointZ, jointX).normalized;

Quaternion localToJointSpace = Quaternion.LookRotation(jointZ, jointY);
Quaternion jointToLocalSpace = Quaternion.Inverse(localToJointSpace);

// Convert the local space rotation into a joint space rotation.
joint.targetRotation = jointToLocalSpace * deltaRotation * localToJointSpace;

@JosephStar318
Copy link

Thank you very much! You saved my day

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment