Quaternion Wizardry for ConfigurableJoint

We’re using the targetRotation on a ConfigurableJoint to mimic an animation. We need help with some angle stuff.

  • It seems targetRotation is relative to the initial rotation of an object. A target of Quaternion.identity will not move the object to match its parent. It will move it to match its initial position.

  • At any time, we know the object’s localRotation, and the localRotation of the object we want to mimic.

  • How do we describe the target’s local rotation relative to our initial position?

In simple terms, let’s say we have an object with:

  • Initial local rotation of 30 degrees
  • Our target’s local rotation is 40 degrees
  • So our joint’s target rotation should be 10 degrees

But what is this in Quaternion math? This nearly works, but is backwards on one axis:

jointC.targetRotation =   target.localRotation * Quaternion.Inverse(initialRotation);

Help, oh great wizards!

This logic works. Seems like a hack though–so now I’m curious what the “pure” math is:

var from:Vector3 = target.localRotation * Vector3.forward;
var to:Vector3 = initialRotation * Vector3.forward;
	
jointC.targetRotation = Quaternion.FromToRotation(from, to);

Did you get any clarity on this issue?

Shouldn’t the targetRotation be relative to the joints local frame set up by joint axis and secondray axis? My gues is that your solution won’t work if you change the joints axis/secondary axis.

I hate to dig up ancient threads, but since my investigation started here I may as well post a complete solution.

I’ve created a set of extension methods for computing a ConfigurableJoint’s targetRotation from a local or world rotation. There are two very important ConfigurableJoint parameters that most available solutions ignore: axis and secondary axis. Together these produce a unique joint coordinate space. Values in this space must be converted to local or world space before transformations are applied. The resulting value must then be converted back to joint space before being assigned to targetRotation.

The joint coordinate space is unusual: its axes are inverted, and it always begins with a rotation of Quaternion.identity (no rotation at all) regardless of its transform’s local or world rotation. This is nothing that some careful quaternion math can’t solve.

I’ve tested these methods under a variety of conditions and have not been able to get them to fail. This solution fully supports arbitrary ConfigurableJoint axes while allowing rotations to be expressed in a transform’s local or world space.

I’ll be maintaining the code in a public Gist: Unity extension methods for computing a ConfigurableJoint.TargetRotation value from a given local or world rotation. · GitHub

ConfigurableJointExtensions.cs:

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;
	}
}

Usage:

Quaternion initialRotation;
ConfigurableJoint joint;

void Awake ()
{
	joint = GetComponent<ConfigurableJoint> ();
	initialRotation = joint.transform.localRotation;
}

void Start ()
{
	joint.SetTargetRotationLocal (Quaternion.Euler (0, 90, 0), initialRotation);
}
9 Likes

I’ve noticed that this code doesn’t work for when the configurable joint is connecting two rigidbodies together.

Thanks a lot for sharing mstevenson !

Does anyone know of the correct transformation from world to local for targetVelocity? Initially, I had figured it would just be in the reference frame of the spring, so I could do the world to local transformation by multiplying the inverse of the spring rotation. However, that does not seem to fully explain the transformation, as the resulting velocity also appears to depend on the initial position delta from the ConnectedAnchor.

hi, im a liitle late but, for me, i can’t use correctly this code, i think than i must define a correct axis and secondaryAxis, but it’s hard to do, do u have an idea, tuto, link or other to explain us how to define them ?
thx

confJoint.targetRotation = Quaternion.Inverse(animatingLimb.transform.localRotation);
1 Like