Skip to content

Instantly share code, notes, and snippets.

@mstevenson
Last active July 22, 2026 00:57
Show Gist options
  • Select an option

  • Save mstevenson/4958837 to your computer and use it in GitHub Desktop.

Select an option

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
Copy Markdown

So is there no way of setting the world-space target on local-configured joint? Ive tried rotating the target to inverse parent rotation, but it doesnt seem to be correct. Any ideas?

@CustomPhase

CustomPhase commented Feb 15, 2019 •

Copy link
Copy Markdown

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

dunky11 commented Oct 14, 2019

Copy link
Copy Markdown

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

dunky11 commented Oct 14, 2019

Copy link
Copy Markdown

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
Copy Markdown

That's so coool!!!!

@iconnary

iconnary commented Mar 30, 2020 •

Copy link
Copy Markdown

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

TrueBeef commented Apr 6, 2020 •

Copy link
Copy Markdown

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

jared-cone commented Apr 26, 2023 •

Copy link
Copy Markdown

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
Copy Markdown

Thank you very much! You saved my day

@BatmanVN

Copy link
Copy Markdown

Pls help me.
public class TheropodJointAngleCopy : MonoBehaviour
{
[Header("Left Foot Transform")]
public Transform tibiaLRef;

public Transform fibulaLRef;

public Transform footLRef;

public Transform footL2Ref;

[Header("Right Foot Transform")]
public Transform tibiaRRef;

public Transform fibulaRRef;

public Transform footRRef;

public Transform footR2Ref;

[Header("Arm Transform")]
public Transform upperArmLRef;

public Transform lowerArmLRef;

public Transform upperArmRRef;

public Transform lowerArmRRef;

[Header("Head & Neck Transform")]
public Transform neckRef;

public Transform neck2Ref;

public Transform headRef;

public Transform jawRef;

[Header("Spine Transform")]
public Transform frontSpineRef;

[Header("Tail Transform")]
public Transform tail1Ref;

public Transform tail2Ref;

public Transform tail3Ref;

public Transform tail4Ref;

public Transform tail5Ref;

[Header("Left Foot Joint")]
public ConfigurableJoint tibiaL;

public ConfigurableJoint fibulaL;

public ConfigurableJoint footL;

public ConfigurableJoint footL2;

[Header("Right Foot Joint")]
public ConfigurableJoint tibiaR;

public ConfigurableJoint fibulaR;

public ConfigurableJoint footR;

public ConfigurableJoint footR2;

[Header("Arm Joint")]
public ConfigurableJoint upperArmL;

public ConfigurableJoint lowerArmL;

public ConfigurableJoint upperArmR;

public ConfigurableJoint lowerArmR;

[Header("Head & Neck Joint")]
public ConfigurableJoint neck;

public ConfigurableJoint neck2;

public ConfigurableJoint head;

public ConfigurableJoint jaw;

[Header("Spine Joint")]
public ConfigurableJoint frontSpine;

[Header("Tails Joint")]
public ConfigurableJoint tail1;

public ConfigurableJoint tail2;

public ConfigurableJoint tail3;

public ConfigurableJoint tail4;

public ConfigurableJoint tail5;

public bool old;
private Dictionary<ConfigurableJoint, Quaternion> startRotations = new Dictionary<ConfigurableJoint, Quaternion>();

private void Awake()
{
    // Lưu lại startRotation của từng joint
    SaveStartRotation(tibiaL);
    SaveStartRotation(fibulaL);
    SaveStartRotation(footL);
    SaveStartRotation(footL2);

    SaveStartRotation(tibiaR);
    SaveStartRotation(fibulaR);
    SaveStartRotation(footR);
    SaveStartRotation(footR2);

    SaveStartRotation(upperArmL);
    SaveStartRotation(lowerArmL);
    SaveStartRotation(upperArmR);
    SaveStartRotation(lowerArmR);

    SaveStartRotation(neck);
    SaveStartRotation(neck2);
    SaveStartRotation(head);
    SaveStartRotation(jaw);

    SaveStartRotation(frontSpine);
    SaveStartRotation(tail1);
    SaveStartRotation(tail2);
    SaveStartRotation(tail3);
    SaveStartRotation(tail4);
    SaveStartRotation(tail5);
}
private void SaveStartRotation(ConfigurableJoint joint)
{
    if (joint != null)
    {
        startRotations[joint] = joint.transform.localRotation;
    }
}


private void FixedUpdate()
{
    ApplyAllJointRotations();

}
private void ApplyAllJointRotations()
{
    ApplyJointRotation(tibiaL, tibiaLRef);
    ApplyJointRotation(fibulaL, fibulaLRef);
    ApplyJointRotation(footL, footLRef);
    ApplyJointRotation(footL2, footL2Ref);

    ApplyJointRotation(tibiaR, tibiaRRef);
    ApplyJointRotation(fibulaR, fibulaRRef);
    ApplyJointRotation(footR, footRRef);
    ApplyJointRotation(footR2, footR2Ref);

    ApplyJointRotation(upperArmL, upperArmLRef);
    ApplyJointRotation(lowerArmL, lowerArmLRef);
    ApplyJointRotation(upperArmR, upperArmRRef);
    ApplyJointRotation(lowerArmR, lowerArmRRef);

    ApplyJointRotation(neck, neckRef);
    ApplyJointRotation(neck2, neck2Ref);
    ApplyJointRotation(head, headRef);
    ApplyJointRotation(jaw, jawRef);

    ApplyJointRotation(frontSpine, frontSpineRef);
    ApplyJointRotation(tail1, tail1Ref);
    ApplyJointRotation(tail2, tail2Ref);
    ApplyJointRotation(tail3, tail3Ref);
    ApplyJointRotation(tail4, tail4Ref);
    ApplyJointRotation(tail5, tail5Ref);
}
public void ApplyJointRotation(ConfigurableJoint joint, Transform refTransform)
{
    if (joint == null || refTransform == null || !startRotations.ContainsKey(joint)) return;

    joint.targetRotation = Quaternion.LookRotation(refTransform.localPosition);
    Quaternion targetRotation = Quaternion.Inverse(refTransform.localRotation);

    joint.targetRotation = targetRotation;


    //Lấy góc quay mong muốn dựa trên Transform tham chiếu
    //Quaternion targetRotation = refTransform.localRotation;

    // Lấy góc quay ban đầu của khớp
    //Quaternion startRotation = startRotations[joint];
    //Quaternion targetRotation = /*Quaternion.Inverse(startRotation) **/ refTransform.localRotation;
    //joint.SetTargetRotationLocal(targetRotation, startRotation); // if i set like this my TRex will wrong

}

}

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