This post is part of my Game Math Series.
Fourier transform and spherical harmonics are mathematical tools that can be used to represent a function as a combination of periodic functions (functions that repeat themselves, like sine waves) of different frequencies. You can approximate a complex function by using a limited number of periodic functions at certain frequencies. Fourier transform is often used in audio processing to post-process signals as combination of sine waves of different frequencies, instead of single streams of sound waves. Spherical harmonics can be used to approximate baked ambient lights in game levels.
We’ll revisit these tools in later posts, so it’s okay if you’re still not clear how they can be of use at this point. First, let’s start somewhere more basic.
Projecting a Vector onto Another
If you have two vectors and , projecting onto means stripping out part of that is orthogonal to , so the result is the part of that is parallel to . Here is a figure I borrowed from Wikipedia:
The dot product of and is a scalar equal to the product of magnitude of both vectors and the cosine of the angle (, using the figure above) between the vectors:
Another way of calculating the dot product is adding together the component-wise products. If and , then:
A follow-up to the alternate formula above is the formula for vector magnitude. The magnitude of a vector is the square root of the dot product of the vector with itself:
The geometric meaning of the dot product is the magnitude of the projection of onto scaled by . Dot product is commutative, i.e. , which means that is also equal to the magnitude of the projection of onto scaled by .
So if you want to get the magnitude of the projection of onto , you need to divide the dot product by the magnitude of :
To get the actual projected vector of onto , multiply the magnitude with the unit vector in the direction of , denoted by :
One important property of dot product is: if it’s positive, the two vectors point in roughly the same direction (); if it’s zero, the vectors are orthogonal (); if it’s negative, the vectors point away from each other ().
For the dot product of two unit vectors, like , it’s just . If it’s 1, then the two vectors point in exactly the same direction (); if it’s -1, then the two vectors point in exactly opposite directions (). So, in order to measure how close the directions two vectors point in, we can normalize both vectors and take their dot product.
Let’s say we have three vectors: , , and . If we want to determine which of and points in a direction closer to where points, we can just compare the dot products of their normalized versions, and . Whichever vector’s normalized version has a larger dot product with points in a direction closer to that of .
A metric often used to measure the difference between two data objects is the root mean square error (RMSE) which is the square root of the average of component-wise errors. For vectors, that means:
It kind of makes sense, because it is exactly the magnitude of the vector that is the difference between and scaled by :
It’s also the square root of the dot product of the difference vector with it self scaled by :
Here’s an important property of projection:
The projection of a vector onto another vector is the vector parallel to that has the minimal RMSE with respect to . In other words, gives you the best scaled version of to approximate .
Also note that if is larger than , it means has a smaller RMSE than with respect to ; thus, points in a direction closer to that o than does.
Now we’re finished with vectors. It’s time to move onto curves.
“Projecting” a Curve onto Another
Let’s consider these three curves:
When working with curves, as opposed to vectors, we need to additionally specify an interval of of interest. For simplicity, we will consider for the rest of this post.
Below is a figure showing what they look like side-by-side within our interval of interest:
Just like vectors, “projecting” a curve onto another curve gives you the best scaled version of to approximate , and the “projection” has minimal RMSE with respect to . To compute the RMSE of curves, we need to first figure out how to compute the “dot product” of two curves.
Recall that the dot product of vectors is equal to the sum of component-wise products:
Mirroring that, let’s sum up the products of samples of curves at regular intervals, and we normalize the sum by dividing it with the number of samples, so we don’t get drastically different results due to different number of samples. If we take 10 samples between to compute the dot product of and , we get:
The more samples we use, the more accuracy we get. What if we take an infinite number of samples so we get the most accurate result possible?
This basically turns into an integral:
So there we have it, one common definition of the “dot product” of two curves:
The integral of the product of two curves over the interval of interest.
Copying the formula from vectors, the RMSE between two curves and is:
In integral form, it becomes:
The “mean” part of the error is omitted since it’s a division by 1, the length of our interval of interest.
To find out which one of the “normalized” version of and has less RMSE with respect to the normalized version of , we take the dot products of the normalized versions of the curves:
The dot product of and is larger than that of and . That means has a lower RMSE than with respect to .
Drawing analogy from vectors, is conceptually “pointing in a direction” closer to that of than does.
Now let’s try finding the best scaled version of that has minimal RMSE with respect to , by computing the projection of onto :
And this is what , , and look like side-by-side:
The projected curve is a scaled-up version of that is a better approximation of than itself; it is the best scaled version of that has the least RMSE with respect to .
Now that you know how to “project” a curve onto another, we will see how to approximate a curve with multiple simpler curves while maintaining minimal error.