Search Unity

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

Question How To create fixed joint at the correct location and rotation (at runtime)

Discussion in 'Physics for ECS' started by Occuros, Aug 11, 2020.

  1. Occuros

    Occuros

    Joined:
    Sep 4, 2018
    Posts:
    299
    The goal is to create a fixed joint as soon as a trigger enter event happens between two cubes (one kinematic without collision, the other fully physics-based with collisions).


    I managed to create a fixed joint if it just needs to attach to the center, but as soon the cubes are rotated it becomes difficult to figure out how to get the proper values so the cube doesn't get teleported when the joint is created.

    Code (CSharp):
    1.    
    2. var fixedComponent = PhysicsJoint.CreateFixed(
    3.                     new RigidTransform(unknownRotationA, unknownOffsetA),
    4.                     new RigidTransform(unknownRotationB, unknownOffsetB));
    5.              
    6. var bodyPair = new PhysicsConstrainedBodyPair(entityA, entityB, false);
    7.  
    The main question is, how to calculate the proper rotation and position offset, so the connection is created at exactly the distance and rotation where the trigger enter event happened (between entityA and entityB).

    I tried with the global position differences, and rotations but that doesn't give the correct result, so probably some tranfromation is required.

    Any help would be appreciated.
     
    Last edited: Aug 11, 2020
  2. steveeHavok

    steveeHavok

    Joined:
    Mar 19, 2019
    Posts:
    481
    I recommend checking out the simple script in the SingleThreadedRagdoll test. That code is showing the basic setup for a BallAndSocket, a LimitedDistance and a Hinge constraint. The code to add extra support for a FixedJoint in that test scene would be:

    Code (CSharp):
    1.             case BasicJointInfo.BasicJointType.Fixed:
    2.                 {
    3.                     var commonPivotPointWorld = math.lerp(pointConPWorld, pointPonCWorld, 0.5f);
    4.  
    5.                     var pivotP = math.transform(math.inverse(bodyPTransform), commonPivotPointWorld);
    6.                     var pivotC = math.transform(math.inverse(bodyCTransform), commonPivotPointWorld);
    7.  
    8.                     var jointFrameP = new BodyFrame(new RigidTransform(bodyPTransform.rot, pivotP));
    9.                     var jointFrameC = new BodyFrame(new RigidTransform(bodyCTransform.rot, pivotC));
    10.                     jointData = PhysicsJoint.CreateFixed(jointFrameP, jointFrameC);
    11.                 }
    12.                 break;
    The RagdollDemo.cs sample script might also be worth checking out. All the Ragdoll and Hinge joints are setup in code, with joint reference frame elements being initially setup in one body space before being mapped, where necessary, to the other body for the second BodyFrame.


    Feel free to ignore the rest of this if the above information is all you want. However, if you indulge me in a little on transforms, as this issue comes up reasonably often. I've found that thinking about transforms in a Parent From Child (rather than a Child To Parent) notation really helps.
    Confusion around Transforms generally comes in two parts:
    1. Vectors are not Transforms!
    2. For transforms, To terminology is more obvious from a maths perspective but From terminology is handier for legibility in code.
    Vectors are not Transforms!
    First if we have two nodes: A at (0,0,0) and B at (0,1,0), we can draw a vector arrow between them. So to get From A To B (or To B From A) we add (0,1,0) to A’s position.
    Normally we just say <A to B> here and vector additions can read left to right.
    However, that basic example makes things to easy assume the same for an identity transform with only an extra translation component but you can’t think of them as the same.

    If considering transforms we are now talking about converting From B’s Space To A’s Space (or To A’s Space From B’s Space).

    So, if we need to convert an object who is at the origin in B’s Space (i.e 0,0,0) into A’s Space, then we use a transform with a translation component of (0,1,0) whose application results in a position of (0,1,0) in A’s Space.
    Normally short hand notation for transforms would read <B to A> (e.g.
    someGameObject.transform.worldToLocalMatrix
    ) but then transform multiplications written down would need to be read right to left.

    Note in the example above the transform <B to A> had a translation component (0,1,0), there a transform <A to B> would a translation component of (0,-1,0) which is different to the vector <A to B> which is (0,1,0). Even though the terminology (i.e. variable names in code) might look very similar (e.g. vAtoB and tAtoB), they are very different operations.

    For transform, use From terminology
    To make transform operations in code look more readable (i.e. transforms can be chained from left to right), within Havok we have tended to the short hand of <A from B> or convert To A’s Space from B’s Space. The addition of the word Space here helps clear up the major difference between the vector and the transform.

    As above, while vector <A from B> and transform <A from B> are named the same they are very different operations.

    Once rotations and scales are added into the transforms the difference between vector and transform becomes more obvious and these trivial translation only examples (that can often lead to confusion), become redundant.

    In short, we use Parent From Child notation for our relative transforms so we can chain things like this...

    Transform worldFromA = worldFromC * inv(BFromC) * BFromA

    ...and make changing from one space to another more explicit and legible in the code.
     
    kro11, Occuros and NotaNaN like this.
  3. Occuros

    Occuros

    Joined:
    Sep 4, 2018
    Posts:
    299
    Hey @steveeHavok, thank you for the elaborate answer.

    I still need to get used to transform math, in mono behaviors its clearer when something gets transformed to local or from local to global space.

    The math library is still a little confusing to me to use, but your examples helped a lot.

    I tried to get inspired by the fixed joint implementation (of the sample project), but that approach seems a lot more complex to the solution you provided.

    I Will try out your simpler approach soon, and again thank you for the help!

    Code (CSharp):
    1.    
    2. var localToWorldA = localToWorldLookup [shouldAttach.EntityA];
    3. var localToWorldB = localToWorldLookup[shouldAttach.EntityB];
    4.  
    5. var worldFromA = Math.DecomposeRigidBodyTransform(localToWorldA.Value);
    6. var worldFromB = Math.DecomposeRigidBodyTransform(localToWorldB.Value);
    7.                                    
    8. RigidTransform bFromA = math.mul(math.inverse(worldFromB), worldFromA);
    9.  
    10.  
    11. var localPositionA = float3.zero;
    12. var localRotationA = quaternion.identity;
    13.  
    14. var localPositionB = math.transform(bFromA, float3.zero);
    15. var localRotationB =  math.mul(bFromA.rot, localRotationA);
    16.                
    17.                
    18. var joint = ecb.CreateEntity();
    19.                
    20. var fixedComponent = PhysicsJoint.CreateFixed(
    21.   new RigidTransform(localRotationA, localPositionA),
    22.   new RigidTransform(localRotationB, localPositionB));
    23.                
    24. var bodyPair = new PhysicsConstrainedBodyPair(shouldAttach.EntityA, shouldAttach.EntityB, false);
    25.  
     
    kro11 and steveeHavok like this.