http://yichuanshen.de/blog/2010/11/13/flipping-elements-with-wpf/
Have you already seen ForgottenTime’s new flip animation eye candy? If not, it’s about time! It took me several days to figure out how to do it…
My first thought was to find out how to do a 4-point-tranformation of a given image. (See figure on the left.) It’s no problem to create an “screenshot” of a UI element and I could easily calculate the four vertices of the transformed image (with given angle and a little bit trigonometry) and transform the original screenshot via the function to achieve a 3D effect. Unfortunately, there’s no (easy and fast) way to do that in C# and Windows Presentation Framework, so I had to think of something else.
After my research on the Internet I came across some official demo WPF applications, also using advanced UI techniques such as flipping. So I dug into the code to find out how they did it. As it turned out, they were using 3D graphics.
WELCOME TO THE 3RD DIMENSION
Strictly speaking a computer screen cannot display real 3D graphics of course, only projections of a 3D space onto a plane… the screen. It’s also called a “viewport”, a 2D window, that allows the user to gaze into the imaginary 3D space behind. Just like we have eyes, a viewport needs a “camera” (to be really precise, a PerspectiveCamera
).
Let’s assume the image we want to flip is a square with the dimensions 129×129.
<!-- XAML code -->
<Viewport3D x:Name="viewport3D" Width="129" Height="129">
<Viewport3D.Resources>
</Viewport3D.Resources>
<Viewport3D.Camera>
<PerspectiveCamera x:Name="cam3D"
FieldOfView="45"
LookDirection="0,0,-1 "
UpDirection="0,1,0" />
</Viewport3D.Camera>
</Viewport3D>
After the window is loaded, we create a two-dimensional object, a square, which represents our image in 3D space and calculate where our camera should be. If all that is done, we can literally rotate the object around the y-axis and thus flip the image around.
THE TWO-DIMENSIONAL OBJECT
Every object in our 3D space is made of triangles. The triangle surface of such an object is called a mesh. It’s relatively easy to build a square out of two triangles as the sketch below shows.
We center the image around the origin, so that the camera can be easily positioned on the z-axis. To create such a simple object (notice this is two-dimensional!) you have to write tons of code:
// C# code
GeometryModel3D model3D;
private void BuildModel() {
// Customize the brushes
// Can be any brush (ImageBrush, DrawingBrush, VisualBrush, ...)
ImageBrush front = new ImageBrush(this.frontImageSource);
ImageBrush back = new ImageBrush(this.backImageSource);
back.Transform = new ScaleTransform(-1, 1, .5, 0); // Flip back image
// Create mesh
MeshGeometry3D mesh = new MeshGeometry3D();
double radius = 129 / 2.0; // 64.5
mesh.Positions.Add(new Point3D(-radius, -radius, 0));
mesh.Positions.Add(new Point3D(radius, -radius, 0));
mesh.Positions.Add(new Point3D(radius, radius, 0));
mesh.Positions.Add(new Point3D(-radius, radius, 0));
mesh.TriangleIndices.Add(0);
mesh.TriangleIndices.Add(1);
mesh.TriangleIndices.Add(2);
mesh.TriangleIndices.Add(0);
mesh.TriangleIndices.Add(2);
mesh.TriangleIndices.Add(3);
mesh.TextureCoordinates.Add(new Point(0, 1));
mesh.TextureCoordinates.Add(new Point(1, 1));
mesh.TextureCoordinates.Add(new Point(1, 0));
mesh.TextureCoordinates.Add(new Point(0, 0));
// Add texture
DiffuseMaterial frontMat = new DiffuseMaterial(front);
DiffuseMaterial backMat = new DiffuseMaterial(back);
frontMat.AmbientColor = backMat.AmbientColor = Colors.White;
model3D = new GeometryModel3D();
model3D.Geometry = mesh;
model3D.Material = frontMat;
model3D.BackMaterial = backMat;
Model3DGroup group = new Model3DGroup();
group.Children.Add(model3D);
group.Children.Add(new AmbientLight(Colors.White));
ModelVisual3D visual = new ModelVisual3D();
visual.Content = group;
viewport3D.Children.Add(visual);
}
THE CAMERA POSITION
The camera has to be some distance away from the square, so that everything is within the camera’s field of view. Especially when the image is rotated by 90? around the y-axis, where it’s nearest to the camera. So how do we calculate the distance?
The sketch above shows the image which has already been rotated by 90?. Let’s first look at the left side of the sketch. As we can see the camera is positioned . We’re going to calculate x
using tangent.
Now if we put the camera at it’s garanteed that everything is visible in the viewport. But as we can see in the sketch above, there’s space below and above (as well as left and right) the original unrotated image (space marked with variable s
) which will make the image appear smaller in the viewport. We have to enlarge the viewport, so that the image will appear normal-sized again. We can calculate s
as follows:
Now we have made all calculations, we can (finally) transform everything into code:
// C# code
private void PositionCamera() {
double radius = 129 / 2.0; // 64.5
// Calculate 3D cam position for flip animation
double x = radius / Math.Tan(degToRad(45 / 2.0));
cam3D.Position = new Point3D(0, 0, x + radius);
// Add border for flip animation
double s = radius * Math.Tan(degToRad(45 / 2.0));
viewport3D.Height = viewport3D.Width = 2 * radius + 2 * s;
}
private void Window_Loaded(object sender, RoutedEventArgs e) {
BuildModel();
PositionCamera();
}
private double degToRad(double deg) {
return deg / 180 * Math.PI;
}
The degToRad
function is needed, because C#’s Trigonometry only takes radian angles.
THE ANIMATION
Now that we have painstakingly set up our beautiful 3D scene, we can finally animate it!
// C# code
public void Flip() {
// Rotate
AxisAngleRotation3D rotation = new AxisAngleRotation3D(new Vector3D(0, 1, 0), 0);
model3D.Transform = new RotateTransform3D(rotation, new Point3D(0, 0, 0));
DoubleAnimation flipAnimation = new DoubleAnimation(0, 180, new Duration(TimeSpan.FromMilliseconds(1000)));
/* To flip back just swap 0 and 180 ;) */
// Do magic!
rotation.BeginAnimation(AxisAngleRotation3D.AngleProperty, flipAnimation);
}
Try it out! What a glorious effect! Once you saw it, you can’t seem to stop starting the animation over and over again.