Search Unity

How do I count the revolutions of a float thats clamped from 0-360?

Discussion in 'Scripting' started by smetzzz, Feb 9, 2017.

  1. smetzzz

    smetzzz

    Joined:
    Mar 24, 2014
    Posts:
    145
    I have a float that adds up and subtracts based on user input. It turns the hand of a clock around 0-360 degrees. When we go around a second time the degree resets to 0. I want to figure out is how do I count how many times I've gone a full revolution around.
    In the end Im trying to get a number that goes not from 0-360 but 0-Infinity.

    Any help is appreciated. Thanks!
     
    Last edited: Feb 10, 2017
  2. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,157
    Division. Keep a second number that stores the value before it is modulo'd and divided it by 360 to get the revolutions.
     
    smetzzz likes this.
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    modulo is the 'remainder' of division

    int divider (\ instead of /) is the 'quotient'... although sadly this doesn't exist in C#, but does in VB.Net of all places.... (it's inherited from DartmouthBASIC)

    Of course you can always just get the quotient by truncating the result from float division (/): Math.Truncate(x / y)

    The quotient of course being how many times you'd "gone around" when getting the modulo.
     
    Last edited: Feb 9, 2017
    smetzzz likes this.
  4. smetzzz

    smetzzz

    Joined:
    Mar 24, 2014
    Posts:
    145
    Hmm, I think Modulo is probably the wrong word for my case. I'm simply counting how many times I go a full 0-360 around. @Ryiah how would you keep that second number? In a loop or if statement? Dividing by 360 will obviously represent one revolution but how do I count up from there with multiple revolutions without the number resetting each time?
     
  5. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,157
    Wait. Isn't that what you're trying to achieve? It might be easier to know what's going on if we saw the code itself.
     
    smetzzz likes this.
  6. smetzzz

    smetzzz

    Joined:
    Mar 24, 2014
    Posts:
    145
    I don't really have any code but I'll write out an example here;

    If I turn the minute hand around multiple times it would be easy to divide by 360:
    turn 0-360 = 1 revolution;
    turn 360-720 = 2 revolutions;
    turn 720-1080 = 3 revolution: etc;

    The problem is the rotation input is clamped between 0-360; As I make a full rotation past 360 my rotation input goes back to 0 and increments from there.
     
  7. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    Don't clamp it to 0-360.

    You instead keep the full raw value, and then you calculate the clamp and the revolutions from that as you need them.

    Code (csharp):
    1.  
    2. public float angle;
    3.  
    4. public float AngleClamped
    5. {
    6.     get { return _angle % 360f; }
    7. }
    8.  
    9. public float Revolutions
    10. {
    11.     get { return (float)System.Math.Truncate(angle / 360f); }
    12. }
    13.  
    Noting that modulo probably isn't the best here, because it doesn't work in negative values the way you probably expect it to. And rather you should use a 'wrap' function. This is mine:

    Code (csharp):
    1.  
    2.         public static float Wrap(float value, float max, float min)
    3.         {
    4.             max -= min;
    5.             if (max == 0)
    6.                 return min;
    7.  
    8.             return value - max * (float)Math.Floor((value - min) / max);
    9.         }
    10.  
    found here:
    https://github.com/lordofduct/space.../blob/master/SpacepuppyBase/Utils/MathUtil.cs
     
    smetzzz and Ryiah like this.
  8. smetzzz

    smetzzz

    Joined:
    Mar 24, 2014
    Posts:
    145
    Im not clamping the rotation. I get the value from transform.rotation.eulerAngle.z; All I have to work with is the 0-360 rotation input. Therein lies my problem...
     
  9. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,521
    Just make an if that increments some value when you reach 359 or something
     
  10. smetzzz

    smetzzz

    Joined:
    Mar 24, 2014
    Posts:
    145
    This works well to increment as I rotate past, but the 'revolution' int doesn't go back down when rotating in reverse.


    if(rotation < 350) {flag = false;}
    if(rotation >= 350 && !flag) {revolution += 1; flag = true;}
     
  11. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    stopping using the transform as the storage of the rotation.

    Rather have your own variable... update it... and then copy that to the transform.

    Separation of view and model.
     
    smetzzz and Ryiah like this.
  12. smetzzz

    smetzzz

    Joined:
    Mar 24, 2014
    Posts:
    145
    Oh man, I didn't think this would be so difficult. Thank you for your help so far.
    I'm confused by this. The transform is the only input I have. I thought there maybe a simple for loop that could count the revolutions of the transform.rotation.z. I can't seem to find anywhere else online that has tackled the problem. It seems relatively straight forward and I was hoping the amount of code talent here would have an easy solution. Maybe I'm looking at it wrong...
     
  13. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,157
    What is rotating the object?
     
    smetzzz likes this.
  14. smetzzz

    smetzzz

    Joined:
    Mar 24, 2014
    Posts:
    145
    This is rotating my object. It works really well and includes velocity to add physics. Its in unity script.

    Code (csharp):
    1.  
    2. #pragma strict
    3. @Range(0, 20)
    4. var mass : float;
    5. var pos : Vector3;
    6. var dragging : boolean;
    7. var velocity : float;
    8. private var previous : float;
    9.  private var t : float;
    10. var hit : RaycastHit;
    11. var layerMask : LayerMask;
    12. var col : Collider;
    13.  
    14. function Update(){
    15.  
    16.       pos = Camera.main.WorldToScreenPoint(transform.position);
    17.       pos = Input.mousePosition - pos;
    18.  
    19.     if (Input.GetMouseButtonDown(0) && (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), hit, Mathf.Infinity,layerMask)) && col == hit.collider)         { previous = Mathf.Atan2(pos.y, pos.x) * Mathf.Rad2Deg; dragging = true;}
    20.     if (Input.GetMouseButtonUp(0) )        {dragging = false;}
    21.  
    22.  
    23.     if (dragging){t=0;    
    24.         velocity = Mathf.Atan2(pos.y, pos.x) * Mathf.Rad2Deg  - previous;
    25.         previous = Mathf.Atan2(pos.y, pos.x) * Mathf.Rad2Deg;
    26.         transform.localRotation.eulerAngles.z  += velocity;
    27. }
    28.      
    29.     else{
    30.     t += Time.fixedDeltaTime;
    31.       transform.localRotation.eulerAngles.z += Mathf.Lerp(velocity, 0 , Mathf.Exp(t)*mass);  
    32.     }}
    33.  
    34.  
     
  15. smetzzz

    smetzzz

    Joined:
    Mar 24, 2014
    Posts:
    145
    To get the 0-360 variable I use transform.rotation.eulerAngles.z to get the Quaternion rather than Euler which gets wonky by rotating only to 270 radians.
     
  16. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,157
    Instead of updating transform.localRotation.eulerAngles.z in that script you want to update the angle in the example code posted by @lordofduct earlier.
     
    smetzzz likes this.
  17. smetzzz

    smetzzz

    Joined:
    Mar 24, 2014
    Posts:
    145
    I see...let me give it a shot.
     
  18. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    You'll have TWO variables. One that is already defined in the transform.rotation (the view) and one stored in your script (the model).

    The 'view' isn't used to store actual useful data. It instead is just there to give a display to what your underlying data is.

    Your 'model' is what is the actual state information.

    Your code updates your model (your custom variable like the source I showed earlier)... and then copies that to the view (the transform).

    You never read state information from the view, because it's considered volatile... it's not actual data, it's just the way we want the data to look. We always consult the model to know the actual state of the data.

    This is useful for incase the 'view' ever changes on you. What if you decide to start making the 'x' axis the face, or maybe its some off weird axis like <1,1,1>. What if you change to 2d, or if you decide to display it digitally (like a digital clock). The view is independent of your data.

    Furthermore, shape your data in a way that makes sense for your data, and transform it as necessary. Is this a clock? How about storing it as a 'TimeSpan' instead, and then copy out the seconds/minutes/etc as rotational values.... degrees = seconds * 6, degrees = hours * 30... that sort of thing.
     
    smetzzz likes this.
  19. smetzzz

    smetzzz

    Joined:
    Mar 24, 2014
    Posts:
    145
    Ok, the only input I have is as a euler which increments 0-270+90 to rotate around. I converted it to a Quaternion to get 0-360 rotation variable to simplify. This would be so easy if I didn't have to go through a clamped rotation variable. I just don't know how I can get linear float from the code I posted above....
     
  20. smetzzz

    smetzzz

    Joined:
    Mar 24, 2014
    Posts:
    145
    Last edited: Feb 10, 2017
  21. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    OK... question... what is it you're actually attempting to do?

    Like WHY do you want to know how many revolutions have occurred?
     
  22. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,157
    You're not treating the euler as an input. It's very much an output. I've tried modifying the code to work with @lordofduct's earlier code but I'm not very knowledgeable of UnityScript (most of us avoid it) and I haven't tested it.

    Code (JavaScript):
    1. #pragma strict
    2. @Range(0, 20)
    3. var mass : float;
    4. var pos : Vector3;
    5. var dragging : boolean;
    6. var velocity : float;
    7. private var previous : float;
    8. private var t : float;
    9. var hit : RaycastHit;
    10. var layerMask : LayerMask;
    11. var col : Collider;
    12.  
    13. var angle: float;
    14.  
    15. function Start() {
    16.     angle = transform.localRotation.eulerAngles.z;
    17. }
    18.  
    19. function Update(){
    20.       pos = Camera.main.WorldToScreenPoint(transform.position);
    21.       pos = Input.mousePosition - pos;
    22.     if (Input.GetMouseButtonDown(0) && (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), hit, Mathf.Infinity,layerMask)) && col == hit.collider)         { previous = Mathf.Atan2(pos.y, pos.x) * Mathf.Rad2Deg; dragging = true;}
    23.     if (Input.GetMouseButtonUp(0) )        {dragging = false;}
    24.     if (dragging){t=0;
    25.         velocity = Mathf.Atan2(pos.y, pos.x) * Mathf.Rad2Deg  - previous;
    26.         previous = Mathf.Atan2(pos.y, pos.x) * Mathf.Rad2Deg;
    27.         angle += velocity;
    28.         transform.localRotation.eulerAngles.z = AngleClamped();
    29. }
    30.  
    31.     else{
    32.     t += Time.fixedDeltaTime;
    33.       angle += Mathf.Lerp(velocity, 0 , Mathf.Exp(t)*mass);
    34.       transform.localRotation.eulerAngles.z = AngleClamped();
    35.     }
    36. }
    37.  
    38. function AngleClamped()
    39. {
    40.     return angle % 360f;
    41. }
    42. function Revolutions()
    43. {
    44.     return (float)System.Math.Truncate(angle / 360f);
    45. }
     
    Last edited: Feb 10, 2017
    smetzzz likes this.
  23. smetzzz

    smetzzz

    Joined:
    Mar 24, 2014
    Posts:
    145
    Yes that makes total sense. Sorry for the meandering topic. I thought counting revolutions would be the way to increment a value based on the transform output. What I really want is a value that can rotate an angle and remain unclamped.
    @Ryiah I was able to work this code but the revolutions toggle between 0-1;
     
  24. smetzzz

    smetzzz

    Joined:
    Mar 24, 2014
    Posts:
    145
    Its this line of code that is doing the work. Mathf.Atan2(pos.y, pos.x) * Mathf.Rad2Deg
    I just don't know how to convert it to an unclamped variable.
     
  25. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    So some quickie Math

    So about Atan function. It is only ever going to return a value from -PI -> +PI, or -180 to +180 after you've converted it back to degrees. Now Vector2.zero is our origin for these calculations. I assume thats the middle of your 2D screen?
    If you click and hit a spot above this, Atan2 will return a value from 0->180 with 0 being anything along the positive x-axis(directly to the right of 0,0), 90 being directly above the center of the screen and 180 being along the negative x-axis(left of the center). Similiar its mirrored for 0->-180 below the middle of the screen.

    At this point if you move around its computing the difference in the angle between your old position and new position and updating the Z-rotation based on this. So it seems like if you move the object this script is attached to up 1 unit and right 1 unit it will rotate 45 degrees positively. Then if you drag it left 2 units to -1, +1 it will continue to rotate positvely another 90 degrees. Lets say our current angle is 176 (we are in quadrant 2). We cross down to quadrant 3 so instantly our angle becomes negative so we move down to -178 (this is only 6 degrees from the positive 176). Which your code correctly detects.
    previous = 176 , current = -178, velocity = -178-176 = -354
    Now 178+ (-352) = -178. which unity wraps around to 182. Exactly 6 degrees forward which is what we want. The problem is our angle holder is now set to -178. Basically we never want to add negative angles to our total angle variable. If we had instead added 360 to velocity till it was positive -354+360 = 6. Then added 178+6 = 182 our total angle would have a nice positive value and we could keep up with revolutions.

    Here is a modified version of @Ryiah code that should not just bounce between 0 and 1 revs:
    Code (CSharp):
    1. #pragma strict
    2. @Range(0, 20)
    3. var mass : float;
    4. var pos : Vector3;
    5. var dragging : boolean;
    6. var velocity : float;
    7. private var previous : float;
    8. private var t : float;
    9. var hit : RaycastHit;
    10. var layerMask : LayerMask;
    11. var col : Collider;
    12. var angle: float;
    13. function Start() {
    14.     angle = transform.localRotation.eulerAngles.z;
    15. }
    16. function Update(){
    17.       pos = Camera.main.WorldToScreenPoint(transform.position);
    18.       pos = Input.mousePosition - pos;
    19.     if (Input.GetMouseButtonDown(0) && (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), hit, Mathf.Infinity,layerMask)) && col == hit.collider)         { previous = Mathf.Atan2(pos.y, pos.x) * Mathf.Rad2Deg; dragging = true;}
    20.     if (Input.GetMouseButtonUp(0) )        {dragging = false;}
    21.     if (dragging){t=0;
    22.         velocity = Mathf.Atan2(pos.y, pos.x) * Mathf.Rad2Deg  - previous;
    23.         previous = Mathf.Atan2(pos.y, pos.x) * Mathf.Rad2Deg;
    24.         // NEW CODE HERE
    25.         while(velocity < 0)
    26.         {
    27.              velocity+=360;
    28.          }
    29.         angle += velocity;
    30.         transform.localRotation.eulerAngles.z = AngleClamped();
    31. }
    32.     else{
    33.     t += Time.fixedDeltaTime;
    34.       angle += Mathf.Lerp(velocity, 0 , Mathf.Exp(t)*mass);
    35.       transform.localRotation.eulerAngles.z = AngleClamped();
    36.     }
    37. }
    38. function AngleClamped()
    39. {
    40.     return angle % 360f;
    41. }
    42. function Revolutions()
    43. {
    44.     return (float)System.Math.Truncate(angle / 360f);
    45. }
    I am also unfamiliar with UnityScript so hopefully that while statement is correctly formatted.
     
    smetzzz likes this.
  26. smetzzz

    smetzzz

    Joined:
    Mar 24, 2014
    Posts:
    145
    Thank You @takatok I have a better sense of how Atan2 works. I wasn't able to get your code to work but I did find this thread http://answers.unity3d.com/questions/808926/track-object-rotation.html The second answer is exactly what I need. I haven't tested for precision yet though. This is what I added to the above script to make it work:
    Code (csharp):
    1.  
    2.          rotationAmount += Mathf.DeltaAngle (delta, angle);
    3.          delta = angle;
    4.      
    5.        
    6. }
    with the rotationAmount I can easily divide by 360 and get the revolutions.
    Also, thank you @Ryiah and @lordofduct for your help. I hope I wasn't too confusing. I learned a bunch along the way. :)
     
    Deleted User and takatok like this.
  27. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    That should do the trick, Delta angle is basically always giving you the positive difference between the angles.
     
    smetzzz likes this.