Jaa


Inspired By Flash Math Creativity: WPF Flowers

I've recently picked up a great book called Flash Math Creativity.  I been inspired by some of the techniques it outlines and have played around with similar ideas in WPF.  The exercise has been a fruitful one, in that I'm making some pleasing computer art while also learning about the differences between Action Script animation and WPF animation.  The first one I played with is flowers, inspired by the work of Glen Rhodes, which is actually featured on the cover of the book.  You can see the results here and download the code here.

The first flower, which has no animation, is pretty simple. I just create 125 rectangles with a fill using a DrawingBrush I created in Blend.  I place each of the petals on the "stage", which in the case is a Grid.  I use Grid instead of Canvas so that I get the goodness of the WPF layout engine for free without having to handle any positioning of the rectangles.  I then tranform the rotation and scale of each one to create the flower effect.  I also swap the ZIndex so that the large petals are in back and the smaller ones are in front.

void OnLoaded(object sender, EventArgs e)
{
    int r = 0;
    double s = 0;
    for (int i=0; i < 125; i++)
    {
        Rectangle rect = new Rectangle();
        rect.Width = 50;
        rect.Height = 50;
        rect.Fill = (DrawingBrush)this.FindResource("petal");
        TransformGroup tg = new TransformGroup();
        s += .01;
        ScaleTransform st = new ScaleTransform(s * s * s + .1, s * s * s + .1);
        r += 27;
        RotateTransform rt = new RotateTransform(r);
        tg.Children.Add(st);
        tg.Children.Add(rt);
        rect.RenderTransform = tg;
        Grid.SetZIndex(rect, -i);
        stage.Children.Add(rect);
    }
}

Where things get a little more interesting is when it comes to animating the flower. In Action Script, this kind of dynamic animation is handled through the very convenient onEnterFrame.  This handler provides a per-frame callback for each individual movie clip, in which a function can be wired up.  There is no equivalent in WPF such that you get a per-frame callback for each element.  The closest thing to this concept is the CompositionTarget.Rendering handler, which gets called per-frame for the entire tree (or page or stage, if you will.)

It is possible to find each element you are looking for when this event gets fired and achieve a similar effect to onEnterFrame.  I did just this in Page3.xaml, which ends up adding the following in addition to the loaded method:

void CompositionTarget_Rendering(object sender, EventArgs e)
{
    foreach (UIElement uie in stage.Children)
    {
        TransformGroup tg = uie.RenderTransform as TransformGroup;
        ScaleTransform st = tg.Children[0] as ScaleTransform;
        if (st.ScaleX > -3)
           st.ScaleX -= .1;
    }
}

 

You can see how I end up having to walk the tree, extracting the children and then acting on the children.  While this works, there are several disadvantages to this methodology.  First, having to figure out the animation for every UI element within a single callback has the potential to get quite unwieldy.  Second, using this method means we don't get all of the goodness of the WPF animation system, things like frame rate indepence, timelines, storyboards, repeat behaviors, autoreverse, etc.  So, let's see what this animation would look like using the WPF animation system:

void Page2_Loaded(object sender, RoutedEventArgs e)
{
    int r = 0;
    double s = 0;
    DoubleAnimation d = new DoubleAnimation(-3, new Duration(TimeSpan.FromSeconds(2)));
    d.AutoReverse = true;
    d.RepeatBehavior = RepeatBehavior.Forever;
    for (int i = 0; i < 125; i++)
    {
        Rectangle rect = new Rectangle();
        rect.Width = 50;
        rect.Height = 50;
        rect.Opacity = .5;
        rect.Fill = (DrawingBrush)this.FindResource("petal2");
        TransformGroup tg = new TransformGroup();
        s += .01;
        ScaleTransform st = new ScaleTransform(s * s * s + .1, s * s * s + .1);
        r += 27;
        RotateTransform rt = new RotateTransform(r);
        tg.Children.Add(st);
        tg.Children.Add(rt);
        rect.RenderTransform = tg;
        Grid.SetZIndex(rect, -i);
        st.BeginAnimation(ScaleTransform.ScaleXProperty, d);
        stage.Children.Add(rect);
    }
}

With this code, I get to wire up an animation to each element -- in fact I'm wiring up the same animation, which simply animates the scale.  I get to use some of the features of the animation system, like AutoReverse and RepeatBehaviors.  And, I'm letting WPF calculate the timing instead of me doing it per-frame.  Much nicer, methinks.

Those seasoned with the WPF samples can probably deduce where I derived the harness you toggle between the different animations. I took the basic idea from Kevin Moore's Bag-o-Tricks and then added the fading between frames from the custom frame in the WPF Feature Fest sample. 

I'm also now pointing all my WPF samples to an HTML page which checks to see if you have the framework installed and directs you to where you get it if you don't.  I took this from the Windows SDK and modified to to auto-redirect if .NET 3.0 is installed.  It is included in the source.

Comments

  • Anonymous
    February 16, 2007
    Useless but pretty:-) Could WPF handle fractals?

  • Anonymous
    February 17, 2007
    this looks pretty! But I'm wondering why you guys always favor CompositionTarget.Rendering over DispatcherTimer? cheers Florian

  • Anonymous
    February 19, 2007
    The comment has been removed

  • Anonymous
    February 19, 2007
    The comment has been removed

  • Anonymous
    February 21, 2007
    There's actually been some good work done on this front as far as making CompositionTarget more timing/framerate friendly. Have you seen this SDK sample? http://msdn2.microsoft.com/en-us/library/ms771738.aspx It adds a timer to CompositionTarget to insure consistent behavior no matter the cpu.

  • Anonymous
    February 21, 2007
    The comment has been removed

  • Anonymous
    February 22, 2007
    Kk -- I'm interested in your (and anyone else who wants to comment) take on experiences on the web that only run on Windows.  Would this be less offensive if it launched a Windows app instead of running inside the browser? Also, have you heard of WPF/e?  Runs on a Mac, is entirely text based (javascript + xml) meaning indexable and transparent.