Search Unity

  1. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Hinge Joint limits resets on activate object

Discussion in 'Physics' started by hampa, Jul 15, 2017.

  1. hampa

    hampa

    Joined:
    Nov 12, 2014
    Posts:
    5
    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.
     
  2. Artaani

    Artaani

    Joined:
    Aug 5, 2012
    Posts:
    423
    We solve this with such approach:

    When we disabled Joint and want to enable it again:
    1. Save current position and rotation of Joint object
    2. Move and rotate Joint object to default position
    3. Activate it, limits will be reset
    4. Move Joint object back to position which saved before

    This is a bit confusing, but it works and seems reliable.
     
  3. NibbleByte3

    NibbleByte3

    Joined:
    Aug 9, 2017
    Posts:
    80
    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!
     
    Last edited: Mar 26, 2018
    Artaani likes this.
  4. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    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:

    Code (CSharp):
    1. var lastRot = transform.localRotation;
    2. transform.localRotation = initRot;
    3. characterJoint.axis = characterJoint.axis; //yes, really
    4. 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
     
    Last edited: Apr 15, 2020
  5. hairibar

    hairibar

    Joined:
    Feb 25, 2017
    Posts:
    7
    This just saved my sanity. Thank you so much.
     
    zorroxanon and SomeGuy22 like this.
  6. Reahreic

    Reahreic

    Joined:
    Mar 23, 2011
    Posts:
    254
    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.
     
  7. NibbleByte3

    NibbleByte3

    Joined:
    Aug 9, 2017
    Posts:
    80
    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?
     
  8. Reahreic

    Reahreic

    Joined:
    Mar 23, 2011
    Posts:
    254
    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.
     
  9. True_Beef

    True_Beef

    Joined:
    Dec 13, 2014
    Posts:
    2
    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".

    Code (CSharp):
    1. Private Quaternion initialRotation_JointToConnected
    2. 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.

    Code (CSharp):
    1. Quaternion currentRotation = joint.transform.rotation; //caches the new rotation that you want the joint to have.
    2. 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
    3. joint.axis = joint.axis; //Triggers a limit recalculation
    4. 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.
     
  10. tarenstrannik

    tarenstrannik

    Joined:
    Oct 1, 2016
    Posts:
    1
    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:

    Code (CSharp):
    1. joint = joint ? joint : GetComponent<Joint>();
    2. if (joint) connectedBody = joint.connectedBody;
    3. else Debug.LogError("No joint found.", this);
    4. m_BaseJointAxis = joint.axis;//Saving values from editor
    5.  
    6. 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
    7. joint.axis = localAxis;//apply this transformed axis to joint
    8.  
    9. 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.
    10.        
    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:
    Code (CSharp):
    1. joint.connectedBody = connectedBody;
    2.    
    3. Quaternion currentRotation = joint.transform.rotation; //caches the new rotation that you want the joint to have.
    4. 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
    5.  
    6. Vector3 localAxis = transform.InverseTransformDirection(transform.parent.transform.TransformDirection(m_BaseJointAxis));
    7. joint.axis = localAxis;//reapplying local axis again
    8.  
    9. //joint.axis = joint.axis; //Triggers a limit recalculation
    10. 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
    Code (CSharp):
    1. initialRotation_JointToConnected = Quaternion.Inverse(joint.transform.rotation) * joint.connectedBody.transform.rotation;
    2. var a = joint.connectedBody.transform.rotation*Quaternion.Inverse(initialRotation_JointToConnected);
    3. 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
     
    Last edited: Nov 18, 2023