Skip to content

Instantly share code, notes, and snippets.

@unitycoder
Last active April 17, 2024 16:06
Show Gist options
  • Save unitycoder/8c632b39d0893a8d6c40 to your computer and use it in GitHub Desktop.
Save unitycoder/8c632b39d0893a8d6c40 to your computer and use it in GitHub Desktop.
Latitude Longitude Position on 3D Sphere
// https://forum.unity.com/threads/latitude-and-longitude-coordinates-in-vector-3-unity.1446328/#post-9066883
static readonly float QUAD = .5f * MathF.PI;
static readonly float TAU = 2f * MathF.PI;
 
static public Vector2 FromPolarAngle(float theta)
  => new Vector2(MathF.Cos(theta), MathF.Sin(theta));
 
static public float ToPolarAngle(Vector2 v)
  => Mod(MathF.Atan2(v.y, v.x), TAU);
 
/// <summary> Conversion from spherical to cartesian coordinates. </summary>
/// <param name="theta"> Polar angle 0..Tau (top-down). </param>
/// <param name="phi"> Azimuthal angle -Pi/2..+Pi/2 where 0 represents equator. </param>
/// <returns> A unit vector. </returns>
static public Vector3 FromSpherical(float theta, float phi) {
  var th = FromPolarAngle(theta); var ph = FromPolarAngle(QUAD - phi);
  return new Vector3(th.x * ph.y, ph.x, th.y * ph.y);
}
 
static public Vector3 FromSpherical(Vector2 coords)
  => FromSpherical(coords.x, coords.y);
 
/// <summary> Conversion from cartesian to spherical coordinates.
/// Returns <see langword="true"/> on success, <see langword="false"/>
/// otherwise. (Values are defined in any case.) </summary>
/// <param name="spherical"> The resulting spherical unit coordinates. </param>
/// <param name="magnitude"> Optional magnitude of the input vector.
/// Leave at 1 when input vector is unit to avoid normalization. </param>
static public bool ToSpherical(Vector3 v, out Vector2 spherical, float magnitude = 1f) {
  var theta = MathF.Atan2(v.z, v.x);
  theta = theta < 0f? theta + TAU : theta;
  var im = (magnitude == 1f)? 1f : 1f / SubstZero(MathF.Max(0f, magnitude), float.NaN);
  var phi = QUAD - MathF.Acos(v.y * im);
  var success = true;
  if(float.IsNaN(theta)) { theta = 0f; success = false; }
  if(float.IsNaN(phi)) { phi = 0f; success = false; }
  spherical = new Vector2(theta, phi);
  return success;
}
 
static public float SubstZero(float v, float subst, float epsilon = 1E-6f) => MathF.Abs(v) < epsilon? subst : v;
static public float Mod(float n, float m) => (m <= 0f)? 0f : (n %= m) < 0f? n + m : n;
// You can now easily transform some 3D coordinates to longitude / latitude (in degrees) if you do
static public Vector2 ToLongLat(Vector3 coords, Vector3 center = default) {
coords -= center;
ToSpherical(coords, out var spherical, coords.magnitude);
if(spherical.x < 0f) spherical.x += 2f * MathF.PI;
return spherical * (180f / MathF.PI);
}
// And back (from longitude / latitude in degrees, to some exact 3D point on the surface)
// https://forum.unity.com/threads/latitude-and-longitude-coordinates-in-vector-3-unity.1446328/#post-9066883
static public Vector3 FromLongLat(Vector2 longLat, Vector3 center = default, float radius = 1f)
  => center + radius * FromSpherical(longLat * (MathF.PI / 180f));
