For a game I need to calculate if items on the map are in range of the player.
The map is the earth.
I'm using the Haversine formula to calculate the distance between the player and each item.
However I did some profiling and found out that all those sin/cos calculations are too slow to allow for smoth gameplay.
Is there some other method to check of two points on earth could be withing a range of x metres?
The method does not need to be exact, but it must be fast and return true if distance <= x. It may also return true if distance > x (but should not always return true).
My test code (LinqPad)
void Main()
{
var lat = 53.553072;
var lng = 9.993023;
var lat0 = 53.553073;
var lng0 = 9.993178;
"Google Maps: 10.02m".Dump(); // 10.02m
$"Euclid: {DistanceEuclid(lat, lng, lat0, lng0)}m".Dump(); // 10,2396639400397m
$"Haversine: {DistanceHaversine(lat, lng, lat0, lng0)}m".Dump(); // 10,2396637520237m
}
const int R = 6371000;
const double PiBy180 = Math.PI / 180;
const double deglen = 111194.93;
double DistanceEuclid(double lat, double lng, double lat0, double lng0)
{
var x = lat - lat0;
var y = (lng - lng0)*Math.Cos(ToRadians(lat0));
return deglen*Math.Sqrt(x*x + y*y);
}
public double DistanceHaversine(double lat, double lng, double lat0, double lng0)
{
var lat1 = ToRadians(lat);
var lat2 = ToRadians(lat0);
var dLat = ToRadians(lat0 - lat);
var dLng = ToRadians(lng0 - lng);
var h = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) + Math.Cos(lat1) * Math.Cos(lat2) * Math.Sin(dLng / 2) * Math.Sin(dLng / 2);
var c = 2 * Math.Atan2(Math.Sqrt(h), Math.Sqrt(1 - h));
return R * c;
}
double ToRadians(double degrees) => degrees * PiBy180;
In the case of short distances where the curvature is negligible, you could use linear approximation and take the Euclidean distance between the points.
A quick and dirty approach for long distance measurements where curvature matters could involve pre-calculating arc lengths between points on the spheroid (i.e. earth) for distinct arc-lengths ahead of time. For instance, you would create an array(s)/lookup table to find approximate 𝘥 from quantized values of φ₀, φ₁ and Δλ (|λ₀-λ₁|) for a hemisphere about the longitudinal (vertical) axis, since the distances are the same for equal and opposite longitudinal differences. You can improve accuracy by increasing the size of the array(s). If your memory budget isn't tight, you can try making it very large.
Improving the accuracy of the approximation might be possible using specific data structures or correction formulas, but I'm not sure.
Then you can compare the range and the distance between the item and the player.
Related
I have a point expressed in lat/long
Position louvreMuseum = new Position( 48.861622, 2.337474 );
and I have a radius value expressed in meters. I need to check if another point, also expressed in lat/long, is inside the circle.
If I were on a flat surface I can simply use the formula
(x - center_x)^2 + (y - center_y)^2 <= radius^2
as deeply explained in these SO answer.
However as per the latitude/longitude usage I can not use that formula because of the spherical nature of the planet.
How can I calculate a distance from any given point to the center to be compared with the radius?
Function to calculate the distance between two coordinates (converted to C# from this answer):
double GetDistance(double lat1, double lon1, double lat2, double lon2)
{
var R = 6371; // Radius of the earth in km
var dLat = ToRadians(lat2-lat1);
var dLon = ToRadians(lon2-lon1);
var a =
Math.Sin(dLat/2) * Math.Sin(dLat/2) +
Math.Cos(ToRadians(lat1)) * Math.Cos(ToRadians(lat2)) *
Math.Sin(dLon/2) * Math.Sin(dLon/2);
var c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1-a));
var d = R * c; // Distance in km
return d;
}
double ToRadians(double deg)
{
return deg * (Math.PI/180);
}
If the distance between the two points is less than the radius, then it is within the circle.
I'm currently working with files that define rectangular shapes in a way that I'm unfamiliar with. Someone told me it might be a matrix, but knowing that doesn't particularly help me with my problem, converting it to points and back.
For example, I have these values:
0.95, -0.28, -0.28, -0.95, 250.0234, 172.1973, -589.0131, 604.8696
These 8 floats make up a rect with the following coordinates, the center being 0:
{X=-778,Y=838}
{X=-303,Y=698}
{X=-399,Y=372}
{X=-874,Y=512}
To get these points I have used the following function, that someone else wrote for use with these files:
static Point[] GetPoints(double d01, double d02, double d03, double d04, double l01, double l02, double p01, double p02)
{
var points = new Point[4];
double a00 = d01 * l01;
double a01 = d02 * l01;
double a02 = d03 * l02;
double a03 = d04 * l02;
double sx1 = p01 - a00 - a02; if (sx1 < p01) sx1 = Math.Ceiling(sx1);
double sy1 = p02 - a01 - a03; if (sy1 < p02) sy1 = Math.Ceiling(sy1);
double sx2 = p01 + a00 - a02; if (sx2 < p01) sx2 = Math.Ceiling(sx2);
double sy2 = p02 + a01 - a03; if (sy2 < p02) sy2 = Math.Ceiling(sy2);
double sx3 = p01 + a00 + a02; if (sx3 < p01) sx3 = Math.Ceiling(sx3);
double sy3 = p02 + a01 + a03; if (sy3 < p02) sy3 = Math.Ceiling(sy3);
double sx4 = p01 - a00 + a02; if (sx4 < p01) sx4 = Math.Ceiling(sx4);
double sy4 = p02 - a01 + a03; if (sy4 < p02) sy4 = Math.Ceiling(sy4);
if (a02 * a01 > a03 * a00)
{
points[0] = new Point((int)sx1, (int)sy1);
points[1] = new Point((int)sx2, (int)sy2);
points[2] = new Point((int)sx3, (int)sy3);
points[3] = new Point((int)sx4, (int)sy4);
}
else
{
points[0] = new Point((int)sx1, (int)sy1);
points[3] = new Point((int)sx2, (int)sy2);
points[2] = new Point((int)sx3, (int)sy3);
points[1] = new Point((int)sx4, (int)sy4);
}
return points;
}
What I'm looking for now is possibly an explanation what these numbers even are, is this something common that I can read up on or is it custom, and a way to convert the points back to the 8 float values.
Can someone help me with this? Without knowing what this even is, it's really hard to find anything :/
p01 and p02 are center coordinates (CenterX and CenterY)
d01-d04 represent cosine and sine of rotation angle (or dx,dy components of unit-length direction vector)
l01 and l02 are half-width and half-height of initial axis-aligned rectangle.
sxi and syi are X and Y-coordinates of ith vertice.
These coordinates are rounded toward the center coordinate.
Finally the vertices are numerated in certain order - either clockwise or counterclockwise (I did not checked)
To reconstruct initial parameters from vertice set:
You can determine center point as middle of two opposite vertices
p01 = (points[0].X + points[2].X) / 2
p02 = (points[0].Y + points[2].Y) / 2
and rotation angle
Angle = atan2(points[1].Y - points[0].Y, points[1].X - points[0].X)
Then correct Angle by +-Pi/2 or +-Pi to the smallest magnitude value (for example, 3/4*Pi=> Pi/4)
Note that initial parameter set angle may differ by +-Pi/2
Find
d01 = Cos(Angle) etc
Note that angle may differ by +-Pi/2 or +-Pi from initial parameter set.
l01 = Abs((points[1].X - points[0].X) * 0.5 / Cos(Angle))
l02 = Abs((points[2].Y - points[1].Y) * 0.5 / Cos(Angle))
Note that l01, l02 may be interchanged due to ambiguity of angle value.
I would like to be able to generate a cluster of points in 3D space that would create a majority of the points within a specified sphere radius (in this case, 4) from the starting point (in this case, 0,0,0). I'd also like it to generate the occasional outlier too.
It would be similar to this:
But I'd like to be able to tweak some of the settings. I will be generating this in C# and I'm familiar with the Random class, and have some familiarity with spheres. I'm not sure how I can piece it all together.
I have this which can generate a point in a sphere, but I want it to cluster around that center point:
// elsewhere in code
private static readonly Random Random = new Random();
private static double NextDouble(double minValue, double maxValue)
{
double next = Random.NextDouble();
return minValue + (next * (maxValue - minValue));
}
// generates random points in sphere
const int r = 6;
double x;
double y;
double z;
do
{
x = NextDouble(-r, 1 + r);
y = NextDouble(-r, 1 + r);
z = NextDouble(-r, 1 + r);
} while (Math.Pow(x, 2) + Math.Pow(y, 2) + Math.Pow(z, 2) > Math.Pow(r, 2));
I think it is easiest to generate random points in spherical coordinates and transform them to cartesian coordinates.
You could generate the radius from the distribution of your choice. Get the two angles from a uniform distribution; one ranging from -pi to pi, the other from 0 to pi.
You can check this article about Spherical Coordinate System. Martin is correct, you will need
a random number from 0 to r set as r
a random number from 0 to pi set as theta
a random number from 0 to 2*pi set as phi
then to transform them to the Cartesian Coordinate System you have to do the following
x = r sin(theta) cos(phi)
y = r sin(theta) sin(phi)
x = r cos(theta)
What you are doing right now will lead to a point cloud of a cubic shape.
If you need further help with the code please do tell.
Ok, let's start with distribution, which obviously only depends on r
So Probability Density Function would be
PDF(x, y, z) = f(x, y, z) dx dy dz
We know f(x,y,z) depends only on r=sqrt(x^2+y^2+z^2)
Lets move to spherical coordinates
PDF(r,theta,phi) = f(r) r^2 sin(theta) dr dtheta dphi
Where r^2 sin(theta) are coming from Jacobian
It could be easily factorized into
P(r) = f(r) r^2 dr
P(theta) = sin(theta) dtheta
P(phi) = dphi
ok, only phi is distribution uniformly so
phi = 2*pi*random()
for theta it is uniform cos(theta), so
cos(theta) = 2*random()-1
and for r you have to know what f(r) is, but distribution would be affected by r^2 term
P(r) = f(r) r^2 dr
I might suggest using gaussian for f(r), but you might want another choice. And of course
sin(theta) = sqrt(1-cos(theta)^2)
x = r * sin(theta) * cos(phi)
y = r * sin(theta) * sin(phi)
z = r * cos(theta)
If I have two points p1 and p2 where p1 is the pivot point and p2 is the original direction the user was headed and they have a number of possible directions to go p3...pn in random sequence. How do I get the angles between the choices and the segment formed by p1,p2 as clockwise(right hand) positive values between 0 and 360 so that I can sort them from least to greatest?
Also the points p1...pn will be in any quadrant, I can’t assume they will always be in the positive x,y direction. The grid is a standard Cartesian grid not screen coordinates so Y gets smaller as you go down not larger.
So in this example (sorry for the poor drawing but Paint was all I had on my laptop) I need to get the angles:
(p2-p1-p3)
( p2-p1-p4)
( p2-p1-p5)
( p2-p1-p6)
In this order(smallest right hand turn to largest right hand turn):
[( p2-p1-p4), ( p2-p1-p6), ( p2-p1-p5), (p2-p1-p3)]
The points in my case are a class called Vertex:
public class Vertex
{
public double X = 0;
public double Y = 0;
public Vertex() { }
public Vertex(double x, double y)
{
X = x;
Y = y;
}
}
And the code for getting the angles and sorting looks like this right now but has a problem:
private static IEnumerable<Vertex> SortByAngle(Vertex original, Vertex pivot, List<Vertex> choices)
{
choices.Sort((v1, v2) => GetTurnAngle(original, pivot, v1).CompareTo(GetTurnAngle(original, pivot, v2)));
return choices;
}
private static double GetTurnAngle(Vertex original, Vertex pivot, Vertex choice)
{
var a = original.X - pivot.X;
var b = original.Y - pivot.Y;
var c = choice.X - pivot.X;
var d = choice.Y - pivot.Y;
var rads = Math.Acos(((a * c) + (b * d)) / ((Math.Sqrt(a * a + b * b)) * (Math.Sqrt(c * c + d * d))));
return (180 / Math.PI * rads);
}
The problem is the above is if I check it for:
original 66,-66
pivot 280,-191
choice 200,-180
I get an angle of 22.460643124 instead of 337.539356876 which means it went counter-clockwise from the original direction to get that angle. I need it to always go clockwise to get the angle.
What am I doing wrong and how do I fix it?
Update: OK so according to what you guys are saying I can probably use some cross product like math to determine CW vs CCW so the new method would look like this:
private static double GetTurnAngle(Vertex original, Vertex pivot, Vertex choice)
{
var a = original.X - pivot.X;
var b = original.Y - pivot.Y;
var c = choice.X - pivot.X;
var d = choice.Y - pivot.Y;
var angle = Math.Acos(((a * c) + (b * d)) / ((Math.Sqrt(a * a + b * b)) * (Math.Sqrt(c * c + d * d))));
angle = (180 / Math.PI * angle);
var z = (choice.X - pivot.X) * (original.Y - pivot.Y) - (choice.Y - pivot.Y) * (original.X - pivot.X);
if (z < 0)
{
return 360 - angle;
}
return angle;
}
Update 2:
Using the accepted solution it now looks like so:
private static double GetTurnAngle(Vertex original, Vertex pivot, Vertex choice)
{
var angle1 = Math.Atan2(original.Y - pivot.Y, original.X - pivot.X);
var angle2 = Math.Atan2(choice.Y - pivot.Y, choice.X - pivot.X);
var angleDiff = (180 / Math.PI * (angle2 - angle1));
if (angleDiff > 0)//It went CCW so adjust
{
return 360 - angleDiff;
}
return -angleDiff;//I need the results to be always positive so flip sign
}
So far as I can tell that works great so far. Thank you guys for the help!
Take a look at atan2 function. It takes delta y and delta x, so can distinguish all angles.
angle1 = atan2(p1.y-p0.y, p1.x-p0.x);
angle2 = atan2(p2.y-p0.y, p2.x-p0.x);
angle = angle2 - angle1;
If angle is negative, then CW, if positive CCW (or other way around depending on your axis orientation). Note |angle| may be > 180, in which case you may want to do 360-|angle| and reverse the CW CCW conclusion if you're after the shortest route.
You find the Dn=direction from p1 to pn (x=pn.x-p1.x and y=pn.y-p1.y) by the formula:
Dn=f(x,y)=180-90*(1+sign(y))* (1-sign(x^2))-45*(2+sign(y))*sign(x)
-180/pi()*sign(x*y)*atan((abs(y)-abs(x))/(abs(y)+abs(x)))
So the angles are Angle(p2-p1-pn)=Dn-D2.
I'm looking for a bit of math help. I have a game were a 2D heightmap is generated and then stretched over a sphere using a length/direction formula. Now I need to know how to calculate the height between 2 points on my heightmap.
What I know:
The array that holds the heightmap
The angle in radians to my object
how many points there are on the heightmap
My problem look somewhat like so:
image
more images
The red and blue lines are the 2 heightmap points, and the light blue is where I'd like to calculate the height at.
Here's my current code to do it, but it doesn't work to well.
public double getheight(double angle)
{
//find out angle between 2 heightmap point
double offset = MathHelper.TwoPi / (heightmap.Length - 1);
//total brainfart attempt
double lowerAngle = offset * angle;
double upperAngle = offset * angle + offset;
//find heights
double height1 = heightmap[(int)lowerAngle];
double height2 = heightmap[(int)upperAngle];
//find offset angle
double u = angle - lowerAngle / (upperAngle - lowerAngle);
//return the height
return height1 + (height1 - height2) * u;
}
from my vegetation code, this seems to work okay, but is to rough to use for units and such, as they jump up/down as they move, due to it using only 1 heightmap point.
double[] hMap = planet.getHeightMap();
double i = hMap.Length / (Math.PI * 2);
this.height = hMap[(int)(angle * i)];
EDIT: example at end based on additional question info
Sounds to me like a linear interpolation - if you look at it from a 2d point of view, you've got two points:
(x1, y1) = point one on heightmap
(x2, y2) = point two on heightmap
and one point somewhere between (x1,x2) at an unknown height:
pu = (xu, yu)
A generic formula for LERP is:
pu = p0 + (p1 - p0) * u
where:
p0 = first value
p1 = second value
u = % where your unknown point lies between (p0,p1)
Here, we'll say p0 == y2 and p1 == y1. Now we need to determine "how far" the unknown point is between x1 and x2 - if you know the angles to the two heightmap points, this is easy:
u = ang(xu) - ang(x1) / (ang(x2) - ang(x1))
Alternatively, you could project your angle out to Max(y1,y2) and get the "unknown x pos" that way, then calculate the above.
So, let's try a contrived example:
p1 = point one in map = (1,2) therefore ang(p1) ~ 57 degrees
p2 = point two in map = (2,4) therefore ang(p2) ~ 114 degrees
note that here, the "x axis" is along the surface of the sphere, and the "y-axis" is the distance away from the center.
pu = object location = py #angle 100 degrees ~ 1.74 radians
px = (1.74 rad - 1 rad ) / (2 rad - 1 rad) = 0.74 / 1.0 = 0.74 => 74%
py = y0 + (y1 - y0) * u
= 2 + (4 - 2) * 0.74
= 2.96
Hopefully I didn't drop or misplace a sign there somewhere... :)
Ok, your example code - I've tweaked it a bit, here's what I've come up with:
First, let's define some helpers of my own:
public static class MathHelper
{
public const double TwoPi = Math.PI * 2.0;
public static double DegToRad(double deg)
{
return (TwoPi / 360.0) * deg;
}
public static double RadToDeg(double rad)
{
return (360.0 / TwoPi) * rad;
}
// given an upper/lower bounds, "clamp" the value into that
// range, wrapping over to lower if higher than upper, and
// vice versa
public static int WrapClamp(int value, int lower, int upper)
{
return value > upper ? value - upper - 1
: value < lower ? upper - value - 1
: value;
}
}
Our Test setup:
void Main()
{
var random = new Random();
// "sea level"
var baseDiameter = 10;
// very chaotic heightmap
heightmap = Enumerable
.Range(0, 360)
.Select(_ => random.NextDouble() * baseDiameter)
.ToArray();
// let's walk by half degrees, since that's roughly how many points we have
for(double i=0;i<360;i+=0.5)
{
var angleInDegrees = i;
var angleInRads = MathHelper.DegToRad(i);
Console.WriteLine("Height at angle {0}°({1} rad):{2} (using getheight:{3})",
angleInDegrees,
angleInRads,
heightmap[(int)angleInDegrees],
getheight(angleInRads));
}
}
double[] heightmap;
And our "getheight" method:
// assume: input angle is in radians
public double getheight(double angle)
{
//find out angle between 2 heightmap point
double dTheta = MathHelper.TwoPi / (heightmap.Length);
// our "offset" will be how many dThetas we are
double offset = angle / dTheta;
// Figure out two reference points in heightmap
// THESE MAY BE THE SAME POINT, if angle ends up
// landing on a heightmap index!
int lowerAngle = (int)offset;
int upperAngle = (int)Math.Round(
offset,
0,
MidpointRounding.AwayFromZero);
// find closest heightmap points to angle, wrapping
// around if we go under 0 or over max
int closestPointIndex = MathHelper.WrapClamp(
lowerAngle,
0,
heightmap.Length-1);
int nextPointIndex = MathHelper.WrapClamp(
upperAngle,
0,
heightmap.Length-1);
//find heights
double height1 = heightmap[closestPointIndex];
double height2 = heightmap[nextPointIndex];
// percent is (distance from angle to closest angle) / (angle "step" per heightmap point)
double percent = (angle - (closestPointIndex * dTheta)) / dTheta;
// find lerp height = firstvalue + (diff between values) * percent
double lerp = Math.Abs(height1 + (height2 - height1) * percent);
// Show what we're doing
Console.WriteLine("Delta ang:{0:f3}, Offset={1:f3} => compare indices:[{2}, {3}]",
dTheta,
offset,
closestPointIndex,
nextPointIndex);
Console.WriteLine("Lerping {0:p} between heights {1:f4} and {2:f4} - lerped height:{3:f4}",
percent,
height1,
height2,
lerp);
return lerp;
}