Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.

Vector3 angle on relative axis

Discussion in 'Scripting' started by HiddenMonk, Jan 25, 2016.

  1. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    I have been trying to figure out how I would go about creating a method similar to Vector3.Angle except it would be Vector3.AngleOnAxis or something.
    For example, I have a currentForwardDirection which is Vector3.forward. I then have another forwardDirection which is (Vector3.right + Vector3.down).normalized. The goal is to get the angle between these two directions, however only in the desired axis, which would be Vector3.forward. So the angle returned would be 45. With just using Vector3.Angle we would get 90.

    I guess the steps would be to first align the vectors in a way so that they are on the same plane of the axis we are trying to get the angle for, and then just use Vector3.Angle.
    I tried something like this
    Code (CSharp):
    1.     public static float AngleOnAxis(Vector3 from, Vector3 to, Vector3 axis)
    2.     {
    3.         Vector3 fromOnPlane = Vector3.ProjectOnPlane(from, axis);
    4.         Vector3 toOnPlane = Vector3.ProjectOnPlane(to, axis);
    5.  
    6.         return ExtVector3.AngleSigned(fromOnPlane, toOnPlane, axis);
    7.     }
    However, it isnt working as desired.
    I think we would need a bit more information such as AngleOnAxis(Vector3 from, Vector3 fromUpAxis, Vector3 to, Vector3 axis)

    Here is an example image of what I am thinking might need to be done.
    Example.png

    Any help is appreciated!
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,032
    From my VectorUtil class:
    https://github.com/lordofduct/space...lob/master/SpacepuppyBase/Utils/VectorUtil.cs
    Code (csharp):
    1.  
    2.         /// <summary>
    3.         /// Find some projected angle measure off some forward around some axis.
    4.         /// </summary>
    5.         /// <param name="v"></param>
    6.         /// <param name="forward"></param>
    7.         /// <param name="axis"></param>
    8.         /// <returns></returns>
    9.         public static float AngleOffAroundAxis(Vector3 v, Vector3 forward, Vector3 axis)
    10.         {
    11.             Vector3 right = Vector3.Cross(axis, forward).normalized;
    12.             forward = Vector3.Cross(right, axis).normalized;
    13.             return Mathf.Atan2(Vector3.Dot(v, right), Vector3.Dot(v, forward)) * MathUtil.RAD_TO_DEG;
    14.         }
    15.  
    What I do is cross axis on forward. This gives us a vector pointing right of forward on the plane defined by axis.

    Then I cross right with axis, this brings forward onto the surface of the plane as well.

    And lastly we just project our desired vector onto these axes, giving us the vector transformed in the space of the plane with 'right' being the local y axes of the plane, and 'forward' being the local x axes of the plane. And because Atan2 measures off the x-axis, we're measuring the angle off of the desired forward.
     
    Last edited: Jan 25, 2016
  3. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    The values I am getting with your latest edit are
    Code (CSharp):
    1. float f = ExtVector3.AngleOffAroundAxis((Vector3.right + Vector3.down).normalized, Vector3.forward, Vector3.forward);
    2. float u = ExtVector3.AngleOffAroundAxis((Vector3.right + Vector3.down).normalized, Vector3.forward, Vector3.up);
    3. float r = ExtVector3.AngleOffAroundAxis((Vector3.right + Vector3.down).normalized, Vector3.forward, Vector3.right);
    4.  
    5. //f = 0, u = 90, r = 90
    6.  
    As displayed with my image above, there should have been at least one 45 degree in there, which leads me to think either I am doing something wrong, or this might not be what I am looking for.
     
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,032
    I think you might be putting in forward and axis backwards... because as those go, 90 degrees IS what should come back for the 2nd and 3rd. Note it's 'axis' that defines the plane you project on... not forward.

    If you swap forward and up, as well as forward and right, in the second and third calls, you'll get 45 degree angles (though signed, so really it'll be -135 and -45).

    This doesn't actually make sense. The angle between any vector and Vector3.forward, around the axis Vector3.forward would be 0. Primarily because no matter how you rotate that other vector around Vector3.forward, it's angle relative to Vector3.forward doesn't change. It's an anomalous state, it doesn't make sense... how far off a vector am I around that vector?

    I mean consider it, if you project that axis onto the plane it defines, it no longer has any magnitude.
     
  5. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,032
    Oh yeah, and sorry about the edit on the code in the last post. It's just after posting it I noticed that this code (which I wrote a long while back) had some unecessary 'normalize' calls which would just make it slower for no reason.
     
  6. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    Your method seems to do what the method in my original post did, which wasnt what was desired (or I am still not using them correctly).
    In a way, what I am looking to do is to find somewhat of a relative angle (which we would probably need more values to work with than just a from, to, and axis.

    In the image I posted, you can see that I am rotating the pink vector around the blue vectors up axis (can be any axis though) so that they are now aligned, and then I will get their angle on a desired axis, such as the right axis.
    In other words, step one is to find the angle required to have the 2 vectors aligned so that I can call Quaternion.AngleAxis(). That rotation would have aligned them in a way that I can pretty much just call Vector3.Angle to get the angle I wanted in my actual desired axis.

    Here is some code I was trying, that kinda worked for some axis.
    Code (CSharp):
    1.  
    2.         public float RelativeAngleOnAxis(Vector3 from, Vector3 fromAxis, Vector3 to, Vector3 axis)
    3.         {
    4.             Vector3 toAligned = AlignOnAxis(to, from, fromAxis);
    5.             return AngleOnAxis(from, toAligned, axis.normalized);
    6.         }
    7.  
    8.         public float AngleOnAxis(Vector3 from, Vector3 to, Vector3 axis)
    9.         {
    10.             Vector3 fromOnPlane = Vector3.ProjectOnPlane(from, axis).normalized;
    11.             Vector3 toOnPlane = Vector3.ProjectOnPlane(to, axis).normalized;
    12.  
    13.             return AngleSigned(fromOnPlane, toOnPlane, axis.normalized);
    14.         }
    15.  
    16.         public Vector3 AlignOnAxis(Vector3 vector, Vector3 targetVector, Vector3 axis)
    17.         {
    18.             return Quaternion.AngleAxis(AngleOnAxis(vector, targetVector, axis), axis) * vector;
    19.         }
    20.  
    21.         public float AngleSigned(Vector3 comparingVector, Vector3 compareToVector, Vector3 directionUp)
    22.         {
    23.             return Vector3.Angle(comparingVector, compareToVector) * LeftOrRight(comparingVector, compareToVector, directionUp);
    24.         }
    25.  
    26.         public float LeftOrRight(Vector3 comparingVector, Vector3 compareToVector, Vector3 directionUp)
    27.         {
    28.             Vector3 crossCompare = Vector3.Cross(comparingVector, compareToVector);
    29.             float direction = Vector3.Dot(crossCompare, directionUp);
    30.  
    31.             if (direction > 0f) return 1f; //right
    32.             if (direction < 0f) return -1f; //left
    33.             return 0f; //forward or backwards
    34.         }

    So something like
    Code (CSharp):
    1.  
    2. //Works. Goal was to get angle of 45 since all we care about is angle on right axis. It gives 45
    3. RelativeAngleOnAxis(Vector3.forward, Vector3.up, (Vector3.right + Vector3.down).normalized, Vector3.right);
    Gives a value of 45, since there is a 45 degrees difference from the "from" vector and "to" vector in the right axis after we have aligned them using the vector3.up.
    However, when I do the below, it fails..
    Code (CSharp):
    1. //Doesnt work. Goal was to get angle of 90 since all we care about is angle on up axis. It gives 45. If instead of (Vector3.right + Vector3.down) we just did Vector3.right, it would give 90
    2. float angle1 = RelativeAngleOnAxis(Vector3.forward, Vector3.right, (Vector3.right + Vector3.down).normalized, Vector3.up);
    Would a video help explain things?

    An example of needing this is if I have a camera looking all around, and I want my players head rotation to only follow the cameras left and right rotation (rotation around relative up axis) and still preserve all its other rotations.
     
    Last edited: Jan 25, 2016
  7. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,032
    What do you mean by aligned?

    Do you mean that the blue line and the pink line are on the same plane as defined by the green line?

    And then you want to measure this projected pink line off the grey dashed line?

    If so it'd be:

    Code (csharp):
    1.  
    2. var a = AngleOffAroundAxis(pink, grey, green);
    3.  
    Because the plane on which we're aligning to is defined by the axis 'green', as well as green being the axis around which we're measuring the angle.

    Grey is the forward, because that's what we're measuring off.

    And pink is the vector in question.




    Maybe...

    Why don't you just get the local rotation of the camera?
     
  8. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    The gray is what the pink would be after it was rotated to be aligned. So in the image on the top, we see a back view and a side view. In the back view we can see the gray and blue line are aligned on a plane, and then the side view is the axis we are wanting the angle for. So we dont know the gray, but after we get it, then we can get the desired angle.
    Perhaps we may not even need this step, but it was just a way to try and get what I wanted.

    I am not sure as to how I can use the local rotation of the camera.
    Lets say my player head is looking down a bit, so its up axis is something like Vector3.up + Vector3.forward and its also currently facing a little to the left due to its idle animation. If my camera up axis is Vector3.up, and I am looking to the right at Vector3.right, I would do my angleonaxis method to figure out that I need to rotate my head around its own bent upAxis by like 100 degrees since it was already 10 degrees to the left.
    I would then later also want to get the cameras up and down angle and use that for my head as well, but not my cameras rolling angle.

    So, with the line of code above that I said worked.. I can look all around me, but only when I look down with my camera will my player head start to look down as well. Even if I am looking right behind me and looking down, my player head will face forward and look down.
    However, with the line of code that didnt work (the code above was disabled when testing this as well)... that was for rotating the head left and right so that when I look behind me the player will look behind (at the moment the code is limited to 90 degrees, but I think that could be fixed maybe by doing another LeftOrRight check to change the angles sign).
    It worked when I was looking straight to my side, but if I was looking to my side and down, my head would start to rotate back to forward, and looking straight down meant I was facing straight forward, which is wrong. It should have ignored the up and down and only cared about left and right.
    This is only an example of its usage.

    I think I played with localEulerAngles before, but things were getting messed up when my world rotation was not the same as my local (such as my character is rotated to walk on walls).
     
    Last edited: Jan 25, 2016
  9. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,032
    OK... I think I see what you want... you're attempting to rotate pink around green so that it rests on a plane defined as passing through blue and green.

    And then you want to measure the angle between that repositioned pink and blue.

    Now you also say:

    We may not... I don't know. It would be better if I knew what you were attempting.

    Which you gave me.

    OK, so. What you want to do is figure out how much to rotate around the head's local up so that the head's forward and camera's forward were equal.

    Let's name these vectors:

    hup - head's up
    hfrw = head's forward
    cfrw = camera's forward

    We're going to rotate around a plane whose normal is hup, because the axis around which we pivot is the hup.

    So already we have a problem, there's no guarantee we can rotate around hup and make it to cfrw. Lets say you tilt your head forward, and then slightly tilt it left. Like you're head's up was at '(up + forward + left).normalized'. If you rotated around that, no matter what you would never reach. Because the plane on which the head rotates does not intersect the camera's forward.

    It just HAPPENS that in your scenario you describe here that you do. But that's just coincidence.

    To generalize a formula we need to consider all possible inputs.

    So, what is supposed to happen if the head is cocked at an angle that doesn't allow a path around it's head.
     
  10. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    Hmm, well to try and make things simple, it seems what can work is if I can just use the localEulerAngles and not overwrite the Z, however the issue with that is the localEulerAngles method will only seem to work if the object (in this case the head) has the Z set to 0, which it wont. If its Z changes then it will be rotating around the wrong axis.
    So how can I get the cameras Angle around its local up and right axis so that I can set the heads local up and right axis rotation with AngleAxis or what ever?
     
  11. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    Ive been trying to find out how to get some kind of rotation on a axis from a quaternion and this is what I have so far that has issues.

    Code (CSharp):
    1.  
    2.         float RotationOnAxis(Quaternion rotation, Vector3 axis)
    3.         {
    4.             Quaternion axisRotation = Quaternion.FromToRotation(rotation * axis, axis) * rotation;
    5.  
    6.             //Even though it says RandomPerpendicular, its not neccesarily random in the sense that it will be different every time you call it. Maybe there is a better way for handling this?
    7.             Vector3 targetDirection = axisRotation * RandomPerpendicular(axis);
    8.             Vector3 currentDirection = RandomPerpendicular(axis);
    9.  
    10.             Debug.DrawRay(transform.position, targetDirection, Color.red);
    11.             Debug.DrawRay(transform.position, currentDirection, Color.blue);
    12.             Debug.DrawRay(transform.position, axis, Color.cyan);
    13.             Debug.DrawRay(transform.position, transform.TransformDirection(axis), Color.magenta);
    14.  
    15.             return ExtVector3.AngleSigned(currentDirection, targetDirection, axis);
    16.         }
    17.  
    18.         Vector3 RandomPerpendicular(Vector3 vector)
    19.         {
    20.             Vector3 perpendicular = Vector3.Cross(vector, Vector3.up);
    21.             if(Mathf.Approximately(perpendicular.sqrMagnitude, 0f)) perpendicular = Vector3.Cross(vector, Vector3.forward);
    22.             return perpendicular.normalized;
    23.         }

    What I am doing is using the axis as a reference for what the quaternion direction would be without any rotation, and then I rotate the axis by the quaternion so that I also have the axis in the new rotated spot.
    So now my rotation is acting like a Quaternion.identity and then the axisRotation is like the original rotation we wanted to find the angle for.

    Then, we need to get a perpendicular vector for our axis and our axis that is rotated to compare to get the angle. We need the perpendicular vector since for example, if the axis is vector3.right, then the perpendicular vector will be the vector that rotates around the axis as desired.

    I called it like this
    Code (CSharp):
    1. float angle = RotationOnAxis(transform.localRotation, transform.right);
    With that, only when I rotate on the x will it affect the angle, however, that is only if I am not already rotated on another axis, such as the up axis. If I rotate my up axis by 90 degrees, then rotating the z or x axis would affect the angle, where as I guess the goal was to still only have the x axis return the angle.

    Am I going in the right direction, or am I still going to run into the same issue as trying to get the vectors aligned?
     
    Last edited: Jan 27, 2016
unityunity