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

How to move on the surface of a sphere using spherical math? (Without Rigidbodies or raycasts)

Discussion in 'Scripting' started by aidinz, May 8, 2017.

  1. aidinz

    aidinz

    Joined:
    Apr 17, 2016
    Posts:
    63
    Hi,

    I want to be able to walk inside a imaginary sphere and facing the center of it. I know I can create the sphere, cast RayCast and inverse normal of the collision point or use Rigidbodies but I want to do it with math but I can't get my head around the math, specially the moving part.

    To put it in another word, imagine super mario galaxy but inverse, being inside the sphere while still walking on the surface, like mario.

    I've been searching for this for hours but can't find what I want. Thanks.
     
  2. hpjohn

    hpjohn

    Joined:
    Aug 14, 2012
    Posts:
    2,190
    Your up direction is always = the surface normal = (sphere center pos - your position)
     
  3. aidinz

    aidinz

    Joined:
    Apr 17, 2016
    Posts:
    63
    Perhaps I wasn't clear enough, I don't want to have an sphere at all. That's why I said "imaginary" in the first sentence.
     
  4. laxbrookes

    laxbrookes

    Joined:
    Jan 9, 2015
    Posts:
    235
    With this math, you won't need an actual "physical" sphere. The math is still the same.
     
    lordofduct likes this.
  5. aidinz

    aidinz

    Joined:
    Apr 17, 2016
    Posts:
    63
    What are you talking about?
     
  6. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    Your surface is spherical, you're saying.

    This means that you can define your surface as the inner side of a sphere.

    You don't need a 'Sphere' GameObject... but there's still a mathematical concept of a sphere that could be used to represent the surface.

    That sphere is defined as a center point, with a radius.

    When you're at a given point... your distance from the center is the radius... if you take a step forward, take that new point relative to the center and just ensure it's magnitude is equal to the radius.

    Code (csharp):
    1.  
    2. var pos = transform.position; //get position
    3. pos += direction * speed * Time.deltaTime; //move forward
    4. var v = pos - center; //get new position relative to center of sphere
    5. pos = center + v.normalized * radius; //constrain position to surface of sphere
    6. transform.position = pos; //set position
    7.  
     
    laxbrookes likes this.
  7. steego

    steego

    Joined:
    Jul 15, 2010
    Posts:
    969
    What you want is probably a spherical coordinate system. In your movement code, just update your azimuth and elevation values, radius stays constant. Then convert back to cartesian coordinates, and assign those to your gameobjects.
     
    camilo_amihan likes this.
  8. aidinz

    aidinz

    Joined:
    Apr 17, 2016
    Posts:
    63
    EXACTLY. Actually I did the conversion for a project before so I had it's code ready, I just had to follow your precious direction (pun not intended!).

    I'm posting the conversion from Cartesian to/from Polar (Spherical) here:

    Code (CSharp):
    1. public static class SphericalMathHelpers
    2. {
    3.  
    4.     public static Vector3 SphericalToCartesian (Vector3 sphericalCoord)
    5.     {
    6.         return SphericalToCartesian(sphericalCoord.x, sphericalCoord.y, sphericalCoord.z);
    7.     }
    8.  
    9.     public static Vector3 SphericalToCartesian (float radius, float azimuth, float elevation)
    10.     {
    11.        
    12.         float a = radius * Mathf.Cos (elevation);
    13.        
    14.         Vector3 result = new Vector3();
    15.         result.x = a * Mathf.Cos(azimuth);
    16.         result.y = radius * Mathf.Sin(elevation);
    17.         result.z = a * Mathf.Sin(azimuth);
    18.  
    19.         return result;
    20.     }
    21.  
    22.     /// <summary>
    23.     /// Return Vector3 is x = radius, y = polar, z = elevation.
    24.     /// </summary>
    25.     /// <param name="cartCoords"></param>
    26.     /// <returns></returns>
    27.     public static Vector3 CartesianToSpherical (Vector3 cartCoords)
    28.     {
    29.         float _radius, _azimuth, _elevation;
    30.  
    31.         if (cartCoords.x == 0)
    32.             cartCoords.x = Mathf.Epsilon;
    33.  
    34.         _radius = Mathf.Sqrt((cartCoords.x * cartCoords.x)
    35.         + (cartCoords.y * cartCoords.y)
    36.         + (cartCoords.z * cartCoords.z));
    37.  
    38.         _azimuth = Mathf.Atan(cartCoords.z / cartCoords.x);
    39.  
    40.         if (cartCoords.x < 0)
    41.             _azimuth += Mathf.PI;
    42.         _elevation = Mathf.Asin(cartCoords.y / _radius);
    43.  
    44.         Vector3 result = new Vector3(_radius, _azimuth, _elevation);
    45.         return result;
    46.     }
    47. }
    and how I'm using it:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class PlayerController : MonoBehaviour
    6. {
    7.     public Transform Target;
    8.     public float speed = 50;
    9.     public float radius = 10;
    10.  
    11.  
    12.     #region UnityCallbacks
    13.  
    14.     void Start ()
    15.     {
    16.        
    17.     }
    18.    
    19.     void Update ()
    20.     {
    21.         if (Input.GetKey(KeyCode.W))
    22.         {
    23.             Vector3 mySphericalCoord = SphericalMathHelpers.CartesianToSpherical( transform.position );
    24.             mySphericalCoord.z += speed * Time.deltaTime;
    25.             transform.position = SphericalMathHelpers.SphericalToCartesian(mySphericalCoord);
    26.         }
    27.  
    28.         if (Input.GetKey(KeyCode.S))
    29.         {
    30.             Vector3 mySphericalCoord = SphericalMathHelpers.CartesianToSpherical(transform.position);
    31.             mySphericalCoord.z -= speed * Time.deltaTime;
    32.             transform.position = SphericalMathHelpers.SphericalToCartesian(mySphericalCoord);
    33.         }
    34.  
    35.         if (Input.GetKey(KeyCode.A))
    36.         {
    37.             Vector3 mySphericalCoord = SphericalMathHelpers.CartesianToSpherical(transform.position);
    38.             mySphericalCoord.y -= speed * Time.deltaTime;
    39.             transform.position = SphericalMathHelpers.SphericalToCartesian(mySphericalCoord);
    40.         }
    41.  
    42.         if (Input.GetKey(KeyCode.D))
    43.         {
    44.             Vector3 mySphericalCoord = SphericalMathHelpers.CartesianToSpherical(transform.position);
    45.             mySphericalCoord.y += speed * Time.deltaTime;
    46.             transform.position = SphericalMathHelpers.SphericalToCartesian(mySphericalCoord);
    47.         }
    48.  
    49.         transform.LookAt(Target);
    50.     }
    51.    
    52.     #endregion
    53. }
    54.  
    I'm wondering if there is a shorter way without these conversions.

    Also is it possible not to "new" vector3's?
     
  9. aidinz

    aidinz

    Joined:
    Apr 17, 2016
    Posts:
    63
    In my calculations I'm rotating around the (0, 0, 0), how can I do it over the target?
     
    Last edited: May 9, 2017
  10. steego

    steego

    Joined:
    Jul 15, 2010
    Posts:
    969
    I would just cache the azimuth and elevation in your PlayerController, instead of converting the position to spherical every time.

    Just add an offset to the transform.position when you set it.

    Or you can create two empty gameobjects, one as the child of the other, have your PlayerController on the child, and then use transform.localPosition instead. You can then move the child anywhere in your scene.
     
  11. aidinz

    aidinz

    Joined:
    Apr 17, 2016
    Posts:
    63
    Great suggestion, did it.

    I changed the Start() to this:
    Code (csharp):
    1.  
    2. cachedSphericalCoord = SphericalMathHelpers.CartesianToSpherical(Target.position + transform.position);
    3.  
    But doesn't seem to work.
     
  12. steego

    steego

    Joined:
    Jul 15, 2010
    Posts:
    969
    I mean when you update the position, do something like this

    Code (csharp):
    1. transform.position = offset + SphericalMathHelpers.SphericalToCartesian(mySphericalCoord);
     
  13. aidinz

    aidinz

    Joined:
    Apr 17, 2016
    Posts:
    63
    Thanks.
    I can't get this work nor the child/parent way you described previously.
     
  14. steego

    steego

    Joined:
    Jul 15, 2010
    Posts:
    969
    I attached an example showing both ways, hope this helps.
     

    Attached Files:

    Warspawn and Obadah like this.