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;
}
Related
I was trying to map the 360 video pixel coordinate to sphere surface coordinate but I couldn't get right result... It just mapped to the wrong position I already know the points of the XY data for 360 video pixels.
how map 2d grid points (x,y) onto sphere as 3d points (x,y,z)
I checked this link and I copied method from this but what I'm getting is not mapped to the desired position.
How can I get radius from the pixels?
I am not sure if I'm passing right radius for imageRadius but I thought it will be circumference/PI to get radius and the video ratio is 4096x2048. I also tried to pass the number 1 because UV is 0-1 but it was not right...
Is Method wrong?
Maybe the method is wrong. I passed random numbers into the imageRadius but couldn't get the right position... If I make X to negative number the seems like little bit closer to result....?
Current Result
https://youtu.be/t0I7Hlb-tbk
It mapped to up right position with the method that I found online...
Project File
https://drive.google.com/a/swordfish-sf.com/file/d/0B45RYzVs0t0_VVdaaHdmNHRWTk0/view?usp=sharing
If somebody can check the Unity project file that will be great...
Current Code
public class mapScript : MonoBehaviour {
public int input = 4098;
float imageRadius = 4098f / Mathf.PI; //2098? 3072? 4098?
float radius;
public GameObject testSphere;
void Start () {
radius = this.transform.localScale.x;
}
void Update () {
imageRadius = input / Mathf.PI;
int currentFrame = (int)this.GetComponent<VideoPlayer>().frame;
testSphere.transform.position = MercatorProjection(mapVals[currentFrame,0],mapVals[currentFrame,1]);
}
Vector3 MercatorProjection(float xVal, float yVal)
{
float lon = (xVal / imageRadius);
float lat = (2 * Mathf.Atan(Mathf.Exp(yVal / imageRadius)) - Mathf.PI / 2);
float calcX = radius * Mathf.Cos(lat) * Mathf.Cos(lon);
float calcY = radius * Mathf.Cos(lat) * Mathf.Sin(lon);
float calcZ = radius * Mathf.Sin(lat);
Vector3 result = new Vector3(calcX,calcY,calcZ);
Debug.Log(result);
return result;
}
float[,] mapVals = new float[,] {
{1969.21f, 928.625f},
{1969.6f, 928.533f},
{1968.92f, 928.825f},
{1968.68f, 929f},
{1968.47f, 929.067f},
{1968.41f, 929.025f},
{1968.48f, 928.992f},
....
};
}
Thank you.
As a side note, the radius is arbitrary. The pixel coordinates only map to the directional coordinates (polar [θ] and azimuthal [ϕ] angles).
We can do this by mapping each pixel to equal θ and ϕ intervals. The diagram below illustrates a low-resolution setup:
Let us adopt the convention that, for an image of with W, ϕ = 0 corresponds to:
Even W: half way between X = floor((W - 1) / 2) and X = ceil((W - 1) / 2)
Odd W: in the middle of the pixel column at X = floor((W - 1) / 2)
The pixel row at Y maps to the equilatitudinal line at θ = (Y + 0.5) / H * π.
To map all pixels in their entirety, let X start at -0.5 instead of 0, and end at W - 0.5; likewise for Y. Since integer coordinates map to the centers of the pixel regions shown above, this allows the whole area of any particular pixel to be addressed. You may need this later on if you plan on doing multi-sampling filtering for e.g. anti-aliasing.
Code:
Vector3 Mercator(float x, float y, int w, int h)
{
// outside of valid pixel region
if (x < -0.5f || x >= w - 0.5f || y < -0.5f || y >= h - 0.5f)
return new Vector3();
float theta = (y + 0.5f) / h * Math.PI;
float phi = ((x + 0.5f) / w - 0.5f) * 2.0 * Math.PI;
float c_t = Math.Cos(theta);
return new Vector3(c_t * Math.Cos(phi), c_t * Math.Sin(phi), Math.Sin(theta));
}
... and multiply the resulting direction vector by any "radius" you like, since it has (basically) nothing to do with the mapping anyway.
I need to be able to check whether the angle between three points (A, B and C) which make up part of a shape is reflex (> PI radians), as in the diagram below (sorry for poor paint skills!):
My points should always be anti-clockwise, and I always want to measure the angle on the inside of the shape.
I am currently doing this using the following code:
//triangle[] is an array of the three points I am testing, corresponding
// to [A, B, C] on the diagram above
//Vectors from B to A and C
PointF toA = PointFVectorTools.difference(triangle[0], triangle[1]);
PointF toC = PointFVectorTools.difference(triangle[2], triangle[1]);
double angle = Math.Atan2(toB.Y, toB.X) - Math.Atan2(toA.Y, toA.X);
//Put angle in range 0 to 2 PI
if (angle < 0) angle += 2 * Math.PI;
return angle > Math.PI;
This has worked in all the cases I have tried up until now, but with these co-ords it does not work:
(Where B=(2,3) )
The angle I get back is ~-0.5, whereas I would expect ~+0.5. Any ideas why this is wrong?
UPDATE
I've attempted to implement Nico's solution, and while I understand it in theory I'm getting a real headache trying to implement it. Here is the code so far:
//Vector A -> B
float dx = triangle[1].X - triangle[0].X;
float dy = triangle[1].Y - triangle[0].Y;
//Left normal = (y, -x)
PointF leftDir = new PointF(dy, -dx);
//Vector B -> C
dx = triangle[2].X - triangle[1].X;
dy = triangle[2].Y - triangle[1].Y;
//Dot product of B->C and Left normal
float dot = dx * leftDir.X + dy * leftDir.Y;
return dot < 0;
In the following, I assume that the x-axis points to the right and the y-axis points upwards. If this is not the case in your scenario, you might need to switch some signs.
If you have the line segment (x1, y1) - (x2, y2) and points are sorted counter-clockwise, you know that the shape is left of the line segment. The orthogonal direction vector that points to the line segment's left is:
leftDir = (y1 - y2, x2 - x1)
Together with the line segment, this direction defines a half space. If the following angle is convex, the third point must lie in this half space. If that's not the case, the angle is concave (which you apparently call reflex):
You can determine if the point lies in the same half space with the dot product:
isConcave = dot(p3 - p2, leftDir) < 0
In code:
float dx = x3 - x2;
float dy = y3 - y2;
float dot = dx * leftDir.x + dy * leftDir.y
return dot < 0;
I'm not sure how toB in your code is defined, and also I'm not familar with PointF.
Anyway you should use the cosine rule c^2 = a^2 + b^2 - 2ab cos(C) (where a,b,c are the lengths of the sides of the triangle, and C is the angle subtending c):
public bool IsReflex(... triangle)
{
var a = GetVectorLength(triangle[0].x, triangle[0].y, triangle[1].x, triangle[1].y);
var b = GetVectorLength(triangle[1].x, triangle[1].y, triangle[2].x, triangle[2].y);
var c = GetVectorLength(triangle[2].x, triangle[2].y, triangle[0].x, triangle[0].y);
var cosC = (c*c - a*a - b*b) / (2*a*b);
var C = Math.Acos(cosC); // this returns a value between 0 and pi
return Math.Abs(C) > (Math.PI/2);
}
private double GetVectorLength(double x0, double y0, double x1, double y1)
{
// using Pythagoras
var sideX = x0 - x1;
var sideY = y0 - y1;
return Math.Sqrt(sideX*sideX + sideY*sideY);
}
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.
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 have 3 particles and one of them is the center particle. I want to rotate other two particle ( stored in particles list ) relative to the center particle with the formula q' = Θq + p where q' is the new position of the rotated particle, Θ is the orientation angle and p is the position of center particle. The initial position of other two particles is stored in initialParticlePosition list. THe problem is I think the angle I calculate is wrong because of the range. I thing I should take the range as [-pi, pi) or something like this. In some parts it calculates correct but sometimes it is wrong. Can someone help me with this code or give me another method of rotating.
{
angle = Math.Acos(Vector2.Dot(heading,new Vector2(0,-1) ));
for (int i = 0; i < 2; i++)
{
tempX = (double)initialParticlePositions[i].X * Math.Cos(angle) - (double)initialParticlePositions[i].Y * Math.Sin(angle) + centerParticle.position.x;
tempY = (double)initialParticlePositions[i].X * Math.Sin(angle) + (double)initialParticlePositions[i].Y * Math.Cos(angle) + centerParticle.position.y;
particles[i].position.x = tempX;
particles[i].position.y = tempY;
}
}
Some methods that might help (angles always in degrees, not rad):
public static double GetAngle(Vector v)
{
return Math.Atan2(v.X, -v.Y) * 180.0 / Math.PI;
}
public static Vector SetAngle(Vector v, double angle)
{
var angleInRads = angle * (Math.PI / 180.0);
var distance = v.Length;
v.X = (Math.Sin(angleInRads) * distance);
v.Y = -(Math.Cos(angleInRads) * distance);
return v;
}
static public Point RotatePointAroundCenter(Point point, Point center, double rotationChange)
{
Vector centerToPoint = point - center;
double angle = GetAngle(centerToPoint);
Vector centerToNewPoint = SetAngle(centerToPoint, angle + rotationChange);
return center + centerToNewPoint;
}
(You should start marking answers that help as answer, click the checkmark outline below the votes on the left, e.g. you could accept this answer)
Edit: Optimized the methods a bit.
The particle positions that are orbiting can be set with a single line of code each:
Assume p1, p2, & p3 are Vector2s and p2 & p3 are orbiting p1.
p2 = Vector2.Transform(p2 - p1, Matrix.CreateRotationZ(rotationChangeP2)) + p1;
p3 = Vector2.Transform(p3 - p1, Matrix.CreateRotationZ(rotationChangeP3)) + p1;
The Matrix.Create...() method will call the two trig functions for you.
edit. the Matrix & Vector2 structures & methods are XNA specific but included here because that's what the OP tagged his Q with.
angle = Math.Acos(Vector2.Dot(heading,new Vector2(0,-1)));
As you suspect, your combination of dot product and Acos will only give you angles in a 180
degree range.
Instead, use Atan2 on your unit vector to get a full range of angles from -pi to pi.
angle = (float)Math.Atan2((double)heading.Y, (double)heading.X);
You may need to negate the Y term if your Y axis is positive in the down direction.