Search Unity

Smart Rotating

Discussion in 'Scripting' started by aaronsullivan, Nov 10, 2005.

  1. aaronsullivan

    aaronsullivan

    Joined:
    Nov 10, 2005
    Posts:
    986
    I'm trying to rotate an object on a specific axis to a specific angle (in degrees.)

    I'm trying to use the built in functions as much as possible, but I can't quite make out how to use this:
    Vector3 RotateTowards (Vector3 from, Vector3 to, float maxRadians, float maxMagnitude)

    Right now, I'm just rotating in one direction always, but I'd like to rotate in the direction that is the shortest rotation amount to my target rotation.

    Something like this:
    Code (csharp):
    1.  
    2. if (Mathf.Round(transform.eulerAngles.x) != Mathf.Round(targetRot)) {
    3.      if (transform.eulerAngles.x - targetRot >= 180)
    4.           transform.Rotate(Vector3.up, Time.deltaTime * rotSpeed);
    5.      else
    6.           transform.Rotate(Vector3.up, -Time.deltaTime * rotSpeed);
    7. }
    8.  
    Only, I realize that the subtraction test against 180 is incorrect. So, yes I can work out all the angle calculations (if angle and target angle are > 0 there is one set of tests and all those combinations) but I'm wondering if there is something I can use instead.

    I've been all over the place with this using target Quaternions and Lerp but that begins switching the use of axis, etc.

    Sum up:
    What does the Vector3.RotateTowards actually do?
    Is there an absolute way to rotate towards an angle and stop at that exact angle on one axis? (handily)

    Any help would be much appreciated. :)
     
  2. aaronsullivan

    aaronsullivan

    Joined:
    Nov 10, 2005
    Posts:
    986
    I've been trying out the longer method and got thinking about some things.

    It's odd to me that LerpAngle never reaches the target, but bounces around it. Shouldn't it set the angle to the target if it goes past it? I don't want that kind of movement anyway, but I was surprised.

    Also, I've seen examples of people doing this and even the documentation suggests this is okay:
    transform.eulerAngles.x = targetAngle;

    When I do that (with C#) I get this error:
    "Cannot modify expression because it is not a variable"

    Since the examples I've seen on the forum are in Javascript I'm guessing this is some difference in the languages I'm missing.

    It seems like such a simple idea: "rotate towards a desired angle until you reach it, then stop at it."
     
  3. Jonathan Czeck

    Jonathan Czeck

    Joined:
    Mar 17, 2005
    Posts:
    1,713
    The error you are getting is some weird "feature" of C#. The solution is something like:

    Code (csharp):
    1. Vector3 angles = transform.eulerAngles;
    2. angles.x = targetAngle;
    3. transform.eulerAngles = angles;
    As far as the rotation stuff... have you considered making a goal rotation expressed as a Quaternion using the EulerAngles->Quaternion constructor, and then Slerping the current transform.rotation to the goal rotation over time?

    HTH,
    -Jon
     
  4. aaronsullivan

    aaronsullivan

    Joined:
    Nov 10, 2005
    Posts:
    986
    Thanks for the tip on the C# difference there. I had a similar workaround but yours is much better. :D

    The Quaternion Slerp was reaching the target angle if I remember right, BUT it would rotate in any old direction it wanted and I wanted to restrict it to rotating on a single axis.

    I'm getting much, much closer now. I've just been trying to use as many of the Unity classes as possible to learn them and I've just been frustrated. I'll post the code that works for me if anyone is interested.

    Anybody know how Vector3.RotateTowards works? I'm still curious.
     
  5. aaronsullivan

    aaronsullivan

    Joined:
    Nov 10, 2005
    Posts:
    986
    So, here is the latest. Being subject to the automatic conversion to Quaternions causes a serious problem in this code (which is modified from working code from my game Space Barrage.)

    When using the transform.Rotate method it will, upon reaching a certain angle, invert the other two axis that haven't been rotated... so, in my case, the x is being rotated negatively from 360 towards 180 and about halfway through the Y and Z axis invert (270 to 90 and 90 to 270) and the X axis compensates so that the object looks like it is the right position/rotation but now the X axis is inverted... so, by the end of the rotation, I'm testing for the wrong condition. It's a little infuriating.


    I guess I'm going to try and check for axis inverting now(?!) There HAS to be a better way... or at least my limited thinking tells me that. :D

    Here's my current code:
    Code (csharp):
    1.  
    2.     bool XRotateTransformTowards(Transform t, float targetAngle, float rotationAmt)
    3.     {
    4.         Vector3 axis = Vector3.down;
    5.         float currentAngle = t.eulerAngles.x;
    6.         Vector3 tempAngles = t.eulerAngles;
    7.         bool reached = false;
    8.        
    9.         if (rotationAmt >= 180f) { //special case - method won't work if we try to rotate > 180(?)
    10.             reached = true;
    11.         }
    12.         else if (currentAngle == targetAngle) { //same angle already?  We are done here.
    13.             reached = true;
    14.         }
    15.         else if (currentAngle > targetAngle) { //one set of rules for this case
    16.             if (currentAngle - targetAngle > 180f) {
    17.                 t.Rotate(axis, rotationAmt); //rotate towards it
    18.                 if (targetAngle + 360f - currentAngle <= rotationAmt ) { //if angle < rotationAmt
    19.                     reached = true;
    20.                 }
    21.             }
    22.             else //same idea but going in the opposite rotation
    23.             {                  
    24.                 t.Rotate(axis, -rotationAmt); //rotate towards it
    25.                 if (currentAngle - targetAngle < rotationAmt) {
    26.                     reached = true;
    27.                 }
    28.             }
    29.         }
    30.         else if (currentAngle < targetAngle) {//opposite rules apply in this case
    31.             if (targetAngle - currentAngle < 180f)
    32.             {  
    33.                 t.Rotate(axis, rotationAmt); //rotate towards it
    34.                 if (targetAngle - currentAngle < rotationAmt ) { //if angle < rotationAmt
    35.                     reached = true;
    36.                 }
    37.             }
    38.             else
    39.             {
    40.                 t.Rotate(axis, -rotationAmt); //rotate towards it
    41.                 if (currentAngle + 360f - targetAngle < rotationAmt) {
    42.                     reached = true;
    43.                 }
    44.             }
    45.         }
    46.        
    47.         //if we've gotten close enough, let's make it as exact as possible
    48.         if (reached == true) {
    49.             tempAngles.x = targetAngle;
    50.             t.eulerAngles = tempAngles;
    51.         }
    52.        
    53.         return reached;
    54.     }
    55.  
     
  6. aaronsullivan

    aaronsullivan

    Joined:
    Nov 10, 2005
    Posts:
    986
    So... I changed tactics completely.

    Instead of the above method, I have my object instantiate an invisible little object with a trigger collider at the angle that I want to reach and after setting a torque on my object I keep raycasting until I hit the little trigger collide object. Once it hits it means I can stop rotating.

    It's still not perfect but it works.

    I ran into some trouble with layerMasks, but got around using them, so that will be a question for a later day. :D
     
  7. Ajaxamander

    Ajaxamander

    Joined:
    Jul 5, 2005
    Posts:
    36
    I use the three-line example provided by nicholas in this thread:
    http://forum.unity3d.com/viewtopic.php?t=1237

    It works very nicely, basically you just "plug in" the forward vector of your rotating object, and a vector from that object that points out at whatever you want to face (which you could construct yourself, ie <1, .5, 0>, for, say, a telescope pointing north with 45° ascension)

    The only edgecase, of course, is that it's not always guaranteed which way (left or right) it will turn if it's completely BEHIND the object that's rotating. But there are a few ways around that.