// blog post: https://unitycoder.com/blog/2016/03/01/latitude-longitude-position-on-3d-sphere-v2/
using UnityEngine;
public class LatLong : MonoBehaviour
{
public Transform marker; // marker object
public float radius = 5; // globe ball radius (unity units)
public float latitude = 51.5072f; // lat
public float longitude = 0.1275f; // long
// Use this for initialization
void Start()
{
// calculation code taken from
// @miquael http://www.actionscript.org/forums/showthread.php3?p=722957#post722957
// convert lat/long to radians
latitude = Mathf.PI * latitude / 180;
longitude = Mathf.PI * longitude / 180;
// adjust position by radians
latitude -= 1.570795765134f; // subtract 90 degrees (in radians)
// and switch z and y (since z is forward)
float xPos = (radius) * Mathf.Sin(latitude) * Mathf.Cos(longitude);
float zPos = (radius) * Mathf.Sin(latitude) * Mathf.Sin(longitude);
float yPos = (radius) * Mathf.Cos(latitude);
// move marker to position
marker.position = new Vector3(xPos, yPos, zPos);
}
}
// https://forum.unity.com/threads/calculate-latitude-and-longitude-using-uv-coordinates-of-a-sphere.1579935/#post-9775563
// Convert UV coordinate to World Coordinate
    public Vector3 UVTo3D(Vector2 uv, Mesh mesh, Transform transform)
    {
        int[] tris = mesh.triangles;
        Vector2[] uvs = mesh.uv;
        Vector3[] verts = mesh.vertices;
       
        for (int i = 0; i < tris.Length; i += 3)
        {
            Vector2 u1 = uvs[tris[i]];      // get the triangle UVs*
            Vector2 u2 = uvs[tris[i + 1]];
            Vector2 u3 = uvs[tris[i + 2]];
 
            // Calculate triangle area - if zero, skip it
            float a = Area(u1, u2, u3);
            if (a == 0)
                continue;
 
            // Calculate barycentric coordinates of u1, u2 and u3
            // If any is negative then point is outside the triangle, skip it
 
            float a1 = Area(u2, u3, uv)/a;
            if (a1 < 0)
                continue;
 
            float a2 = Area(u3, u1, uv)/a;
            if (a2 < 0)
                continue;
 
            float a3 = Area(u1, u2, uv)/a;
            if (a3 < 0)
                continue;
 
            // Point inside the triangle - find mesh position by interpolation…
            Vector3 p3D = a1 * verts[tris[i]] + a2 * verts[tris[i + 1]] + a3 * verts[tris[i + 2]];
 
            // return it in world coordinates:
            return transform.TransformPoint(p3D);
        }
        return Vector3.zero;
    }
 
    /// Calculate signed triangle area using a kind of “2D cross product”:
    private float Area(Vector2 p1, Vector2 p2, Vector2 p3)
    {
        Vector2 v1 = p1 - p3;
        Vector2 v2 = p2 - p3;
        return (v1.x * v2.y - v1.y * v2.x) / 2;
    }
@unitycoder
Copy link
Author

@jwvanderbeck
Copy link

Any idea how to go the OTHER way? Given a Vector3 on the sphere, what is its latitude and longitude?

@unitycoder
Copy link
Author

@jwvanderbeck
Copy link

jwvanderbeck commented Sep 29, 2022

Actually I finally manmaged to get it working for my Unity project with this code:

var lng = Mathf.Atan2(z, x) * Mathf.Rad2Deg;
var lat = (float)Mathf.Acos(y) * Mathf.Rad2Deg;
                    
// this bit is to flip and quarter rotate the latitude number to put it where you would expect
// (assuming you are an earthling)
// with 0 at the equator, 90 at the north pole and -90 at the south pole
lat = (lat - 90f) * -1f;

// Just a note here that this all orients with the "prime meridian" being the +X or Vector3.right
// which may not be what is desired, as it might make more sense to align with the forward vector
// Not worrying about this now but in theory it should be a simple +90 to the longitude value

@jwvanderbeck
Copy link

What was throwing me for a loop was I had some of the z/y values flipped around due to reading so many other sources that used a different coordinate handedness than Unity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment