Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF Animation that bends and follows some path geometry

Tags:

c#

animation

wpf

Okay, so I'm working on a loading screen and I want to flare it up a bit.

Basically what I am trying to do is animate an object along path geometry data...I emphasize 'along' because keeping a fixed object along the path on a tangent is not what I would like to do

This is the best representation of what I am trying to do:

enter image description here

I can use a matrix transform to send this border element along the path but it winds up coming out as a tangential animation that moves and rotates with the path, but does not bend to fit the shape of the path...Here is an example of that:

<Border Background="Black" BorderBrush="Transparent" Width="20" Height="20">
<Border.RenderTransform>
    <MatrixTransform x:Name="MatrixT">
        <MatrixTransform.Matrix>
            <Matrix/>
        </MatrixTransform.Matrix>
    </MatrixTransform>
</Border.RenderTransform>
<Border.Triggers>
    <EventTrigger RoutedEvent="Border.Loaded">
        <BeginStoryboard>
            <Storyboard>
                <MatrixAnimationUsingPath Storyboard.TargetName="MatrixT" Storyboard.TargetProperty="Matrix" DoesRotateWithTangent="True" Duration="0:0:5" RepeatBehavior="Forever">
                    <MatrixAnimationUsingPath.PathGeometry>
                        <PathGeometry Figures="M201.1,50.501C201.1,78.138,178.737,100.501,151.1,100.501L150.799,100.501C123.162,100.501,114.933,77.834,100.8,50.501L100.8,50.5C86.666,23.167,78.437,0.5,50.8,0.5L50.5,0.5C22.863,0.5,0.500000000000014,22.863,0.500000000000014,50.5L0.500000000000014,50.501C0.500000000000014,78.138,22.863,100.501,50.5,100.501L50.8,100.501C78.437,100.501,86.666,77.834,100.8,50.501L100.8,50.5C114.933,23.167,123.162,0.5,150.799,0.5L151.1,0.5C178.736,0.5,201.1,22.863,201.1,50.501L201.1,50.501z" PresentationOptions:Freeze="True"/>
                    </MatrixAnimationUsingPath.PathGeometry>
                </MatrixAnimationUsingPath>
            </Storyboard>
        </BeginStoryboard>
    </EventTrigger>
</Border.Triggers>

I have come up with an alternative solution that looks really sharp, but I wanted to pitch this question out to the community to see if they have any ideas on how to accomplish this task (or if it is even possible)...I have done some extensive googling on the matter and have come up with nothing on how to accomplish this in an effective means.

The requirements:

  1. It must follow (or 'bend along') the path
  2. It must be able to scale in size without breaking the animation (many of the stroke animation representations I have seen of this can only operate at one size without re-configuring the animation properties)...a viewbox is perfectly acceptable to accomplish this

If the shape can be tapered and faded out on the trailing side that would be an even bigger plus (see the image above), but that might be more than is possible

EDIT: To clarify what I mean by 'bend'...I mean figure B below...figure A is the standard that I have traditionally seen:

enter image description here

like image 417
Robert Petz Avatar asked Jun 13 '13 18:06

Robert Petz


2 Answers

Morphing your shape to a path would be very difficult in WPF. However there is a close approximation possible by animating two separate paths, and also animating two clipping regions at the same time.

Given below is XAML for an approximation of what you want. If you look carefully at the crossing point of the infinity symbol, you will notice a slight discontinuity in the smoothness of the shading during transition. This is because I was a little bit arbitrary in my setting of the Start, End, and Offset points for the LinearGradientBrush objects. A little work on those will smooth out that transition. You could even choose to animate the properties on the brushes to help with that.

