World Globe with Latitude and Longitude

I am looking for a tutorial on creating a world globe, that the user can turn on axis and have latitude and longitude shown on cursor position.

I know there are assets in the store that can do this, but I’m wanting to start from scratch.

It needs to be in C#

Any ideas.

Does the world globe have any obstacles you can jump on?

If not, make the gravity a variable that does not rely on collisions.

I’m 9 years old and that’s all I can say about this topic, sorry

look for some regular lat long calculation methods,
maybe math - 3D coordinates on a sphere to Latitude and Longitude - Stack Overflow

Turn around Axis: https://docs.unity3d.com/ScriptReference/Transform.RotateAround.html
(or use the Quaternion-Class and set the rotation)

you may have my now relatively old LatLong class
it is from a time when I was still getting accustomed to quaternions

it’s loaded with math and research, and should work just nicely, it was used for an in-game globe as a way to uniquely toponymize mesh chunks

I had to abandon this after I realized that I was suffering from megalomania
still, working with a large zoomable sphere had its charms, I learned a lot while doing it
and I was endlessly mesmerized watching my generators carve continents with tectonic forces
I ended up learning geology as well lol

now it sits on my hard drive doing nothing
I’m sure I’d do it differently and with more confidence nowadays should I need it again

anyway it works in radians internally, but supports conversions to and from degrees, quaternions, and 3D directions
as well as implicit literal conversions to and from vector2

admittedly, there are things that could’ve been less complicated, maybe
I don’t know if you’ll be able to learn everything there is to it

but here you go

using UnityEngine;

namespace YourNamespace {

  public struct LatLong {

    public const float PI = Mathf.PI;
    public const float TWO_PI = 2f * PI;
    public const float PI_HALF = .5f * PI;

    static readonly LatLong x_nPole = new LatLong(0f, 0f);
    static readonly LatLong x_sPole = new LatLong(PI, TWO_PI, arbitrary: true);
    static readonly LatLong x_forward = new LatLong(PI_HALF, 0f);
    static readonly LatLong x_back = new LatLong(PI_HALF, PI);
    static readonly LatLong x_left = new LatLong(PI_HALF, -PI_HALF); // TODO check the longitude sign
    static readonly LatLong x_right = new LatLong(PI_HALF, PI_HALF); // TODO check the longitude sign

    public static LatLong nPole { get { return x_nPole; } }
    public static LatLong sPole { get { return x_sPole; } }
    public static LatLong forward { get { return x_forward; } }
    public static LatLong back { get { return x_back; } }
    public static LatLong left { get { return x_left; } }
    public static LatLong right { get { return x_right; } }

    // Forward vector (0, 0, 1) is Lat: 90, Long: 0 (in radians)
    // Lat 0 is North pole, Lat 180 is South pole (in radians)
    public static LatLong FromDirection(Vector3 direction) {
      float latitude = Mathf.Acos(direction.y); // latitude (phi) normally acos(v.y/radius)
      if(float.IsNaN(latitude)) latitude = (direction.y > 0f) ? 0f : PI;
      float longitude = Mathf.Atan2(direction.x, direction.z); // longitude (theta)
      return new LatLong(latitude, longitude);
    }

    public static LatLong FromQuaternion(Quaternion r) {
      return LatLong.FromDirection(r * Vector3.forward);
    }

    // Lat: 0-180; Long: 0-360 (in radians)
    public static Quaternion ToQuaternion(float latitude, float longitude) {
      if(float.IsNaN(latitude) || latitude <= 0f) {
        return Quaternion.Euler(-90f, 0f, 0f);

      } else if(latitude >= PI) {
        return Quaternion.Euler(90f, 0f, 0f);

      } else {
        float ctheta = Mathf.Cos(longitude * .5f), cphi = Mathf.Cos(latitude * .5f);
        float stheta = Mathf.Sin(longitude * .5f), sphi = Mathf.Sin(latitude * .5f);

        // normally "cphi * ctheta, -sphi * stheta, cphi * stheta, sphi * ctheta", but X and Y have been negated in order to flip Z
        // according to https://stackoverflow.com/questions/32438252/efficient-way-to-apply-mirror-effect-on-quaternion-rotation

        return new Quaternion(-cphi * ctheta, sphi * stheta, cphi * stheta, sphi * ctheta) * Quaternion.Euler(90f, 0f, 0f);
      }
    }

    public static Quaternion ToQuaternion(LatLong latLong) {
      return ToQuaternion(latLong.latitude, latLong.longitude);
    }

