question

EmonHaque-1485 avatar image
0 Votes"
EmonHaque-1485 asked EmonHaque-1485 edited

How to animate right triangle in a circle

I've 5 Path: circlePath, ballpath, radiusPath, sinePath, cosinePath. First one doesn't have any animation and the rest 4 have 5 animations. Right now, it looks like this:

84249-test1.gif

I want those three LineSegment to make right triangle always. cosinePath has one PointAnimationUsingKeyFrames for EndPoint and sinePath has two, one for StartPoint and the other for EndPoint. ballpath moves with a MatrixAnimationUsingPath and the radiusPath with DoubleAnimation. Here's the code for the custom control:

 class Trig : FrameworkElement   
 {
     VisualCollection children;
     Path circlePath, ballpath, radiusPath, sinePath, cosinePath;
     EllipseGeometry circle, ball;
     LineGeometry cosine, sine;
     double cx, cy, radius, padding;

     public Trig() {     
         padding = 20;
         circle = new EllipseGeometry();
         sine = new LineGeometry();
         cosine = new LineGeometry();
         ball = new EllipseGeometry() { Center = new Point(0, 0), RadiusX = 5, RadiusY = 5 };

         circlePath = new Path() { Stroke = Brushes.LightBlue, StrokeThickness = 3, Data = circle };
         sinePath = new Path() { Stroke = Brushes.Blue, StrokeThickness = 1, Data = sine };
         cosinePath = new Path() { Stroke = Brushes.Red, StrokeThickness = 1, Data = cosine };
         ballpath = new Path() { Fill = Brushes.LightCoral, Data = ball, RenderTransform = new MatrixTransform() };
         radiusPath = new Path() {
             Stroke = Brushes.Black,
             StrokeThickness = 0.5,
             StrokeDashCap = PenLineCap.Round,
             StrokeDashArray = new DoubleCollection(new double[] { 5,5}),
             Data = new LineGeometry()
         };
         children = new VisualCollection(this) { circlePath, sinePath, cosinePath, ballpath, radiusPath };
         Loaded += animate;
     }

     void animate(object sender, RoutedEventArgs e) {
         radiusPath.RenderTransform = new RotateTransform(0, cx, cy);
         var ballAnim = new MatrixAnimationUsingPath() {
             Duration = TimeSpan.FromSeconds(10),
             RepeatBehavior = RepeatBehavior.Forever,
             PathGeometry = PathGeometry.CreateFromGeometry(circle)
         };
         var radiusAnim = new DoubleAnimation() {
             To = 360,
             Duration = TimeSpan.FromSeconds(10),
             RepeatBehavior = RepeatBehavior.Forever
         };
         var cosineAnim = new PointAnimationUsingKeyFrames() {
             KeyFrames = {
                 new LinearPointKeyFrame(new Point(cx + radius, cy), TimeSpan.FromSeconds(0)),
                 new LinearPointKeyFrame(new Point(cx, cy), TimeSpan.FromSeconds(2.5)),
                 new LinearPointKeyFrame(new Point(cx - radius, cy), TimeSpan.FromSeconds(5)),
             },
             RepeatBehavior = RepeatBehavior.Forever,
             AutoReverse = true
         };
         var sineStartAnim = new PointAnimationUsingKeyFrames() {
             KeyFrames = {
                 new LinearPointKeyFrame(new Point(cx + radius, cy), TimeSpan.FromSeconds(0)),
                 new LinearPointKeyFrame(new Point(cx, cy), TimeSpan.FromSeconds(2.5)),
                 new LinearPointKeyFrame(new Point(cx - radius, cy), TimeSpan.FromSeconds(5)),
             },
             RepeatBehavior = RepeatBehavior.Forever,
             AutoReverse = true
         };
         var sineEndAnim = new PointAnimationUsingKeyFrames() {
             KeyFrames = {
                 new LinearPointKeyFrame(new Point(cx + radius, cy), TimeSpan.FromSeconds(0)),
                 new LinearPointKeyFrame(new Point(cx, cy + radius), TimeSpan.FromSeconds(2.5)),
                 new LinearPointKeyFrame(new Point(cx - radius, cy), TimeSpan.FromSeconds(5)),
                 new LinearPointKeyFrame(new Point(cx, cy - radius), TimeSpan.FromSeconds(7.5)),
                 new LinearPointKeyFrame(new Point(cx + radius, cy), TimeSpan.FromSeconds(10))
             },
             RepeatBehavior = RepeatBehavior.Forever
         };
         radiusPath.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, radiusAnim);
         ballpath.RenderTransform.BeginAnimation(MatrixTransform.MatrixProperty, ballAnim);
         cosine.BeginAnimation(LineGeometry.EndPointProperty, cosineAnim);
         sine.BeginAnimation(LineGeometry.EndPointProperty, sineEndAnim);
         sine.BeginAnimation(LineGeometry.StartPointProperty, sineStartAnim);
     }

     protected override Size MeasureOverride(Size availableSize) {
         cx = availableSize.Width / 2;
         cy = availableSize.Height / 2;
         circle.Center = new Point(cx, cy);
         radius = Math.Min((availableSize.Width - 2 * padding) / 2, (availableSize.Height - 2 * padding) / 2);
         circle.RadiusX = circle.RadiusY = radius;
         ((LineGeometry)radiusPath.Data).StartPoint = new Point(cx, cy);
         ((LineGeometry)radiusPath.Data).EndPoint = new Point(cx + radius, cy);
         cosine.StartPoint = new Point(cx, cy);
         cosine.EndPoint = new Point(cx + radius, cy);
         sine.StartPoint = sine.EndPoint = new Point(cx + radius, cy);
         foreach (UIElement child in children) 
             child.Measure(availableSize);
         return availableSize;
     }
     protected override Size ArrangeOverride(Size finalSize) {
         foreach (UIElement child in children)
             child.Arrange(new Rect(child.DesiredSize));
         return finalSize;
     }
     protected override Visual GetVisualChild(int index) => children[index];
     protected override int VisualChildrenCount => children.Count;
 }

