How to animate right triangle in a circle

Emon Haque 3,176 Reputation points
2021-04-04T03:04:48.22+00:00

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 Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,824 questions
{count} votes

Accepted answer
  1. Emon Haque 3,176 Reputation points
    2021-04-04T15:17:49.257+00:00

    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.

    0 comments No comments

0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.