1

I have a WPF Storyboard (with RepeatBehavior=Forever) containing two DoubleAnimationUsingPath animations. These animations trace a circular path, which I create through adding an EllipseGeometry. One animation is used to control the X position, and the other animation is used to control the Y position, of another object which appears to orbit using the circular path.

The animation always follows the path in a clockwise direction, and always starts in the middle on the right side of the circle. I need to force the animation to play in reverse (i.e. counterclockwise along the path). Ideally I'd also like to be able to control the starting position, but this isn't a necessity right now.

The AutoReverse property on the animation does what I want, but obviously then it alternates between clockwise and counterclockwise - and I need the animation to loop, always in a counterclockwise direction.

I also tried to use a ScaleTransform with ScaleX = -1 in order to flip the ellipse, hoping that this would help - but it hasn't.

Is there any way to force a DoubleAnimationUsingPath to follow a specific direction along the path?

John
  • 5,452
  • 8
  • 37
  • 37

2 Answers2

1

You can switch between two paths with the same coordinates but reverse directions. here is a sample using MatrixAnimationUsingPath in which the animation direction is cw when UI element loads and it changes to ccw when mouse pointer enters the blue rectangle.

    <Canvas ClipToBounds="False" Width="400" Height="400">
        <Path StrokeThickness="10" StrokeDashArray="" StrokeDashCap="Flat" StrokeDashOffset="0" StrokeStartLineCap="Flat" StrokeEndLineCap="Flat" StrokeLineJoin="Miter" StrokeMiterLimit="10" Stroke="#000000">
            <Path.Data>
                <PathGeometry Figures="M 150,250 C 150,120 300,120 300,250 C 300,390 150,390 150,250 Z" />
            </Path.Data>
        </Path>
        <Rectangle Fill="Blue"  RenderTransformOrigin="0.5,0.5" Width="10" Height="10" Margin="-5">
            <Rectangle.RenderTransform>
                <TransformGroup>
                    <MatrixTransform x:Name="tt">
                        <MatrixTransform.Matrix>
                            <Matrix />
                        </MatrixTransform.Matrix>
                    </MatrixTransform>
                </TransformGroup>
            </Rectangle.RenderTransform>
            <Rectangle.Triggers>
                <EventTrigger RoutedEvent="FrameworkElement.Loaded">
                    <BeginStoryboard>
                        <Storyboard>
                            <MatrixAnimationUsingPath Duration="0:0:05" Storyboard.TargetName="tt" 
                                                      Storyboard.TargetProperty="Matrix" 
                                                      AutoReverse="False" 
                                                      DoesRotateWithTangent="True" 
                                                      RepeatBehavior="Forever"
                                                     >
                                <MatrixAnimationUsingPath.PathGeometry>
                                    <PathGeometry Figures="M 150,250 C 150,120 300,120 300,250 C 300,390 150,390 150,250 Z">
                                    </PathGeometry>
                                </MatrixAnimationUsingPath.PathGeometry>
                            </MatrixAnimationUsingPath>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
                <EventTrigger RoutedEvent="FrameworkElement.MouseEnter">
                    <BeginStoryboard>
                        <Storyboard>
                            <MatrixAnimationUsingPath Duration="0:0:05" Storyboard.TargetName="tt" 
                                                      Storyboard.TargetProperty="Matrix" 
                                                      AutoReverse="False" 
                                                      DoesRotateWithTangent="True" 
                                                      RepeatBehavior="Forever"
                                                     >
                                <MatrixAnimationUsingPath.PathGeometry>
                                    <PathGeometry Figures="M 150,250 C 150,390 300,390 300,250 C 300,120 150,120 150,250 Z" >
                                    </PathGeometry>
                                </MatrixAnimationUsingPath.PathGeometry>
                            </MatrixAnimationUsingPath>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Rectangle.Triggers>
        </Rectangle>
    </Canvas>
Bahman_Aries
  • 4,658
  • 6
  • 36
  • 60
  • Thanks for the suggestion. It looks like the key to this is that there are two separate paths though - one for the clockwise direction and one for the counter-clockwise direction. I was hoping to find a solution that would allow for the animation to use a single path - or, at least programmatically manipulate the path to create the clockwise and counter-clockwise alternatives dynamically. – John Jun 03 '15 at 06:43
1

An alternative to DoubleAnimationUsingPath would be a custom animation that provides sinus values. It has an Amplitude property that controls the amplitude and by its sign also the direction of the sinus animation. There is also a StartAngle property that controls at which angle the animation starts.

public class SinusAnimation : DoubleAnimationBase
{
    public static readonly DependencyProperty AmplitudeProperty =
        DependencyProperty.Register(
            "Amplitude", typeof(double), typeof(SinusAnimation));

    public static readonly DependencyProperty StartAngleProperty =
        DependencyProperty.Register(
            "StartAngle", typeof(double), typeof(SinusAnimation));

    public double Amplitude
    {
        get { return (double)GetValue(AmplitudeProperty); }
        set { SetValue(AmplitudeProperty, value); }
    }

    public double StartAngle
    {
        get { return (double)GetValue(StartAngleProperty); }
        set { SetValue(StartAngleProperty, value); }
    }

    protected override double GetCurrentValueCore(double defaultOriginValue,
        double defaultDestinationValue, AnimationClock animationClock)
    {
        var result = defaultOriginValue;
        var p = animationClock.CurrentProgress;

        if (p.HasValue)
        {
            result = Amplitude * Math.Sin(
                p.Value * Math.PI * 2d + StartAngle * Math.PI / 180d);
        }

        return result;
    }

    protected override Freezable CreateInstanceCore()
    {
        return new SinusAnimation();
    }
}

This is some simple XAML that creates circular motion of a small blue circle. By different Amplitudes it might as well create elliptical trajectories, and by different StartAngles also all kinds of Lissajous figures.

<Canvas Margin="200">
    <Path Fill="Blue">
        <Path.Data>
            <EllipseGeometry RadiusX="10" RadiusY="10"/>
        </Path.Data>
        <Path.Triggers>
            <EventTrigger RoutedEvent="Loaded">
                <BeginStoryboard>
                    <Storyboard Duration="0:0:2" RepeatBehavior="Forever">
                        <local:SinusAnimation
                            Storyboard.TargetProperty="(Canvas.Left)"
                            Amplitude="100"
                            Duration="0:0:2" RepeatBehavior="Forever"/>
                        <local:SinusAnimation
                            Storyboard.TargetProperty="(Canvas.Top)"
                            Amplitude="100" StartAngle="90"
                            Duration="0:0:2" RepeatBehavior="Forever"/>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Path.Triggers>
    </Path>
</Canvas>

Edit: In order to reverse a DoubleAnmationUsingPath you can simply set the Transform property of the used PathGeometry to an appropriate ScaleTransform:

<PathGeometry x:Key="path">
    <PathGeometry.Transform>
        <ScaleTransform ScaleX="-1"/>
    </PathGeometry.Transform>
    ...
</PathGeometry>
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • 1
    Nice! when I changed `StartAngle` to "270", rotation got reversed. – Bahman_Aries Jun 02 '15 at 08:33
  • 2
    Also when you set one Amplitude to a negative value. – Clemens Jun 02 '15 at 08:35
  • Thanks! This does work really well for a circle, although I was hoping to find a solution that would work with an arbitrary path. – John Jun 03 '15 at 06:41
  • Please see my edit. If that doesn't work for you for any reason, please show us how you created a PathGeometry from an EllipseGeometry. Another alternative would be to create the elliptical curve from a set of ArcSegments in appropriate order. – Clemens Jun 03 '15 at 07:26