Search Unity

How to rotate parent so that child faces camera

Discussion in 'Scripting' started by blamejane, Sep 14, 2019.

  1. blamejane

    blamejane

    Joined:
    Jul 8, 2013
    Posts:
    209
    Hey guys, I've been racking my brain for two days trying anything and everything I can to get this seemingly simple rotation to happen.

    It seems like it should be simple. I have a parent object with a few child objects. They all different forward axis's. If the user clicks on child 1, I want the parent to rotate around its Y axis, until the child (forward axis - Z) is facing the camera. Then, if they click on child 2, I want the parent to rotate so that child 2 forward axis is facing the camera.

    Maybe the z axis doesn't matter here. I really just want the user to see the part/object they've selected to be visible to them. Like planets orbiting the sun; whichever planet is selected, the sun and all its child planets should spin around till the selected child planet is in view. Why is this so difficult?

    Again, the parent only should rotate on Y till selected child visible in camera. I've tried testing vector3 and Quaternion solutions. I've tried using coroutines and the Update method. I just can't figure it out.

    Here's what I have in Update...

    Code (csharp):
    1.  
    2.     void Update()
    3.     {
    4.  
    5.         if (_currentPart == null || !_rotateToSelection) return;
    6.  
    7.         Vector3 targetDirection = Camera.main.transform.position - _currentPart.transform.position;
    8.  
    9.         Quaternion targetLook = Quaternion.LookRotation(targetDirection);
    10.  
    11.         //Quaternion targetRot = Quaternion.Inverse(transform.rotation) * _currentPart.transform.rotation;
    12.         Quaternion targetRot = Quaternion.Inverse(targetLook) * _currentPart.transform.rotation;
    13.  
    14.         var angle = Quaternion.Angle(transform.rotation, targetRot);
    15.         if (angle <= 0)
    16.         {
    17.             // quit processing
    18.             _rotateToSelection = false;
    19.         }
    20.  
    21.         transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRot, speed * Time.deltaTime);
    22.  
    23.         Debug.Log("name: "+_currentPart.name + " targetRot: " + targetRot.ToString() + " angle: " + angle);
    24.     }
    25.  
    26.  
     
  2. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    1,104
    What does your current code do, and how is it different from what you wanted?

    When you say you want the child to face the camera, do you mean that you want the line from the center to the child to point towards the camera, or do you mean that you want the child's forward axis to point towards the camera? If the former, I'm not sure what you're trying to do on line 12; if the latter, I'm not sure what you're trying to do on line 7.

    Checking if the output of Quaternion.Angle is <= 0 (line 15) is almost certainly a bad idea, since this will only be true if the two rotations match exactly, and floating-point round-off error might prevent that from ever being true. You should instead check if it's less than some small number, like maybe 0.01f. (But if this were your only problem, your program's behavior would probably look basically correct.)
     
  3. blamejane

    blamejane

    Joined:
    Jul 8, 2013
    Posts:
    209
    Thanks for your help @Antistone

    My current code rotates the parent on its Y axis, in a nice smooth manner, and then gracefully stops; in the wrong spot. I want the parent to stop when the selected child is "in front of"/"visible to"/"centered on" the user/screen. I think I want the childs forward axis pointing at the camera; but whatever is easiest to at the very least rotate each part in front of the camera.

    I'm not sure about the above code, as I've just been piecing snippets together as I find code to try.

    The checking of the Quaternion.Angle was my way of "canceling" the processing of the Update method, which I thought was a good approach ; )

    EDIT: Regarding the child-Z forward axis, I have control over this as the meshes are mine. I can modify the pivot/centers in anyway necessary.
     
    Last edited: Sep 14, 2019
  4. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    1,104
    Is there a pattern to where it stops? For instance, does it maybe always stop 90 degrees off from the correct spot?

    Sounds like what you want is actually the other thing--for the child to be between the center and the camera.

    Here's another way of thinking about the distinction. You've already got the sun, with a bunch of planets around it. Now imagine that one of those planets has a face drawn on it. Do you want the planet in front of the camera, or do you want the planet looking at the camera (with its face pointed towards it)?

    If you want the planet in front of the camera, then you don't care about the planet's rotation, you just care about its position.

    If you want the planet looking at the camera, then you don't care about the planet's position, but you DO care about its rotation.

    I would start by getting rid of line 12 (calculating "targetRot") and just use "targetLook" instead, and see what happens. That might be all you need.

    But I notice there's nothing in your code that takes the camera into account, so at best your code will only work if the camera is in a specific convenient location. You might need to multiply your target rotation by the look rotation from the center to the camera or something like that.

    Well, that's very likely why you're having trouble. Computers are like evil genies: they'll grant your wishes, but you'd better phrase your wishes exactly right, because they're going to give you exactly what you asked for, NOT what you meant.

    Because of that, cobbling together code from multiple sources without understanding what it does is pretty unlikely to achieve your goals. (And even if it appears to work, sometimes it might be doing something weird behind-the-scenes that could cause problems later.)

    You don't necessarily need to understand how a piece of code does something, but you usually do need to at least understand what it does in order to safely combine it with other code.

    It's a reasonable approach of knowing when you're done, except that you should be using an "approximately zero" comparison instead of "exactly zero", because it might not ever actually reach exactly zero.
     
  5. blamejane

    blamejane

    Joined:
    Jul 8, 2013
    Posts:
    209
    There's no pattern that I can find. Trust me that is usually how I solve things, but I couldn't find any rhyme or reason.

    Yes, this.

    I removed line 12 and the parent still rotates to some other spot than where my child object is located. I should mention (in case it's not evident) the script runs on my parent obejct.

    Maybe so. I have a stationary camera, just looking at the sun/planets. The sun is located at the world center. How should I take into account this stationary camera?

    I agree, and not understanding something first has always tripped me up. I'm definitely a brute-force type coder. : P

    Maybe it would be simpler to put a rotate around world origin script on the camera, but I would still need to figure out how to get the camera to stop it the correct spot. Also, this problem has me so bugged, I couldn't possible quit now...gotta solve it.

    So in an effort to understand better. How does the camera "know" when the part is centered? That is, using the below modified Update method, the sun rotates a little, but not around enough for my liking. Why is that? Does the camera see enough of the selected planet that the sun stops rotating. I know it's doing exactly what I'm telling it to do. The problem is I don't know how to phrase, or tell it, what I want it to do.

    The other problem I see is that the parent/sun only rotates to the first planet. Never rotates again when I pick a diff planet. So the targetLook thinks it's in the correct spot for both planets? I don't know. I'm at a loss


    Would changing the comparison from "<= 0" to "<= 1" be a better solution?

    I've modified the Update method to look as so:

    Code (csharp):
    1.  
    2.    void Update()
    3.     {
    4.         if (_currentPart == null) return;
    5.         Vector3 targetDirection = Camera.main.transform.position - _currentPart.transform.position;
    6.         Quaternion targetLook = Quaternion.LookRotation(targetDirection);
    7.    
    8.         transform.rotation = Quaternion.RotateTowards(transform.rotation, targetLook, speed * Time.deltaTime);
    9.     }
    10.  
    11.  
     
  6. blamejane

    blamejane

    Joined:
    Jul 8, 2013
    Posts:
    209
    So I see a problem in my unity scene, which was causing the problem with rotations failing. That is I had a dummy "locator" for each planet at one time, and these dummy locators were basically still assigned as the child gameobjects.

    So I have much better rotations now. But not perfect. That is, the planets aren't centered in front of the camera. They become visible to the camera, rotating around from the back-side of the sun, but they don't continue to rotate around and stop directly in front of the center of the screen; between the camera and the sun
     
  7. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    1,104
    It doesn't. It's got nothing to do with whether your object is in frame or not; that's just coincidental.

    The look rotation from X to Y is the direction that X would have to face in order to be looking towards Y. For example, if you wrote
    X.rotation = Quaternion.LookRotation(Y.position - X.position);

    That would cause X to look towards Y.

    Your current code is calculating the direction that currentPart would need to look in order to see the camera. But then instead of applying that rotation to currentPart, it's applying it to"this" (whatever object your script is running on). Which means "this" is now staring at a point in space that is offset from the camera by the same amount that currentPart is offset from "this". Probably not what you wanted.

    I don't think that's entirely true. You seem to have the vocabulary at least mostly figured out; you're using things like LookRotation and RotateTowards and so forth, and you appear to be using them correctly.

    I think the problem is that you don't know what series of steps that will produce the result that you want. It's like you know what city you want to go to, but you don't know what series of roads will lead to that city. Your computer needs turn-by-turn directions, and you know how to tell the computer to turn, but you don't actually know which turn to take.

    So you need to break your problem down into smaller steps ("turn-by-turn directions") that will build up into the result you want.

    It seems to me there's two important angles here:
    1. The angle of the planet relative to the sun.
    2. The angle of the sun relative to the camera.
    Conceptually, you need the sun to rotate so that it puts the planet "forward", and then rotate from there so that "forward" faces the camera.

    Rotating to put the planet forward is basically the opposite of looking at the planet; if the planet is on your right, you need to rotate left to put it in front of you. So the rotation for that is probably something like

    rotateForPlanet = Quaternion.Inverse(Quaternion.LookRotation(planet.localPosition))


    (This is based on the assumption that the planet is a child of the sun, so it's location position will be its position in the sun's reference frame. Note that this is not the same as planet.position - sun.position if the sun has rotated!)

    Making the sun look at the camera is simply what we said earlier:

    rotateForCamera = Quaternion.LookRotation(camera.position - sun.position)


    To combine two rotations together, you multiply them, something like this:

    totalRotation = rotateForCamera * rotateForPlanet


    Note that multiplication is NOT commutative for Quaternions; A*B isn't the same as B*A. The second rotation is applied "in the reference frame" of the first one. In this case, since you are planning for both rotations to only affect yaw, it probably doesn't matter, but in the general case it could.