Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Smooth planetary gravity?

Discussion in 'Scripting' started by TylerPerry, Apr 15, 2014.

  1. TylerPerry

    TylerPerry

    Joined:
    May 29, 2011
    Posts:
    5,577
    I'm wondering if people could give me some ideas on how to make a smoother planetary gravity? See, I just aline the player to the normal of a collider, but obviously when the player moves over to a new face it jerks to that position which isn't very good. I'm wondering if people could give me some advice on methods to smooth it out.

    This is my test code with no smoothing:

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class MoveScript : MonoBehaviour {
    6.  
    7.     public float speed;
    8.     public float sensitivityX;
    9.        
    10.     public float jumpForce;
    11.  
    12.     public float smoothFactor;
    13.     public float gravity;
    14.    
    15.     public LayerMask mask;
    16.  
    17.     private bool isGrounded;
    18.     private Vector3 _movement;
    19.     private float vForce;
    20.  
    21.  
    22.  
    23.     void Update () {
    24.  
    25.         Ray ray = new Ray(transform.position,transform.TransformDirection(Vector3.down));
    26.         RaycastHit hit;
    27.  
    28.         if(Physics.SphereCast(ray,0.2f,out hit,mask))
    29.         {
    30.             if(Vector3.Distance(hit.point, transform.position) < 1f)
    31.             {
    32.                 isGrounded = true;
    33.             }
    34.             else
    35.             {
    36.                 isGrounded = false;
    37.             }
    38.             transform.rotation = Quaternion.LookRotation( Vector3.Cross( transform.right, hit.normal), hit.normal);
    39.         }
    40.        
    41.         vForce = gravity;
    42.  
    43.         if(Input.GetButtonDown("Jump"))
    44.         {
    45.             vForce = jumpForce;
    46.         }
    47.  
    48.         if(isGrounded){
    49.             _movement = new Vector3(Input.GetAxis("Horizontal") * speed, 0, Input.GetAxis("Vertical") * speed);
    50.             Debug.Log("Hello");
    51.         }
    52.         else {
    53.             _movement = new Vector3(0,vForce,0);
    54.         }
    55.  
    56.         rigidbody.velocity = transform.TransformDirection(_movement);
    57.         //rigidbody.AddRelativeForce(0,-9.81f,0);
    58.  
    59.         transform.Rotate(0, Input.GetAxis("Mouse X") * sensitivityX, 0);
    60.  
    61.     }
    62. }
    63.  
    I can easily smooth the rotation by lerping or slerping the rotation, like so

    Code (csharp):
    1.  
    2. transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation( Vector3.Cross( transform.right, hit.normal), hit.normal), Time.deltaTime * smoothFactor);
    3.  
    But that isn't good as the character should be properly aligned all the time. I think the best way to do this would be to take the surrounding faces normals and get a sort of gradient from them to the current face then change the characters rotation based on this? Its hard to explain but maybe this visualisation will make sense:

    $NormalsDemonstration2D.png

    The green line is the normal of the face the character is currently on, the yellow lines are the normals of the surrounding faces, the black lines are the faces and the red lines are the proposed smoothing of the faces. But this process seems complex and I can't think of how to do it.

    I'm wondering if anyone has any suggestions on how to ether do my process or any suggestions on other ways to smooth the movement?

    Thanks :D
     
  2. shaderop

    shaderop

    Joined:
    Nov 24, 2010
    Posts:
    942
    Why not use the vector from the center of the planet to the player's position as your normal?
     
  3. TylerPerry

    TylerPerry

    Joined:
    May 29, 2011
    Posts:
    5,577
    In what crazy universe are planets all round?!?! But seriously l use the normal of the surface as my planets might sometimes be strange shapes, like a torus for example.
     
  4. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    You said you want it to be smooth but then you said that they must be properly aligned at all times. Applying some kind of smoothing is insinuating that there will be at least some time where the player is not completely aligned with the surface normal.
     
  5. TylerPerry

    TylerPerry

    Joined:
    May 29, 2011
    Posts:
    5,577
    My wording was wrong, I mean I don't want the player to move over time, using lerp the player rotates smoothly but their is a delay because of how it works. I need a solution with no delay. It doesn't need to be aligned to the normal it just needs to be alined to something.
     
  6. bigmisterb

    bigmisterb

    Joined:
    Nov 6, 2010
    Posts:
    4,221
    Spherical planets should always be aligned to the center of that planet. If you have odd shapes like the torus suggested then you will want the normal from the raycast hit.

    I noticed that you are using a SphereCast to get your hit points. SphereCasts have a tendency to throw odd normals depending on the angle that they hit things. If you are going to use then use a RayCast, this always gives you the normal from whatever it hit. This, unfortunately is probably also not right.

    Having a center of gravity is the best way to go. If not, then you will want to create a world gravity collider that you throw raycats on. Here is a good example:

    You want your world to be a torus, but then you also want things on your world, like different object or things to walk on, slopes, hills and such. If you used that collider as your "gravity" then you would always get odd directions, however, if you have a generic torus that is the foundation (a mesh that is the lowest point on the game geometry) You RayCast that, that gives you your "up" direction and then you let physics do the rest.

    If you are simply building a planet, always use the center of that planet for your up direction.
     
  7. TylerPerry

    TylerPerry

    Joined:
    May 29, 2011
    Posts:
    5,577
    Thanks for the reply. I'm already going to have mesh colliders as my planets and then have other stuff on them(as to not have the characteralined to the side of a mountain for example) but none of my planets are round, and even if they were their would be things like loops and such that work with the gravity. Also I'm pretty sure there isn't an issue with perfectly round planets as they would use a sphere collider which is perfectly round.
     
  8. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,614
    How are you using lerp? There should be no delay...

    And there's plenty of tutes and docs around telling people to use it incorrectly in a way that introduces delay, so it's worth checking.
     
  9. TylerPerry

    TylerPerry

    Joined:
    May 29, 2011
    Posts:
    5,577
    Thanks for replying. I'm currently using it just like:

    Code (csharp):
    1. transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation( Vector3.Cross( transform.right, hit.normal), hit.normal), Time.deltaTime * smoothFactor);
    the smooth factor is just a float, mine is 1 but i've done higher like 5. By delay I mean that once the player stops moving the character still continues to rotate until it is aligned correctly with the surface normal, but this is no good as once the player stops moving they need to be at a position that makes sense.

    Perhaps an analogy to the lerp problem would one of Newton's laws that states "A body at rest wants to stay at rest" as thats pretty much what the lerp is doing ATM.
     
    Last edited: Apr 16, 2014
  10. TylerPerry

    TylerPerry

    Joined:
    May 29, 2011
    Posts:
    5,577
    I was playing around with lerp some more and found a not ideal solution, by Slerping it like this:

    Code (csharp):
    1.  
    2.     transform.rotation = Quaternion.Slerp(
    3.     transform.rotation,
    4.     Quaternion.LookRotation( Vector3.Cross( transform.right, hit.normal), hit.normal),
    5.     Time.deltaTime *(smoothFactor * Mathf.Abs(Input.GetAxis("Vertical") + Input.GetAxis("Horizontal")))
    6.     );
    7.  
    I can get a somewhat ok result, as it isn't moving when the player is stopped, still not the best though as now my player is sometimes rotated a little off.
     
  11. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,614
    TIme.deltaTime is almost always the wrong value to stick in a lerp function, because it stops it from being an actual lerp and turns it into a MoveTowards with some kind of... logarithmic function?

    Anyway, I've explained why this ain't right (for intended use cases) at least twice in detail in the past. Search for me + lerp and you should find it with a little digging.
     
  12. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,614
    I did the digging for you. This talks about positions, but the same applies to rotations:
    http://forum.unity3d.com/threads/19...()-problem-!?p=1353160&viewfull=1#post1353160

    What you probably want to do is transition between the normals of different faces with weight based on distance from each, or something like that, using the lerp function to find the interpolated result. Note that you'll probably want to do something nicer to handle where 3 (or more) faces meet, though. It can still be based on a lerp, but needs to work on more samples.
     
    Last edited: Apr 16, 2014
  13. shaderop

    shaderop

    Joined:
    Nov 24, 2010
    Posts:
    942
    With the exception of Discworld, all of them.
     
  14. TylerPerry

    TylerPerry

    Joined:
    May 29, 2011
    Posts:
    5,577
    Yes, thats how I was thinking would be the best way to do it too, but it seems pretty hard, I'd need to grab the vertex positions of the hit triangle and then check against all the triangles in the mesh and compare their positions to find the neighbouring triangles(I think), something that sounds like it would be expensive, then get the normals of them and find the right rotation. TBH it sounds to time consuming and hard to write.

    And with the Lerp thing, so "t" isn't time at all but a decimal representation of where between the two values should be?
     
  15. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,614
    Yep. It's the "interpolation factor", a value between 0 and 1, where 0 represents the "from" value exactly and 1 the "to" value.

    I don't think this needs to be too hard. You just need to have a weighted average between the nearest set of normals, changing the weighting based on distance. Lerp would do fine it if you only need had worry about two at a time.
     
  16. shaderop

    shaderop

    Joined:
    Nov 24, 2010
    Posts:
    942
    You can use barycentric coordinates to interpolate values along the face of a triangle.

    Actually there's an example in the docs that does exactly that, i.e. interpolate normals:

    RaycastHit.barycentricCoordinate
     
  17. TylerPerry

    TylerPerry

    Joined:
    May 29, 2011
    Posts:
    5,577
    Cool, thanks for that info.

    By set of normals you mean the normals of the surrounding faces yeah? I'm thinking maybe I could raycast down around the player as well then take the normals that are from the faces.

    Sorry to sound ignorant, but what is this? I saw it when I was doing the alignment before but the demo just seems to output the normal with no smoothing or anything? When I search BarycentricCoordinate it seems like its just to find a position on a triangle?
     
  18. shaderop

    shaderop

    Joined:
    Nov 24, 2010
    Posts:
    942
    All you need to know is this: If you know the barycentric coordinates of a point on a triangle, then you can use that to calculate the position of that point in space as a weighted sum of the positions of the triangle's vertices.

    So for triangle with vertices at v1, v2, and v3, and a point p with barycentric coordinates (bc1, bc2, bc3), the position of p in space is:

    p = v1 * bc1 + v2 * bc2 + v3 * bc3

    But you can also you the barycentric coordinates to calculate the average of any value at v1, v2, and v3 at point p. So for normals n1, n2, n3 at v1, v2, v3 respectively, the normal at p would be:

    Np = Nv1 * bc1 + Nv2 * bc2 + N3 * bc3

    You can even use it to calculate the average color, weightmap, etc.

    And the example actually does calculate the normal as an average of the normals at the triangle's verts. The closer you get to v1, the more the normal will match the normal at v1, and the closer your approach the center of the triangle, the more the normal will match the average of all three normals at the points of the triangle. So it will be smooth
     
    FoxyTechSupport likes this.
  19. TylerPerry

    TylerPerry

    Joined:
    May 29, 2011
    Posts:
    5,577
    Cool, it turns out I was getting the same thing because I had hard edges on my object(Atleast I think that was it) now it looks promising. I'll be back with some code later tonight :D
     
  20. TylerPerry

    TylerPerry

    Joined:
    May 29, 2011
    Posts:
    5,577
    It works absolutely awesome!

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class AlignToNormal : MonoBehaviour {
    6.  
    7.     public float groundedDistance = 1f;
    8.  
    9.     public LayerMask mask;
    10.  
    11.     [HideInInspector]
    12.     public bool isGrounded = false;
    13.  
    14.     private MeshCollider oldCollider;
    15.  
    16.     private Vector3[] normals;
    17.     private int[] triangles;
    18.  
    19.     void Update () {       
    20.         Ray ray = new Ray(transform.position,transform.TransformDirection(Vector3.down));
    21.         RaycastHit hit;
    22.  
    23.         if (!Physics.Raycast(ray,out hit,mask))
    24.             return;
    25.         if(Vector3.Distance(hit.point,transform.position) < groundedDistance)
    26.         {
    27.             isGrounded = true;
    28.         }
    29.         else
    30.         {
    31.             isGrounded = false;
    32.         }
    33.         MeshCollider meshCollider = hit.collider as MeshCollider;
    34.         //if (meshCollider == null || meshCollider.sharedMesh == null)
    35.         //  return;
    36.        
    37.         Mesh mesh = meshCollider.sharedMesh;
    38.  
    39.         if(meshCollider != oldCollider){
    40.             normals = mesh.normals;
    41.             triangles = mesh.triangles;
    42.             oldCollider = meshCollider;
    43.         }
    44.  
    45.         Vector3 n0 = normals[triangles[hit.triangleIndex * 3 + 0]];    
    46.         Vector3 n1 = normals[triangles[hit.triangleIndex * 3 + 1]];
    47.         Vector3 n2 = normals[triangles[hit.triangleIndex * 3 + 2]];
    48.        
    49.         Vector3 baryCenter = hit.barycentricCoordinate;
    50.        
    51.         Vector3 interpolatedNormal = n0 * baryCenter.x + n1 * baryCenter.y + n2 * baryCenter.z;
    52.         interpolatedNormal = interpolatedNormal.normalized;
    53.  
    54.  
    55.  
    56.         Transform hitTransform = hit.collider.transform;
    57.         interpolatedNormal = hitTransform.TransformDirection(interpolatedNormal);
    58.  
    59.         transform.rotation = Quaternion.LookRotation( Vector3.Cross( transform.right, interpolatedNormal), interpolatedNormal);
    60.         Debug.DrawRay(hit.point, interpolatedNormal);
    61.    
    62.     }
    63. }
    64.  
    The only issue is that when I move to a new face their is a jerk, I'm not sure why that happens.
     
  21. bigmisterb

    bigmisterb

    Joined:
    Nov 6, 2010
    Posts:
    4,221
    Does this happen when your face normals not match up? Like where materials swap or some such?
     
  22. TylerPerry

    TylerPerry

    Joined:
    May 29, 2011
    Posts:
    5,577
  23. TylerPerry

    TylerPerry

    Joined:
    May 29, 2011
    Posts:
    5,577
    Just incase someone runs into this at some point, the issue was the movement script not the gravity script. Because the character was being pulled down, but the collider still had polygons so it bumped when it moved over the edges(At Least thats what I think happened).
     
  24. 3agle

    3agle

    Joined:
    Jul 9, 2012
    Posts:
    508
  25. FuzzyQuills

    FuzzyQuills

    Joined:
    Jun 8, 2013
    Posts:
    2,871
    That webplayer was extremely dizzying! And i am used to seeing bumping like that. if it's a problem, try using interpolation on the rigidbody.

    And another question: mind if i use this in my own game? are you going to release an asset on this? If so, that would be great! :)
     
  26. TylerPerry

    TylerPerry

    Joined:
    May 29, 2011
    Posts:
    5,577
    I won't release it on the asset store but feel free to use it :D I'll post a more recent one in a a few moments(IIRC the error is fixed in the new one)
     
  27. TylerPerry

    TylerPerry

    Joined:
    May 29, 2011
    Posts:
    5,577
  28. FuzzyQuills

    FuzzyQuills

    Joined:
    Jun 8, 2013
    Posts:
    2,871
    Thanks! And I am not a fan of the asset store, so dropbox will do! :D

    Now let's see if it is possible to make an anti-gravity fighting game... or a racer! BTW, if you were wondering about this late reply, it was thanks to the fact that firefox looks amazing, and all my bookmarks are in chrome... :D