Silverlight 3 Tutorial - PlaneProjection and Perspective 3D

Skill

Silverlight 3 Tutorial - PlaneProjection and Perspective 3D

Posted in:

Silverlight 3 has unleashed a host of new features available to us developers, and one that should provide a new level of user interface design is the introduction of perspective 3D. This features allows us to apply projections to any UIElement to give the appearance of 3 dimensions. In this tutorial, we're going to build a simple flipbook that lets users flip through images as if they were turning the pages on a book.

The example app we're building today is below. When the user clicks on an image the page will 'turn' to reveal the next image. Go ahead and play around a little. Just so you know, the images start with the Enterprise-E and end with the Enterprise-A.



The XAML for this app is actually very simple. If this were WPF, the entire app could have probably been written in XAML, however Silverlight still has some limitations that made that impossible.

<UserControl x:Class="Projection3D.MainPage"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            mc:Ignorable="d"
            d:DesignWidth="640"
            d:DesignHeight="480">
  <UserControl.Resources>
    <Style x:Key="_imageStyle"
          TargetType="Image">
      <Setter Property="Margin"
             Value="275,0,0,0" />
      <Setter Property="Width"
             Value="275" />
      <Setter Property="Stretch"
             Value="Uniform" />
    </Style>
  </UserControl.Resources>
  <Grid x:Name="LayoutRoot">
    <Image Style="{StaticResource _imageStyle}"
          Source="Images/Enterprise-A.jpg"
          MouseLeftButtonUp="FlipImage" />
    <Image Style="{StaticResource _imageStyle}"
          Source="Images/Enterprise-B.jpg"
          MouseLeftButtonUp="FlipImage" />
    <Image Style="{StaticResource _imageStyle}"
          Source="Images/Enterprise-C.jpg"
          MouseLeftButtonUp="FlipImage" />
    <Image Style="{StaticResource _imageStyle}"
          Source="Images/Enterprise-D.jpg"
          MouseLeftButtonUp="FlipImage" />
    <Image Style="{StaticResource _imageStyle}"
          Source="Images/Enterprise-E.jpg"
          MouseLeftButtonUp="FlipImage" />
    <TextBlock Text="Click an image to turn the page."
              VerticalAlignment="Bottom"
              HorizontalAlignment="Center" />
  </Grid>
</UserControl>

Everything here is pretty straight forward. I created a style so I didn't have to duplicate the same attributes for every image. In WPF you wouldn't have to set the style explicitly on each Image element, however Silverlight doesn't support style inheritance by type yet.

In WPF, I would have used style triggers to play a storyboard whenever an image was clicked, however Silverlight also doesn't support triggers. This means everything else must be done in code inside the click event for the images.

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace Projection3D
{
  public partial class MainPage : UserControl
  {
    // Holds the current zIndex.  Used to make sure images
    // that are being flipped are on top of all other images.
    private int _zIndex = 10;

    public MainPage()
    {
      InitializeComponent();
    }

    private void FlipImage(object sender, MouseButtonEventArgs e)
    {
      Image image = sender as Image;

      // Make sure the image is on top of all other images.
      image.SetValue(Canvas.ZIndexProperty, _zIndex++);

      // Create the storyboard.
      Storyboard flip = new Storyboard();

      // Create animation and set the duration to 1 second.
      DoubleAnimation animation = new DoubleAnimation()
      {
        Duration = new TimeSpan(0, 0, 1)
      };

      // Add the animation to the storyboard.
      flip.Children.Add(animation);

      // Create a projection for the image if it doesn't have one.
      if (image.Projection == null)
      {
        // Set the center of rotation to -0.01, which will put a little space
        // between the images when they're flipped.
        image.Projection = new PlaneProjection()
        {
          CenterOfRotationX = -0.01
        };
      }

      PlaneProjection projection = image.Projection as PlaneProjection;

      // Set the from and to properties based on the current flip direction of
      // the image.
      if (projection.RotationY == 0)
      {
        animation.To = 180;
      }
      else
      {
        animation.From = 180;
        animation.To = 0;
      }

      // Tell the animation to animation the image's PlaneProjection object.
      Storyboard.SetTarget(animation, projection);

      // Tell the animation to animation the RotationYProperty.
      Storyboard.SetTargetProperty(animation,
        new PropertyPath(PlaneProjection.RotationYProperty));

      flip.Begin();
    }
  }
}

There's definitely a lot here, but I'll step through it piece by piece. The only field I have is to store the z-Index. This is used to move the currently flipping image to the top of the stack that way it won't appear underneath other images.

Let's now move to the FlipImage event handler. After I cast the sender as an Image, I set the z-Index so it appears on top of everything else. Next, I create the storyboard and the animation. Since I'm going to animate a rotation property, I'll need a DoubleAnimation.

Now for the important part. If the image doesn't already have a Projection (which means it's never been flipped), I create one for it and set the CenterOfRotationX to -0.01. This is the offset, as a percentage of the total width of the image, that the image will rotate around. I didn't leave it 0 because I wanted a small space between the images when they were flipped. I'm using a PlaneProjection, which will simply rotate an element on the X, Y, and/or Z planes. Silverlight also supports a Matrix3DProjection if you've got more sophisticated rotation requirements.

The next thing I do is check which direction the image should be flipped and set the animation's To and From properties accordingly. The rotation properties are set in degrees, so I'll either set it to 180 to flip it to the left, or 0 to flip it back to the right.

The last few things I do are to tell the DoubleAnimation what object is being animated and what property is being animated on that object. To get the image to flip like a book, I need to rotate it around the Y axis, which is why I'm animating the RotationY property. I then simply tell the Storyboard to go.

A lot of the setup of the Storyboard and DoubleAnimation could have been done in XAML, and I originally had them in there. In WPF, you can change the target of a Storyboard at runtime with an overloaded Begin function, however that's not available in Silverlight. This means the code looked a little ugly when they were resources, so I moved everything into code.

That's it for this tutorial. With a surprisingly small amount of code we were able to create a neat flipbook effect using Silverlight and perspective 3d. I've attached a Visual Studio 2008 project with the above example below. If you've got any questions, please leave a comment.

HiTechMagic
07/19/2009 - 17:13

Nice effect.

Actually you can do this in XAML only with Siverlight 3. SL 3 supports Behaviours which can trigger storyboards on many mouse events, including the basic MouseLeftButtonDown.

By putting a second duplicate (and reversed) image on the back of each, with a Behavior to trigger the reversal effect, I was able to reproduce the same effect with no code at all. The only other change I made was the have the storyboard change ZIndex at the halfway point, so that the back image is shown instead of the front enabling the reverse mouse event.

Cheers

reply

The Reddest
07/20/2009 - 10:17

Sounds great! Can you post some example XAML? I'd like to see how the behaviors are used.

reply

jseiffer
07/31/2009 - 10:42

Great Post!! I love that you did it in the code behind since I believe that is the best way to do things.

It was an eye opener for me, I didn't realize that I can use double animation to set the to and from for any property. It's weird but I always thought it was used for opactiy only.

reply

BattleChess
02/06/2010 - 13:21

So this could potentially be used to do an isometric 3d game (like a chessboard). Provided the whole board was an image, or a 2d array of tiles that would each be projected in some kind of game loop.

reply

Add Comment

Put code snippets inside language tags:
[language] [/language]

Examples:
[javascript] [/javascript]
[actionscript] [/actionscript]
[csharp] [/csharp]

See here for supported languages.

Javascript must be enabled to submit anonymous comments - or you can login.