Charles Petzold



Animating 3D Non-Affine Transforms

August 9, 2007
Roscoe, N.Y.

I woke up this morning thinking that I'd be writing a Matrix3DAnimation class today, and it turned out I didn't have to. (Not today, anyway.)

For several days now, I've had a graphics animation rolling around in my head. It involved a square cuboid that would be normal size on both ends, but pinched somewhere between those ends, rather like this:

But the pinched part would move back and forth, almost as if the square cuboid were being passed through the eye of a needle.

I wanted to use a 3D non-affine transform for this job, but I knew I couldn't simply apply it to a single figure to achieve the effect I wanted. The non-affine transforms possible with a 4×4 transform matrix can only taper from one end to the other. The taper can't be reversed in the middle. I knew I'd need two adjacent cubes animated oppositely to achieve this particular effect.

I figured I'd be determining a couple of non-affine transforms for the extreme positions of the animation, and then I'd need to animate between two Matrix3D objects. That would require a Matrix3DAnimation class, which doesn't exist in the Windows Presentation Foundation. But when I actually sat down to do the math, I discovered I could do it entirely in XAML.

Let's begin with two adjacent unit cubes, one to the right of the YZ plane and one to the left, but centered on the X axis, so the Y and Z coordinates of the vertices are 0.5 and -0.5, and X coordinates are 0 and -1 (for the cube on the left) and 0 and 1 (for the cube on the right). The "eye of the needle" is the origin of the 3D axis.

Let's look at the cube to the right of the YZ axis. At the beginning of the animation, I want it to look like the right tapered section of the figure shown above. The right edge of the cube is at an X coordinate of 2, and the left edge of the cube is 1/10th the normal size. Here's a little chart showing the desired numeric transforms for combinations of the vertices. (Notice that Y and Z coordinates can be handled similarly.)

Beginning of Animation
 x = 0x = 1
y&z = -0.5x' = 0
y' = -0.05
z' = -0.05
x' = 2
y' = -0.5
z' = -0.5
y&z = 0.5x' = 0
y' = 0.05
z' = 0.05
x' = 2
y' = 0.5
z' = 0.5

Keep in mind that the generalized non-affine transforms are (using property names of the Matrix3D structure):

The denominator must be the same for the three formulas. In this case, it's easiest to begin with y' and z':

That means that the formula for x' must have the same denominator, so:

At the end of the animation, I wanted the cube to be very short along the X axis, so I set up a table like this:

End of Animation
 x = 0x = 1
y&z = -0.5x' = 0
y' = -0.05
z' = -0.05
x' = 0.25
y' = -0.5
z' = -0.5
y&z = 0.5x' = 0
y' = 0.05
z' = 0.05
x' = 0.25
y' = 0.5
z' = 0.5

The transform formulas are:

It is now obvious that only one element of the matrix is changing during the animation. Let's call it K. Here's the 4×4 transform matrix:

K00-9
0100
0010
00010

Wouldn't it be nice to set up a DoubleAnimation targetting the M11 property of a Matrix3D element?. You can't do it! Animation targets must be dependency properties, and any class that implements dependency properties must derive from DependencyObject, and Matrix3D can't derive from DependencyObject because it's not even a class — it's a structure.

That's why I figured I'd need to target the Matrix property of a MatrixTransform3D element, and that I'd need to do this job by writing a Matrix3DAnimation class to interpolate between two Matrix3D objects. Of course, like virtually all properties in the WPF 3D classes, the Matrix property itself is a dependency property.

But wait! That matrix I just derived can actually be expressed as the product of two matrices:

100-9
0100
0010
00010
×
K000
0100
0010
0001
=
K00-9
0100
0010
00010

The matrix on the left is still a non-affine transform, but all the elements are constant. The matrix on the right of the times sign is a simple scale transform. To animate the composite transform, all that needs to be done is to use a DoubleAnimation to animate the ScaleX property of a ScaleTransform3D element!

And when you think about it like that, it's obvious: You need a non-affine transform to taper the figure, but then only a simple ScaleTransform3D to scale it along the X axis.

When constructing the XAML file, I decided to add a third transform: A TranslateTransform3D that shifts a unit cube centered on the origin to a unit cube sitting to the right of the YZ plane. This transform is applied first, followed by the MatrixTransform3D followed by the ScaleTransform3D.

The left section of the figure is constructed similarly, except that the TranslateTransform3D shifts the unit cube to the left of the YZ plane, and the MatrixTransform3D has an M14 property of 9 rather than a -9 to taper in the other direction. The two ScaleX animations have to go in opposite directions: for the cube on the right, the animation is from 2 to 0.25, and for the cube on the left, from 0.25 to 2. The animations can then reverse and repeat forever.

Because both figures begin with a unit cube centered around the origin, I decided to define it as a shareable resource, along with the material that colors both cubes. The complete XAML file is here:

ItsEasierToPassASquareCuboidThroughTheEyeOfANeedle.xaml

And now, a program and blog entry that I figured would occupy much of the day is finished before 2:00 PM EDT.

And that's why I love XAML.

Buy my book and we'll both be happy!
Amazon.com BookSense.com quantumbooks
Barnes & Noble Amazon Canada Amazon UK
Amazon Français Amazon Deutsch Amazon Japan