I’m gonna start off our discussion of Physics Engines with some posts about the Math that makes them tick. Don’t worry, I don’t have a degree in Math, so these posts will be long on rambling sentences, and short on Greek symbols. For this one I’m gonna skip the equations all together and just try to talk about the meaning of the most basic types in a Physics Engine: Points and Vectors.
There isn’t much that’s more fundamental to a Physics Engine then Points and Vectors. And for this reason, it’s important to spend some time getting them right. In my path to the Dubious Engine I have actually written a basic Vector library a handful of times. I even wrote one in Haskell to see if it did a better job of handling some of the awkward language edge cases that kept creeping up with C++. What is it about Points and Vectors that are so hard to get right? Well looked at from one angle, they’re pretty much exactly the same thing, 3 floats representing X, Y, and Z coordinates. But looked at from another angle, they have absolutely nothing in common. A Point represents a position in space, or where an object is. A Vector represents a direction and a magnitude, which isn’t really easy to summarize. Can you represent a Point with a Vector? Sure you can, like I said, they’re both 3 floats. But should you use the same class for both? I’d argue that you should not. Let’s discuss why.
Take a moment to consider addition. Does it make sense to add two Vectors together? Yes, absolutely, it’s entirely natural. If you had high school Physics I imagine you spent a lot of time adding Vectors. The mechanics are simple, you just add each of the three coordinates to each other. But what about Points? Does it make sense to add two Points? Surprisingly, no. Sure the math behind it is trivial, it’s the same as with a Vector. But does that have meaning? Let’s say you live in Philadelphia and I live in New York? Both of our positions in space can be represented as Points. But what does it mean to add Philadelphia to New York? It doesn’t mean anything. What would the locations of Philadelphia plus New York represent? I couldn’t even guess. So if addition of Points is meaningless, surely subtraction is too? Oddly enough, subtraction does have meaning. If you take Philly and subtract New York you are left with a Vector that defines the direction and distance from New York to Philadelphia. Remember that a Vector is a direction and magnitude. So this Vector’s direction would be South West, and the magnitude would be about 100 miles. Now if you start with the New York Point and you add this Vector, the result is the Philadelphia Point. Let’s summarize this:
- Addition and subtraction of Vectors is correct
- Addition of Points is meaningless
- Subtraction of Points results in a Vector
- Addition of a Point and a Vector results in a Point
This probably isn’t the result you were expecting when you first started thinking of Points and Vectors. I can tell you from experience that it took me many years to start thinking along these lines. Can you write a Physics Engine that ignores all of this and just uses a Vector for everything? Of course you can, my first few attempts did exactly this. However I found I was always creating bugs where I’d write an interface that expected a position, but I would eventually send it a Vector, and the whole thing would fail and I’d spend a lot of time trying to re-learn the algorithm to understand why. With this new representation I’m much clearer on the roles of Points and Vectors and it helps me keep things straight.
So with that in mind, how do we write the code? You could be tempted to just make them exactly the same thing, and just use a typdef to give them different names. After all, the actual mechanics of addition, subtraction, and equality are the same. But functions like
get_length() make no sense with a Point, but are fundamental to a Vector, so it’s best to avoid this approach. You could be tempted to use inheritance, and move the common functionality to the base class Point and put the Vector specific functionality in the Vector. This is a little cleaner in that you can reuse common code, but the polymorphism is awkward because inheritance represents an “is-a” relationship, and as discussed above, a Vector is not a Point. Another trick you could try is composition, where a Vector just contains a Point, but this leads to circular dependencies in your code. In order to implement Point subtraction a Point must depend on a Vector, but in order to implement a Vector it must depend on a Point. This will really piss off your compiler, which is generally not a good thing to do.
So this leads us to the solution I ended up with, a third class called a Triple, which is nothing more then an abstract thing that contains 3 floats. This Triple isn’t even a class, it’s just a lowly struct, and it implements the absolute basics, addition, subtraction, and equality. Both a Point and a Vector contain a Triple, and a lot of their basic functions are implemented as a simple pass through. They both build on the Triple to implement their type specific functionality.
Now that you understand my reasoning behind Points and Vectors, it should be pretty easy to understand the code. Here are links to my Triple, Point, and Vector implementations. For now, ignore the templates, we will discuss their usage later.
- Triple – Simple struct of 3 floats. Handles basic math and equality
- Point – Represents a 3D point in space.
- Vector – Represents a direction and magnitude.
|<< See Also||Contents||Vector Types >>|