View Full Version : Useful trigonometry functions!
Therion
04-18-2007, 08:59 AM
Some useful angle functions:
GetAngle: Returns the angle between point x,y - x2,y2 (360º = 360000)
GetDist: Returns euclidian distance between 2 points
GetDistX: X increment for given angle in given distance
GetDistY: Same for GetDistX, but for Y.
The only issue i'm having is that GetAngle gives a slightly incorrect angle, anyone trigonometric-wise can give me some advice about this?
int GetAngle(int x, int y, int x2, int y2)
{
int distx = x2 - x;
int disty = y2 - y;
int ang;
if ( distx == 0 )
{
if (disty < 0)
ang = 270000;
else
ang = 90000;
return ang;
}
else
{
ang = ( int )( atan( disty / distx ) * 180000 / M_PI);
if (distx > 0)
ang = -ang + 180000;
else
ang = -ang;
return ang;
}
}
int GetDist(int x, int y, int x2, int y2)
{
int distx = x2 - x;
int disty = y2 - y;
return ( int )sqrt( ( distx * distx )
+ ( disty * disty ) );
}
float GetDistX(int angle, float distance)
{
return (float)(cos(angle * M_PI / 180000) * distance);
}
float GetDistY(int angle, float distance)
{
return (float)(-sin(angle * M_PI / 180000) * distance);
}
EDIT: Removed some useless casting o_O i dont know what the heck was i thinking...
EDIT2: Added this function
NearAngle: Does a smooth transition between 2 angles, returning the next logical angle in the transition, given the proper "smoothness" (i.e.: 10 is a good number)
int NearAngle(int angle, int dest, int smoothness)
{
if (smoothness < 1)
smoothness = 1;
int temp;
if (angle < dest && dest-angle > 180000)
{
angle += 360000;
}
if (angle > dest && angle-dest > 180000)
{
angle -= 360000;
}
if (angle < dest)
{
temp = dest-angle;
angle += temp / smoothness;
if (angle > dest) angle = dest;
}
else
{
temp = angle-dest;
angle -= temp / smoothness;
if (angle < dest) angle = dest;
}
if (angle < 0) return angle + 360000 ;
if (angle >= 360000) return angle - 360000 ;
return angle ;
}
Hope someone finds this useful...
If you find a bug don't hesitate to tell me!
Thanks!
jcottier
04-18-2007, 10:16 AM
You should use floating point precision and radian.
It might have been wise 10 years ago to use integer instead of float for CPU saving but you shouldn't bother now. Floating point calculation are really fast now and so much more natural to use.
JC
Therion
04-18-2007, 10:58 AM
Thanks for the advice!... I tend to make these useless optimizations often, i think i should start thinking the fact that my code will run in something faster than a 486 :) .
RinkuHero
04-18-2007, 02:38 PM
Another useful function is a function to return the distance between two angles.
For instance, what's the distance between 5 degrees and 355 degrees? 10 degrees. The way I figured out (which seems to work, although it's probably not the simplest way) is:
angle_difference = abs((angle1 mod 360) - (angle2 mod 360));
if (angle_difference > 180)
then angle_difference = 360 - angle_difference;
return (angle_difference);
andrew
04-18-2007, 03:24 PM
it's also kinda dangerous to be dividing ints, passing ints into functions that take floats/doubles, etc... at any moment the compiler could round your values to integers and you'd lose precision (which would cause errors)
for example: atan( disty / distx ) is going to have problems if disty and distx are integers and the compiler passes an int into atan() -- imagine what happens when distx and disty are small
Bad Sector
04-18-2007, 04:20 PM
Depends on what you're doing actually. If you do lots of muls and divs, using floating point is faster, but if you do subs and adds using fixed point arithmetic is not only faster but more precise. You might have more precise results using doubles or long doubles instead of floats -and internally the CPU converts all floating point numbers to 80bit anyway- but moving doubles and long doubles around in memory is slower than moving floats and from experiments i've found that although computationally they might be the same in cycles, due to the memory bottleneck they're slower.
The ideal method is to break your operations in batches of integers and floats where needed, and convert between these between the batches.
Of course you have to worry about all these only if you experience slowdowns in your game because of the CPU usage and only if you cannot manage to figure out a better algorithm :-).
Applewood
04-18-2007, 04:33 PM
Another useful function is a function to return the distance between two angles.God yeah, the trouble I remember having trying to figure this one out with no conditionals! Gave up in the end and have something very similar looking to yours now. It's one of those bits of code I make sure to never lose!
andrew
04-18-2007, 10:44 PM
Depends on what you're doing actually. If you do lots of muls and divs, using floating point is faster, but if you do subs and adds using fixed point arithmetic is not only faster but more precise. You might have more precise results using doubles or long doubles instead of floats -and internally the CPU converts all floating point numbers to 80bit anyway- but moving doubles and long doubles around in memory is slower than moving floats and from experiments i've found that although computationally they might be the same in cycles, due to the memory bottleneck they're slower.
Right, but a float is the same size as an int... it's fairly rare to have to use doubles unless there is an explicit need for super-precision (i.e. physics engines)
In general, I couldn't imagine that there's any reason to go through the hassle of fixed point these days unless you are SERIOUSLY cpu-bound. The algorithm itself should always be the first place to look for speedup. For these sorts of simple 2D math functions, unless you're calling them 100000000 times a second, there's not much point in making your life difficult...
Also, you're probably losing more time doing float to int casts than the actual math takes... (with code like: x = (int) atan2(some_int / some_int2)).
tolworthy
04-19-2007, 12:47 AM
Does anyone know of code that can approximate tangents? I use a simplified language that doesn't have any trig functions. It doesn't need to be very precise, it's just used for situations like "is ego standing close to X." Right now I just use a long series of "if" statements but there must surely be a better way?
jankoM
04-19-2007, 01:22 AM
I think it's the best approach to make or get a 2d Vector library and put all this things in-there (distances, angles, inclusions, addition, scalar, rotate...) and move it from the core up. Working with vectors to move, accelerate, position..... stuff is so much neat than allways changing x and y to me. I had such library in c++ that I got on net and then added my stuff. I can post it if anyone wishes...
Bad Sector
04-19-2007, 01:34 AM
Also, you're probably losing more time doing float to int casts than the actual math takes... (with code like: x = (int) atan2(some_int / some_int2)).
Yeah, that's why i said that the ideal case (if you really need this sort of optimization) is to split the operations in int and float batches.
But really, with the emerge of multi-core CPUs, it's much better to find parallelizing algorithms and work with threads than try to get yet another cycle. At the future, the CPU speed won't be measured in how many operations per sec a single thread can do, but how many cores it will have any how many operations can per second can be performed in all these cores. So the ideal code in such systems will be the code that takes advantage of the multicore environment.
@tolworthy:
Isn't something like abs((ego.x - other.x)*(ego.x - other.x) + (ego.y - other.y)*(ego.y*other.y)) < min_distance*min_distance enough?
ChrisP
04-19-2007, 03:13 AM
@tolworthy:
Isn't something like abs((ego.x - other.x)*(ego.x - other.x) + (ego.y - other.y)*(ego.y*other.y)) < min_distance*min_distance enough?
That's more or less what I'd use, though I'd modify it to (ego.x-other.x)*(ego.x-other.x) + (ego.y-other.y)*(ego.y-other.y) < min_distance*min_distance. (You had a multiplication instead of a subtraction, and the absolute value isn't necessary since the LHS will always be positive anyway, assuming there are no complex numbers involved.)
jimflip
04-19-2007, 07:44 AM
Our engine is all fixed point based :) very useful if your not just targeting desktop computers ;)
tolworthy
04-19-2007, 10:46 AM
Isn't something like abs((ego.x - other.x)*(ego.x - other.x) + (ego.y - other.y)*(ego.y*other.y)) < min_distance*min_distance enough?
That's good for distances, but something I forgot to mention (sorry!) is that this is a 2D game with a different sprite for each direction that ego might face. So the angle is important. Using real trig (or at least a fake version) is not essential for my game, but would just be more elegant and would avoid those irritating "face the wrong way" or "spin around" bugs. :)
Bad Sector
04-19-2007, 01:09 PM
That's more or less what I'd use, though I'd modify it to (ego.x-other.x)*(ego.x-other.x) + (ego.y-other.y)*(ego.y-other.y) < min_distance*min_distance. (You had a multiplication instead of a subtraction, and the absolute value isn't necessary since the LHS will always be positive anyway, assuming there are no complex numbers involved.)
Oops! Yeah, you are right, i just had the "function(square + square)" writing in my mind (with function being "sqrt") and "abs" just popped out :-P.
ChrisP
04-20-2007, 12:44 AM
That's good for distances, but something I forgot to mention (sorry!) is that this is a 2D game with a different sprite for each direction that ego might face. So the angle is important. Using real trig (or at least a fake version) is not essential for my game, but would just be more elegant and would avoid those irritating "face the wrong way" or "spin around" bugs. :)
I'm not sure exactly what you mean, but if you mean that you want to test "is ego facing the object?" as well as "is ego close to the object?", then look into the dot product (http://mathworld.wolfram.com/DotProduct.html). You need to obtain two vectors (http://en.wikibooks.org/wiki/Vector): One that points from ego to the object (I'll call it A) and one that has the direction that the ego is facing (I'll call it B). The length of these vectors is not important.
Then compute: A_x*B_x + A_y*B_y. If the result is zero, then the vectors are perpendicular (so ego is facing exactly 90 degrees to the left or the right of the object). If the result is positive, then ego is facing more-or-less towards the object (within a 180-degree arc). If it's negative, then ego is facing away from the object.
Note that the X component of A is just (object.x-ego.x), and similarly the Y component of A is (object.y-ego.y). So the above is equivalent to (object.x-ego.x)*B_x + (object.y-ego.y)*B_y. How you calculate the vector B will depend on how you're encoding the sprite's direction.
This happens because the dot product of two vectors is closely related to the cosine between those two vectors. The MathWorld page I linked to above has further explanation (if you don't mind that it's a bit technical).
tolworthy
04-20-2007, 12:49 AM
If the result is positive, then ego is facing more-or-less towards the object (within a 180-degree arc).
Thanks, that's interesting. I'll have to experiment. Ego can face in eight possible directions, and usually being one direction off is not a huge problem. But occasionally, in close-up shots, facing 45 degrees to the wrong direction looks completely wrong. I'll have a look at dot product stuff. Thanks again.
Sol_HSA
04-20-2007, 01:27 AM
Does anyone know of code that can approximate tangents?
Taylor series..
tan(x) = x + x^3/3 + 2x^5/15 + 17x^7/315 + ...
Google for Taylor series. You can approximate just about anything with taylor, and the more work you do the better the approximation gets.
jankoM
04-20-2007, 01:32 AM
this is the library I said... it has dot product amongst things.. it's very simple but gives a lot of power.
#ifndef NVECTOR_NOMIND_TECH_001_INCLUDED_
#define NVECTOR_NOMIND_TECH_001_INCLUDED_
/*
The author of this Vector class in Olivier renault who made exceptionaly great
polycolly tutorial/demo. I (janko metelko) used this lib and changed it to what I
needed / renamed it and stuff... but the 99.9% of thanks is to **Olivier Renault**.
This NVector is made to work with PTK (phelios)
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "NMath.h"
#include "KInput.h"
#define PI 3.141592f // the venerable pi
#define PITIMES2 6.283185f // 2 * pi
#define PIOVER2 1.570796f // pi / 2
#define E 2.718282f // the venerable e
#define SQRT2 1.414214f // sqrt(2)
#define SQRT3 1.732051f // sqrt(3)
#define GOLDEN 1.618034f // the golden ratio
#define DEGTORAD 0.017453f // convert degrees to radians
#define RADTODEG 57.29578f // convert radians to degrees
typedef unsigned char u_char;
typedef unsigned short u_short;
typedef unsigned int u_int;
typedef unsigned long u_long;
//#define for if(0); else for
#define for if(0){} else for
class NMatrix;
class NVector
{
public:
float x,y;
static const NVector& Blank() { static const NVector V(0, 0); return V; }
public:
inline NVector(void)
{}
inline NVector(float Ix,float Iy)
: x(Ix)
, y(Iy)
{}
inline NVector &operator /=(const float Scalar) { x /= Scalar; y /= Scalar; return *this; }
inline NVector &operator *=(const float Scalar) { x *= Scalar; y *= Scalar; return *this; }
inline NVector &operator +=(const NVector &Other) { x += Other.x; y += Other.y; return *this; }
inline NVector &operator -=(const NVector &Other) { x -= Other.x; y -= Other.y; return *this; }
inline float operator ^ (const NVector &V) const { return (x * V.y) - (y * V.x); } // cross product
inline float operator * (const NVector &V) const { return (x*V.x) + (y*V.y); } // dot product
inline NVector operator * (float s) const { return NVector(x*s, y*s); }
inline NVector operator / (float s) const { return NVector(x/s, y/s); }
inline NVector operator + (const NVector &V) const { return NVector(x+V.x, y+V.y); }
inline NVector operator - (const NVector &V) const { return NVector(x-V.x, y-V.y); }
friend NVector operator * (float k, const NVector& V) { return NVector(V.x*k, V.y*k); }
NVector operator * (const NMatrix& M) const;
NVector operator ^ (const NMatrix& M) const;
NVector& operator *=(const NMatrix& M);
NVector& operator ^=(const NMatrix& M);
inline NVector operator -(void) const { return NVector(-x, -y); }
inline float Length(void) const { return (float) sqrt(x*x + y*y); }
float Normalise(void)
{
float fLength = Length();
if (fLength == 0.0f)
return 0.0f;
(*this) *= (1.0f / fLength);
return fLength;
}
NVector Direction(void) const
{
NVector Temp(*this);
Temp.Normalise();
return Temp;
}
float Angle(const NVector& xE)
{
float dot = (*this) * xE;
float cross = (*this) ^ xE;
// angle between segments
float angle = (float) atan2(cross, dot);
return angle;
}
NVector& Rotate(float angle)
{
float tx = x;
x = (float)x * (float)cos(angle) - y * (float)sin(angle);
y = (float)tx * (float)sin(angle) + y * (float)cos(angle);
return *this;
}
NVector& Rotate(const NVector& xCentre, float fAngle)
{
NVector D = *this - xCentre;
D.Rotate(fAngle);
*this = xCentre + D;
return *this;
}
void Clamp(const NVector& min, const NVector& max)
{
x = (x < min.x)? min.x : (x > max.x)? max.x : x;
y = (y < min.y)? min.y : (y > max.y)? max.y : y;
}
void Randomise(const NVector& xMin, const NVector& xMax)
{
x = NMath::frand(xMax.x - xMin.x) + xMin.x;
y = NMath::frand(xMax.y - xMin.y) + xMin.y;
}
static NVector getRandom(const NVector& xMin, const NVector& xMax){
NVector Temp(0, 0);
Temp.Randomise(xMin, xMax);
return Temp;
}
static NVector getMouse(){
//i return mouse pos as NVector (this func is bind with PTK)
return NVector(KInput::getMouseX(), KInput::getMouseY());
}
bool isInRect(NVector edge, NVector size){
//i return mouse pos as NVector (this func is bind with PTK)
if (x > edge.x && y > edge.y){ //in two steps so it is a little faster
if (x < edge.x + size.x && y < edge.y + size.y)
return true;
}
return false;
}
/*void Render(void) const
//TODO: make it render with PTK
{
glColor3f(1.0f, 1.0f, 0.1f);
glPointSize(3.0f);
glBegin(GL_POINTS);
glVertex2f(x, y);
glEnd();
}*/
};
#endif
20thCenturyBoy
04-20-2007, 01:41 AM
You know you don't need all those inline keywords if the function is defined in the class (like the operators) ;)
</pedant>
jankoM
04-20-2007, 01:56 AM
99.9% thanks fo to Olivier Renault... so does the blame ;) . I stopped programing in c++ since then and went to java. I wasn't too good in c++. The language was quite ok, but I hated the compiler "bureaucracy".
Applewood
05-14-2007, 12:09 PM
Quote:
Another useful function is a function to return the distance between two angles.
God yeah, the trouble I remember having trying to figure this one out with no conditionals! Gave up in the end and have something very similar looking to yours now. It's one of those bits of code I make sure to never lose!Now I remember what I got stuck on, coz I'm here again.
What I'd kill for is for someone to post code that actually gives the shortest direction to move in, to get to a given angle.
My actual problem: I want to rotate from a given angle to another one in the correct (shortest) direction, ie by determining to add on some scalar or subtract it, to reach my desired angle, iyswim.
jcottier
05-14-2007, 12:13 PM
This is my angle class, it keeps all the values beetween -pi and pi.
The function your are looking for is interpolate, it will interpolate beetween 2 angles and the result is what you expect: the shortess direction.
class Angle
{
public:
static const float NOTANANGLE;
float value;
float SetBounds(float f)
{
if (f>0)
{
float k = float(int(f/(2*PI)));
f = (f-k*2*PI);
if (f>PI)
f = f-2*PI;
return f;
}
else
{
f = -f;
float k = float(int(f/(2*PI)));
f = (f-k*2*PI);
if (f>PI)
f = f-2*PI;
return -f;
}
}
Angle(){value=0;};
Angle(float f) {value = SetBounds(f);};
operator float () const {return value;};
void operator+=(const Angle &a) {value = SetBounds(value+a.value);};
static Angle Interpolate(Angle aa1, Angle aa2, float k)
{
float a1 = aa1;
float a2 = aa2;
if (fabsf(a1-a2)>=PI)
{
if (a1<a2) a2-=2*PI;
else a1-=2*PI;
}
float angle = a1*(1-k)+a2*k;
return angle;
}
};
JC
Applewood
05-14-2007, 12:16 PM
OMG! Is this a record reply time ?
Many thanks - saved me some major hair pulling, hopefully :)
Applewood
05-14-2007, 12:33 PM
But I still can't get my hack and slash of that to work. :(
I'm working in degrees and I can't seem to port it. This seems like it should be so damned easy, but I just get a mental block.
My angles are also any-sized, so I call a clip routine to get them back into 0-360 range beforehand. It's really sophisticated, not!
tF32 CMath::KeepAnglePositive (tF32 Angle)
{
while (Angle<0)
Angle+=360;
return Mod(Angle,360.0F);
}
tF32 CMath::AngleInterpolate (tF32 Angle1,tF32 Angle2,tF32 Scalar)
{
Angle1=KeepAnglePositive(Angle1);
Angle2=KeepAnglePositive(Angle2);
return Angle1*(1-Scalar)+Angle2*Scalar;
}
jcottier
05-14-2007, 12:35 PM
to works, it has to be between -PI and PI,
so I guess it need to be beetween -180 and 180 degrees not 0 and 360
JC
jcottier
05-14-2007, 12:40 PM
Take off 180 from your angle and use my function : just change PI into 180.
In theroy, it should work. Then put it back into your units [0-360].
JC
Applewood
05-14-2007, 02:08 PM
D'oh, thanks again. Now I look again I see I trimmed off to much stuff - thought it was all bounds checking.
This is damned handy, thanks. The solution I came up with wayback was nothing like as elegant as this iirc.
Moose2000
05-14-2007, 02:20 PM
On a related note, something I find very useful is to have a separate 'degrees' and 'radians' class, rather than one 'angle'. That way you can have comprehensible angles in your data, the system libraries can take their radians, opengl can take its degrees, and the compiler type checking handles any converting that needs to take place.
Applewood
05-14-2007, 02:47 PM
I'm coming around to that way of thinking myself tbh. All my engine is classed up with this exception as I don't really use angles much - I'm more of a vector man at heart.
Doing what I'm doing now though, I'm starting to feel the need.
electronicStar
05-14-2007, 05:36 PM
This pseudocode function should be easily transfered to your programming language, it solves the problem in a compact function
ia=initial angle of the player (0-360)
da=desired angle to turn to(0-360)
min(x,y)=a function that returns the min value between x and y
sgn(x) returns the sign of x (-1,0,1)
float findShortestAngle(ia,da)
{
ia=ia mod 360
da=da mod 360
r1=da-ia
r2=min(ia,360-ia)+min(da,360-da)
If Abs(r1)<r2 Then return r1 Else return r2*Sgn(ia-da)
}
Applewood
05-15-2007, 01:53 AM
Woot, I'm gonna use this too. Thanks :)
Therion
05-15-2007, 06:32 AM
I have a problem with this function:
double GetAngle(int x, int y, int x2, int y2)
{
int distx = x2 - x;
int disty = y2 - y;
double ang;
if ( distx == 0 )
{
if (disty < 0)
ang = 270000;
else
ang = 90000;
return ang;
}
else
{
ang = atan( (double)disty / (double)distx ) * 180000 / M_PI;
if (distx > 0)
ang = -ang + 180000;
else
ang = -ang;
return ang;
}
}
It lacks precision if the distance between the points is too far.
Somebody can give me some advice on this -like what i need to change in this function- ?, I'm not very keen in trigonometrics.
Thanks!
Applewood
05-15-2007, 01:53 PM
You're mixing ints with doubles isn't helping Any reason why you're not just using float or double throughout ?
vBulletin v3.6.0, Copyright ©2000-2008, Jelsoft Enterprises Ltd.