If I disable/enable a hinge joint the limits reset to match the new rotation of the attached rigidbodies.
Is there a fix or workaround to disable this behaviour.
If I disable/enable a hinge joint the limits reset to match the new rotation of the attached rigidbodies.
Is there a fix or workaround to disable this behaviour.
We solve this with such approach:
When we disabled Joint and want to enable it again:
This is a bit confusing, but it works and seems reliable.
I just hit this exact problem and will probably do the same.
This behaviour very un-intuitive and should be written with BIG BOLD TEXT in the documentation.
Edit: It didnât work at first. I reset my character back to T-pose and then set to the desired pose. Joints still were broken (even with waiting some frames after T-Pose). Had to deactivate and activate it a second time after T-pose to force them rebind. Weird!
I ran into this exact problem while using ragdolls. Basically what was happening was that I had a character who has some character joints always active but just in kinematic mode so I can toggle the physics at will. Then when my character was disabled from SetActive() and re-enabled, the ragdoll mode after that point made the joints fly everywhere and bend in angles they shouldnât. Turns out that when you re-enable an object it does this angle recalculation even though you never actually make a change to the connected body or the angles/axis/etc.
This tiny consequence was so obscure I thought it was a bug for the longest time. Initially I thought that the jointâs axis was just âgetting out of whackâ after SetActive(), so I tried to just fiddle with the axis parameter whenever I enter ragdoll mode in order to âreset itâ. It kind of worked, but when my characterâs leg bent for an animation I found that the angles were still screwed up even after setting the axis parameter at that moment. Thatâs when I realized the jointâs angles were actually âcorrectâ, it was just correct based on the current instance of time instead of the initial rotation. Thanks to that experiment, I have found that you can cause this angle recalculation just by setting the CharacterJoint.axis property.
With that piece of knowledge, this is the code I ended up using to fix it:
var lastRot = transform.localRotation;
transform.localRotation = initRot;
characterJoint.axis = characterJoint.axis; //yes, really
transform.localRotation = lastRot;
I do that for every joint in my ragdoll whenever I enter the âragdollâ mode, i.e. physics is active for the rigidbodies, i.e. isKinematic is false. initRot is the local rotation that I retrieve at Awake(). This way, in case the angles get wrecked due to animation combined with a SetActive angle retrigger, the initial rotation is reset, you set the axis to literally itself which somehow causes a calculation, then put the rotation back to where it was. Now you have an angle which is based on the initial rotation instead of wherever it moved last.
Really weird quirk of Unity physics, and as expected, not documented anywhere. It was so hard to nail down what was causing this. But finally I have an understanding and was able to find this post. Thanks for everyoneâs suggestions above, it helped even years later.
EDIT: Typo
This just saved my sanity. Thank you so much.
Iâm trying to get my head around this but cant seem to get the desired behavior.
My joint limits are only respected if the connected objects are in their initial identity rotations when the limits were configured. If I rotate one of the components so that on start itâs in a more natural position for the parentâs orientation, then the limits are out of alignment by the amount I rotated the joint.
This is very frustrating as it means the joint canât be posed in the editor and on play has to âfall into positionâ in order to respect the limits.
How can I set the objects rotation, independently of the joints alignment and without affecting the limit ranges? Iâve tried every permutation of axis and secondaryAxis I can think of to no effect. Axis needed to be set so that the configurable Joints upper and lower X limits can be applied to the objects Z rotation.
Iâve written to Unity (paid) support and they said that this is a feature request, put it on the pile with the rest and gave me no ETA when it will be added. That was 3 years ago. This is what they gave me: https://fogbugz.unity3d.com/default.asp?1171931_2pj5rcg1
But it doesnât give me much info about status
Dumb question: canât you just put your character in the desired relax pose in the prefab and setup joints for that pose? Or you want to special pose in the current scene?
No dumb questions. (For the most part)
The objects are metal pins with rigged streamers attached, most of the time, their orientation works with gravity. Aka, pin aligned to world Z, streamer aligned with world Y (gravity). Unfortunately, not all pins are placed into their start position in this state, leading the the streamer being at odds with gravity. EG: Pin sticking horizontally out of a vertical wall, vs vertically out of the ceiling.
On play some streamers, have to rotate to respond to gravity, causing a visual anomaly as some things âfallâ for no âin-game reasonâ, aside from physics kicking in. I was hoping to avoid having to run a bunch of physics ticks after the scene is loaded for the items at odds to come to rest before hiding the loading screen and actually starting, by pre-rotating the parent bone of the streamer so that it aligned with gravity.
So if the above solutions didnât work for anyone, this one might. After battling with this Issue for a day or so, Iâve found a method that should work no matter the setup of your parents and objects. The concept is to take the rotation of the joint-object at initialization and transform that quaternion to be relative to the connected bodyâs coordinate plane. You then cache it as the âInitial Rotationâ.
Private Quaternion initialRotation_JointToConnected
initialRotation_JointToConnected = Quaternion.Inverse(joint.transform.rotation) * joint.connectedBody.transform.rotation; //Your new rotation according to the connected bodies coordinate plane. I do this on Awake, and cache it for as long as the object remains in the scene.
Now say your joint has been moved, rotated etc and you now want to enable it in a new location/rotation. Firstly, cache the new current rotation that you want, then take the initial rotation and rotate it to the coordinate plane of the connected body. You then set the Joint rotation to it, reset the axes, and finally put the joint back to the new current rotation.
Quaternion currentRotation = joint.transform.rotation; //caches the new rotation that you want the joint to have.
joint.transform.rotation = Quaternion.Inverse(initialRotation_JointToConnected) * joint.connectedBody.transform.rotation; //Rotates the cached initial rotation to once again match the coordinate plane of the connected body
joint.axis = joint.axis; //Triggers a limit recalculation
joint.transform.rotation = currentRotation; //Puts the joint object back to where you want it!
I have a script attached to every joint, and all this happens on enable. Itâs a bit confusing but once you understand the core of what is happening, this makes a lot of sense and can fit any sort of hierarchy joint setup. Hope this saves someone wasted time fussing around with the axes.
Some extension of True_Beefâs code:
Explanation: Iâve tried to create turntable (for VR) with functionality to move handle to the needed place on the record and activating some actions on that et cetera, at cetera. With possibility to move turntable while handle is in the XR socket. Long story short - I needed to have possibility to disable and enable hinge joint when handle is in socket (without this i had problems with physics).
But also I needed to rotate hadle not just around local Y, but also having it rotated up for several degrees by X. So using (0,1,0) as hinge joint axis gave me the problem, that this y axis of hinge was in local space of handle (i.e. with x rotation of handle), and relatively to the parent X rotation of the handle wasnât conserved, while rotating it around this y axis.
So to solve this, Iâve changed following:
joint = joint ? joint : GetComponent<Joint>();
if (joint) connectedBody = joint.connectedBody;
else Debug.LogError("No joint found.", this);
m_BaseJointAxis = joint.axis;//Saving values from editor
Vector3 localAxis = transform.InverseTransformDirection(transform.parent.transform.TransformDirection(m_BaseJointAxis)); //transform joint axis from editor as if it was in local space of parent of joint, without x rotation, and not it self to the global space and back to the local space of joint
joint.axis = localAxis;//apply this transformed axis to joint
initialRotation_JointToConnected = Quaternion.Inverse(joint.transform.rotation) * joint.connectedBody.transform.rotation; //Your new rotation according to the connected bodies coordinate plane. I do this on Awake, and cache it for as long as the object remains in the scene.
I.e. additionally to saving initial rotation, Iâve transformed hinge axis, so handleâs rotation doesnât affect axis rotation
Additionally, after joint reactivation:
joint.connectedBody = connectedBody;
Quaternion currentRotation = joint.transform.rotation; //caches the new rotation that you want the joint to have.
joint.transform.rotation = joint.connectedBody.transform.rotation*Quaternion.Inverse(initialRotation_JointToConnected); //Rotates the cached initial rotation to once again match the coordinate plane of the connected body
Vector3 localAxis = transform.InverseTransformDirection(transform.parent.transform.TransformDirection(m_BaseJointAxis));
joint.axis = localAxis;//reapplying local axis again
//joint.axis = joint.axis; //Triggers a limit recalculation
joint.transform.rotation = currentRotation; //Puts the joint object back to where you want it!
Iâve also added recalculation of joint axises again.
(Also, it seems, that correct order in rotating joint should be
joint.transform.rotation = joint.connectedBody.transform.rotation*Quaternion.Inverse(initialRotation_JointToConnected);
because if doing it likeTrue_Beef
gave me wrong result: I tried apply rotation and back rotation at the same position (like
initialRotation_JointToConnected = Quaternion.Inverse(joint.transform.rotation) * joint.connectedBody.transform.rotation;
var a = joint.connectedBody.transform.rotation*Quaternion.Inverse(initialRotation_JointToConnected);
Debug.Log(joint.transform.rotation + " " + a);
)
and recieved
(-0.01900, -0.70685, -0.01900, 0.70685)(-0.01900, -0.70685, 0.01900, 0.70685)
as result, not the same rotation, as expected. And if change order as I wrote, all seems correct
Hope it will helps