- Home /
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
Answer by mstevenson · Feb 15, 2013 at 06:48 AM
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:
Thank you. $$anonymous$$ake sure your angular motor mode is set to position, if it is not it will try to reach targetAngularVelocity as well at the same time.
Thank you!! I've been scratching my head about this for the last couple of weeks...
Originally I had tried converting local to/from joint space by usin Quaternion.FromToRotation(axis, Vector3.right), but without the secondary axis as a term, that will fail on several situations.
Your method is far more reliable. I love how you created the jointToWorld rotation.
Cheers!
While this is an old thread, I just wanted to share my experience. @mstevenson 's answer worked for me and gave me the correct rotation, but it had one issue. If you have say 30 configurable joints in the scene, there is a significant slow down. I think the reason is because there are two cross functions. Anyway I came up with an alternative that worked for me and is faster for my 30 joints.
/// <summary>
/// Sets the target rotation of the joint to be the given rotation relative to the original rotation
/// </summary>
/// <param name="joint">The joint whose target rotation is to be set</param>
/// <param name="currentRotation">The orientation you would like the joint to be in</param>
/// <param name="originalRotation">The original orientation of the joint</param>
public static void SetTargetRotation(this ConfigurableJoint joint, Quaternion currentRotation, Quaternion originalRotation)
{
joint.targetRotation = Quaternion.identity * (originalRotation * Quaternion.Inverse(currentRotation));
}
I've added the function to my helpers repo here.
@Oxters, does your extension replace the SetTargetRotationInternal() method that deals with Cross products?
The function I wrote is it's own thing. So you would make the static class with only the function I wrote in it.
Answer by The-Arc-Games · Jul 03, 2012 at 08:42 AM
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.