    public static Quaternion ToQuaternion(Vector3 direction) {
      return FromDirection(direction).ToQuaternion();
    }

    float _lat;
    public float latitude { get { return _lat; } set { _lat = _unwind(value, PI); } }

    float _lon;
    public float longitude { get { return _lon; } set { _lon = _unwind(value, TWO_PI); } }

    float _unwind(float v, float m) { v = v % m; if(v < 0f) v += m; return v; }

    // arbitrary is used to bypass unwinding of the angles
    // it's just a workaround for some specific cases, not useful in a general sense
    public LatLong(float latitude, float longitude, bool arbitrary = false) {
      _lat = latitude;
      _lon = longitude;
      if(!arbitrary) Set(latitude, longitude);
    }

    public void Set(float latitude, float longitude) {
      _lat = _unwind(latitude, PI);
      _lon = _unwind(longitude, TWO_PI);
    }

    // skips unwinding of the angles
    public void SetArbitrary(float latitude, float longitude) {
      _lat = latitude;
      _lon = longitude;
    }

    // takes care of the polar transition
    public LatLong Rotate(float latitudeDelta, float longitudeDelta) {
      longitude += longitudeDelta;
      latitude += latitudeDelta + ((_lat < 0f || _lat > PI) ? PI : 0f);
      if(_lat < 0f) _lat = -_lat; else if(_lat > PI) _lat -= PI;
      return this;
    }

    public LatLong Rotate(Vector2 delta) { return Rotate(delta.x, delta.y); }

    public LatLong Inverse { get { return new LatLong(PI - _lat, PI + _lon); } }

    static public LatLong fromDegrees(Vector2 degrees) {
      return fromDegrees(degrees.x, degrees.y);
    }

    static public LatLong fromDegrees(float latitudeDeg, float longitudeDeg) {
      return new LatLong(latitudeDeg * Mathf.Deg2Rad, longitudeDeg * Mathf.Deg2Rad);
    }

    // this is garbage, as it was never used with hashmaps
    // I recommend using Cantor pairing with the two hash codes instead
    public override int GetHashCode() {
      return _lon.GetHashCode() ^ (_lat.GetHashCode() << 2) ^ 1157;
    }

    public bool Equals(LatLong other) {
      return _lat.Equals(other._lat) && _lon.Equals(other._lon);
    }

    public override bool Equals(object obj) {
      if(obj is LatLong ll) return Equals(ll);
      return false;
    }

    public static bool operator ==(LatLong a, LatLong b)   { return a.Equals(b); }
    public static bool operator !=(LatLong a, LatLong b)   { return !a.Equals(b); }

    public static LatLong operator -(LatLong a, LatLong b) { return new LatLong(a.latitude - b.latitude, a.longitude - b.longitude); }
    public static LatLong operator +(LatLong a, LatLong b) { return new LatLong(a.latitude + b.latitude, a.longitude + b.longitude); }

    public static LatLong operator +(LatLong a, Vector2 b) { return new LatLong(a.latitude + b.x, a.longitude + b.y); }
    public static LatLong operator +(Vector2 a, LatLong b) { return b + a; }

    public static LatLong operator *(float n, LatLong ll)  { return new LatLong(ll.latitude * n, ll.longitude * n); }
    public static LatLong operator *(LatLong ll, float n)  { return n * ll; }

    public static LatLong operator /(LatLong ll, float n)  { return new LatLong(ll.latitude / n, ll.longitude / n); }

    public static implicit operator Vector2(LatLong ll)    { return new Vector2(ll._lat, ll._lon); }
    public static implicit operator LatLong(Vector2 v)     { return new LatLong(v.x, v.y); }

    public static explicit operator Quaternion(LatLong ll) { return ll.ToQuaternion(); }
    public static explicit operator LatLong(Quaternion q)  { return LatLong.FromQuaternion(q); }

    public override string ToString() {
      Vector2 degs = this.ToDegrees();
      return string.Format("[LatLong: lat={0:0.000}°, lon={1:0.000}°]", degs.x, degs.y);
    }

    public Vector2 ToVector2()       { return new Vector2(_lat, _lon); }
    public Vector2 ToDegrees()       { return ToVector2() * Mathf.Rad2Deg; }

    public Vector3 ToDirection()     { return ToQuaternion() * Vector3.forward; }
    public Quaternion ToQuaternion() { return LatLong.ToQuaternion(this); }
     
  }

}