Introduction
Following my first article in this series,
Simulating rain in Silverlight, I tried to optimize the algorithm that generates the rain. After the big interest several weeks ago, in this article this time I made some major changes in the code that eventually result in smoother, faster and non-memory consuming technique to simulate rain effect in Silverlight application.
This new solution of the problem is based on the first one that you may get acquainted with in the first part of this series -
Simulating rain in Silverlight.
Before I begin with the new approach, here is a demo of the same rain, but much more intense, a flood rain...
View live demo - 1000 drops per second
Download source code
Changes in a brief
The solution that I present you now will dramatically affect the memory that the rain simulation consumes. One of the major drawbacks in the first attempt was that it eventually turned out that in some moment the Silverlight application tries to use your entire RAM. It may happen after hours pouring, or days, but it will. This increasing usage of memory affected the smoothness and CPU consumption as well. However, the algorithm for generating the drops is pretty well designed and working, but it definitely needs some optimization when it comes to memory usage.
The secret key - reusing the drops!
From this perspective, I directed my efforts to exactly this issue. The main difference in this new idea is that I could reuse the drops that have already finished their storyboard. Imagine the following – you have a storyboard that lasts, for example, M milliseconds, C – drops that must be animated per a single interval and N – the interval duration in milliseconds. Then after M milliseconds you can reuse all drops that have finished their pouring. But how many are these drops? Having N milliseconds for an interval and C drops per interval, you have 1000 / N intervals per second and thus (1000 / N) * C drops in a second in total. Multiplying this by M / 1000, you result in the number of drops that will be poured by the end of the first storyboard. This is exactly the number of drops that you need to infinitely simulate the rain. You just need to track the current drop to reuse in a global collection.
private
List<Drop> sceneDrops;
private
int
intervalStartIndex;
In the constructor, I initialize the collection and fill it with drops (with no rain effects on them applied – these are added in the algorithm):
int
dropsCount = Convert.ToInt32(Constants.ANIMATION_DURATION * Math.Ceiling((
double
)1000 * (
double
)
this
.DropsPerInterval / (
double
)
this
.DropsInterval));
this
.sceneDrops =
new
List<Drop>(dropsCount);
this
.intervalStartIndex = 0;
for
(
int
i = 0; i <
this
.sceneDrops.Capacity; ++i)
{
Drop drop =
new
Drop(
this
.DropSettings);
this
.sceneDrops.Add(drop);
}
The new algorithm looks like this:
if
(
this
.intervalStartIndex >=
this
.sceneDrops.Count)
{
this
.intervalStartIndex = 0;
}
Random rand =
new
Random();
for
(
int
currentDrop =
this
.intervalStartIndex; currentDrop <
this
.intervalStartIndex +
this
.DropsPerInterval; ++currentDrop)
{
// Add the drop to the scene
if
(!
this
.Scene.Children.Contains(
this
.sceneDrops[currentDrop]))
{
this
.Scene.Children.Add(
this
.sceneDrops[currentDrop]);
}
// Generate the drop depth and scale it appropriately
double
depth = rand.NextDouble();
this
.sceneDrops[currentDrop].RenderTransform =
new
ScaleTransform
{
ScaleX = depth,
ScaleY = depth,
};
// Generate angle for the falling drop
double
angle = rand.NextDouble() * 10;
this
.sceneDrops[currentDrop].RenderTransform =
new
RotateTransform
{
Angle = angle,
};
// Generate the drop distance from most left border
this
.sceneDrops[currentDrop].Margin =
new
Thickness(Convert.ToInt32(rand.NextDouble() *
this
.Width), 0, 0, 0);
// Animate the drop falling
this
.sceneDrops[currentDrop].Animate();
}
this
.intervalStartIndex +=
this
.DropsPerInterval;
Using GPU Acceleration
So, that was for the memory optimization. There is one more thing that can be done to improve the smoothness. Caching. To enable composition caching at the plug-in level you must set the value of an EnableGPUAcceleration param element to true as part of the object tag that declares the Silverlight plug-in.
<
param
name
=
"enableGPUAcceleration"
value
=
"true"
/>
This allows you to take advantage of the hardware acceleration in the GPU. All this can yield in significant performance improvements in some specific scenarios. To specify that the drop should be cached and benefit from GPU acceleration, I use a BitmapCache object.
<
Grid.CacheMode
>
<
BitmapCache
RenderAtScale
=
"4"
/>
</
Grid.CacheMode
>
You can read more about CacheMode in MSDN.
Conclusion
After all above, the first rain simulation I made several weeks ago now looks like a polished one. Thanks to your feedback I was encouraged to spend some time searching for a better approach. As a conclusion, it, again, as it always happens, is obvious and straightforward to realize, just waiting for you to discover it, escaping from the boundaries that you build in front of you...