Click here to Skip to main content
Click here to Skip to main content

Particle Effects in WPF

, 24 Oct 2007 CPOL
Rate this:
Please Sign up or sign in to vote.
An article on the practical use of particle effects in WPF.
Screenshot - WPFParticleEffects1.jpg

Table of Contents

Introduction

If you have ever played a video game and seen an explosion or smoke trail of some kind, it was most likely created using particle effects. Since I started working with WPF I have often wondered about the many effects that can be created through styling, bitmap effects, storyboards, etc. I decided to use some of these tools in order to implement a basic particle system using WPF.

The Design

Particle System Background

There is a lot of literature on the net about particle systems, so I will not go into too much detail about them. However, there are some basic elements that everyone should be familiar with. A particle system, in an object oriented language, can simply be described as a class which creates objects called particles and manages their position over time.

Particles

In my particle system I created a particle class which defines a position, mass, velocity, force and lifespan. The Particle class inherits from System.Windows.Controls.Control so it also contains graphical properties such as Opacity and Background.

I wanted the particle system to contain multiple spawn points for particles. So I created the Emitter class to manage the creation and initial values of particles. An Emitter is created by the ParticleSystem class and contains a number of min/max dependency properties for most of the properties found in a Particle. These min/max dependency properties define a range of possible values (i.e. MinHorizontalVelocity and MaxHorizontalVelocity define the range of a particle's velocity in the x-direction).

The Emitter class inherits from System.Windows.Controls.Control and provides an abstract base for creating emitters in the system. The class contains three overridable methods which govern the creation of Particles: GenerateParticles, AddParticles and UpdateParticles. The method GenerateParticles is used to create a number of particles up to MaxParticles (Note: The property MinParticles does exist but currently has no function). Each Particle created is added to the ParticleSystem through an ObservableCollection named Particles. AddParticles is reserved for any additional processing the developer may need while generating particles for the first time. Finally UpdateParticles is used to initialize and reinitialize (occurs when a Particle exceeds its lifespan and is reused) the particles.

Particle Update

Once an Emitter generates particles it is up to the ParticleSystem to update their Position and Velocity. This is handled via an UpdateRender event declared in the ParticleSystem. The event fires every rendering frame and is the ideal place to update the particles.

// render every frame
CompositionTarget.Rendering += UpdateRender;

...
foreach (Particle particle in Particles)
{
    // If the particle is alive
    if (particle.IsAlive)
    {
        // calculate the forces acting on the particle at the current time
        particle.Force = ComputeForces(particle, time);

        // Update the velocity vectors for this particle based on the time
        Vector v = particle.Velocity;
        double vx = v.X + time * (particle.Force.X / particle.Mass);
        double vy = v.Y + time * (particle.Force.Y / particle.Mass);
        particle.Velocity = new Vector(vx, vy);

        // If the particle is not an anchor update its position 
        // based on the time
        if (!particle.IsAnchor)
        {
            double px = particle.Position.X + time * particle.Velocity.X;
            double py = particle.Position.Y + time * particle.Velocity.Y;
            particle.Position = new Point(px, py);
        }
        particle.LifeSpan -= time; // update the particles lifespan
    }
}
...

If a Particle has exceeded its lifespan and has not been reused then the particle's IsAlive property is set to false. During each rendering update if the particle's IsAlive property is true then the particle is updated in the following order: Force, Velocity and Position (as shown above). All of the forces exerted on a particle at a given time are computed and added to the particle's Force property. Once that is finished the velocity at a given time is computed and used to update the Position of the particle. Whenever the particle's Position is updated, the Top and Left Canvas properties for that particle are also updated. This causes the graphical aspect of the particle to move.

// Particle.cs
...
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
    base.OnPropertyChanged(e);

    // if the Position, Width or Height have changed then 
    // update the position of the particle on the canvas
    if (e.Property.Name.Equals("Position") || 
        e.Property.Name.Equals("Width") || 
        e.Property.Name.Equals("Height"))
    {
        Canvas.SetLeft(this, Position.X - this.Width / 2d);
        Canvas.SetTop(this, Position.Y - this.Height / 2d);
    }
    ...

Since a particle inherits from System.Windows.Controls.Control it has a Width and a Height property. In order to position the particle at its center these properties need to be taken into account. The Particle class also has an IsAnchor property. This property is used to denote a particle whose position should not change over time. The render update checks this property and skips the position update accordingly. Since the position is never updated when a particle is anchored, the Canvas Top and Left properties are never initialized. To avoid having these properties in an uninitialized state, the Canvas Top and Left properties are updated when the Width or Height of a particle changes.

Forces

The Force class was created as an abstract base class to create different kinds of force effects throughout the system. The class exposes a single method named ApplyForce which is used by the ParticleSystem to update a particle's Force property. The ParticleSystem class contains a collection of Force objects and some internal forces as well. Each Force object is iterated over and the ApplyForce method is called which returns a Vector. The resulting Vector along with the internal force vectors are added up and used to set the Particle object's Force property.

The two internal forces found in the ParticleSystem class are Gravity and Drag. Gravity and Drag are implemented as DependencyProperty objects. Both of these forces are applied to each Particle object found in the system's Particles collection. Gravity is simply multiplied by the Particle object's Mass property. The result is then added to the Particle object's Force property. Then the Drag is multiplied by the Velocity and subtracted from the Force property.

// set the gravity and drag forces for a particle.
double forceX = (particle.Mass * Gravity.X) - (particle.Velocity.X * Drag.X);
double forceY = (particle.Mass * Gravity.Y) - (particle.Velocity.Y * Drag.Y);
...
return new Vector(forceX, forceY);

Another force commonly found in particle systems is a Spring. A Spring can be attached to multiple Particle objects and provides a force between those two objects. Instead of adding the Spring object to the Forces collection found in ParticleSystem I created a separate collection in each Particle. The Spring collection is named Connections and is responsible for maintaining the number of connections made between particles. The Force for each connection is calculated during the particle system's render update.

// the change in position between the particle and the particle 
// it is connected to.
double deltaX = particle.Position.X - connectedParticle.Position.X;
double deltaY = particle.Position.Y - connectedParticle.Position.Y;

// the change in velocity between the particle and the particle 
// it is connected to.
double deltaVX = particle.Velocity.X - connectedParticle.Velocity.X;
double deltaVY = particle.Velocity.Y - connectedParticle.Velocity.Y;

// the force vector applied to the particle
double fxParticle = -(ks * (Math.Abs(deltaX) - r) + kd * 
    ((deltaVX * deltaX) / Math.Abs(deltaX))) * (deltaX / Math.Abs(deltaX));
double fyParticle = -(ks * (Math.Abs(deltaY) - r) + kd * 
    ((deltaVY * deltaY) / Math.Abs(deltaY))) * (deltaY / Math.Abs(deltaY));

// the force vector applied to the particle that is connected
double fxConnectedParticle = -fxParticle;
double fyConnectedParticle = -fyParticle;

The Spring force can be used to create a mass-spring system. This allows for the creation of a new type of object which I call a Meatball. A Meatball is a group of three Particle objects which are each connected to one another via a Spring. The following picture shows a representation of a Meatball.

Meatball.jpg

As any Particle in the Meatball moves away from another Particle, the Spring between those two particles causes force to be applied, moving the other Particle. This ensures that all three Particle objects will move with one another and the Meatball will remain intact. The effect is a clump of Particle objects that appear to be stuck together. This allows for effects such as liquids and gels (anything that might flow instead of scatter).

Emitters

The system contains classes for three types of emitters: PointEmitter, LineEmitter and MeatballEmitter. Like all emitters the PointEmitter inherits from the Emitter base class. A PointEmitter is positioned in a Canvas based on its X and Y dependency properties. Every particle which spawns from the PointEmitter starts at that position. Additionally, the Emitter class contains a DependencyProperty named MinPositionOffset and another named MaxPositionOffset which provide an offset range for the particle's Position. The LineEmitter is very similar to the PointEmitter except instead of spawning particles at a specific X, Y position, it spawns particles along a line defined by the dependency properties: X1, Y1, X2 and Y2. Unlike the previous two emitters the MeatballEmitter does not explicitly generate particles. Instead it makes use of the Meatball class to generate a group of particles forming a mass-spring system. Since a Meatball contains three Particle objects the MeatballEmitter only generates MaxParticle divided by three Meatball objects.

Style

Since the particle system was written using C# and WPF, I took advantage of XAML as much as I could. The particle system class was created as an ItemsControl and given its own look and feel via the generic.xaml file. I used a Canvas to position the particles and an ItemsControl to contain them. A snippet from the generic.xaml file can be seen below:

<Style TargetType="{x:Type local:Engine.Controls.ParticleSystem}">
<Setter Property="Template">
  <Setter.Value>
    <ControlTemplate TargetType=
        "{x:Type local:Engine.Controls.ParticleSystem}">
      <Border Background="{TemplateBinding Background}"
              BorderBrush="{TemplateBinding BorderBrush}"
              BorderThickness="{TemplateBinding BorderThickness}">
        <StackPanel Orientation ="Vertical" >
          <!--<span class="code-comment"> Used to display FrameRate --></span>
          <!--<span class="code-comment">TextBlock Text="{TemplateBinding FrameRate}" 
            Foreground="White" Height="24" /--></span>
          <Grid>
          <!--<span class="code-comment"> ItemsControl for displaying Forces --></span>
              <!--<span class="code-comment">ItemsControl Background="Transparent" 
                    ItemsSource="{TemplateBinding Forces}"
                        ItemsPanel="{StaticResource ItemsCanvasTemplate}" /--></span>
              <!--<span class="code-comment"> ItemsControl for displaying Emitter --></span>
              <!--<span class="code-comment">ItemsControl Background="Transparent" 
                    ItemsSource="{TemplateBinding Emitters}"
                        ItemsPanel="{StaticResource ItemsCanvasTemplate}" /--></span>
              <!--<span class="code-comment"> ItemsControl for displaying Particles --></span>
              <ItemsControl Background="Transparent" 
                ItemsSource="{TemplateBinding Particles}"
                        ItemsPanel="{StaticResource ItemsCanvasTemplate}" />
              <!--<span class="code-comment"> Canvas for additional UIElements contained in a 
                    particle system --></span>
              <Canvas x:Name="ParticleCanvas" Background="Transparent" 
                    IsItemsHost="True"  />
          </Grid>
        </StackPanel>
      </Border>
    </ControlTemplate>
  </Setter.Value>
</Setter>
</Style>

A ParticleSystem can accept multiple UIElement objects as children. The Canvas found in the style shown above acts as the host for these children.

Like the ParticleSystem class, the Particle class also has its own style. Every Particle in the system is displayed using the following style.

<Style TargetType="{x:Type local:Engine.Controls.Particle}">
<Setter Property="Template">
  <Setter.Value>
    <ControlTemplate TargetType="{x:Type local:Engine.Controls.Particle}">
      <Grid>
        <Ellipse Fill="{TemplateBinding Background}" 
            Stroke="{TemplateBinding BorderBrush}"
                 StrokeThickness="{TemplateBinding BorderThickness}" />
      </Grid>
    </ControlTemplate>
  </Setter.Value>
</Setter>
</Style>

The particles found in this system are drawn as ellipses with a Width and Height set by the Emitter that generates them. The Fill, Stroke and StrokeThickness are each supplied by the Particle properties Background, BorderBrush and BorderThickness, respectively. A SolidColorBrush is used to set the Background property. The Brush in conjunction with a DependencyProperty named ColorKeyFrames (found in the Emitter base class) facilitates a change in Fill color over time. The ColorKeyFrames property is of type ColorKeyFrameCollection which is simply enough a collection that can be populated with ColorKeyFrame objects. By setting the Background property to a SolidColorBrush and adding the ColorKeyFrame property to a Storyboard Timeline the Background color can be changed over time. Since the Particle has a finite lifespan it makes sense to use LifeSpan as the total length, in seconds, for the Storyboard. Another Timeline, managing the particle's Opacity property, was created and added to the Storyboard. This controls the particle's transparency over its LifeSpan.

// init the storyboard
mStoryboard = new Storyboard();
// in order to preserve zero opacity the storyboard should hold at end.
mStoryboard.FillBehavior = FillBehavior.HoldEnd;

// create the parallel timeline which contains the color and opacity animations
ParallelTimeline pt = new ParallelTimeline(TimeSpan.FromSeconds(0));

// the opacity animation through the particles lifespan
DoubleAnimation daOpacity = new DoubleAnimation(StartOpacity, EndOpacity,
    new Duration(TimeSpan.FromSeconds(this.LifeSpan)));
Storyboard.SetTargetName(daOpacity, this.Name);
Storyboard.SetTargetProperty
    (daOpacity, new PropertyPath(Particle.OpacityProperty));

// the color animation through the particles lifespan
ColorAnimationUsingKeyFrames daBackground = new ColorAnimationUsingKeyFrames();
daBackground.Duration = new Duration(TimeSpan.FromSeconds(this.LifeSpan));
// set by the emitter LinearColorKeyFrames property
daBackground.KeyFrames = BackgroundColors; 
// a unique name is required for each brush
Storyboard.SetTargetName(daBackground, String.Format("{0}Brush", this.Name)); 
Storyboard.SetTargetProperty
    (daBackground, new PropertyPath(SolidColorBrush.ColorProperty));

// add the animations to the timeline
pt.Children.Add(daOpacity);
pt.Children.Add(daBackground);

// add the timeline to the storyboard
mStoryboard.Children.Add(pt);

Using the Code

The classes being used in the XAML file can be found in the PlayGround.Engine.Controls, PlayGround.Engine.Emitters, and PlayGround.Engine.Forces namespaces. So it is important that a reference to these namespaces be included in SandBox.xaml.

<Window x:Class="PlayGround.SandBox"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Engine="clr-namespace:PlayGround.Engine.Controls"
    xmlns:Emitter="clr-namespace:PlayGround.Engine.Emitters"
    xmlns:Force="clr-namespace:PlayGround.Engine.Forces"
    Title="SandBox" Height="300" Width="300"
    >
    <Grid Background="Transparent" >
    </Grid>
</Window>

In the code above you can see that I also added a Grid to contain the ParticleSystem. In between the Grid tags is where I added a ParticleSystem. Each ParticleSystem contains a collection of Emitter objects named Emitters and a collection of Force objects named Forces. A ParticleSystem with a PointEmitter can be added as follows.

<Grid>
    <Engine:ParticleSystem Background="Transparent" x:Name="MyParticleSystem" >
        <Engine:ParticleSystem.Emitters>
            <Emitter:PointEmitter X="135" Y="25" MaxParticles="140"
                         MinHorizontalVelocity="-1.0" 
                         MaxHorizontalVelocity="1.0"
                         MinVerticalVelocity="-13.0" MaxVerticalVelocity="0.0"
                         MinLifeSpan="1" MaxLifeSpan="4" >
                <Emitter:PointEmitter.LinearColorKeyFrames>
                    <ColorKeyFrameCollection>
                        <LinearColorKeyFrame Value="Red" KeyTime="0%" />
                    </ColorKeyFrameCollection>
                </Emitter:PointEmitter.LinearColorKeyFrames>
            </Emitter:PointEmitter>
        </Engine:ParticleSystem.Emitters>
    </Engine:ParticleSystem>
</Grid>

Each Emitter has a MaxParticles property which determines how many particles should be created. The MaxHorizontalVelocity and MinHorizontalVelocity properties manage the range of a Particle object's Velocity property in the x-direction. Conversely the MaxVerticalVelocity and MinVerticalVelocity properties manage the range of a Particle object's Velocity property in the y-direction. The MaxLifeSpan and MinLifeSpan properties are responsible for maintaining the longevity of each Particle.

Setting the horizontal velocity to a number between -1.0 and 1.0 and the vertical velocity between -13.0 and 0.0; results in a stream of particles which move swiftly upwards and very slightly to the left and right. A lifespan of 1 to 4 seconds ensures that Particle objects will not travel far during their lifecycle and continue to regenerate at a rapid pace. The end effect is a flame-like spread of Particle objects.

In order to create a better representation of fire, additional colors are added to the ColorKeyFrames property of the PointEmitter via a ColorKeyFrameCollection.

<Engine:PointEmitter X="135" Y="25" MaxParticles="140"
                     MinHorizontalVelocity="-1.0" MaxHorizontalVelocity="1.0"
                     MinVerticalVelocity="-13.0" MaxVerticalVelocity="0.0"
                     MinLifeSpan="1" MaxLifeSpan="4" >
    <Engine:PointEmitter.ColorKeyFrames>
        <ColorKeyFrameCollection>
            <LinearColorKeyFrame Value="Yellow" KeyTime="0%" />
            <LinearColorKeyFrame Value="Orange" KeyTime="20%" />
            <LinearColorKeyFrame Value="Red" KeyTime="50%" />
            <LinearColorKeyFrame Value="Gray" KeyTime="90%" />
        </ColorKeyFrameCollection>
    </Engine:PointEmitter.ColorKeyFrames>
</Engine:PointEmitter>

In the example above I added four ColorKeyFrame objects of type LinearColorKeyFrame. The above example shows the particle's color moving linearly from Yellow to Orange to Red to Gray reaching each color at a specific percentage of the particle's life.

Combining the change in color with a change to the particle's Opacity over time adds to the effect considerably. The example below shows the addition of the StartOpacity and EndOpacity properties to the PointEmitter.

<Emitter:PointEmitter X="135" Y="25" MaxParticles="140"
                     MinHorizontalVelocity="-1.0" MaxHorizontalVelocity="1.0"
                     MinVerticalVelocity="-13.0" MaxVerticalVelocity="0.0"
                     StartOpacity="0.3" EndOpacity="0.0"
                     MinLifeSpan="1" MaxLifeSpan="4" >
    <Emitter:PointEmitter.ColorKeyFrames>
        <ColorKeyFrameCollection>
            <LinearColorKeyFrame Value="Yellow" KeyTime="0%" />
            <LinearColorKeyFrame Value="Orange" KeyTime="20%" />
            <LinearColorKeyFrame Value="Red" KeyTime="50%" />
            <LinearColorKeyFrame Value="Gray" KeyTime="90%" />
        </ColorKeyFrameCollection>
    </Emitter:PointEmitter.ColorKeyFrames>
</Emitter:PointEmitter>

A PointEmitter is great for flames and such but in order to get different effects other Emitter objects are required. A LineEmitter can change a stream of fire into a full-fledged conflagration. Below is an example of how a LineEmitter can be used.

<Emitter:LineEmitter X1="4" Y1="0" X2="46" Y2="0" MaxParticles="300"
                    MinHorizontalVelocity="-1.0" MaxHorizontalVelocity="1.0"
                    MinVerticalVelocity="-14.0" MaxVerticalVelocity="0.0"
                    StartOpacity="0.3" EndOpacity="0.0"
                    MinLifeSpan="1.1" MaxLifeSpan="4.2" >
        <Emitter:LineEmitter.ColorKeyFrames>
        <ColorKeyFrameCollection>
            <LinearColorKeyFrame Value="Yellow" KeyTime="0%" />
            <LinearColorKeyFrame Value="Orange" KeyTime="20%" />
            <LinearColorKeyFrame Value="Red" KeyTime="50%" />
            <LinearColorKeyFrame Value="Gray" KeyTime="90%" />
        </ColorKeyFrameCollection>
    </Emitter:LineEmitter.ColorKeyFrames>
</Emitter:LineEmitter>

Forces play an important role in the dispersion of particles in a system. The following is an example of how the internal forces, Gravity and Drag, are used in the system.

<Engine:ParticleSystem.Gravity>
    <Vector X="0" Y="1.3" />
</Engine:ParticleSystem.Gravity>
<Engine:ParticleSystem.Drag>
    <Vector X="3.3" Y="0.3" />
</Engine:ParticleSystem.Drag>

This gives the system a downward Gravity and slows particle velocity over time considerably more in the X-direction than in the Y-direction.

A MeatballEmitter can take advantage of the Gravity force as well as the Spring forces found in a Meatball to create a goo-like effect.

<Engine:ParticleSystem Width="60" Height="30" Background="Transparent"
                       x:Name="GooParticleSystem" Grid.Column="1" >
  <Engine:ParticleSystem.Emitters>
    <Emitter:MeatballEmitter X1="8" Y1="28" X2="48" Y2="28" MaxParticles="90"
                         MinHorizontalVelocity="0.0" 
                         MaxHorizontalVelocity="0.0"
                         MinVerticalVelocity="0.0" MaxVerticalVelocity="0.0"
                         StartOpacity="1.0" EndOpacity="1.0"
                         MinLifeSpan="40" MaxLifeSpan="40"
                         MinMass="2.0"  MaxMass="10.0"
                         MinSpringConstant="1.0" MaxSpringConstant="6.0"
                         MinDampeningConstant="0.01" 
                         MaxDampeningConstant="0.10"
                         MinRestLength="1.0" MaxRestLength="6.0"
                         MinParticleWidth="6.0" MaxParticleWidth="6.0"
                         MinParticleHeight="8.0" MaxParticleHeight="12.0" >
      <Emitter:MeatballEmitter.ColorKeyFrames>
        <ColorKeyFrameCollection>
          <LinearColorKeyFrame Value="Black" KeyTime="0%" /v
        </ColorKeyFrameCollection>
      </Emitter:MeatballEmitter.ColorKeyFrames>
    </Emitter:MeatballEmitter>
  </Engine:ParticleSystem.Emitters>
  <Engine:ParticleSystem.Gravity>
    <Vector X="0" Y="0.4" />
  </Engine:ParticleSystem.Gravity>
  <Engine:ParticleSystem.Drag>
    <Vector X="10.0" Y="0.3" />
  </Engine:ParticleSystem.Drag>
  <Button Width="60" Height="30" Canvas.Left="0" Canvas.Top="0" 
        Canvas.ZIndex="100" Click="MyButtonClicked" Content="GOO" />
</Engine:ParticleSystem>

The XAML code above creates a ParticleSystem with a single MeatballEmitter. The MeatballEmitter generates thirty Meatball objects across the line starting at point 8,28 and ending at point 48,28. The particles have no initial Velocity (velocity will be generated by Gravity). The particles are completely opaque (the StartOpacity and EndOpacity are both set to 1.0) and they will each last for forty seconds before being reused. The mass of any particle can range from 2.0 to 10.0. The properties MinSpringConstant/MaxSpringConstant, MinDampeningConstant/MaxDampeningConstant and MinRestLength/MaxRestLength are used by the Spring forces in each Meatball. A spring constant between 1.0 and 6.0 will cause a rigid to somewhat flexible springing motion. The dampening constant between 0.01 and 0.10 ensures that the spring will oscillate for a while before coming to rest at a length between 1.0 and 6.0. For the goo-like effect the width of each particle should be constant but the height can vary to provide a drip-like Meatball. The entire effect is partially hidden behind a button. On the button's click event the system is started or stopped, depending on its previous state.

protected void MyButtonClicked(object sender, EventArgs e)
{
    // if it is not running start it, otherwise stop it
    if (!mIsGooRunning)
        GooParticleSystem.Start();
    else
        GooParticleSystem.Stop();
    mIsGooRunning = !mIsGooRunning;
}

Points of Interest

Particle systems can be powerful tools used to create amazing effects. Unfortunately, using WPF, performance issues significantly reduce the possibilities. Most of the processing is done using software rendering which limits the number of Particle objects that can be drawn without slowing the system to a crawl. This being the case, the number of particles used for all the effects should range in the low hundreds and the total number of particles in an application should not exceed a thousand particles. For effects which are not persistent such as fire and explosion two hundred to three hundred particles can be adequate. For spring-mass systems which try and simulate effects such as liquids or gels the limited number can handicap the effect.

As I was testing the system I found that not only do the number of Particle objects matter but also the number of Storyboard objects. With just two I found the system was slowing down considerably. At one point I toyed around with the idea of having the motion of all the particles controlled by storyboards. After running a few preliminary tests, it was evident that doing so would result in a much slower system. So the system limits itself to one storyboard for each Particle object which controls both the Opacity and Background color for the Particle.

The adjustment of the Background color via the Storyboard Timeline could have been avoided if WPF allowed for other blending modes besides opacity blending. For instance I could have used additive blending to get the fire effect using a single reddish base color. This would also allow for the goo effect to have multiple colors without changing opacity (resulting in smoky effect). I briefly looked into BitmapEffects to see if I could implement an additive blending mode. Unfortunately is does not appear that access to the RenderingContext is available, only access to the UIElement being affected. So you cannot find what the initial color of the pixel being written to is.

During design I implemented two ways to create Spring forces. The first gave each particle a Connections property of type List<Spring>. When looping through the Force calculations this List was iterated over and the results added to the calculation. The other way was to add Spring objects to a collection in the ParticleSystem. Then, when calculating the forces, the entire collection was iterated over searching for a match on the current Particle. The second method was ultimately slower but allowed Spring objects to be easily shown in the UI of the system. In order to show Spring objects in the first (and distributed) implementation an ObservableCollection<Spring> would need to be created containing these objects. Then another ItemsControl would need to be added to the ParticleSystem's style using the collection as an ItemSource.

The particle system turned out to be an interesting exercise in WPF. It helped show me some of the strong and weak points of the technology. I enjoy being able to plan out a particle system in XAML and not having to worry about code but I dislike the speed limitations. Overall the particle system works great for the occasional "button on fire" and "explosions" but further enhancements will need to be made to both this project and WPF in order to create effects produced using other architectures.

Future Enhancements

The system can easily be enhanced by adding new Emitter classes. Both a PolygonEmitter and PathEmitter would be handy. Additionally, an Emitter for a more robust mass-spring system would be nice too. Given time I would like to have created a branching mass-spring system, where roots of the system are anchored in place and Particle objects are generated from each root and attached via springs. Then each child can generate a Particle object and so forth and so on. I believe something like this would produce a better Goo effect than the one presented in the example.

History

  • Initial Version (October 2007)

References

  • Andrew Witkin. Physically Based Modeling: Particle System Dynamics. SIGGRAPH 2001 Course Notes. 2001.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Tony Gordon

Unknown
No Biography provided

Comments and Discussions

 
GeneralNice Article PinmemberDave Lee from Washington16-Jul-09 13:31 
GeneralLiquid like effect please help Pinmembernewbieprogrammerguy5-Apr-09 5:39 
GeneralIt caught my attention PinmemberJohan Fourie30-Oct-07 14:52 
GeneralI like it, buy my cpu PinmemberJuan Pablo G.C.26-Oct-07 5:02 
GeneralRe: I like it, buy my cpu PinmemberReelix8-Dec-08 21:28 
Yea... Using up 95% of a Dual-Core CPU isn't all that nice....
 
If you find a better way, gimme a yell, since it looks REALLY cool :p
 
-= Reelix =-

GeneralWow PinmvpJosh Smith26-Oct-07 3:31 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141220.1 | Last Updated 24 Oct 2007
Article Copyright 2007 by Tony Gordon
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid