Friday, May 3, 2013

Creating a smoke using Particle System in Silverlight

Here’s what we’re trying to achieve: demo 

One of a fellow programmer came up with a question of “How do you create Smoke effect using Silverlight?” Well there may be plenty of code, third-party tools and plug-ins strewn over the internet for this, but out of a professional curiosity I got into the business of creating one out of my own hands.

The whole concept of creating smoke (without fire ;) ) can be done through what they call as Particle System. It’s a technique where in you employ very small graphical objects in large numbers to simulate effects like say Smoke, Fire, Snow, Dust, etc…

We need the smoke to be moving freely, so a canvas would be a best bet for the layout.
<Canvas x:Name="myCanvas" Background="White">
</Canvas>

From this point on the rest of the Smoke effect would be programmed in code behind file (C-Sharp in my case)

Let’s think a minute about smoke, shall we? Smoke starts at a point, very concentrated (Generation Point). According to the complex laws of physics, it simply rises up becoming dilute all the way. Finally blending well with the air where it seems to disappear (Vanishing Point).


Understanding the dynamics of smoke

Our approach would be to consider smoke as made up of small particles, which would originate at the Generation Point. These particles as time passes would simply rise up, becoming transparent by smaller percentages as it goes and finally becoming invisible at the Vanishing Point.

These particles are going to be ellipses. Let’s have a method which does just that.

public static Ellipse createEllipse()
{
Ellipse objEllipse = new Ellipse();
//Smaller sized particles
objEllipse.Width = 1;
objEllipse.Height = 1;

//Giving the particles a Smokey look
RadialGradientBrush rgbObj = new RadialGradientBrush();

GradientStop gsObj1 = new GradientStop();
gsObj1.Color = Colors.Transparent;
gsObj1.Offset = 2;
GradientStop gsObj2 = new GradientStop();
gsObj2.Color = Colors.DarkGray;
gsObj2.Offset = 0.5;
GradientStop gsObj3 = new GradientStop();
gsObj3.Color = Colors.Black;
gsObj3.Offset = 0.001;

rgbObj.GradientStops.Add(gsObj1);
rgbObj.GradientStops.Add(gsObj2);
rgbObj.GradientStops.Add(gsObj3);

objEllipse.Fill = rgbObj;

//Set the position of the ellipses to the Generation Point
Canvas.SetTop(objEllipse, 300);
Canvas.SetLeft(objEllipse, 150);
return objEllipse;
}

While the Silverlight application initializes, we’d simply call the following line to add a new smoke particle:
myCanvas.Children.Add(createEllipse());
Well, this is just one particle. By every tick of the clock, new particles should be generated to give the effect of the smoke billowing. This calls for a DispatcherTimer instance which would give me an event for say every 2 millisecond.
System.Windows.Threading.DispatcherTimer timer = new System.Windows.Threading.DispatcherTimer();
timer.Interval = new TimeSpan(2);
timer.Tick += timer_Tick;
timer.Start();
At every tick, we need to be doing a set of tasks based on a few decisions.

Firstly, we cannot afford to overload the memory/screen with infinite number of particles. When a smoke particle simply disappears beyond the vanishing point, we can move them down to the generation point and reuse them. For this purpose, let’s have a limit of 1000 particles that can be freshly generated. If the screen has less than 1000 of them, we can go ahead and generate a new one.
if (myCanvas.Children.Count < 1000){
generateFireParticles();
}
Secondly, at every instant – *all* the smoke particles should perform the following tasks:
  • Rise up
Canvas.SetTop(item, Canvas.GetTop(item) - 1.25);
  • Grow and become lighter in visibility
item.Opacity = item.Opacity - 0.009;
item.Width = item.Width + 0.075;
item.Height = item.Height + 0.075;
  • And take a wavy path up. Smoke particles take a simply chaotic path up, they just don’t go up in a straight line.
Random randObj = new Random(10);
Canvas.SetLeft(item, Canvas.GetLeft(item) - (Math.Pow(-1, randObj.Next(5)) * randObj.Next(2)));
Thirdly, Move every vanished particle back to its original point.
if (item.Opacity < 0.0001){
item.Opacity = 1;
item.Width = item.Height = 1;
Canvas.SetTop(item, 150);
Canvas.SetLeft(item, 300);
}
So, considering all above, my timer tick event would look like this:
void timer_Tick(object sender, EventArgs e)
{
if (myCanvas.Children.Count < 1000)
{
generateFireParticles();
}
foreach (Ellipse item in myCanvas.Children)
{
if (item.Opacity < 0.0001)
{
item.Opacity = 1;
item.Width = item.Height = 1;
Canvas.SetTop(item, Y);
Canvas.SetLeft(item, X);
}
else
{
item.Opacity = item.Opacity - 0.009;
item.Width = item.Width + 0.075;
item.Height = item.Height + 0.075;
Canvas.SetTop(item, Canvas.GetTop(item) - 1.25);
Canvas.SetLeft(item, Canvas.GetLeft(item) - (Math.Pow(-1, randObj.Next(5)) * randObj.Next(2)));
}
}
}
Thus, was how we can generate a smoke effect in Silverlight using Particle System. With a minor change in the color and nature of the ellipse, you can create Fire or other similiar effects.

No comments:

Post a Comment