views:

101

answers:

1

The following code draws a line, rotates it 30 degrees around its left end, restores it to its original position, rotates it 30 degrees around its right end, and then repeats several times.

How can I sequence these rotations without restoring the line to its original position in between? The first rotation (around the left endpoint) causes the right endpoint to move; so I would like the next rotation to be around its new position.

The net effect of the sequence should be to make the line segment "walk" forward.

(Note that this code uses the same angle over and over again. But I need a solution that will also work if the angle is different every time.)

XAML:-

<UserControl x:Class="Rotation.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">
    <Grid x:Name="LayoutRoot">
        <Canvas Width="500" Height="500">
            <Line Name="TheLine" X1="100" Y1="200" X2="200" Y2="200" Stroke="Black" StrokeThickness="5"></Line>           
        </Canvas>
    </Grid>
</UserControl>

Code:-

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace Rotation
{
    public partial class MainPage : UserControl
    {
        double x1, y1, x2, y2;

        public MainPage()
        {
            InitializeComponent();

            for (int i = 0; i < 5; i++)
            {
                _animations.Add(() => { return rot(true, -30); });
                _animations.Add(() => { return rot(false, 30); });
            }
            _enumerator = _animations.GetEnumerator();

            x1 = TheLine.X1;
            x2 = TheLine.X2;
            y1 = TheLine.Y1;
            y2 = TheLine.Y2;

            this.Loaded += delegate(object sender, RoutedEventArgs e)
            {
                RunNextAnimation();
            };
        }

        List<Func<Storyboard>> _animations = new List<Func<Storyboard>>();
        IEnumerator<Func<Storyboard>> _enumerator;

        public void AnimationCompleted(object sender, EventArgs args)
        {
            RunNextAnimation();
        }

        void RunNextAnimation()
        {
            if (_enumerator.MoveNext())
            {
                Func<Storyboard> fn = _enumerator.Current;
                if (fn != null)
                {
                    Storyboard board = fn();
                    board.Completed += AnimationCompleted;
                    board.Begin();
                }
            }
        }


        public Storyboard rot(bool aroundLeft, double angle)
        {
            Storyboard board = new Storyboard();
            int duration = 5;


            if (true)
            {
                RotateTransform rot = new RotateTransform();

                if (aroundLeft)
                {
                    rot.CenterX = x1;
                    rot.CenterY = y1;
                }
                else
                {
                    rot.CenterX = x2;
                    rot.CenterY = y2;
                }

                TheLine.RenderTransform = rot;

                DoubleAnimation an = new DoubleAnimation();
                an.Duration = new Duration(new TimeSpan(0, 0, duration));
                an.From = 0;
                an.To = angle;

                board.Children.Add(an);
                Storyboard.SetTarget(an, TheLine);
                Storyboard.SetTargetProperty(an, new PropertyPath("(UIElement.RenderTransform).Angle"));
            }

            return board;
        }


    }
}
+1  A: 

I figured out one way to do this: With each rotation, compute where the line's endpoints will move to. Then before starting the next rotation, move the line so that its position and angle reflect the desired center of that rotation.

Here's the code to do it. Instead of a line, I'm now using a canvas containing a composite shape, which is a little more general purpose.

XAML:

<UserControl x:Class="Rotation.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">
    <Grid x:Name="LayoutRoot">
        <Canvas Width="500" Height="500">
            <Canvas Name="TheRect" Canvas.Left="100" Canvas.Top="100" Width="100" Height="20">
                <Rectangle Canvas.Left="0" Canvas.Top="0" Width="100" Height="20" Stroke="Black" StrokeThickness="1"></Rectangle>
                <Ellipse Width="10" Height="10" Fill="Blue" Canvas.Left="0" Canvas.Top="0" />
            </Canvas>
        </Canvas>
    </Grid>
</UserControl>

C#:

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace Rotation
{
    public partial class MainPage : UserControl
    {
        double x1, y1, x2, y2, w;
        double lastAngle;

        public MainPage()
        {
            InitializeComponent();

            for (int i = 0; i < 10; i++)
            {
                _animations.Add(() => { return rot(true, 30); });
                _animations.Add(() => { return rot(false, -30); });
            }
            _enumerator = _animations.GetEnumerator();

            x1 = (double)TheRect.GetValue(Canvas.LeftProperty);
            y1 = (double)TheRect.GetValue(Canvas.TopProperty);
            w = (double)TheRect.GetValue(Canvas.WidthProperty);
            x2 = x1 + w;
            y2 = y1;
            lastAngle = 0.0;

            this.Loaded += delegate(object sender, RoutedEventArgs e)
            {
                RunNextAnimation();
            };
        }

        List<Func<Storyboard>> _animations = new List<Func<Storyboard>>();
        IEnumerator<Func<Storyboard>> _enumerator;

        public void AnimationCompleted(object sender, EventArgs args)
        {
            RunNextAnimation();
        }

        void RunNextAnimation()
        {
            if (_enumerator.MoveNext())
            {
                Func<Storyboard> fn = _enumerator.Current;
                if (fn != null)
                {
                    Storyboard board = fn();
                    board.Completed += AnimationCompleted;
                    board.Begin();
                }
            }
        }


        public Storyboard rot(bool aroundLeft, double angle)
        {
            Storyboard board = new Storyboard();
            int duration = 5;


            if (true)
            {
                TheRect.SetValue(Canvas.LeftProperty, aroundLeft ? x1 : x1 - w*(1 - Math.Cos(lastAngle * Math.PI / 180)));
                TheRect.SetValue(Canvas.TopProperty, aroundLeft ? y1 : y2);

                RotateTransform rot = new RotateTransform();
                rot.CenterX = aroundLeft ? 0 : w;
                rot.CenterY = aroundLeft ? 0 : 0;
                rot.Angle = aroundLeft ? lastAngle : -lastAngle;

                TheRect.RenderTransform = rot;

                DoubleAnimation an = new DoubleAnimation();
                an.Duration = new Duration(new TimeSpan(0, 0, duration));
                an.From = lastAngle;
                an.To = lastAngle + angle;

                board.Children.Add(an);
                Storyboard.SetTarget(an, TheRect);
                Storyboard.SetTargetProperty(an, new PropertyPath("(UIElement.RenderTransform).Angle"));

                // and for next time around:
                lastAngle += angle;

                if (aroundLeft)
                {
                    // rotating will move x2,y2; compute the updated values for next time
                    double x0 = x2 - x1;
                    double y0 = y2 - y1;

                    double sin = Math.Sin(angle * Math.PI / 180.0);
                    double cos = Math.Cos(angle * Math.PI / 180.0);

                    x2 = x1 + (x0 * cos) - (y0 * sin);
                    y2 = y1 + (x0 * sin) + (y0 * cos);
                }
                else
                {
                    // rotating will move x1, y1; compute the updated values for next time
                    double x0 = x1 - x2;
                    double y0 = y1 - y2;

                    double sin = Math.Sin(angle * Math.PI / 180.0);
                    double cos = Math.Cos(angle * Math.PI / 180.0);

                    x1 = x2 + (x0 * cos) - (y0 * sin);
                    y1 = y2 + (x0 * sin) + (y0 * cos);
                }

            }

            return board;
        }


    }
}
Eric