Search Unity

Rotate an object so its child matches another object's child rotation

Discussion in 'Scripting' started by bbQsauce, Jan 8, 2018.

  1. bbQsauce

    bbQsauce

    Joined:
    Jun 29, 2014
    Posts:
    53
    Hello, i'm trying to make a procedural level generation where an object has as childs one or more "connectors" which are empty game objects.

    When i spawn a new objects, one of his connectors should match the position of the old object's connector and its forward Z direction should be opposite of the old connector.
    The problem is that it only matches the Z direction so the attached object sometimes results in a not wanted rotation


    This is the code, i tried doing the same thing with transform.right dir but sometimes rotates the object 180 degrees to match the transform.right axes. Someone knows how to fix this?

    Code (CSharp):
    1. void MatchExits(Connector oldExit, Connector newEntry)
    2.     {
    3.         Transform newModule = newEntry.transform.parent;
    4.  
    5.         Quaternion rot = Quaternion.FromToRotation(newEntry.transform.forward, -oldExit.transform.forward);
    6.         newModule.transform.rotation = rot * newModule.transform.rotation;
    7.      
    8.         //Quaternion rot2 = Quaternion.FromToRotation(newEntry.transform.right, oldExit.transform.right);
    9.         //newModule.transform.rotation = rot2 * newModule.transform.rotation;
    10.  
    11.         Vector3 translation = oldExit.transform.position - newEntry.transform.position;
    12.         newModule.transform.position += translation;
    13.  
    14.     }
     
  2. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    Are the parent and child supposed to be rotated the same? As in parent rotation matches other parent, then child is just no rotation?
    I was trying to work this out as a test, but wasn't 100% confident that I understood your criteria :)
     
  3. bbQsauce

    bbQsauce

    Joined:
    Jun 29, 2014
    Posts:
    53
    I'm not sure if i understand correctly, but i never touch connector (child) rotation, i rotate and translate the whole object (childs included). The connector is an empty game object with its forward pointing out in the direction another object should be attached to.
     
  4. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    Ah, excellent. okay.
    I'm pretty sure you can just do: newNode.transform.forward = -oldNode.transform.forward ?
    Unless I have been confused, again lol

    Just for the record, I was pretty sure I knew the answer to the question I asked (the one you gave), but I thought I'd ask just to be extra sure.
     
  5. bbQsauce

    bbQsauce

    Joined:
    Jun 29, 2014
    Posts:
    53
    With node you mean the empty child right?
    You mean object.transform.forward = -oldNode.transform.forward maybe?
    Because your code rotates the node and not the parent object which is what i need to rotate.
    If that's what you mean then it's not gonna work, because the parent rotation doesn't match the connector's rotation. The object's forward doesn't necessarily match the child's forward.

    By the way here's the link to the guide i followed: https://gamedevelopment.tutsplus.co...ngeons-with-procedural-recipes--gamedev-14360

    If you scroll down there's this image which visualize what i need to do. I couldn't make the MatchExits() function of the guide work for my case specifically, not sure why though :\
     
  6. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    There's a little bit of confusion, for me, in that your first answer said you never rotate the child object you just rotate the parent, and then you said the object's rotation doesn't necessarily match the child's..
     
  7. bbQsauce

    bbQsauce

    Joined:
    Jun 29, 2014
    Posts:
    53
    I'm sorry i'm not a native english speaker so things get messy in my head lol

    With this i meant i never rotate the children individually, but i rotate the parent which automatically rotates the children.
     
    Last edited: Jan 9, 2018
  8. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    Ya, okay.. No problem about the language. You're doing pretty well :)
    Just that one confusion part, but then you cleaned it up, by repeating what I thought you had said 2 posts ago.

    parentNew.transform.forward = -parentOld.transform.forward;

    Does that work for you? I don't have fancy pieces like the gif you posted , but for me with a simple cube that line of code is doing it for me?
     
  9. bbQsauce

    bbQsauce

    Joined:
    Jun 29, 2014
    Posts:
    53
    I meant that that the object's forward doesn't point in the same direction as the child's forward.



    So if i do:
    parentNew.transform.forward = -parentOld.transform.forward;
    The 2 children of the 2 objects will not face themselves.

    I need to get the rotation between the 2 childs and rotate the objects accordingly
     
  10. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    Okay, that was probably slow on my part.. I see what you mean now. :)
    I will try to figure that out now lol
     
  11. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    Edit: So, when I re-tested this, it isn't working well lol. It will only work if the parent rotations are none, to begin with. That can't be that useful. Oh well.. was fun to try.

    Hmm.. So, I'm going to probably give up on this now. It was fun to try to figure it out lol
    Seems simple, but.. obviously not so much for me :)

    If you only rotate on the y-axis, I think maybe this will work:
    Code (csharp):
    1.  
    2. Transform ct = cube1.transform;
    3. Transform ch = ct.GetChild(0);
    4.  
    5. Transform myc = transform.GetChild(0);
    6.  
    7. transform.forward = -ch.forward;
    8. transform.rotation = Quaternion.FromToRotation(Vector3.forward, -myc.forward);
    9.  
    where 'cube1' is the old' and 'transform', the new..
    This is working for me, if the old cube is rotated only on the y, and its child can be rotated on its own local y, too.
    The new one (transform) child can be rotated on the y, also.

    Would be curious to see any other answers. :)
     
    Last edited: Jan 9, 2018
    bbQsauce likes this.
  12. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    1 last update/answer lol.
    Okay, now I found something that works, except if the z axis of rotation has been modified, then things are a little off for the position, but I believe the forward still matches the -forward of the child object... oh boy :)
    Code (csharp):
    1.  transform.rotation = Quaternion.LookRotation(-ch.forward, Vector3.up) * Quaternion.Inverse(myc.localRotation);
    And 1 more update ( this code seems to handle even the x-axis rotated on the first game object):
    Code (csharp):
    1. transform.rotation = ct.rotation * Quaternion.Inverse(ch.localRotation) * Quaternion.Inverse(myc.localRotation);
     
    Last edited: Jan 9, 2018
    bbQsauce likes this.
  13. bbQsauce

    bbQsauce

    Joined:
    Jun 29, 2014
    Posts:
    53
    Hey thanks for the answers! I'm gonna try that out and see if i can find a solution.
     
  14. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    Cool hopefully something works. :) lol
     
  15. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,748
    I'm joining this thread a little late but I thought I would chime in because I actually downloaded that tutorial's repo last month and checked it out.

    I got it working and I think it's pretty spiffy what he did. I then tried to extend it with some of my own pieces and here is some things to note: the main takeaways from his "3d-procedural-dungeon" scene and extending it is this:

    - Make each of your own piece examples have NO rotation and NO scaling in their root transform

    - Make each connecting point an immediate child to that root node.

    Now, from there, you can go and put any other geometry you want any other way you want to on separate sub-GameObjects. underneath your root object.

    Good luck with procedural stuff! It's my personal favorite area of game engineering.
     
  16. bbQsauce

    bbQsauce

    Joined:
    Jun 29, 2014
    Posts:
    53
    Hey thanks for the reply, unfortunately i don't think his algo would work for my case.
    He does:
    newPiece = RotateAround(child2.position, Vector3.up, correctiveRotation);
    But my map has also pieces that need to be attached vertically and that does not allow it since it only rotates around global Y axis, it cannot rotate a piece so it connects to a connector pointing up or down.
     
  17. bbQsauce

    bbQsauce

    Joined:
    Jun 29, 2014
    Posts:
    53
    It turns out my previous solution was actually working properly, there's only a problem when the new child's forward is exactly like the forward of the old child because the function:
    Quaternion rotZ = Quaternion.FromToRotation(newChild.transform.forward, -oldChild.transform.forward);
    Can produce two different rotations if the rotation between the directions is exactly 180 degrees.



    The solution is to check if the forwards of the two childs are the same (which means the rotation between them is 180°) then rotate the object slightly on the right axis of the child.

    Then do the same thing with the right axis of the child, though this time check if the right of the first child is equal to the negated right of the second child, because we want the two children to have opposite forwards but the same rights. Then rotate on the child's forward axis slightly.

    Full code:
    Code (CSharp):
    1.  
    2.                 if (oldChild.forward == newChild.forward)                  
    3.                     newObject.Rotate(newChild.right, 10);
    4.                
    5.                 Quaternion rotZ = Quaternion.FromToRotation(newChild.forward, -oldChild.forward);
    6.                 newObject.transform.rotation = rotZ * newObject.transform.rotation;
    7.  
    8.                 if (oldChild.right == -newChild.right)              
    9.                     newObject.Rotate(child2.forward, 10);              
    10.  
    11.                 Quaternion rotX = Quaternion.FromToRotation(newChild.right, oldChild.right);
    12.                 newObject.transform.rotation = rotX * obj2.transform.rotation;
    13.  
    14.                 Vector3 translation = oldChild.position - newChild.position;
    15.                 newObject.transform.position += translation;
    16.  
    It's not exactly clean but it works, if someone has a better solution feel free to post ^^
     
  18. VinylPixels

    VinylPixels

    Joined:
    Dec 10, 2016
    Posts:
    13
    Hello from the future :) I approached the problem from a different perspective and here's the solution I came up with:

    We want a situation where both "connection points" have the same position and the same rotation in world space. This means that the position and rotation of the "new chunk" in local spaces of both connection points needs to be the same.

    As counterintuitive as it may seem, I took the new chunks "forward", "up" and "position" and did InverseTransformDirction and InverseTransformPoint to get those values in the "child connection point local space". Then you take those local values and do TransformDirection and TransformPosition on the other connection point to get "world space" values.

    Then you simply move the chunk to the given position and set the rotation to Quaternion.LookDirection(worldForward, worldUp).

    Since the chunk position and rotation in the child connection point local space didn't change (the point moved with the chunk) and now the chunk has the same local position and rotation in the other connection point local space - both local points end up in the same position and with the same rotation in world space :)

    Code (CSharp):
    1.        
    2.         var localForward = childPoint.transform.InverseTransformDirection(chunk.transform.forward);
    3.         var localUp = childPoint.transform.InverseTransformDirection(chunk.transform.up);
    4.         var localPosition = childPoint.transform.InverseTransformPoint(chunk.transform.position);
    5.      
    6.         var worldForward = connectionPoint.transform.TransformDirection(localForward);
    7.         var worldUp = connectionPoint.transform.TransformDirection(localUp);
    8.         var worldPosition = connectionPoint.transform.TransformPoint(localPosition);
    9.  
    10.         chunk.transform.position = worldPosition;
    11.         chunk.transform.rotation = Quaternion.LookRotation(worldForward, worldUp);
    12.  
     
    Last edited: Aug 28, 2020
  19. BolverkGames

    BolverkGames

    Joined:
    May 21, 2015
    Posts:
    2
    This code worked like a charm! If the connection points were to point in the same direction (like mine did) I just changed -oldChild.forward to oldChild.forward.

    The code posted by VinylPixels however did not work
     
  20. xacce

    xacce

    Joined:
    Nov 10, 2013
    Posts:
    6
    VinylPixels code its working but for turn and direction repeat. It works correctly if the directions are different. And the naming is confusing, of course. The version of the code that works correctly for points with symmetric direction looking away from the object.
    Renamed/ and support cross spots directions example:

    Code (CSharp):
    1.      var rightConnectorRotConj = math.conjugate(quaternion.LookRotation(-rightConnector.transform.forward, rightConnector.transform.up));
    2.    
    3.             var localForward = math.rotate(rightConnectorRotConj, rightParent.transform.forward);
    4.             var localUp = math.rotate(rightConnectorRotConj, rightParent.transform.up);
    5.             var localPosition = math.rotate(rightConnectorRotConj, rightParent.transform.position - rightConnector.transform.position) / rightConnector.transform.localScale;
    6.             var worldForward = leftConnector.transform.TransformDirection(localForward);
    7.             var worldUp = leftConnector.transform.TransformDirection(localUp);
    8.             var worldPosition = leftConnector.transform.TransformPoint(localPosition);
    9.  
    10.             rightParent.transform.position = worldPosition;
    11.             rightParent.transform.rotation = Quaternion.LookRotation(worldForward, worldUp);
    rightConnector - moved child connecting spot
    rightParent - movedBlock
    leftConnector - static left block
    Directions:https://disk.yandex.ru/i/IApwKJuAfxvP0w (cloud link)
    Result: https://disk.yandex.ru/i/_jfhbHpIXysKPQ (cloud link)
     
  21. Kushulain

    Kushulain

    Joined:
    Sep 20, 2012
    Posts:
    19
    A generic way of doing it (assuming both transform don't need to have opposite direction, like in your example)

    Code (CSharp):
    1.  
    2. public static void MoveRootSoChildMatchTarget(Transform root, Transform rootChild, Transform target)
    3.         {
    4.             // Find the rotation offset between the root and the rootChild inside. (subtracting rotations)
    5.             var rotationOffset = target.rotation * Quaternion.Inverse(rootChild.rotation);
    6.  
    7.             // Rotate root so the rootChild is aligned with the target.
    8.             root.rotation = rotationOffset * root.rotation;
    9.  
    10.             // Move the root so the rootChild is at the target.
    11.             root.position += target.position - rootChild.position;
    12.         }
    13.  
     
    seeyam likes this.
  22. sondyr

    sondyr

    Joined:
    Apr 13, 2014
    Posts:
    7
    Worked like a charm! Thank you Kushulain