Search Unity

Deriving a similar rotation on a new plane

Discussion in 'Scripting' started by Bluvarth, Apr 17, 2019.

  1. Bluvarth

    Bluvarth

    Joined:
    Jul 11, 2017
    Posts:
    11
    In short, my project requires the ability to step from one surface onto another, and while I have a very vague understanding on how to do this, I can't seem to get all the pieces of the puzzle in one place. My goal is to step from one plane to another smoothly, keeping the general rotation that you currently have after you transition. Any help on the vector math behind this would be greatly appreciated!

    upload_2019-4-16_21-45-56.png
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    I have a function I call 'GetForwardTangent':
    Code (csharp):
    1.  
    2.         /// <summary>
    3.         /// Returns a vector orthogonal to up in the general direction of forward.
    4.         /// </summary>
    5.         public static Vector3 GetForwardTangent(Vector3 forward, Vector3 up)
    6.         {
    7.             return Vector3.Cross(Vector3.Cross(up, forward), up);
    8.         }
    9.  
    I call it this because I imagine it as if you're standing on a curve where based on the normal vector of the curve and the general direction you're facing on that curve, we're calculating what the tangent vector would be for that curve.

    So yeah, to this you'd pass in your entities forward vector (the capsule in your pic) for the 'forward' parameter, and the normal vector of the surface you're wanting to stand on.

    This will result in what the forward aught to be on that surface.

    ...

    The general idea is that you take the cross product twice.

    A cross product gives a vector orthogonal to the 2 input vectors (orthogonal means 3-dimensionally perpendicular). So imagine if you point your pointer finger forward, and your thumb up. If you then point your middle finger perpendicular to those... your middle finger would be the cross product of your pointer and thumb.

    If we then cross your middle finger and thumb, we'd get a vector orthogonal to those. If All were perfectly square to one another this would be your pointer finger... but if they weren't square this would be where your pointer finger should point so that it were perfectly square.
    understanding_wpf_3d_2.png

    ...

    Anyways, now that you have what the forward aught to be you then calculate the rotation with Quaternion.LookRotation:
    Code (csharp):
    1.  
    2. var normal = *get normal of surface*;
    3. transform.rotation = Quaternion.LookRotation(GetForwardTangent(transform.forward, normal), normal);
    4.  
    WARNING

    This will severely fail if you happen to try to move to a new rotation that just so happens to be a surface that is parallel to your current forward.

    The cross product of 2 parallel vectors is a zero vector <0,0,0> (because technically there are an infinite number of vectors orthogonal to parallel vectors). A rotation that faces in the direction of a zero vector does not exist... this scenario is very rare and requires instantaneously moving from a surface to another that is 90 degrees off one another and while facing at just the right angle... but it 'could' happen and you may want to validate the returned vector before calculating the LookRotation.
     
    Last edited: Apr 17, 2019
  3. Bluvarth

    Bluvarth

    Joined:
    Jul 11, 2017
    Posts:
    11
    Still wrapping my head around cross-product but I threw the code in for the moment, any perpendicular plane seems to have trouble computing, any thoughts?


    EDIT: Overhangs also give a mirrored rotation to what you'd expect
     
  4. csofranz

    csofranz

    Joined:
    Apr 29, 2017
    Posts:
    1,556
    I've recently built a small script that allows my object to walk on a sphere (an irregularly shaped Asteroid, to be precise). After some Analysis I realized that I was overthinking the problem.

    The most important thing to realize is that your object's up vector (in your transform) and the normal vector of the ground you stand on must align. And that's pretty much all you Need to do.
    You don't need to figure out the vector math itself because in Unity, you always have a mesh to walk on, and each Point of the mesh has a pre-calculated normal that is radily accessible. Use that instead of complex formulas.

    You get the ground's up vector by raycasting down (which is your transform's -up), and get the normal at the Point where it hits (usually hit.normal), and then simply Quaternion.LookRotation with your own transform's Forward and the normal vector. This will automatically align your object with the inclination of the ground it is on, without messing up the direction it is heading.

    Now, if you want to get fancy, don't cast straight down, but slightly Forward, and start lerping to the new up vector when you move (but only when you move).
     
    Last edited: Apr 17, 2019
  5. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    As I had stated, a plane whose normal is parallel to the forward will have issues.
     
  6. csofranz

    csofranz

    Joined:
    Apr 29, 2017
    Posts:
    1,556
    Hmmm. A plane E perpendicular plane F cannot have a normal parallel to F
     
  7. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    a wall 90 degrees to the ground will have a normal that points parrallel to the ground.

    The player's forward is also parallel to the ground.

    Those 2 vectors are thusly parrallel.
    parrallel.png

    Note that my original post to a solution where you were walking up onto a gradual change in plane. Like in your original picture.

    Jumping to a perfectly orthogonal plane takes a bit of extra edge case checking. A quick hack fix is to take a forward vector from the player that is slightly rotated away from the ground around it's right vector. This will adjust it away from being parallel.
     
    Last edited: Apr 17, 2019
  8. Bluvarth

    Bluvarth

    Joined:
    Jul 11, 2017
    Posts:
    11
    I suppose my image wasn't clear, I'm going to go with that hack fix in the end, but it's not just when the forward is parallel, but instead whenever the normal is on the same plane as all of the current possible "forward" vectors. I watched some videos on cross-product so I think I kind of get where this comes from, and thanks once again.

    I think the last thing I need to fix this is some method of getting the distance between the plane of the normal and the object. Can't just raycast -hit.normal in case the player isn't necessarily next to the object.
     
    Last edited: Apr 17, 2019
  9. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,493
  10. Bluvarth

    Bluvarth

    Joined:
    Jul 11, 2017
    Posts:
    11
    As closure I'll just post the seemingly completely functional version of this function below (Accounting for perpendicular and overhanging faces)


    Code (CSharp):
    1. // if the normal is within reason of perpendicular (necessary for non orthagonal perpendicualr faces)
    2.             if (Mathf.Abs(Vector3.Dot(hit.normal, transform.up)) <= 0.00001)
    3.             {
    4.                 Vector3 rightDir = Vector3.Cross(hit.normal, transform.up).normalized; // get the vector to your right relative to the wall
    5.                 float rightness = Vector3.Dot(rightDir, transform.forward);             // Find how far to the right you're looking (-1 to 1)
    6.                 Quaternion newRotation = Quaternion.LookRotation(Quaternion.AngleAxis(90 * rightness, hit.normal) * transform.up, hit.normal); // the normal vector rotated left or right up to 90 degrees
    7.                 testOBJ.transform.rotation = newRotation;
    8.             }
    9.             else
    10.             {
    11.                 var normal = hit.normal;
    12.                 if (Vector3.Dot(hit.normal, transform.forward) > 1) normal = -normal; // if an overhang use the negative normal (mirror)
    13.                 testOBJ.transform.rotation = Quaternion.LookRotation(GetForwardTangent(transform.forward, normal), normal); // face the forward tangent
    14.             }