How to use target rotation on a configurable joint to make the object point in a certain direction?

Hello, I’m trying to set up the player carrying a box in my first person game, using a configurable joint, and I’ve managed to get it all working except from the rotation of the box. I would like it such that the front face of the box always towards the player (on the horizontal plane) and the bottom face of the player always points downwards in the direction of gravity. The configurable joint is set connecting the box to the player camera, so that the box is the child of the camera, which in turn is the child of the player object. This is necessary for smooth movement of the box with the player. All the configurable joint properties are set in local space.

However, I can’t for the life of me understand quaternions, and I can’t get the rotation to work. Any help would be greatly appreciated. Ideally, the box’s rotation as the player moves should be like that found in Portal, but with a “bouncier” feel due to the physics joint.

Thank you in advance,

The Spaniard

A configurable joint’s target rotation is difficult to compute. Joints have their own coordinate space, and they always begin with a rotation of Quaternion.identity (equivalent to no rotation at all) regardless of their transform’s local or world rotation.

I’ve written two extension methods that allow you to easily set a ConfigurableJoint’s target rotation using either a local or world rotation value. You’ll need to cache your joint’s local or world rotation on Start (depending on which method you intend to use) so that these methods can counter-rotate the value during the calculation.

Example usage:

var startRotation = transform.localRotation;
var myJoint = GetComponent<ConfigurableJoint> ();
myJoint.SetTargetRotationLocal (Quaternion.Euler (0, 90, 0), startRotation);

ConfigurableJointExtensions.cs:

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

I’ve posted this code to Gist and will keep it updated if any modifications are required as I continue using these methods:

You usually sort this by means of parenting.
Basically, very much likely the box model will have some ‘local’ axes that are different from unity’s world axes, and thus what’s “forward” or “up” for the box, faces in a different direction than unity’s world.

To fix this, you create an empty gameobject, assign the box model as a child of this, and orient -the box- model to align to the parent, world aligned object.

This way, if you

parentgameobject.transform.LookAt(Vector3.Forward);

you’ll see that the box actually looks forward.
The only inconvenience is that you will need to apply joint, collider and rigidbody to the -parent-, and thus handling becomes a little less intuitive.

I tried @mstevenson’s solution, but it didn’t work for me. Perhaps I was using it wrong, perhaps not… either way, I made my own simplified version of the extension for the configurable joint.

Link here: Unity extension method for setting a ConfigurableJoint.TargetRotation to a world rotation · GitHub . Hopefully it saves someone a few days of headache and math.

203892-lookat.gif

note: beware, my version has some issues when the axes are set to unusual values… For my uses it worked correctly.