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