Avoiding the use of angles

Started by
5 comments, last by alvaro 12 years ago
Iv'e seen it mentioned a few times in various places that when using vectors (currently working in 2D, suppose translates to 3D as well) that the use of angles can generally be avoided, and the resulting cos/sin/atan stuff eliminated. Is there some good resources on this area of maths, since I personally can not see how this works? e.g. given a turret I currently have something like:

Not real code, and simplifies a number of things. Like no turret angle restrictions, and less parameters gettings passed around. e.g. a reference to the firing unit and target getting passed to the bullet, and the turret gettings its target position is in AI code to deal with some prediction stuff (so the turret basically ends up with a Unit *target, and Vector2 targetPos).
[source lang="cpp"]
//Tank/Ship whatever tells the turret that it moved in some way to parentPos with parentOrientation
//So turret can update its location (used for rendering, target picking, etc)
//Turret then needs to tell each of its weapon barrels its new location as well
void Turret::updatePos(Vector2 parentPos, float parentOrientation)
{
this->parentOrientation = parentOrientation;
pos = Vector2.fromMagDir(offset.getMag(), offset.getDir() + parentOrientation);
float worldOrientation = wrapAngle(orientation+ parentOrientation)
for (Barrels::iterator barrel = barrels.begin(); barrels != barrels.end(); ++barrels)
barrel->updatePos(pos, orientation);
}
//Weapon barrel is told its owner (e.g. some turret, or a unit directly) has a new location and
//needs to recalculate its. Used for rendering, spawning bullets
void Barrel.updatePos(Vector2 parentPos, float parentOrientation)
{
pos = Vector2.fromMagDir(offset.getMag(), offset.getDir() + parentOrientation);
orientation = parentOrientation;
}

//Fixed time step update for a turret with a valid target
//Turn towards target and fire if lined up well enough
void Turret::updateTarget()
{
//Turn to face the target, max of turnRate radians per step
//Full code will lead the target rather than just use target->getPos alone
Vector2 rel = target->getPos() - pos;
float dir = rel.getDir();
float ddir = wrapAngle(dir - (orientation + parentOrientation));

if (ddir >= -turnRate && ddir <= turnRate)
{
orientation = wrapAngle(dir - parentOrientation);
fire();
}
else if(ddir > 0)
orientation += turnRate;
else
orientation -= turnRate;

//Turret moved again, so need to update barrels location
for (Barrels::iterator barrel = barrels.begin(); barrels != barrels.end(); ++barrels)
barrel.updatePos(pos, orientation + parentOrientation);

//fire weapon. fireAngle could be fairly big for weapons with homing capabilities or large deviation
//Full code also calculates fireAngle based on bullet turn rate and speed
ddir = wrapAngle(dir - (orientation + parentOrientation));
if (abs(ddir) <= fireAngle)
fire();
}
//Fire a bullet from the barrel
void Barrel.doFire()
{
float bulletDir = orientation + random(-deviation, +deviation);
new Bullet(pos, bulletDir);
}
[/source]
Advertisement
I have been preaching avoidance of angles for a while. When people use an angle they generally mean one of two things:
(1) a direction
(2) a rotation

In 2D geometry, you should prefer to use a unit-length vector instead in the first case, and a unit-length complex number in the second case; or you can just think of both of them as a unit-length complex number, although you may lose some type safety. In 3D geometry, you very quickly get into trouble if you use angles, because you need two angles to specify a direction (latitude and longitude) and three angles to specify a rotation (roll, pitch and yaw), and composing two rotations is horribly hard. Instead, you can still use unit-length vectors to indicate directions and rotations can be represented as unit-length quaternions.

Let's concentrate on the 2D case. If you want to add two angles, you probably mean to either apply a rotation to a direction or combine two rotations. In either case, multiplication of complex numbers does the trick. If you want to know what rotation maps one direction into another, interpret both directions as unit-length complex numbers and divide them.

I am not exactly sure what the code you posted does, but if you describe some task, I'll be happy to write some code that demonstrates how to avoid using angles.
Ok, I've tried to make the code clearer. Its basically a simplified and condensed version of what I've currently got in my game to make turrets work.

The problem cases I have been having I guess mostly come down to:

  • Dealing with objects that are attached to other objects, but with some offset (e.g. say a turret on the front left part of a vehicle, not its centre). Specifically when this "parent" object rotates. (e.g. like the updatePos stuff)
  • Dealing with rotation. Its easy to set a unit vectors direction to be the direction from point A to B, but what if I want to limit its turn rate? What if I want to add some random angle to a vector?
  • Comparing the direction objects are facing / heading. E.g. to see if a turret is near enough to facing the target or not (same goes for fixed weapons, and many other cases).

By complex number you mean a number with a real and imaginary part? How does that work / is relevant in this context?
This is just basic linear algebra; if you're interested in a theoretical grounding, any decent textbook should do the trick.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]


By complex number you mean a number with a real and imaginary part? How does that work / is relevant in this context?
[/quote]
Yes, precisely. If you identify the point (x,y) with the number x+iy, you can translate by adding a complex number, and you can rotate by multiplying by a unit-length complex number. I'll show you some examples below.


  • Dealing with objects that are attached to other objects, but with some offset (e.g. say a turret on the front left part of a vehicle, not its centre). Specifically when this "parent" object rotates. (e.g. like the updatePos stuff)


You may want to have a class Movement (or Transform, but I think "movement" captures the meaning better) that contains a rotation and a translation. You can compose two Movements, with the result being another Movement. So the vehicle and the turret both have Movements associated with them, and you need to compose the movement of the vehicle with the Movement of the turret (relative to the vehicle) to get the global Movement for the turret.

If you need help with this Movement class, I can try to write some sample code.


  • Dealing with rotation. Its easy to set a unit vectors direction to be the direction from point A to B, but what if I want to limit its turn rate?

[/quote]

rotation = target_direction / current_direction;
if (rotation.real() < cos(max_angle))
rotation = Complex(cos(max_angle), sign(rotation.imag())*sin(max_angle));
current_direction *= rotation;



What if I want to add some random angle to a vector?
[/quote]


double random_angle = (2.0*drand48()-1.0) * max_angle;
Complex random_rotation(cos(random_angle), sin(random_angle));
vector *= random_rotation;



  • Comparing the direction objects are facing / heading. E.g. to see if a turret is near enough to facing the target or not (same goes for fixed weapons, and many other cases).

[/quote]

rotation = desired_direction / current_direction;
if (rotation.real() >= cos(threshold_angle))
...


Notice that some of the cos() and sin() calls in there have arguments that are constant. They are only needed because you are specifying the rate or the threshold for similarity as angles. If you also think of them as unit-length vectors instead, you rarely need any trigonometric calls at all.


By complex number you mean a number with a real and imaginary part? How does that work / is relevant in this context?

Yes, precisely. If you identify the point (x,y) with the number x+iy, you can translate by adding a complex number, and you can rotate by multiplying by a unit-length complex number. I'll show you some examples below.
[/quote]

OK, but why is it a complex number? How does a position or direction vector become a complex number? Is there actually some maths behind that, or is it just because the multiplication and division operations are defined in a convenient way? What happens to a 3rd dimension?

None of the vector/complex numbers/matrices/etc maths I ever had at school covered this use, so any suggestions on goods books/resources that do?



  • Dealing with objects that are attached to other objects, but with some offset (e.g. say a turret on the front left part of a vehicle, not its centre). Specifically when this "parent" object rotates. (e.g. like the updatePos stuff)


You may want to have a class Movement (or Transform, but I think "movement" captures the meaning better) that contains a rotation and a translation. You can compose two Movements, with the result being another Movement. So the vehicle and the turret both have Movements associated with them, and you need to compose the movement of the vehicle with the Movement of the turret (relative to the vehicle) to get the global Movement for the turret.

If you need help with this Movement class, I can try to write some sample code.
[/quote]

