惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

G
Google Developers Blog
博客园 - 三生石上(FineUI控件)
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
MongoDB | Blog
MongoDB | Blog
小众软件
小众软件
Y
Y Combinator Blog
博客园 - 聂微东
Google DeepMind News
Google DeepMind News
D
Docker
罗磊的独立博客
Microsoft Security Blog
Microsoft Security Blog
D
DataBreaches.Net
B
Blog
Vercel News
Vercel News
Recent Announcements
Recent Announcements
GbyAI
GbyAI
阮一峰的网络日志
阮一峰的网络日志
T
The Blog of Author Tim Ferriss
H
Hackread – Cybersecurity News, Data Breaches, AI and More
P
Proofpoint News Feed
酷 壳 – CoolShell
酷 壳 – CoolShell
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Microsoft Azure Blog
Microsoft Azure Blog
宝玉的分享
宝玉的分享

博客园 - 东风125

.NET Actor Model Implementations Differ in Approach WCF 数据服务 4.5 XML 文档和数据 Why GUID primary keys are a database’s worst nightmare Conway's Game of Life: An Exercise in WPF, MVVM and C# A CIRCULAR PROGRESSBAR STYLE USING AN ATTACHED VIEWMODEL First MarkDown Blog Folder Recursion with C# 39. Volume Rendering Techniques Service Station - An Introduction To RESTful Services With WCF WPF Wonders: Transformations (and Robots!) FoLlow 的技术博客 利用索引降低并发事务引起的锁【转】 企业级应用架构模型-并发,事务,锁 Web service standards: SOAP, REST, OData, and more A Simple MVVM Example[Forward] The Truth About .NET Objects And Sharing Them Between AppDomains Three Sources of a Solid Object-Oriented Design 加快Bitmap的访问速度
Flipping elements with WPF
东风125 · 2015-08-05 · via 博客园 - 东风125

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…

Four-point-transformation

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.

Sketch: Image

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?

Sketch: Camera Position

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 (0|0|64.5). We’re going to calculate x using tangent.

\begin{align*}\tan 22.5^{\circ}&=\frac{64.5}{x}\\\Rightarrow x&=\frac{64.5}{\tan 22.5^{\circ}}\end{align*}

Now if we put the camera at \left(0|0|\frac{64.5}{\tan 22.5^{\circ}}+64.5\right) 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:

\begin{align*}\tan22.5^{\circ}&=\frac{s}{64.5}\\\Rightarrow s&=64.5*\tan22.5^{\circ}\end{align*}

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.