Search Unity

Rotation interpolation with constant speed

Discussion in 'Scripting' started by rsx, May 4, 2010.

  1. rsx

    rsx

    Joined:
    Nov 4, 2009
    Posts:
    87
    Hello,

    I'm working on a tower defense game and we want to implement a turn speed stat in to the towers. I don't want to slerp the quaternions because it is percentage based so if it has to move farther around the rotation, it will move faster, short = slower. I want constant speed until the angle to target is less than the increment, at which time slerp, or even a jump to look-at will do.

    I also want to output to two Eular angles so I can drive one turret in Y and a child 'gun' in X, but if I can solve the initial problem, I can figure this part out.

    I'll post some code in a sec, but some background might help as a clue to my level of understanding...

    This is my first game project but I work in feature film animation/VFX so I am used to working with transforms. The problem is I have been spoiled and have really only had to use API functions or nodes to bounce between spaces to figure stuff out. I am finding Unity's transforms a little more limiting so I'm sinking deeper in to the math...and it hurts my head! I specialize in creature work, so I am a technical user rather than a developer, even though I do write a lot of code...I guess I'm not willing to call my self hardcore ;).

    Anyway...

    I also need to be able to switch targets so I would either need a co-routine that checked for a change in targets, or do it all in Update().

    First, the slerp version, which most have seen before in some form:
    Code (csharp):
    1.  
    2. public class AnimateTurret : MonoBehaviour
    3. {
    4.     #region Public Properties
    5.     public float rotateSpeed = 0.05f;
    6.     public GameObject turretY;
    7.     public GameObject target;
    8.     #endregion
    9.  
    10.  
    11.     #region Events
    12.     /// <description>
    13.     ///     Runs on frame update
    14.     /// </description>
    15.     private void Update()
    16.     {
    17.         // Store the rotation before anything changes.
    18.         Quaternion sourceRot = turretY.transform.rotation;
    19.        
    20.         // Get a vector towards the target for a new Quaternion oriented to 'lookAt' target.
    21.         Vector3 relativePos = target.transform.position - turretY.transform.position;
    22.         Quaternion destRot = Quaternion.LookRotation(relativePos, turretY.transform.up);
    23.        
    24.         // Rotate by <this.rotateSpeed>%
    25.         Quaternion slerpedRot = Quaternion.Slerp(sourceRot, destRot, this.rotateSpeed);
    26.                
    27.         // Only use the Eular Y data from the slerp
    28.         turretY.transform.eulerAngles = new Vector3(sourceRot.eulerAngles.x,  
    29.                                                              slerpedRot.eulerAngles.y,
    30.                                                              sourceRot.eulerAngles.z);
    31.     }
    32.    
    33.     #endregion
    34. }
    35.  
    36.  
    I can get and write all kinds of data, but I still don't know how to interpolate between two Quaternions in a fixed amount per frame. E.g. add an angle increment (is this multiplying by a vector? How might I get the vector?) Do I have to do something to axis and angles then write it back directly? Obviously, I'm weak on the applied math.

    I'm hoping someone will have an idea I can research further before posting more information. This is already long-winded.

    Thank you.
     
  2. andeeeee

    andeeeee

    Joined:
    Jul 19, 2005
    Posts:
    8,768
    I think you might find it easier to use the physics engine for this rather than code it yourself. You could add a hinge joint to the turret with a soft spring and some damping (you can do this from the inspector without any coding). Then, in the script, set the joint's target angle to wherever you want it to face:-
    Code (csharp):
    1. hingeJoint.spring.targetPosition = <angle you want to face>;
    With this approach, the turret will smoothly turn to face a new target while it is already rotating towards something else. You can set a maximum rotation speed with the rigidbody's maxAngularVelocity property.
     
  3. rsx

    rsx

    Joined:
    Nov 4, 2009
    Posts:
    87
    Thanks for the tip! I finished coding it myself about 10 minutes ago though, hah!

    I will definitely give your way a try. I'm curious what the performance difference would be, assuming they work the same. Now that I have both, it is a question I should ask.

    However, because I'm doing this entirely with vectors, my code below seems very lite. WDYT?

    I'm not sure if this is the best way mathematically, but it works. It requires the objects are oriented so their Y axis is aligned with global Y.

    The turret which only rotates in X (handles altitude) was much more challenging!

    Here is the Y turret. I'm still learning so feedback would be very welcome:
    Code (csharp):
    1.  
    2.     private float turnToTarget()
    3.     {
    4.         Transform turretYTrans = this.turretY.transform;
    5.         Transform targetTrans = this.target.transform;
    6.        
    7.         // Get a vector from the turret towards the target
    8.         Vector3 targetVect = targetTrans.position - turretYTrans.position;
    9.        
    10.         // Set Y to be equal so we are on a XZ plane relative to the turret
    11.         Vector3 turretVect = turretYTrans.forward;
    12.         targetVect.y = turretVect.y;
    13.                        
    14.         // Rotate at the turn speed until less than one increment remains        
    15.         float angleToTarget = Vector3.Angle(turretVect, targetVect);
    16.         float addAngle = 0;
    17.         if (angleToTarget > this.turnSpeed)
    18.         {
    19.             addAngle = this.turnSpeed;
    20.         }
    21.         else   // Use the angle directly.
    22.         {
    23.             addAngle = angleToTarget;
    24.         }
    25.  
    26.         // Compute the vector perpendicular to the start and destination
    27.         //      vectors. When the vectors cross over, the perpindicular
    28.         //      vector will change from pointing up to pointing down, so
    29.         //      the location in Y will become negative. This will allow us  
    30.         //      to rotate around in the shortest direction.
    31.         Vector3 perp = Vector3.Cross(turretVect, targetVect);
    32.         addAngle *= Math.Sign(perp.y); // The sign of a number is only 1 or -1.
    33.        
    34.         // Add the new angle of change to whatever the turret was at before
    35.         turretYTrans.Rotate(0, addAngle, 0);
    36.  
    37.         return angleToTarget;
    38.     }
    Sorry for the formatting weirdness. I copied this straight out of Unities code editor set to a fixed-width font.

    It's funny, I had so many different directions to explore that this tiny bit of fairly simple code took me much longer than expected. It was a great learning exercise though! I still wonder if I could have done it with Slerp in a co-routine using some kind of adjusting ratio of percentage to distance around. Perhaps when I learn more about co-routines I'll know.

    Thanks for the reply,
     
  4. rsx

    rsx

    Joined:
    Nov 4, 2009
    Posts:
    87
    I'm trying out the hinge joint to control my turning turret. I can read the spring's target position just fine...
    Code (csharp):
    1. Debug.Log(this.turretY.hingeJoint.spring.targetPosition);
    ...but when I try and set it:
    Code (csharp):
    1. this.turretY.hingeJoint.spring.targetPosition = 0;
    I get this error:
    Any idea what I'm doing wrong? According to the example in the docs, this should not be read-only. (which would probably be a different error message anyway.)

    I feel like I'm missing something simple :oops:


    A more serious issue with this solution is that Max Angular Velocity on the RigidBody doesn't seem to clamp, like it claims. I'm not sure of the actual effect, but it is relative to the spring settings (higher spring value still makes it move faster and a lower max velocity setting seems to decrease the need to dampen - maybe it is somehow applied first? If that is possible?). I was hoping it would really clamp to a fixed velocity, like my coded version does.

    Using the spring would be really elegant if I could get what I want out of it. It would be far more flexible, and probably perform better.
     
  5. andeeeee

    andeeeee

    Joined:
    Jul 19, 2005
    Posts:
    8,768
    Sorry, I overlooked the fact that you were using C# for some reason...

    In C#, fields of a struct property can't be set individually, rather you have to set the whole struct at once:-
    Code (csharp):
    1. JointSpring spr = hingeJoint.spring;
    2. spr.targetPosition = 0;
    3. hingeJoint.spring = spr;
    As regards using vector arithmetic, my experience is that it often makes for short, elegant solutions to geometric problems. Generally speaking it is no slower than using trig functions directly and there are a few tricks you can use to make it much faster in some cases.
     
  6. rsx

    rsx

    Joined:
    Nov 4, 2009
    Posts:
    87
    I can't believe I didn't think of that! I was getting it right, but setting it wrong.

    Just like getting a rotation, working on it and setting it back.

    Cheers,