Resumé

Silverlight 3 Beta TrackballBehavior

Cristian Merighi () 0.00

Combine PlaneProjection and Blend 3 Behaviors to interactively rotate an UIElement the 3D way in Silverlight 3 Beta.
This article is obsolete. Some functionalities might not work anymore. Comments are disabled.

Silverlight 3 Beta brings along, as we all know, many new possibilities in terms of graphics rendering and assets' management if compared with its previous version. With this article I aim to describe a couple of these brand new features and, at the same time, to provide some "useful" code (at least I'll try).

So let's pick out from the lottery bulle the PlaneProjection class and the Behavior extension that comes with Blend 3 libraries.

Before we start I think an introduction could be useful:

  • PlaneProjection: that's the one and only implementation of the base class System.Windows.Media.Projection so far. It is also some serious break-through income into the "flat" (in the bidimensional meaning) world of Silverlight, unfortunately it just permits trivials 3D effects and developers cannot pretend to lean on it alone to implement a fully featured 3D engine.
    Hopefully it won't remain alone for long...
    You can see plane projection in action by giving appropriate value to UIElement's Projection property.
  • Behavior (aka Microsoft.Expression.Interactivity.Behavior<T>): in its concept it is similar to the Sys.UI.Behavior of ASP.Net Ajax Framework, it allows to centralize, reuse and atomize functionalities in addition to the standard native ones of any Silverlight object.
    Implementation is straightforward, it is just needed to override the base methods OnAttached and OnDetaching (think as about initialize and dispose in ASP.Net Ajax) and bind the just built behavior to the desired element by some tiny markup declarations...

We're still missing the goal of this tutorial: we want to create a TrackballBehavior that permit, once attached to an UIElement, to rotate it in a 3D fashion using mouse and keyboard modifiers.
Warning: this is a simplified trackball implementation, the resulting behavior is similar to that you are maybe get used in the Autodesk® 3ds Max environment by combining Alt+MiddleClick+Drag.

Here's the code of the Behavior:

public class TrackballBehavior : Behavior<UIElement>

{

    protected override void OnAttached()

    {

        base.OnAttached();

        // custom initialization

        this.AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;

        this.AssociatedObject.MouseLeftButtonUp += AssociatedObject_Reset;

    }

    protected override void OnDetaching()

    {

        // custom disposure

        this.AssociatedObject.MouseLeftButtonDown -= AssociatedObject_MouseLeftButtonDown;

        this.AssociatedObject.MouseLeftButtonUp -= AssociatedObject_Reset;

        //

        base.OnDetaching();

    }

    private bool active = false;

    private Size size = Size.Empty;

    private Point origin = new Point();

    PlaneProjection existingProj = null;

    void AssociatedObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)

    {

        if (active)

        {

            active = false;

            Application.Current.RootVisual.MouseMove -= this.AssociatedObject_MouseMove;

            Application.Current.RootVisual.MouseLeftButtonUp -= this.AssociatedObject_MouseLeftButtonUp;

            size = Size.Empty;

            e.Handled = true;

        }

    }

    void AssociatedObject_MouseMove(object sender, MouseEventArgs e)

    {

        if (!active) return;

 

        double d = Math.Min(size.Width, size.Height);

        Point actual = e.GetPosition(Application.Current.RootVisual);

        double delta_x = actual.X - origin.X;

        double delta_y = actual.Y - origin.Y;

        // do the maths...

        double angle_x = 90D * delta_y / d;

        double angle_y = -90D * delta_x / d;

        double angle_z = 0D;

        if (this.existingProj != null)

        {

            // I don't take care of center and offsets,

            // improve it yourself if you do... ;)

            angle_y += existingProj.RotationY;

            angle_z += existingProj.RotationZ;

            double ycheck = Math.Abs(existingProj.RotationY % 360D);

            if (270D > ycheck && ycheck > 90D)

                angle_x *= -1D;

            angle_x += existingProj.RotationX;

 

        }

        this.AssociatedObject.Projection = new PlaneProjection

        {

            CenterOfRotationX = .5D,

            CenterOfRotationY = .5D,

            CenterOfRotationZ = .5D,

            RotationX = angle_x,

            RotationY = angle_y,

            RotationZ = angle_z

        };

    }

    void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)

    {

        if (!active)

        {

            active = Keyboard.Modifiers == ModifierKeys.Control;

            if (active)

            {

                if (size.IsEmpty)

                    size = this.AssociatedObject.RenderSize;

                origin = e.GetPosition(Application.Current.RootVisual);

                Application.Current.RootVisual.MouseMove += this.AssociatedObject_MouseMove;

                Application.Current.RootVisual.MouseLeftButtonUp += this.AssociatedObject_MouseLeftButtonUp;

                if (this.AssociatedObject.Projection as PlaneProjection != null)

                    this.existingProj = (PlaneProjection)this.AssociatedObject.Projection;

                else

                    this.existingProj = null;

                e.Handled = true;

            }

        }

    }

    void AssociatedObject_Reset(object sender, MouseButtonEventArgs e)

    {

        if (!active && Keyboard.Modifiers == ModifierKeys.Shift)

        {

            this.AssociatedObject.Projection = null;

            e.Handled = true;

        }

    }

}

So how does our Behavior ...behave?! The gesture Ctrl+MouseDown triggers it, then by dragging around you'll se the associated object rotating consequently.
Shift+Click resets the default position by removing the rotations.

A simple sample aplication is picted by the following Xaml markup: TrackballBehavior is there associated to an image object.

<UserControl x:Class="TestSilverlightApplication.Trackball"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   xmlns:i="clr-namespace:Microsoft.Expression.Interactivity;assembly=Microsoft.Expression.Interactivity" 

   xmlns:pacem="clr-namespace:Pacem.Silverlight.Toolkit;assembly=Pacem.Silverlight.Toolkit"

   >

    <Grid x:Name="LayoutRoot" Background="White">

        <Image Source="images/sample.jpg" x:Name="img" Width="400" Height="225"

              HorizontalAlignment="Center" VerticalAlignment="Center">

            <Image.Projection>

                <PlaneProjection CenterOfRotationX="0.5"

                                CenterOfRotationY="0.5"

                                CenterOfRotationZ="0.5"

                                RotationY="30"/>

            </Image.Projection>

                <i:Interaction.Behaviors>

                <pacem:TrackballBehavior x:Name="trackball"/>

            </i:Interaction.Behaviors>

        </Image>

    </Grid>

</UserControl>

Live demo is available, so it is the source code.

Nota bene: demo and code have been implemented on top of Silverlight 3 Beta, I do not tend nor intend to modify the content of this article to match the future releases of the Silverlight plugin.

zip file « Silverlight TrackballBehavior

Take care. Bye.

feedback
 

Syndicate

Author

Cristian Merighi facebook twitter google+ youtube

Latest articles

Top rated

Archive

Where am I?

Author

Cristian Merighi facebook twitter google+ youtube

I'm now reading

Feeds