#798 – Rotation Transforms Using Homogeneous Coordinates

You can apply a rotation transform to a 2D point using a 2 x 2 transformation, multiplying it by a 2 x 1 point matrix.  This results in a second 2 x 1 point matrix that represents the transformed (rotated) point.

798-001

In WPF, however, rotation transforms are done using homogeneous coordinates, which means that we multiply a 3 x 3 transformation matrix by a 3 x 1 point matrix.

798-002

In the resulting 3 x 1 matrix, the first and second rows contain the x and y values of the transformed (rotated) point.

Advertisement

#790 – How a Rotation Transform Works

A 2D rotation transform in WPF is accomplished by using a transformation matrix.  The transformation matrix is multiplied by another matrix representing a single 2D point to be transformed.  The resulting matrix describes the transformed point.  When rotating a user interface element, this transformation is done individually on each point to generate the set of points representing the transformed element.

The transformation operation for rotation looks like:

790-001

Where ϴ represents the Angle property of the ScaleTransform, indicating the number of degrees to rotate the point in a clockwise direction.

This leads to the equations:

790-002

#769 – Rotation Transforms

You can use a rotation transform to rotate a user interface element.

To rotate an element, you specify the number of degrees to rotate, in a clockwise fashion.  A negative number will rotate the object counter-clockwise.  By default, the object is rotated around a point at its center.

You specify rotation using a RotateTransform element, setting values for the Angle property.

Note that rotating an element normally doesn’t change the element’s ability to respond to user input.

Here’s a simple example:

    <StackPanel Orientation="Vertical">
        <Button Content="Push Me" HorizontalAlignment="Center" Padding="10,5" Margin="5"/>
        <Button Content="Push Me" HorizontalAlignment="Center" Padding="10,5" Margin="5">
            <Button.LayoutTransform>
                <RotateTransform Angle="15"/>
            </Button.LayoutTransform>
        </Button>
        <Button Content="Push Me" HorizontalAlignment="Center" Padding="10,5" Margin="5">
            <Button.LayoutTransform>
                <RotateTransform Angle="-15"/>
            </Button.LayoutTransform>
        </Button>
        <Button Content="Push Me" HorizontalAlignment="Center" Padding="10,5" Margin="5">
            <Button.LayoutTransform>
                <RotateTransform Angle="-90"/>
            </Button.LayoutTransform>
        </Button>
    </StackPanel>

769-001

#755 – Implementing Rotational Inertia during Touch Manipulation

In the same way that you can support inertia as a result of touch manipulation during translation and expansion, you can also set up rotational inertia.  When the user rotates an element using touch, the element has some initial rotational velocity (in deg/ms) when they lift their fingers off the screen.  You can then specify a desired value for a rotational deceleration (deg/ms^2).

As with translation and expansion, you specify the desired rotational deceleration in a handler for the ManipulationInertiaStarting event.  In the example below, we display the initial rotational velocity to the console and then specify a deceleration of 100 degrees/sec^2.  (Reduce velocity by 100 deg/sec each second).

        private void Image_ManipulationInertiaStarting(object sender, ManipulationInertiaStartingEventArgs e)
        {
            // Rotational inertia - 100 deg/sec^2 deceleration
            Console.WriteLine(string.Format("Initial rotational velocity = {0} deg/sec",
                e.RotationBehavior.InitialVelocity * 1000.0));
            e.RotationBehavior.DesiredDeceleration = 100.0 / (1000.0 * 1000.0);
        }

755-001

#742 – Using Touch Manipulation Events to Rotate an Element

In addition to using the touch manipulation events to handle translation of an element, we can use the same mechanisms to allow a user to rotate an element using touch.

We can do both translation and rotation in the same event handler.  The ManipulationDelta object gives us both a translation (vector) and a rotation (angle).  Both are automatically incorporated into the ManipulationDelta object, based on how the user is touching the screen.  The user can translate by sliding one finger around and can rotate by placing two fingers on the object and rotating it.

We transform the element by calling two different functions of the underlying Matrix, for both translation and rotation.

Here’s the XAML, containing a single Image control that we’ll interact with.

    <Canvas Name="canvMain" Background="Transparent">
        <Image Source="JamesII.jpg" Width="100"
               IsManipulationEnabled="True"
               RenderTransform="{Binding ImageTransform}"
               ManipulationStarting="Image_ManipulationStarting" ManipulationDelta="Image_ManipulationDelta"/>
    </Canvas>

Here is the source code, with the updated ManipulationDelta event handler.

    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = this;

            ImageTransform = new MatrixTransform();
        }

        private MatrixTransform imageTransform;
        public MatrixTransform ImageTransform
        {
            get { return imageTransform; }
            set
            {
                if (value != imageTransform)
                {
                    imageTransform = value;
                    RaisePropertyChanged("ImageTransform");
                }
            }
        }

        private void Image_ManipulationStarting(object sender, ManipulationStartingEventArgs e)
        {
            // Ask for manipulations to be reported relative to the canvas
            e.ManipulationContainer = canvMain;
        }

        private void Image_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
        {
            ManipulationDelta md = e.DeltaManipulation;
            Vector trans = md.Translation;
            double rotate = md.Rotation;

            Matrix m = imageTransform.Matrix;

            // Find center of element and then transform to get current location of center
            FrameworkElement fe = e.Source as FrameworkElement;
            Point center = new Point(fe.ActualWidth / 2, fe.ActualHeight / 2);
            center = m.Transform(center);

            // Update matrix to reflect translation/rotation
            m.Translate(trans.X, trans.Y);
            m.RotateAt(rotate, center.X, center.Y);

            imageTransform.Matrix = m;
            RaisePropertyChanged("ImageTransform");

            e.Handled = true;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(string prop)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(prop));
        }
    }

742-001