how to make PathAnimation?

Jun 29, 2013 at 1:27 PM
Now I would like to change the storyboard to the artefact as below code.

ArtefactAnimator.AddEase(ep, ????, PathUtils.ParsePathGeometryString(PathAStr),
                           3, AnimationTransitions.ElasticEaseOut, 1);

I do not know what kind of property I should use?

        var animationPath = new PathGeometry();
        var pFigure = new PathFigure {StartPoint = new Point(0, -300)};
        var pBezierSegment = new PolyLineSegment();
        foreach (var t in markPosition)
        {
            pBezierSegment.Points.Add(new Point(t.X, t.Y));
        }
        pFigure.Segments.Add(pBezierSegment);
        animationPath.Figures.Add(pFigure);

        // Freeze the PathGeometry for performance benefits.
        animationPath.Freeze();


        var animationX = new DoubleAnimationUsingPath
            {
                PathGeometry = animationPath,
                Source = PathAnimationSource.X,
                Duration = new Duration(TimeSpan.FromMilliseconds(durationWithPath))
            };


        var animationY = new DoubleAnimationUsingPath
            {
                PathGeometry = animationPath,
                Source = PathAnimationSource.Y,
                Duration = new Duration(TimeSpan.FromMilliseconds(durationWithPath))
            };


        Storyboard.SetTargetProperty(animationX, new PropertyPath("(Canvas.Left)"));
        Storyboard.SetTargetProperty(animationY, new PropertyPath("(Canvas.Top)"));
        Storyboard.SetTargetName(animationX, targetname);
        Storyboard.SetTargetName(animationY, targetname);
Coordinator
Jun 29, 2013 at 2:50 PM
** NOTE - I didn't test this code, but it should give you a general idea **

It looks like you're just moving an object so the only properties you need to animate are "x" and "y". In this case we'll assume the object is in a Canvas so X = Canvas.Left and Y = Canvas.Top. You can keep all your code up to and including "animationPath.Freeze();" because that is just constructing your object's data.

According to http://msdn.microsoft.com/en-us/library/ms747393.aspx#paths it looks like you'll need to put your Geometry into a UIElement and then animate the position on the UIElement.

Create a Path Element to animate and set it's data to your geometry
    Path path = new Path();
    path.Data = pathGeometry;
You then need to throw this path into a Canvas somewhere otherwise you'll never see it
    Canvas parent = new Canvas(); // this should reference a Canvas in your XAML
    parent.Children.Add(path);
Before we can animate X and Y we have to make sure they are set, an easy way to initialize them is:
    if (double.IsNaN(Canvas.GetLeft(path)))
    {
        Canvas.SetLeft(path, 0);
    }

    if (double.IsNaN(Canvas.GetTop(path)))
    {
        Canvas.SetTop(path, 0);
    }  
Now we can choose how and where we want to animate the object

No delay
    double delay = 0; 
Convert your milliseconds to seconds
    double time = durationWithPath / 1000; 
Pick an ease type
    PercentHandler ease = AnimationTransitions.CubicEaseOut;
Choose a destination
    double x = 10;
    double y = 20;
Animate the values
    path.SlideTo(x, y, time, ease, delay);
Jun 29, 2013 at 10:52 PM
HI jgraup:

Thank you for your replay, it makes me some ideas. But my question is it seems that the animation is related to Path,not the object.
in fact, my goal is make one rectangle moved with this path.
Coordinator
Jun 30, 2013 at 12:28 AM
Edited Jun 30, 2013 at 12:30 AM
Ah! Ok, animating a position along a path. Well, sounds like you just need to animate a value from 0 to 1, then interpolate the point on the path.

Artefact Animator is really there to provide an easy ability to change a value to another value over time, so in this case it will only indirectly help you.

You need to create a function that takes a 0-1 value and converts that to a usable position. At that point you can animate a separate property and apply the percentage from the update to your path interpolation function.

I'm not sure the best way to animate an indirect value as I haven't used this library in years but here is a dirty hack that will do everything except determine your point along a given geometry.

