My previous post was short on Math and code, and long on Philosophy. Just what is a Point and a Vector? How do they differ? How are they the same? Well I’m afraid we’re gonna start this one off in a similar manner. However this time the topic is Vectors and Unit Vectors. For those that are newer to this topic, a Unit Vector is simply a Vector with a length of 1. So with an explanation that simple, it should be pretty straightforward to write the code right? Well… no. Just as a Point could be written as a Vector, a Unit Vector could be written as a Vector, but doing it that way doesn’t always fit. I have tried a number of different techniques to represent these things, I’ve made them exactly the same class, I’ve used typedefs, I’ve used inheritance (even private inheritance). In my current Physics Engine, they’re completely different classes. Let’s take a look at why.
The first reason is one of class design. A pretty standard function of a Vector is float length()
. This is useful for a Vector, but completely meaningless for a Unit Vector. The very definition of a Unit Vector is that its length is 1, so why have a function? Still while that’s a silly function to have, it wouldn’t break anything. But how about void set_length( float new_length )
? This could be useful in a Vector, but it’s forbidden in a Unit Vector. Similarly any of the +=, -=, *=, /=
operators don’t make any sense for a Unit Vector. Addition would take a Unit Vector and add another Unit Vector to it, creating a Unit Vector that has a length of 2. And since the definition of a Unit Vector is that its length is 1, this is clearly broken.
How about converting between the types? Taking a Vector and setting the length to 1 (ie making it a Unit Vector) is called “normalizing.” If you only have one general Vector class, how would you write a normalize
function? You could have it so that the function acted on the Vector itself (and thus subtly changed it to a Unit Vector) or you could have one return a new Vector that is actually a Unit Vector. Neither of these are very satisfying solutions because both of them result in a promise that the compiler can not check. But with an actual Unit_vector
type you can make a constructor like this:
Unit_vector( const Vector& copy );
In that case it’s very obvious that you’re creating a normalized version of the passed in Vector. And what’s better is that the compiler can enforce that you have an actual Unit Vector and not just something that a comment says is a Unit Vector. Never trust the comments.
The third reason for making them different is an optimization. There are a lot of functions that are quite complex when dealing with Vectors, but actually very simple when dealing with Unit Vectors. If your two types are not actually different types then you’re forced to always write the complex version, even when you know that your inputs will always be Vectors of length 1. Or what’s worse, you could write functions that assume the inputs will always be Vectors of length 1, and put a big comment on them that says “Only call this with vectors of length 1” and then a few weeks later you’ll call it with a Vector of some other length and spend days trying to figure out why it doesn’t work.
For a simple example of this, consider the case of trying to find the angle between 2 vectors. If you use dot products then the equation looks like this (don’t worry if you don’t know what a dot product is yet, we’ll cover that soon enough):
cos(angle) = dot_product( u, v ) / (u.length() * v.length())
Finding the length of a Vector is sort of expensive, it involves a square root. With a Unit Vector you already know what the length is, it’s 1. So this becomes the much simpler:
cos(angle) = dot_product( u, v )
As you can see, you’ve removed two calls to Vector::length()
, a multiplication, and a division. More subtly, you’ve also removed the need to check for zero length Vectors and the possible division by zero.
So there’s the end of the Philosophy for today. Much as a Point and a Vector are best represented by entirely separate things, so too are a Vector and a Unit Vector. You can see this in my code. Notice how in the Unit_vector class, operations that will change the length return Vectors. This tells the compiler that the type is changing, so you can enforce the run time length at compile time. This will help keep out the bugs and confusion.
<< Points and Vectors | Contents | Vector Math >> |