Search Unity

How to rotate to face camera around only ONE axis?

Discussion in 'Scripting' started by JoeStrout, Aug 31, 2015.

  1. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    I've been struggling with this one for most of an hour, and it's time to admit defeat.

    I'm just trying to make a 3D axis indicator, like so:
    Those axis lines are textured quads. I need each of these to rotate to face the camera as well as possible, but to do so around only one axis.

    For example, the one labeled "X" needs to rotate only around the X axis. It should do this so that the line and arrow stay as visible as possible, rather than getting skinny like the Z axis in this picture. Yet we can't have it rotating in Y or Z, because then it would no longer properly indicate the X axis. So, I just need the one angle to pass to Quaternion.Euler (for the X rotation) that makes this thing face the camera as well as possible.

    I've tried various dot products, use of the camera.rotation.eulerAngles.x directly, etc., but haven't found anything that works properly.

    I feel there must be a simple solution here, but it's late and I'm just not seeing it. Can anybody help me out?
     
    ROBYER1 likes this.
  2. TheSniperFan

    TheSniperFan

    Joined:
    Jul 18, 2013
    Posts:
    712
    So just so I know I get this right:
    You have an object (say x) and you want it to face the camera as it moves. However, you only want it to rotate around the x-axis.
    Am I correct?
     
  3. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Yes, that's correct. So of course it can't exactly face the camera, but it should face it as closely as possible.

    However, after sleeping on it, I decided that a simple 3D model would better serve me in this case than a textured quad anyway. So I no longer have this burning need... but if somebody has a solution, I'd be glad to hear it anyway!
     
  4. TheSniperFan

    TheSniperFan

    Joined:
    Jul 18, 2013
    Posts:
    712
    I see. You'll be glad to hear that it's extremely simple. :D

    Say you have an gameobject with the orientation
    r0 = (x0, y0, z0)

    You use Unity's API to calculate the rotation so that it faces the camera (Transform.LookAt() for example). This gives you a new orientation that faces the camera on all axes.
    r1 = (x1, y1, z1)

    Given that you only want it to face the camera on the x-axis, this gives you the desired orientation
    r2 = (x1, y0, z0)

    You can set the rotation of the object immediately or slerp it, depending on your needs.

    I hope this helps.
     
  5. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Alas, it's not that simple. This is among the many variations I tried last night. Just to be sure, I tried it again; here's the code:

    Code (CSharp):
    1.     void LateUpdate() {
    2.         Quaternion r1 = Quaternion.LookRotation(transform.position - Camera.main.transform.position, Vector3.up);
    3.         Vector3 euler2 = transform.eulerAngles;
    4.         transform.rotation = Quaternion.Euler(r1.eulerAngles.x, euler2.y, euler2.z);
    5.     }
    Here's the result:

    The script has assigned a rotation of 339° around the X axis (leaving 0 for Y and Z). But by turning the script off and manually adjusting the X rotation, I can get this, which is much better:

    This is a rotation of 275° around X. To me, it seems clear that this is much more "facing the camera" than what the script produces.

    I should point out that this approach does work for reasonably shallow angles. I've used it in the past to make a character turn toward a target, for example, despite slight tilts due to the terrain. But it doesn't seem to work very well for extreme angles. I think this is because of the assumptions inherent in Euler angles.
     
  6. TheSniperFan

    TheSniperFan

    Joined:
    Jul 18, 2013
    Posts:
    712
    That might be it.
    It's almost 1am over here, so I won't look into this right now. But it seems that this should be reasonably easy. If the purely rotational approach failed, I'll see whether this works via "regular" linear algebra.
     
  7. TheSniperFan

    TheSniperFan

    Joined:
    Jul 18, 2013
    Posts:
    712
    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public class AScript : MonoBehaviour {
    4.   [SerializeField]
    5.   private Transform x_object;
    6.   [SerializeField]
    7.   private Transform y_object;
    8.   [SerializeField]
    9.   private Transform z_object;
    10.  
    11.   private Transform transform;
    12.  
    13.   private void Awake() {
    14.   transform = GetComponent<Transform>();
    15.   }
    16.  
    17.   private void Update() {
    18.   Vector3 desired_normal_x = x_object.position - transform.position;
    19.   Vector3 desired_normal_y = y_object.position - transform.position;
    20.   Vector3 desired_normal_z = z_object.position - transform.position;
    21.  
    22.   x_object.rotation = Quaternion.LookRotation(
    23.   new Vector3(0.0f, desired_normal_x.y, desired_normal_x.z), Vector3.up);
    24.  
    25.   y_object.rotation = Quaternion.LookRotation(
    26.   new Vector3(desired_normal_y.x, 0.0f, desired_normal_y.z), Vector3.up);
    27.  
    28.   z_object.rotation = Quaternion.LookRotation(
    29.   new Vector3(desired_normal_z.x, desired_normal_y.y, 0.0f), Vector3.up);
    30.   }
    31. }
     
  8. mezzostatic

    mezzostatic

    Joined:
    Aug 18, 2016
    Posts:
    13
    this uses an invisible object that orbits the billboard at the same position as the camera for 2 axes and the same as the billboard for the third axis, then look at the object. however, I used a cube for the billboard and flattened it, so i can resize the cube so that the desired faces are always facing the camera, for a quad this may do it sideways, it's a good start thought you have to rotate 90 degs after if you have a quad and it's side on.

    1. #pragma strict
      var follow : GameObject;
      var cam : GameObject;
      function Start () {
      }
      function Update () {
      follow.transform.position.x= cam.transform.position.x;
      follow.transform.position.y= cam.transform.position.y;
      follow.transform.position.z= transform.position.z;

      transform.LookAt(follow.transform);
      //transform.eulerAngles.x = 0;
      //transform.eulerAngles.y = 0;
      }
     
  9. GeorgeMincila

    GeorgeMincila

    Joined:
    Feb 28, 2016
    Posts:
    36
    This is an old thread, but perhaps it will help others looking for a solution to this problem. I managed to get the desired result by having another empty GameObject attached to the object that is our main subject, that is needed to be rotated on only 1 axis.

    Make this empty GameObject LookAt the Camera. Take the localRotation on the desired axis and apply that to the object that has the actual content, leaving the angles on the other axis untouched.
    Please take note to use localRotation and not rotation.

    Code in my case below:

    Code (CSharp):
    1.         GameObject.Find("Measurement" + measureNo + "Text").transform.GetChild(1).LookAt(Camera.main.transform);
    2.         Vector3 tempQ = GameObject.Find("Measurement" + measureNo + "Text").transform.GetChild(0).localRotation.eulerAngles;
    3.         tempQ.y = GameObject.Find("Measurement" + measureNo + "Text").transform.GetChild(1).localRotation.eulerAngles.y;
    4.         GameObject.Find("Measurement" + measureNo + "Text").transform.GetChild(0).localRotation = Quaternion.Euler(tempQ);
     
  10. MSQTobi

    MSQTobi

    Joined:
    Feb 12, 2018
    Posts:
    8
    Old thread but here is my rather simple solution to the problem.
    I disliked having to have an extra game object and all solutions proposed seemed more complicated then they needed to be (Unless I'm missing something o_O)

    Code (CSharp):
    1. public class BillboardGO : MonoBehaviour
    2. {
    3.     public enum AllignmentAxis{ X, Y, Z, custom}
    4.     public AllignmentAxis alignmentAxis;
    5.     public Vector3 customAxis;
    6.     void Update()
    7.     {
    8.         Vector3 upAxis = Vector3.zero;
    9.         Vector3 loopAt = Camera.main.transform.position;
    10.         switch (alignmentAxis)
    11.         {
    12.             case AllignmentAxis.X:
    13.                 upAxis.x = 1;
    14.                 loopAt.x = transform.position.x;
    15.                 break;
    16.             case AllignmentAxis.Y:
    17.                 upAxis.y = 1;
    18.                 loopAt.y = transform.position.y;
    19.                 break;
    20.             case AllignmentAxis.Z:
    21.                 upAxis.z = 1;
    22.                 loopAt.z = transform.position.z;
    23.                 break;
    24.             case AllignmentAxis.custom:
    25.                 Vector3 normalized = customAxis.normalized;
    26.                 var plane = new Plane(normalized, transform.position);
    27.                 loopAt = plane.ClosestPointOnPlane(loopAt);
    28.                 upAxis = customAxis;
    29.                 break;
    30.         }
    31.         transform.LookAt(loopAt, upAxis);
    32.     }
    33. }
    This makes the object always face towards the camera while being limited to the specified world axis.
    I also added the option to specify a custom axis, which might be useful if you want to rotate something around a surface normal of a mesh for example.

    Greetings
    MSQTobi
     
  11. palex-nx

    palex-nx

    Joined:
    Jul 23, 2018
    Posts:
    1,748
    1. Rotate to face camera as usual.
    2. Project resulting rotated vector on the appopriate plane.
    3. DONE
     
  12. juvelezm

    juvelezm

    Joined:
    May 23, 2018
    Posts:
    9
    This is the simplest way to do it:
    Code (CSharp):
    1.     void Update() {
    2.         Vector3 lookPos = target.transform.position - transform.position;
    3.         lookPos.y = 0;
    4.         transform.rotation = Quaternion.LookRotation(lookPos);
    5.     }
    6.  
     
    Burnt3nds and viniciuscupello like this.