First register an empty Getter/Setter
void RegisterEmpty()
{
    var EmptyGetterSetter = new GetterSetter
    {
        Getter = (obj, data) => data.ValueStart,
        Setter = (obj, data, per) =>
        {
            if (double.IsNaN((double)data.ValueStart)) data.ValueStart = 0D;
            data.Data = EaseHelper.EaseValue((double)data.ValueStart, (double)data.ValueEnd, per);
        }
    };
    AnimationTypes.RegisterGetterSetter("empty", EmptyGetterSetter);
}
You only want to call this once.
 RegisterEmpty();
Then create a variable that will store your position as a percentage along the path
double posPercentage = 0;
Now create a function that will produce a point from a path
private Point PathPercentToPoint(double pointPercentage)
{
    // convert percentage to path point
    double x = pointPercentage * 100D; // <--- This should be a position from your Path
    double y = pointPercentage * 100D; // <--- This should be a position from your Path

    return new Point(x, y);
}
And then one which will call the ease, convert the percentage to a point, and apply the value on your target object
private void AnimatePath ()
{
    // target percentage
    double destinationPercentageAlongPath = _rnd.NextDouble();

    //speed
    double time = 1;

     // create animation
    EaseObject o = ArtefactAnimator.AddEase(tester, "empty", destinationPercentageAlongPath, time, AnimationTransitions.CubicEaseOut);

    // set the start value directly because our getter/setter doesn't do it for us
    o.Props["empty"].ValueStart = posPercentage; 

     // apply your position changes on the update
    o.OnUpdate((eo, p) =>
    {
        // store the info coming out
        posPercentage = (double)eo.Props["empty"].Data;

        // convert the percentage to an actually point
        Point position = PathPercentToPoint(posPercentage);

        // set the position of your animated object
        Canvas.SetLeft((UIElement) o.Target, position.X);
        Canvas.SetTop((UIElement) o.Target, position.Y);

        // Debug.WriteLine(o.Target + "  ease: " + p + " --> " + posPercentage + " --> " + position);
    }); 
}
Then call it!
AnimatePath();
Jun 30, 2013 at 1:48 PM
HI:

after I read your solution and idears,I found that two solution confuse me. maybe I did not describe the problem very clearly.

In fact ,I would like move one object with the position I defined before,

the complement method is as following:

targetname is the register name for the object which will be animation, markPosition is the point set .



public static Storyboard CreateAnimationFromMarkedPositon(string targetname,Point[] markPosition)
    {


       if (markPosition.Length < 1) return null;

        durationWithPath = 5000;

        var animationPath = new PathGeometry();
        var pFigure = new PathFigure {StartPoint = new Point(0, -300)};
        var pBezierSegment = new PolyLineSegment();
        foreach (var t in markPosition)
        {
            pBezierSegment.Points.Add(new Point(t.X, t.Y));
        }
        pFigure.Segments.Add(pBezierSegment);
        animationPath.Figures.Add(pFigure);

        // Freeze the PathGeometry for performance benefits.
        animationPath.Freeze();


        var animationX = new DoubleAnimationUsingPath
            {
                PathGeometry = animationPath,
                Source = PathAnimationSource.X,
                Duration = new Duration(TimeSpan.FromMilliseconds(durationWithPath))
            };


        var animationY = new DoubleAnimationUsingPath
            {
                PathGeometry = animationPath,
                Source = PathAnimationSource.Y,
                Duration = new Duration(TimeSpan.FromMilliseconds(durationWithPath))
            };


        Storyboard.SetTargetProperty(animationX, new PropertyPath("(Canvas.Left)"));
        Storyboard.SetTargetProperty(animationY, new PropertyPath("(Canvas.Top)"));
        Storyboard.SetTargetName(animationX, targetname);
        Storyboard.SetTargetName(animationY, targetname);

        var story = new Storyboard();
        story.Children.Add(animationX);
        story.Children.Add(animationY);

       return story;
    }

