Recently, I found it necessary to point a Model3DGroup instance along a direction vector. I’ve solved this problem a few times before. For example, in my Managed DirectX implementation (may it rest in peace), it was simple to do with Euler angles:
Quaternion q = Quaternion.RotationYawPitchRoll( phi, theta, 0 );
Alas, no more.
I tried every combination of RotateTransform3D:
rt.Rotation = new AxisAngleRotation3D( new Vector3D( 0, 1, 0 ), phiD );
rt2.Rotation = new AxisAngleRotation3D( new Vector3D( 0, 0, 1 ), thetaD );
And raw matrix operations:
MatrixTransform3D xform = new MatrixTransform3D();
Matrix3D m3d = new Matrix3D();
m3d.Rotate( new Quaternion( new Vector3D( 0, 1, 0 ), phiD ) );
Matrix3D m3d2 = new Matrix3D();
m3d2.Rotate( new Quaternion( new Vector3D( 0, 0, 1 ), thetaD ) );
m3d *= m3d2;
xform.Matrix = m3d;
tg.Children = xform;
But no dice.
The problem was that the transformations were applied independently, not cumulatively. Meaning that after my yaw rotation was applied, the pitch rotation was applied in the world reference frame, instead of the model frame.
Desperate, I appealed to the WPF product team for help. Greg Schechter kindly provided me with this solution:
// Generate a rotation that will rotate from pointing along v1 to pointing along v2.
Vector3D v1, v2;
double angle = Math.Acos(Vector3D.DotProduct(v1, v2) / (v1.Length * v2.Length));
Vector3D perpVector = Vector3D.CrossProduct(v1, v2);
var rot = new AxisAngleRotation3D(perpVector, angle * 180 / Math.PI);
This code simply finds the angle between the model’s current direction vector v1 and the target direction vector v2. It then generates an axis of rotation perpVector which is normal to the plane containing the two vectors. Finally, the model is rotated around perpVector by angle degrees. And don’t forget the radians-to-degrees conversion.
Worked like a charm the first time, and I did a little happy dance. Kind of a jig, actually.