One of the many, many new features coming in Silverlight 3 (and featured in Silverlight 3 Beta 1, which shipped this week) is the perspective transform. In the Silverlight MyComix viewer I built for Silverlight 1.0, I faked the 3D rotation of a comic book cover around the Y-axis by using a ScaleTransform to squeeze the cover image horizontally and a SkewTransform to alter the angle of the top and bottom edges, but the effect wasn’t perfect because Silverlight gave me no way to add perspective by displaying the top and bottom edges at different angles. In addition, I had to compute the parameters for the transforms using trigonometry.

Silverlight 3’s PlaneProjection class makes it incredibly easy to implement such effects, and to do so with the added benefit of perspective. To rotate an image around the Y-axis through its center, you declare the image like this:

<Image x:Name=”CoverImage” Source=”…”>

    <Image.Projection>

        <PlaneProjection x:Name=”Rotator” />

    </Image.Projection>

</Image>

 

And to perform the rotation, you simply manipulate the value of the PlaneProjection’s RotationY property using an animation, a MouseMove handler, element-to-element binding, or whatever.

To demonstrate, I wrote a simple viewer that uses a PlaneProjection to rotate a cover image as you move the mouse across the page with the left button depressed. I included extra logic to swap the front and back cover images at 90 and 270 degrees to create the illusion that you really are spinning a comic book:

 

Here’s the XAML code-behind class containing the logic that manipulates the PlaneProjection. The mouse-event handlers are attached to the Grid control that contains the Image:

public partial class MainPage : UserControl

{

    private bool _down = false;

    private double _lastx = -1;

    private double _increment = 2.0;

    private BitmapImage _front =

        new BitmapImage(new Uri(“Images/Front.jpg”, UriKind.Relative));

    private BitmapImage _back =

        new BitmapImage(new Uri(“Images/Back.jpg”, UriKind.Relative));

    private bool _isfront = true;

 

    public MainPage()

    {

        InitializeComponent();

    }

 

    private void LayoutRoot_MouseLeftButtonDown(object sender,

        MouseButtonEventArgs e)

    {

        _lastx = e.GetPosition((FrameworkElement)sender).X;

        _down = true;

    }

 

    private void LayoutRoot_MouseLeftButtonUp(object sender,

        MouseButtonEventArgs e)

    {

        _lastx = -1;

        _down = false;

    }

 

    private void LayoutRoot_MouseMove(object sender, MouseEventArgs e)

    {

        if (_down)

        {

            double x = e.GetPosition((FrameworkElement)sender).X;

            double angle = Rotator.RotationY;

 

            if (x > _lastx) // Rotate right

            {

                angle -= _increment;

 

                if (angle < 0.0)

                    angle += 360.0;

 

                if (angle == 270.0)

                {

                    CoverImage.Source = _isfront ? _back : _front;

                    _isfront = !_isfront;

                    angle = 90.0;

                }

 

                Rotator.RotationY = angle;

            }

            else if (x < _lastx) // Rotate left

            {

                angle += _increment;

 

                if (angle >= 360.0)

                    angle -= 360.0;

 

                if (angle == 90.0)

                {

                    CoverImage.Source = _isfront ? _back : _front;

                    _isfront = !_isfront;

                    angle = 270.0;

                }

 

                Rotator.RotationY = angle;

            }

 

            _lastx = x;

        }

    }

 

    private void LayoutRoot_MouseLeave(object sender, MouseEventArgs e)

    {

        _lastx = -1;

        _down = false;

    }

}

Wasn’t that easy? And best of all: no trig!