I would like to move the object with the position which defined by markPostion.

from your solution 2, it seems that it is not necessary for me to calculate the percent since the position is defined, the object must be moved with such position.

so now if i would like to translate these code with ArtefactAnimator.AddEase, how can I write the code>
Coordinator
Jun 30, 2013 at 6:46 PM
So, I guess it's been years since I've used Storyboards and I've never used DoubleAnimationUsingPath. To get us on the same track and have an example that makes sense to both of us, can you look at the example at http://msdn.microsoft.com/en-us/library/system.windows.media.animation.doubleanimationusingpath.aspx and tell me if that is similar to what you're trying to accomplish.

If it is, then I'll attempt to modify that example.
Coordinator
Jun 30, 2013 at 8:30 PM
Edited Jun 30, 2013 at 8:33 PM
Hope this helps:
// register empty ease getter/setter. 
TryCreateEmptyGetSet();

// animate
RunAnimation();

public void RunAnimation()
{
    // create new target for testing
    Rectangle aRectangle = new Rectangle
    {
        Width = 30, 
        Height = 30, 
        Fill = Brushes.Red,
        Name = "aRectangle"
    };

    // add to our window
    LayoutRoot.Children.Add(aRectangle);
             
    // target 
    UIElement target = aRectangle; // <- reference the object you want to move here

    // positions
    Point[] markPosition = CreatePoints(); // <-- supply your own points

    // start point
    Point startPt = new Point(0, 0); // <-- set your start Point

    // 5 seconds
    const double duration = 5000 / 1000;  // <-- length of animation
    
    // animate target
    EaseObject eo = CreateAnimationFromMarkedPositon(target, markPosition, startPt, duration);
}

public static EaseObject CreateAnimationFromMarkedPositon(UIElement target, Point[] markPosition, Point startPt, double duration)
{ 
    // Create Geometry from points
    var animationPath = CreatePolyLineGeometry (startPt, markPosition);
             
    EaseObject o = ArtefactAnimator.AddEase(target, "None", "doesn't matter what goes here", duration, AnimationTransitions.CubicEaseOut); 
    o.OnUpdate((eo, p) =>
    {
        // convert percentage to point
        Point position = PathPercentToPoint(animationPath, p);
               
        // set position on your animated object
        Canvas.SetLeft((UIElement) o.Target, position.X);
        Canvas.SetTop((UIElement) o.Target, position.Y); 
    });
    return o;
}

// CREATE AN EMPTY GET/SET
private static void TryCreateEmptyGetSet()
{
    if (!AnimationTypes.GetterSetterHash.ContainsKey("None"))
    {
        var emptyGetterSetter = new GetterSetter
        {
            Getter = (obj, data) => data.ValueStart,
            Setter = (obj, data, per) => { }
        };
        AnimationTypes.RegisterGetterSetter("None", emptyGetterSetter);
    }
}

// VALUES WE CAN TEST
private static Point[] CreatePoints()
{
    return new Point[]
    {
        new Point(35, 100),
        new Point(135, 0),
        new Point(160, 0),
        new Point(180,190),
        new Point(285,200),
        new Point(310, 100),
    };
}

// CREATE GEOMETRY
public static PathGeometry CreatePolyLineGeometry(Point startPt, IEnumerable<Point> positions)
{
    var animationPath = new PathGeometry();
    PathFigure figure = new PathFigure
    {
        StartPoint = startPt
    };

    PolyLineSegment segment = new PolyLineSegment(positions, false);

    figure.Segments.Add(segment);
    animationPath.Figures.Add(figure); 
    animationPath.Freeze();

    return animationPath;
}

// GET POINT ON PATH
public static Point PathPercentToPoint(PathGeometry animationPath, double pointPercentage)
{
    if (animationPath == null ) return new Point();

    Point pt;
    Point tangent;
    animationPath.GetPointAtFractionLength(pointPercentage, out pt, out tangent);

    return pt;
}