private static Animation MakeLinkAnimation(Ani<Rect> src, Ani<Rect?> dst)
        {
            var p1 = src.Select(e => new Point(e.Right, e.Top.LerpTo(e.Bottom, 0.5)));
            var p4 = src.Combine(dst, (s, d) => {
                var cy = d.GetValueOrDefault().Top.LerpTo(d.GetValueOrDefault().Bottom, 0.5);
                var sy = s.Top.LerpTo(s.Bottom, 0.5);

                return new Point(
                    d.GetValueOrDefault().Right,
                    cy + (sy-cy)/10);
            });
            var xmax = p1.Combine(p4, (e1, e2) => e1.X.Max(e2.X) + 10);

            var p2 = p1.Combine(xmax, (e, x) => new Point(x, e.Y));
            var p3 = p4.Combine(xmax, (e, x) => new Point(x, e.Y));
            var p3a = p4.Select(e => e + new Vector(2, 2));
            var p3b = p4.Select(e => e + new Vector(2, -2));
            var stroke = dst.Select(e => e.HasValue ? (Brush)Brushes.Black : Brushes.Transparent);
            return new Animation {
                new LineSegmentDesc(p1.Combine(p2, (e1, e2) => e1.To(e2)), stroke: stroke),
                new LineSegmentDesc(p2.Combine(p3, (e1, e2) => e1.To(e2)), stroke: stroke),
                new LineSegmentDesc(p3.Combine(p4, (e1, e2) => e1.To(e2)), stroke: stroke),
                new LineSegmentDesc(p3a.Combine(p4, (e1, e2) => e1.To(e2)), stroke: stroke),
                new LineSegmentDesc(p3b.Combine(p4, (e1, e2) => e1.To(e2)), stroke: stroke),
            };
        }
 public static Ani<Rect> SweepRightAndDown(this Ani<Point> point, Ani<Size> size)
 {
     return size.Combine(point, (s, p) => new Rect(p, s));
 }
 public static Animation ShowComplex(Ani<Brush> fill,
                                 Ani<Brush> valueStroke,
                                 Ani<Complex> value,
                                 Ani<Point> position,
                                 Ani<double> unitRadius,
                                 Ani<Brush> valueGuideStroke = null,
                                 Ani<double> phaseOffset = null,
                                 Ani<Turn> rotation = null,
                                 Ani<Brush> sweepFill = null,
                                 Ani<double> squish = null,
                                 Ani<double> sweepScale = null)
 {
     phaseOffset = phaseOffset ?? 0;
     rotation = rotation ?? Turn.Zero;
     sweepFill = sweepFill ?? fill;
     squish = squish ?? 1.0;
     sweepScale = sweepScale ?? 0.5;
     var phaseRadius = from v in value
                       from s in sweepScale
                       select s * Math.Max(0.05, v.Magnitude);
     var mag = value.Select(e => e.Magnitude*e.Magnitude);
     return new Animation {
         new PolygonDesc(
             phaseOffset.Combine(value, position, unitRadius, phaseRadius, (o, v, p, r, f) => PhaseCurve(v.Phase, f * r, o).Select(e => e + p).ToArray().AsEnumerable()),
             stroke: sweepFill.Select(e => e.LerpTo(Brushes.Black, 0.5, lerpAlpha: false)),
             fill: sweepFill.Select(e => e.LerpToTransparent(0.5)),
             strokeThickness: value.Select(e => Math.Min(Math.Abs(e.Phase)*10,Math.Min(e.Magnitude*3,1)))),
         new RectDesc(
             pos: position.Combine(unitRadius, (e, r) => new Rect(e.X - r, e.Y - r, 2*r, 2*r)),
             stroke: valueGuideStroke ?? valueStroke,
             strokeThickness: 0.5,
             dashed: 4,
             rotation: rotation,
             rotationOrigin: new Point(0.5, 0.5)),
         // squared magnitude filling
         new RectDesc(
             pos: position.Combine(unitRadius, mag, squish, (e, r, v, s) => new Rect(e.X - r, e.Y + (1 - 2 * v) * r, 2 * r * s, 2 * r * v)),
             fill: fill,
             rotation: rotation,
             rotationOrigin: mag.Combine(squish, (v,s) => new Point(s < 0.001 ? 0 : (1/s)/2, 1 - 0.5/v))),
         // current value arrow
         Arrow(position,
               value.Combine(phaseOffset, (e, p) => e*Complex.Exp(Complex.ImaginaryOne*p))
                    .Combine(unitRadius, (v, r) => r*new Vector(v.Real, -v.Imaginary)),
               stroke: valueStroke)
     };
 }
 private static Animation ShowComplexSum(Ani<Brush> fill1,
                                    Ani<Brush> fill2,
                                    Ani<Brush> valueStroke,
                                    IReadOnlyList<Ani<Complex>> values,
                                    Ani<Point> position,
                                    Ani<Point> target,
                                    Ani<Vector> positionDelta,
                                    Ani<double> unitRadius,
                                    Ani<double> time)
 {
     var sum = (Ani < Complex > )Complex.Zero;
     var animation = new Animation();
     foreach (var i in values.Count.Range()) {
         animation.Things.Add(
             ShowComplex(
                 fill1.Combine(time, (s, t) => s.LerpToTransparent(t.SmoothTransition(0, 2, 1, 1, 1))),
                 valueStroke.Combine(time, (s, t) => s.LerpToTransparent(t.SmoothTransition(0, 0.1, 0.1, 1, 1))),
                 values[i],
                 position.Combine(positionDelta,
                                  time,
                                  sum,
                                  unitRadius,
                                  target,
                                  (p, d, t, s, u, p2) => (p + d*i).LerpTo(p2 + new Vector(s.Real*u, s.Imaginary*-u), t.SmoothTransition(0, 0, 1, 1, 1))),
                 unitRadius,
                 valueStroke.Combine(time, (s, t) => s.LerpToTransparent(t.SmoothTransition(0, 1, 1, 1, 1))),
                 sweepFill: fill1.Combine(time, (s, t) => s.LerpToTransparent(t.SmoothTransition(0, 0, 1, 1, 1)))),
             Lifetime.Immortal);
         sum = sum.Combine(values[i], (s, v) => s + v);
     }
     animation.Things.Add(
         ShowComplex(
             fill2.Combine(time, (s, t) => s.LerpToTransparent(t.SmoothTransition(1, 1, 1, 1,0, 0))),
             valueStroke.Combine(time, (s, t) => s.LerpToTransparent(t.SmoothTransition(1, 1, 1,0, 0, 0))),
             sum,
             target,
             unitRadius,
             valueStroke.Combine(time, (s, t) => s.LerpToTransparent(t.SmoothTransition(1, 1, 1,0, 0, 0))),
             sweepFill: fill2.Combine(time, (s, t) => s.LerpToTransparent(t.SmoothTransition(1, 1, 1, 0, 0)))),
         Lifetime.Immortal);
     return animation;
 }
        private static Animation ShowComplexProduct(Ani<Brush> fill1,
                                                    Ani<Brush> fill2,
                                                    Ani<Brush> valueStroke1,
                                                    Ani<Brush> valueStroke2,
                                                    Ani<Complex> value1,
                                                    Ani<Complex> value2,
                                                    Ani<Point> position,
                                                    Ani<double> unitRadius,
                                                    Ani<double> time,
                                                    Ani<Brush> fill3 = null)
        {
            if (fill1 == null) throw new ArgumentNullException("fill1");
            if (fill2 == null) throw new ArgumentNullException("fill2");
            if (valueStroke1 == null) throw new ArgumentNullException("valueStroke1");
            if (valueStroke2 == null) throw new ArgumentNullException("valueStroke2");
            if (value1 == null) throw new ArgumentNullException("value1");
            if (value2 == null) throw new ArgumentNullException("value2");
            if (position == null) throw new ArgumentNullException("position");
            if (unitRadius == null) throw new ArgumentNullException("unitRadius");
            if (time == null) throw new ArgumentNullException("time");
            fill3 = fill3 ?? fill1;
            return new Animation {
                // vertical input
                ShowComplex(fill1.Combine(time, (s, t) => s.LerpToTransparent(t.LerpTransition(0, 1, 1, 1, 1))),
                            valueStroke1,
                            time.Combine(value1,
                                         value2,
                                         (t, v1, v2) => Complex.FromPolarCoordinates(
                                             v1.Magnitude.LerpTo(v1.Magnitude*v2.Magnitude, t.SmoothTransition(0, 0, 1, 1, 1)),
                                             v1.Phase)),
                            position,
                            unitRadius,
                            rotation: Turn.FromNaturalAngle(Math.PI/2),
                            phaseOffset: time.Combine(value2, (t, v) => t.SmoothTransition(0, 0, 0, 1, 1)*v.Phase.ProperMod(Math.PI*2)),
                            sweepFill: fill1.Combine(time, (f, t) => f.LerpToTransparent(t.SmoothTransition(0, 0, 0, 0, 1))),
                            valueGuideStroke: Brushes.Transparent),

                // horizontal input
                ShowComplex(fill2.Combine(time,
                                          (s, t) => s.LerpToTransparent(t.LerpTransition(0, 1, 1, 1, 1))),
                            valueStroke2.Combine(time, (s, t) => s.LerpToTransparent(t.LerpTransition(0, 0, 0, 0, 1))),
                            time.Combine(value1,
                                         value2,
                                         (t, v1, v2) => Complex.FromPolarCoordinates(
                                             v2.Magnitude.LerpTo(v1.Magnitude*v2.Magnitude, t.SmoothTransition(0, 0, 1, 1, 1)),
                                             v2.Phase)),
                            position,
                            unitRadius,
                            sweepFill: fill2.Combine(time, (s, t) => s.LerpToTransparent(t.LerpTransition(0, 0, 0, 0, 1))),
                            valueGuideStroke: Brushes.Transparent),

                // result
                ShowComplex(fill3.Combine(time,
                                          (s, t) => s.LerpToTransparent(t.LerpTransition(1, 0, 0, 0, 0))),
                            Brushes.Transparent,
                            time.Combine(value1,
                                         value2,
                                         (t, v1, v2) => Complex.FromPolarCoordinates(
                                             v1.Magnitude < 0.001 ? 0 : v2.Magnitude.LerpTo(v1.Magnitude*v2.Magnitude, t.SmoothTransition(0, 0, 1, 1, 1)),
                                             v2.Phase + v1.Phase)),
                            position,
                            unitRadius,
                            valueGuideStroke: valueStroke1,
                            sweepFill: fill3.Combine(time, (f, t) => f.LerpToTransparent(t.SmoothTransition(1, 1, 1, 1, 0))),
                            squish: time.Combine(value1, (t, v) => Math.Pow(t.SmoothTransition(v.Magnitude, v.Magnitude, 1, 1, 1), 2)))
            };
        }
 private static Animation Arrow(Ani<Point> start,
                                Ani<Vector> delta,
                                Ani<Brush> stroke = null,
                                Ani<double> thickness = null,
                                Ani<double> wedgeLength = null,
                                Ani<double> dashed = null)
 {
     wedgeLength = wedgeLength ?? 5;
     thickness = thickness ?? 1;
     return new Animation {
         new PointDesc(start, fill: stroke, radius: thickness.Select(e => e*0.5)),
         new LineSegmentDesc(
             pos: start.Combine(delta, (p, d) => new LineSegment(p, d)),
             stroke: stroke,
             thickness: thickness,
             dashed: dashed),
         new[] {+1, -1}.Select(
             s => new LineSegmentDesc(
                      pos: start.Combine(
                          delta,
                          wedgeLength,
                          (p, d, w) =>
                          new LineSegment(p + d, (s*d.Perp().Normal() - d.Normal()).Normal()*Math.Min(w, d.Length/2))),
                      stroke: stroke,
                      thickness: thickness,
                      dashed: dashed))
     };
 }