Friday, November 2, 2012

Complex numbers vs. 2D rotations

This is rather obvious, but if you're not too comfortable with complex numbers or you're struggling with 2D rotations, it might help you.

Let's start with a 2D vector [x,y]. To rotate this, we'd multiply by a matrix [[cos(w), -sin(w)],[sin(w),cos(w)]] leading to a vector [x*cos(w)-y*sin(w),x*sin(w)+y*cos(w)]. Not too complex so far.

Now, let's pick a complex number such as a+i*b. This could also be written as r*e^(i*w), where r=|a+i*b|. The complex exponent then expands to r*(cos(w)+i*sin(w)). So we have a rotation and uniform scaling as r*[[cos(w),-sin(w)],[sin(w),cos(w)]] or simply [[a,-b],[b,a]], where a = r*cos(w) and b=r*sin(w).

So complex multiply comes down to 2D rotation and a uniform scaling coefficient. This not only means you can represent 2D rotations as complex numbers, but because any complex number is essentially just a 2D vector on the "complex plane" you can represent 2D rotations as 2D vectors (special case that doesn't work for higher dimensions; something similar can be done in 3D with quaternions though).

Let's look at an application: Suppose we have a quadrilateral sprite described by 4 points. We want to point that sprite in a particular direction. We might have an image of an arrow and it has some velocity vector (which we add to it's position at regular intervals), and you want to point the arrow in the direction it's moving and draw the thing on screen. One solution would be to use atan2() to solve for the angle of the velocity, then use the cos-sin formula to build the rotation matrix.. but because we can treat the velocity as a complex numbers, we can simply (1) normalize it to get rid of the scaling and (2) do complex multiplies with all the point (treating them as complex numbers). You don't even need a single cos() or sin() for this (just an sqrt() for the normalize).

Nothing very magical about any of the above, but maybe it'll help someone bridge their mental models of the two concepts (2D vectors and complex numbers).

PS. Another potentially useful application is to treat stereo signals as complex numbers (with "imaginary side channel" or something). You can then "rotation pan" (essentially a simple extension of classic equal power panning) with simple complex multiplies (and get the uniform gain as by-product too). You can even FFT such representation, multiply by an FFT of a real-only FIR, and when you do an IFFT, you've essentially filtered the two channels independently (even if in spectral domain they aren't "separate" as such). Why this works should be obvious when you look at the matrix representation.