Played around a bit. Not really sure I like the name Movement since its just a position + orientation (as opposed to detailing any movement over time by itself) and what's its relative to but couldn't really think of a better name. I made it a part of the Object class with a localPos and localOrientation member variable and an Object *parent (where null means no parent, and thus local position == the world position). Object::getPos and Object::getOrientation then recursively calculate the objects position in the world.


Notice that some of the cos() and sin() calls in there have arguments that are constant. They are only needed because you are specifying the rate or the threshold for similarity as angles. If you also think of them as unit-length vectors instead, you rarely need any trigonometric calls at all.
[/quote]
Well chances are at some point I'll do a conversion when the data is loaded to save repeated conversions of the same value. But it seems better to me to keep the data text files themselves and the theory behind the games simulation using degrees and degrees per second (as opposed to unit vectors, or the currently used radians and radians per step, which are hard to deal with without a calculator).

[quote name='alvaro' timestamp='1334282917' post='4930794']

By complex number you mean a number with a real and imaginary part? How does that work / is relevant in this context?

Yes, precisely. If you identify the point (x,y) with the number x+iy, you can translate by adding a complex number, and you can rotate by multiplying by a unit-length complex number. I'll show you some examples below.
[/quote]

OK, but why is it a complex number? How does a position or direction vector become a complex number? Is there actually some maths behind that, or is it just because the multiplication and division operations are defined in a convenient way? What happens to a 3rd dimension?

None of the vector/complex numbers/matrices/etc maths I ever had at school covered this use, so any suggestions on goods books/resources that do?[/quote]

Complex-number multiplication can be described in polar coordinates as multiplying the moduli of the operands and adding their arguments. This is what makes complex numbers so handy to compute rotations in 2D.

The 3D equivalent of this is the use of quaternions to represent 3D rotations. The formula to apply a rotation represented by a quaternion to a 3D vector is a little bit more complicated than just multiplying, but it's not too hard.




  • Dealing with objects that are attached to other objects, but with some offset (e.g. say a turret on the front left part of a vehicle, not its centre). Specifically when this "parent" object rotates. (e.g. like the updatePos stuff)


You may want to have a class Movement (or Transform, but I think "movement" captures the meaning better) that contains a rotation and a translation. You can compose two Movements, with the result being another Movement. So the vehicle and the turret both have Movements associated with them, and you need to compose the movement of the vehicle with the Movement of the turret (relative to the vehicle) to get the global Movement for the turret.

If you need help with this Movement class, I can try to write some sample code.
[/quote]

Played around a bit. Not really sure I like the name Movement since its just a position + orientation (as opposed to detailing any movement over time by itself) and what's its relative to but couldn't really think of a better name. I made it a part of the Object class with a localPos and localOrientation member variable and an Object *parent (where null means no parent, and thus local position == the world position). Object::getPos and Object::getOrientation then recursively calculate the objects position in the world.
[/quote]

The name "movement" perhaps is not standard (I was translating "movimiento" from Spanish). Wikipedia uses "Euclidean move", but those allow for symmetries that don't preserve the orientation. For the ones that do preserve the orientation (the ones we are talking about) Wikipedia uses "rigid motion" or "direct isometry".



Notice that some of the cos() and sin() calls in there have arguments that are constant. They are only needed because you are specifying the rate or the threshold for similarity as angles. If you also think of them as unit-length vectors instead, you rarely need any trigonometric calls at all.
[/quote]
Well chances are at some point I'll do a conversion when the data is loaded to save repeated conversions of the same value. But it seems better to me to keep the data text files themselves and the theory behind the games simulation using degrees and degrees per second (as opposed to unit vectors, or the currently used radians and radians per step, which are hard to deal with without a calculator).
[/quote]

It is generally better to write the code using whatever convention makes more mathematical sense, and convert to more familiar schemes only when interfacing with humans. This piece of advice includes things like counting from 0, using fractions instead of percentages and using timestamps in UTC (at least in programs that may need to handle multiple time zones).

This topic is closed to new replies.

Advertisement