<Window x:Class="AnimationTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525"
        Background="#FF486CBF">
  <Viewbox>
    <Grid>
      <Canvas Width="50" Height="50"
              HorizontalAlignment="Left"
              VerticalAlignment="Top">
        <Canvas.Clip>
          <RectangleGeometry Rect="0,0,50,55">
            <RectangleGeometry.Transform>
              <TranslateTransform x:Name="_clip1"/>
            </RectangleGeometry.Transform>
          </RectangleGeometry>
        </Canvas.Clip>
        <Path StrokeStartLineCap="Round"
              StrokeEndLineCap="Round"
              StrokeThickness="10"
              RenderTransformOrigin="0.5,0.8571"
              Data="M 5,25 c 0,-25 40,-25 40,0">
          <Path.Stroke>
            <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
              <GradientStop Color="#FFFFFFFF" Offset="0"/>
              <GradientStop Color="#00FFFFFF" Offset="0.7"/>
            </LinearGradientBrush>
          </Path.Stroke>
          <Path.RenderTransform>
            <RotateTransform x:Name="_rot1" />
          </Path.RenderTransform>
          <Path.Triggers>
            <EventTrigger RoutedEvent="Path.Loaded">
              <BeginStoryboard>
                <Storyboard>
                  <DoubleAnimation From="360" To="0"
                                   Duration="0:0:3"
                                   RepeatBehavior="Forever"
                                   Storyboard.TargetName="_rot1"
                                   Storyboard.TargetProperty="Angle"/>
                  <DoubleAnimationUsingKeyFrames Storyboard.TargetName="_clip1"
                         Storyboard.TargetProperty="Y"
                         RepeatBehavior="Forever">
                    <DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0"/>
                    <DiscreteDoubleKeyFrame KeyTime="0:0:1.5" Value="25"/>
                    <DiscreteDoubleKeyFrame KeyTime="0:0:2.8" Value="55"/>
                    <DiscreteDoubleKeyFrame KeyTime="0:0:4.5" Value="-30"/>
                    <DiscreteDoubleKeyFrame KeyTime="0:0:5.8" Value="0"/>
                    <DiscreteDoubleKeyFrame KeyTime="0:0:6" Value="0"/>
                  </DoubleAnimationUsingKeyFrames>
                </Storyboard>
              </BeginStoryboard>
            </EventTrigger>
          </Path.Triggers>
        </Path>
      </Canvas>

      <Canvas Width="50" Height="50"
              HorizontalAlignment="Left"
              VerticalAlignment="Top"
              Margin="40,0,0,0">
        <Canvas.Clip>
          <RectangleGeometry Rect="0,0,50,55">
            <RectangleGeometry.Transform>
              <TranslateTransform x:Name="_clip2"/>
            </RectangleGeometry.Transform>
          </RectangleGeometry>
        </Canvas.Clip>
        <Path StrokeStartLineCap="Round"
              StrokeEndLineCap="Round"
              StrokeThickness="10"
              RenderTransformOrigin="0.5,0.8571"
              Data="M 5,25 c 0,-25 40,-25 40,0">
          <Path.Stroke>
            <LinearGradientBrush StartPoint="1,0" EndPoint="0,0">
              <GradientStop Color="#FFFFFFFF" Offset="0"/>
              <GradientStop Color="#00FFFFFF" Offset="0.7"/>
            </LinearGradientBrush>
          </Path.Stroke>
          <Path.RenderTransform>
            <RotateTransform x:Name="_rot2" />
          </Path.RenderTransform>
          <Path.Triggers>
            <EventTrigger RoutedEvent="Path.Loaded">
              <BeginStoryboard>
                <Storyboard>
                  <DoubleAnimation From="0" To="360"
                         Duration="0:0:3"
                         RepeatBehavior="Forever"
                         Storyboard.TargetName="_rot2"
                         Storyboard.TargetProperty="Angle"/>
                  <DoubleAnimationUsingKeyFrames Storyboard.TargetName="_clip2"
                         Storyboard.TargetProperty="Y"
                         RepeatBehavior="Forever">
                    <DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="55"/>
                    <DiscreteDoubleKeyFrame KeyTime="0:0:1.5" Value="-30"/>
                    <DiscreteDoubleKeyFrame KeyTime="0:0:2.8" Value="0"/>
                    <DiscreteDoubleKeyFrame KeyTime="0:0:4.5" Value="25"/>
                    <DiscreteDoubleKeyFrame KeyTime="0:0:5.8" Value="55"/>
                    <DiscreteDoubleKeyFrame KeyTime="0:0:6" Value="55"/>
                  </DoubleAnimationUsingKeyFrames>
                </Storyboard>
              </BeginStoryboard>
            </EventTrigger>
          </Path.Triggers>
        </Path>
      </Canvas>
    </Grid>
  </Viewbox>
</Window>

An important point to note is that the clipping regions need to be applied to the Canvas objects. If they are applied to the Path objects, like you would normally do for an image, the clipping region then gets rotated along with the Path by the RenderTrasform. Not the desired effect.

like image 99
Stewbob Avatar answered Oct 24 '22 15:10

Stewbob


You could just put a lot of circles on the path and animate their diameters.

like image 23
Emond Avatar answered Oct 24 '22 17:10

Emond