- Home /
Moving in Unity with real world coordinates (latitude-longitude to 3d position)
So, I've got an Arduino device getting data from an external GPS and sending the lat long coordinates to Unity via bluetooth. What I'm struggling with is how exactly to visualize movement in the real world in Unity, so let's say I move 5m forward I'd want my character to move forward as well. I scoured the web for a way to convert these lat and long values to a xyz vector:
private Vector3 PolarToCartesian(float latitude, float longitude)
{
//an origin vector, representing lat,lon of 0,0.
Vector3 origin = new Vector3(0, 0, 1);
//build a quaternion using euler angles for lat,lon
Quaternion rotation = Quaternion.Euler(latitude, longitude, 0);
Vector3 point = rotation * origin;
return point;
}
and this seems to be working fine, but when I enter some coordinates as start coordinates, and then tell the character to move to coordinates that are 1.5km away in the real world, the calculations in Unity return pretty much the exact same vector and the character just doesn't move, i.e if I take the start of a street in Vienna with coords 48.195932, 16.339999
this gives me a vector of (0.2, -0.7, 0.6)
. Taking the coordinates of the end of that same street 48.202385, 16.361278
, the difference is so small that I get pretty much the same vector. Any ideas as to how to go about solving this, maybe it's just a scaling issue with the numbers I'm getting an Unity units or am I looking at it from a completely wrong angle here?
I am looking for the same question. Have been you @Jaetriel found any solution?,I am looking for the same question. Have been you found any solution?
I've done this kind of thing before, but I don't remember using Quaternion.Euler. It seems to be right, though; and even if it isn't, I believe results would just be inverted.
Maybe the problem here is that you're expecting point to be in a real-world scale? Latitude and Longitude are just angles; you're getting the position on a spherical world with a diameter of 2. You would need to multiply point by the real radius of the Earth, which is 6,371 kilometers.
If things are still too imprecise, it could be because you're using floats? Using doubles would give you more precision, although I don't expect it to be necessary for most common cases. Also, doubles don't work with Unity's quaternion.
There are libraries for this sort of thing, though. Some of them even consider that the Earth is not a perfect sphere. I'd really recommend you to look for a good one if you're doing lots of geolocation stuff. You know, why reinvent the wheel? That said, if you just want a quick simple solution in repo form, this library has the same API than System.Device.Location.GeoCoordinate, which is normally not available in Unity.
Answer by andrew-lukasik · Jul 20, 2021 at 01:47 PM
This will resolve your issue, just make sure to enable _originFromPlayerPosition
. The solution is to increase scale, math precision and implement floating origin
to avoid floating-point rendering/transform issues.
left to right:
3d game world space
debug 2d coordinates
debug 3d spherical coordinates
// src*: https://gist.github.com/andrew-raphael-lukasik/26d14f6c9abd0c410d83305222ca829b
using UnityEngine;
using Math = System.Math;
public class GeoCoordToCart : MonoBehaviour
{
[SerializeField] Vector3 _worldSize = new Vector3(100,0,100);
[SerializeField] Vector3 _worldOrigin = new Vector3(0,0,0);
[SerializeField] bool _originFromPlayerPosition = false;
[SerializeField] GeoCoord _playerPosition = new GeoCoord{ label="Player 0" , latitude=48.195932 , longitude=16.339999 };
[SerializeField] float _worldPointSize = 1f;
[SerializeField][Min(0.01f)] float _gizmoScale = 10f;
#if UNITY_EDITOR
void OnValidate ()
{
if( _originFromPlayerPosition )
{
UnityEditor.Undo.RecordObject( this , "Origin From Player Position" );
MoveOrigin( _playerPosition );
}
}
void OnDrawGizmos ()
{
Vector3 origin = transform.position;
// draw sphere:
Gizmos.color = Color.cyan;
Gizmos.DrawWireSphere( origin , _gizmoScale );
Gizmos.color = new Color(0,1,1,0.05f);
Gizmos.DrawSphere( origin , _gizmoScale );
// draw rect:
Gizmos.color = Color.cyan;
Vector2 rect = new Vector2( _gizmoScale*4f , _gizmoScale*2f );
Vector3 rectCenterWorld = origin - Vector3.right*_gizmoScale*4f;
Gizmos.DrawWireCube( rectCenterWorld , new Vector3(rect.x,rect.y,0) );
// draw game world bounds:
Gizmos.DrawWireCube( _worldOrigin , _worldSize );
for( int i=_coordinates.Length-1 ; i!=-1 ; i-- )
{
var coord = _coordinates[i];
// sphere:
{
Vector3 spherePoint = coord.ToVector3UnitSphere();
Vector3 worlPos = origin + spherePoint*_gizmoScale;
Gizmos.color = Color.yellow;
Gizmos.DrawSphere( worlPos , _gizmoScale*0.01f );
UnityEditor.Handles.Label( worlPos , coord.ToString() );
}
// rect:
{
Vector2 v2point = coord.ToVector2();
Vector3 worlPos = rectCenterWorld + Vector3.Scale(v2point,rect*0.5f);
Gizmos.color = Color.yellow;
Gizmos.DrawSphere( worlPos , _gizmoScale*0.01f );
UnityEditor.Handles.Label( worlPos , coord.ToString() );
}
// game world:
{
Vector3 v3point = coord.ToVector3();
Vector3 worlPos = _worldOrigin + Vector3.Scale(v3point,_worldSize*0.5f);
Gizmos.color = Color.green;
Gizmos.DrawCube( worlPos , new Vector3(_worldPointSize,_worldPointSize,_worldPointSize) );
UnityEditor.Handles.Label( worlPos , coord.ToString() );
}
}
}
#endif
void MoveOrigin ( GeoCoord coord ) => _worldOrigin = -Vector3.Scale( coord.ToVector3() , _worldSize*0.5f );
[SerializeField] GeoCoord[] _coordinates = new GeoCoord[]{
new GeoCoord{ label="" , latitude=0 , longitude=0 } ,
new GeoCoord{ label="Reykjavík" , latitude=64.1335484 , longitude=-21.9224813 } ,
new GeoCoord{ label="Kraków" , latitude=50.0468548 , longitude=19.9348336 } ,
new GeoCoord{ label="Paris" , latitude=48.8589507 , longitude=2.2770204 } ,
new GeoCoord{ label="Buenos Aires" , latitude=-34.6156625 , longitude=-58.503338 } ,
new GeoCoord{ label="Ho Chi Minh" , latitude=10.7553411 , longitude=106.4150395 } ,
new GeoCoord{ label="İstanbul" , latitude=41.0054958 , longitude=28.8720965 } ,
new GeoCoord{ label="Nairobi" , latitude=-1.3030364 , longitude=36.7773568 } ,
new GeoCoord{ label="Dublin" , latitude=53.3244431 , longitude=-6.3857857 } ,
new GeoCoord{ label="London" , latitude=51.5287718 , longitude=-0.2416804 } ,
new GeoCoord{ label="New York" , latitude=40.6976637 , longitude=-74.119764 } ,
new GeoCoord{ label="Starbase" , latitude=25.9884963 , longitude=-97.1632233 } ,
new GeoCoord{ label="Vancouver" , latitude=49.2578263 , longitude=-123.1939434 } ,
new GeoCoord{ label="Honolulu" , latitude=21.1579153 , longitude=-158.5605044 } ,
new GeoCoord{ label="Mumbai" , latitude=19.0825223 , longitude=72.7411016 } ,
new GeoCoord{ label="Tehran" , latitude=35.6970118 , longitude=51.2097352 } ,
new GeoCoord{ label="Hongkong" , latitude=22.3529913 , longitude=113.9876156 } ,
new GeoCoord{ label="Sydney" , latitude=-33.8479255 , longitude=150.6511036 } ,
new GeoCoord{ label="Dakar" , latitude=14.7110139 , longitude=-17.5360369 } ,
new GeoCoord{ label="Natal" , latitude=-4.8730191 , longitude=-34.9832028 } ,
new GeoCoord{ label="Ushuaia" , latitude=-54.8228355 , longitude=-68.3797813 } ,
new GeoCoord{ label="Caracas" , latitude=10.4683841 , longitude=-66.9605778 } ,
new GeoCoord{ label="Cape Town" , latitude=-33.9152193 , longitude=18.3751883 } ,
new GeoCoord{ label="Якутск" , latitude=62.0312622 , longitude=129.5613624 } ,
new GeoCoord{ label="Roma" , latitude=41.9099856 , longitude=12.3955715 } ,
new GeoCoord{ label="Gibraltar" , latitude=36.129508 , longitude=-5.3708525 } ,
new GeoCoord{ label="Longyearbyen" , latitude=79.2639353 , longitude=4.3014832 } ,
new GeoCoord{ label="Lima" , latitude=-12.0185972 , longitude=-77.1280131 } ,
new GeoCoord{ label="Santiago" , latitude=-33.4727088 , longitude=-70.7702586 } ,
new GeoCoord{ label="Atqasuk" , latitude=70.539337 , longitude=-157.9874001 } ,
new GeoCoord{ label="Douala" , latitude=4.0359007 , longitude=9.6715926 } ,
new GeoCoord{ label="Stazione Mario Zucchelli" , latitude=-74.6958608 , longitude=164.0881707 } ,
new GeoCoord{ label="Alert" , latitude=82.5058286 , longitude=-62.5571841 } ,
new GeoCoord{ label="Vienna p0" , latitude=48.195932 , longitude=16.339999 } ,
new GeoCoord{ label="Vienna p1" , latitude=48.202385 , longitude=16.361278 } ,
};
}
[System.Serializable]
public struct GeoCoord
{
public string label;
public double latitude;
public double longitude;
public override string ToString () => this.label;
public Vector2 ToVector2 () => new Vector2( (float) Math.Sin( this.longitude * Deg2Rad * 0.5 ) , (float) Math.Sin( this.latitude * Deg2Rad ) );
public Vector3 ToVector3 ( float elevation = 0 ) => new Vector3( (float) Math.Sin( this.longitude * Deg2Rad * 0.5 ) , elevation , (float) Math.Sin( this.latitude * Deg2Rad ) );
public Vector3 ToVector3UnitSphere () => Quaternion.Euler(0,-(float)this.longitude,0) * ( Quaternion.Euler(-(float)this.latitude,0,0) * Vector3.forward );
const double Deg2Rad = Math.PI / 180.0;
}
Here is an example. Scale is set to 1 000 000
+ floating origin results in (+/-) 100 units of distance between those coordinates. Naturally, with some additional math, you can calculate an exact scale that will match 1 unit with 10 or 100 meters of real world distance.
1 unit - 1 meter probably won't be a good idea since geolocation offers no such precision in 2021. But one can compensate for that if your really need it, I guess.