and in MainWindow.xaml, I've used that like this:

 <Grid Margin="20">
     <cc:Trig />
 </Grid>

Is there any easier way to do this type of animations?


EDIT

If I replace sineEndAnim with this

 var sineEndAnim = new PointAnimationUsingPath() {
     PathGeometry = PathGeometry.CreateFromGeometry(circle),
     Duration = TimeSpan.FromSeconds(10),
     RepeatBehavior = RepeatBehavior.Forever
 };

the StartPoint move along the ball. Now the EndPoint of cosine and StarPoint of sine is the problem!

84273-test1.gif

windows-wpf
test1.gif (381.5 KiB)
test1.gif (866.8 KiB)
· 2
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.


Maybe you can use a single animation for ball only. For lines use the Line class instead of LineGeometry and the binding features.

1 Vote 1 ·

Exactly, I've posted the solution with Line. For the sweeping angle, I still have to use two animations one for IsLargeArcProperty and the other for PointProperty. Can those be eliminated somehow?

0 Votes 0 ·

1 Answer

EmonHaque-1485 avatar image
0 Votes"
EmonHaque-1485 answered

With Line:

 class AnotherTrig : FrameworkElement
 {
     VisualCollection children;
     EllipseGeometry circle, ball;
     ArcSegment angle;
     Path circlePath, ballpath, anglePath;
     Line sine, cosine, hypoteneous;
     double cx, cy, radius, padding;

     public AnotherTrig() {
         padding = 20;
         circle = new EllipseGeometry();
         angle = new ArcSegment() { IsStroked = true, SweepDirection = SweepDirection.Clockwise, Size = new Size(40, 40), IsLargeArc = true };
         ball = new EllipseGeometry() { Center = new Point(0, 0), RadiusX = 5, RadiusY = 5 };
         circlePath = new Path() { Stroke = Brushes.LightBlue, StrokeThickness = 3, Data = circle };
         ballpath = new Path() { Fill = Brushes.LightCoral, Data = ball, RenderTransform = new MatrixTransform() }; 
         anglePath = new Path() {
             Fill = new SolidColorBrush(Color.FromArgb(50, 0, 0, 100)),
             Stroke = Brushes.Black,
             StrokeThickness = 1,
             Data = new PathGeometry() {
                 Figures = {
                     new PathFigure() {
                         Segments = {
                             new LineSegment(){ IsStroked = false},
                             angle
                         }
                     }
                 }
             }
         };
         sine = new Line() { Stroke = Brushes.Blue, StrokeThickness = 1 };
         cosine = new Line() { Stroke = Brushes.Red, StrokeThickness = 1 };
         hypoteneous = new Line() {
             Stroke = Brushes.Black,
             StrokeThickness = 0.5,
             StrokeDashCap = PenLineCap.Round,
             StrokeDashArray = new DoubleCollection(new double[] { 5, 5 }),
         };
         children = new VisualCollection(this) { circlePath, sine, cosine, ballpath, hypoteneous, anglePath };
         ballpath.RenderTransform.Changed += angleChanged;
         Loaded += animate;
     }

     void angleChanged(object sender, EventArgs e) {
         var mat = (sender as MatrixTransform).Matrix;
         hypoteneous.X2 = cosine.X2 = sine.X1 = sine.X2 = mat.OffsetX;
         hypoteneous.Y2 = sine.Y2 = mat.OffsetY;
     }

     void animate(object s, EventArgs e) {       
         var circleGeo = PathGeometry.CreateFromGeometry(circle);
         var angleGeo = new PathGeometry() {
             Figures = {
                 new PathFigure() {
                     StartPoint = new Point(cx + 40, cy),
                     Segments ={(anglePath.Data as PathGeometry).Figures[0].Segments[1].Clone()}
                 }
             }
         };
         circleGeo.Freeze();
         angleGeo.Freeze();

         var ballAnim = new MatrixAnimationUsingPath() {
             Duration = TimeSpan.FromSeconds(10),
             RepeatBehavior = RepeatBehavior.Forever,
             PathGeometry = circleGeo
         };
         var angleAnim = new PointAnimationUsingPath() {
             PathGeometry = angleGeo,
             RepeatBehavior = RepeatBehavior.Forever,
             Duration = TimeSpan.FromSeconds(10)
         };
         var angleIsLargeAnim = new BooleanAnimationUsingKeyFrames() {
             KeyFrames = {
                 new DiscreteBooleanKeyFrame(false, TimeSpan.FromSeconds(0)),
                 new DiscreteBooleanKeyFrame(true, TimeSpan.FromSeconds(5)),
                 new DiscreteBooleanKeyFrame(false, TimeSpan.FromSeconds(10))
             },
             RepeatBehavior = RepeatBehavior.Forever
         };
         ballpath.RenderTransform.BeginAnimation(MatrixTransform.MatrixProperty, ballAnim);
         angle.BeginAnimation(ArcSegment.IsLargeArcProperty, angleIsLargeAnim);
         angle.BeginAnimation(ArcSegment.PointProperty, angleAnim);
     }

     protected override Size MeasureOverride(Size availableSize) {
         cx = availableSize.Width / 2;
         cy = availableSize.Height / 2;
         radius = Math.Min((availableSize.Width - 2 * padding) / 2, (availableSize.Height - 2 * padding) / 2);
         circle.RadiusX = circle.RadiusY = radius;
         circle.Center = ((PathGeometry)anglePath.Data).Figures[0].StartPoint = new Point(cx, cy);
         hypoteneous.X1 = cosine.X1 = cx;
         hypoteneous.Y1  = cosine.Y1 = cosine.Y2 = sine.Y1 = cy;
         (((PathGeometry)anglePath.Data).Figures[0].Segments[0] as LineSegment).Point = new Point(cx + 40, cy);
         angle.Point = new Point(cx + 40, cy - 0.1);

         foreach (UIElement child in children)
             child.Measure(availableSize);
         return availableSize;
     }
     protected override Size ArrangeOverride(Size finalSize) {
         foreach (UIElement child in children)
             child.Arrange(new Rect(child.DesiredSize));
         return finalSize;
     }
     protected override Visual GetVisualChild(int index) => children[index];
     protected override int VisualChildrenCount => children.Count;
 }

It's perfect:

84317-test1.gif

All that's left is handle resize.


test1.gif (810.9 KiB)
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.