So when I implemented my primitive 3D “renderer”, something I had to think about was how to implement drag rotation. Prior to writing the code, and prior to even thinking about using 3D models for visualising eigenvectors, I had pondered about how drag rotation should work.
In three dimensions, if you specify two unit vectors (normalise them if they’re not unit vectors), there are infinitely many rotations from the first vector to the second, because you can take any such rotation then left-compose it with a rotation around the second axis to get another such rotation. However, in general, there is a unique shortest rotation that follows a great circle path. (It’s not unique exactly when it’s an antipodal rotation.) The axis of this rotation is the cross product of the vectors and the angle around the axis to rotate is the arccosine of the dot product. The resulting rotation can be represented in multiple ways, such as axis-angle, Euler angles, a 3×3 matrix, a quaternion, etc.
In the quaternion case, the above operation of generating a rotation quaternion from two vectors can be done without using any trigonometric functions at all, which is an optimisation that’s apparently used a lot in video games and such. I won’t go into detail about this.
The very first idea I had for 2D dragging was something like this. Say you have some kind of surface lying above whatever you’re rendering; then a drag from to would be treated as the shortest rotation from to after appropriate normalisation. I was being intellectually lazy about what kind of surface to use when making my visualisation demo so I just picked a flat surface , which seems to work reasonably well/intuitively with my face-transitive models.
Or is the “intuitiveness” a side effect of conditioning because everyone else also uses the same idea to convert 2D drags to 3D rotations, or is it just that the surface used doesn’t really matter all that much? Who knows.
One peculiar result of using the above dragging convention (at least with a flat surface) is that dragging in a circle will rotate the model in the same direction as the dragging. This might or might not be useful, but it’s interesting to note nonetheless.
After this was implemented in my renderer, I added an alternative convention where the rotation was location-independent; a drag from to would produce the same rotation as a drag from to , or, more symmetrically, from to . The rotation is then generated by the two vectors and where is some constant. (Note that since these two vectors are reflections along the z-axis, their cross product must be perpendicular to the z-axis and hence lie on the xy-plane. This applies to the rotation axis too, since it’s just a scalar multiple of the cross product.)
Unlike the first convention, you can effect an arbitrarily large rotation in a single drag with this convention simply by dragging in one direction far enough; with the first convention, dragging along a straight line passing through the origin results in a net rotation by only π.
This drag→rotation convention also leads to the even stranger result that dragging in a circle will make the model rotate in the opposite direction. Or, I guess it’s not all that strange if you try to think about what dragging in a circle is really doing.
The above seems to suggest that we should be able to interpolate between the two presented conventions so that dragging in a circle leads to zero net rotation. Given the two 2D vectors and , define the following vectors, where is the height of our surface to project the cursor onto.
Further define as a function producing a quaternion out of two vectors, normalising them as necessary. The result is undefined if the vectors are degenerate (one of them is zero, they’re parallel/antiparallel, etc.). As an abuse of notation, we’ll use this function on a pair of 2D vectors, in which case you should assume the z-coordinate is 1.
The two conventions are then and , from which we see that the obvious way to interpolate this is to use for some value of , and indeed, using seems to result in zero net rotation when dragging in circles, or, in other words, the resulting rotation depends only on where you start/stop the drag and is path-independent.
How would one go about proving that this is really path-independent? Quaternion multiplication is not commutative, so we can’t just convert an infinite composition of infinitesimal rotations into an integral of something by taking the logarithm of the associated quaternions.