///<summary>Causes a rotating rectangle that grows and fades in then shrinks and fades out to be shown.</summary>
 public static Lifetime AnimateSpinningRectangleExplosion(this Game game,
                                                          PerishableCollection<UIElement> controls,
                                                          Func<double, Point> position,
                                                          Color color,
                                                          TimeSpan duration,
                                                          double rotationsPerSecond,
                                                          double finalRadius) {
     var rect = new Rectangle {
         RenderTransformOrigin = new Point(0.5, 0.5)
     };
     var life = game.AnimateWith(
         duration,
         (step, portion, ellapsed) => {
             var nearHalfPortion = (portion - 0.5).Abs()*2;
             var radius = 0.LerpTo(finalRadius, 1 - nearHalfPortion);
             var pos = position(portion);
             rect.Width = rect.Height = radius*2;
             rect.RenderTransform = new TransformGroup {
                 Children = new TransformCollection {
                     new RotateTransform(rotationsPerSecond*ellapsed.TotalSeconds*360),
                     new TranslateTransform(pos.X - radius, pos.Y - radius)
                 }
             };
             rect.Fill = new SolidColorBrush(color.LerpToTransparent(nearHalfPortion));
         });
     controls.Add(rect, life);
     return life;
 }
        ///<summary>Handles making line controls for each connector in the game, as while as showing their death animations.</summary>
        public static void SetupDrawConnectorsAsControls(this Game game,
                                                         PerishableCollection<UIElement> controls,
                                                         TimeSpan deathFadeDuration,
                                                         double deathFinalThicknessFactor,
                                                         Color cutBangColor,
                                                         TimeSpan cutBangDuration,
                                                         double cutBangMaxRadius,
                                                         Color propagateBangColor,
                                                         TimeSpan propagateBangDuration,
                                                         double propagateBangRotationsPerSecond,
                                                         double propagateBangMaxRadius) {
            game.Connectors.CurrentAndFutureItems().Subscribe(
                e => {
                    // create a line control to visually represent the line
                    var con = e.Value;
                    var thickness = con.Child.Radius * 0.1;
                    var lineControl = new Line {
                        Stroke = new SolidColorBrush(Colors.Black),
                        StrokeThickness = thickness,
                    };

                    // reposition line control during each game loop, until the connector is dead
                    game.LoopActions.Add(
                        step => lineControl.Reposition(con.Line),
                        e.Lifetime);

                    // show a bang if the connector is cut
                    e.Lifetime.WhenDead(() => {
                        if (con.CutPoint == null || con.CutDir == null) return;
                        var cutLineControl = new Line {
                            Stroke = new SolidColorBrush(cutBangColor),
                            StrokeThickness = thickness,
                        };
                        var p = con.CutPoint.Value;
                        var d = con.CutDir.Value*cutBangMaxRadius;
                        var life = game.AnimateWith(
                            cutBangDuration,
                            (step, prop, elapsed) => {
                                var c = 1 - (prop - 0.5).Abs()*2;
                                cutLineControl.Reposition((p - d*c).To(p + d*c));
                                cutLineControl.StrokeThickness = prop*cutBangMaxRadius/2;
                                cutLineControl.Stroke = new SolidColorBrush(cutBangColor.LerpToTransparent(1 - c));
                            });
                        controls.Add(cutLineControl, life);
                    });

                    // show a bang travelling along the connector when it dies
                    e.Lifetime.WhenDead(() =>
                        game.AnimateSpinningRectangleExplosion(
                            controls,
                            p => (con.CutPoint == null ? con.Line.Start : con.CutPoint.Value.ClosestPointOn(con.Line)).To(e.Value.Line.End).LerpAcross(p),
                            propagateBangColor,
                            propagateBangDuration,
                            propagateBangRotationsPerSecond,
                            propagateBangMaxRadius));

                    // expand and fade out the line control after the connector dies
                    var controlLife = e.Lifetime.ThenResurrect(() => game.AnimateWith(
                        deathFadeDuration,
                        (step, portion, dt) => {
                            lineControl.StrokeThickness = thickness * 1.LerpTo(deathFinalThicknessFactor, portion);
                            lineControl.Stroke = new SolidColorBrush(Colors.Black.LerpToTransparent(portion));
                            //lineControl.Reposition(e.Value.Line);
                        }));

                    controls.Add(lineControl, controlLife);
                },
                game.Life);
        }
        ///<summary>Handles making ellipse controls for each ball in the game, as while as showing their death animations.</summary>
        public static void SetupDrawBallsAsControls(this Game game, PerishableCollection<UIElement> controls, TimeSpan deathFadeOutDuration, double deathFinalRadiusFactor) {
            game.Balls.CurrentAndFutureItems().Subscribe(
                e => {
                    // create an ellipse control to visually represent the ball
                    var ball = e.Value;
                    var color = ball.Hue.HueToApproximateColor(period: 3);
                    var ellipseControl = new Ellipse {
                        Width = ball.Radius * 2,
                        Height = ball.Radius * 2,
                        VerticalAlignment = VerticalAlignment.Top,
                        HorizontalAlignment = HorizontalAlignment.Left,
                        Fill = new SolidColorBrush(color)
                    };

                    // 'root' balls have a black border
                    if (ball.Generation == 1) {
                        ellipseControl.StrokeThickness = 3;
                        ellipseControl.Stroke = new SolidColorBrush(Colors.Black);
                    }

                    // reposition ellipse control during each game loop, until ball is dead
                    game.LoopActions.Add(
                        step => ellipseControl.Reposition(ball.Pos, ball.Radius),
                        e.Lifetime);

                    // once ball is dead, expand and fade out the ellipse
                    var controlLifetime = e.Lifetime.ThenResurrect(() =>
                        game.AnimateWith(
                            deathFadeOutDuration,
                            (step, portion, dt) => {
                                // fade out
                                ellipseControl.Fill = new SolidColorBrush(color.LerpToTransparent(portion));
                                // expand
                                var radius = ball.Radius * 1.LerpTo(deathFinalRadiusFactor, portion);
                                ellipseControl.Reposition(ball.Pos, radius);
                            }));

                    controls.Add(ellipseControl, controlLifetime);
                },
                game.Life);
        }