Today I’ve got a fun snowfall effect that seems to settle and accumulate on objects in the scene over time.
This is drawn with <canvas>, where the snow settles across a polygonal boundary of the foreground objects. Actually, we have two distinct collision zones.
The red boundary is a hard stop, but the green is probabilistic. Once a flake crosses there’s a chance of it stopping each frame, so it almost appears the snow is falling around the objects in 3D and gives a nice realism.
To cap the whole thing off there’s animated smoke from the chimney and a flickering glow of an nice warm fire in all the windows. Ooh, cosy!
Over time the effect blankets the objects, which you can see toward the end of the video above (total runtime on that is about 30 minutes by the way, I hadn’t tweaked the fall rate).
How did it work? Pretty easily really.
You can view a live demo here – view source for the goods.
We store the boundary segments as arrays of X and Y percentages, then run a simple point-line intersection test on the right segment.
self._prescaledmap = [
[0,0.93],
[0.474,0.92],
[0.474,0.99],
[0.529,0.98],
[0.529,0.92],
[1,0.93]
];
self._prescaledpartialmap = [
[0.22285714285714287,0.9265033407572383],
[0.2662857142857143,0.49888641425389757],
[0.29828571428571427,0.799554565701559],
[0.30657142857142856,0.7193763919821826],
[0.3191428571428571,0.8641425389755011],
[0.3497142857142857,0.5879732739420935],
[0.37314285714285717,0.8240534521158129],
[0.404,0.5167037861915368],
[0.4361428571428571,0.8084632516703786],
[0.4382857142857143,0.6859688195991092],
[0.46014285714285713,0.6859688195991092],
[0.4604285714285714,0.7204677060133631],
[0.5392857142857143,0.7209220489977728],
[0.5564285714285714,0.8262806236080178],
[0.5735714285714286,0.6726057906458798],
[0.59,0.821826280623608],
[0.619,0.534521158129176],
[0.6564285714285715,0.8930957683741648],
[0.6678571428571428,0.779510022271715],
[0.6835714285714286,0.9242761692650334]
];
self._scaleMaps = function(){
self.collisionmap = self._scaleMap(self._prescaledmap,true);
self.partialmap = self._scaleMap(self._prescaledpartialmap,true);
}
self._scaleMap = function(tmap,minVal){
//scale a collision map
var smap = [],miny=1;
for (var i=0;i<tmap.length;i++){
smap.push( [ tmap[i][0] * self._width, tmap[i][1] * self._height ] );
if (tmap[i][1] < miny) miny = tmap[i][1];
}
if (minVal) smap.push(miny * self._height);
return smap;
}
self.testCollision = function(){
if (self._mapCollision(self.controller.collisionmap)){
return CollideType.FULL;
}else if (self._mapCollision(self.controller.partialmap)){
return CollideType.PART;
}else return CollideType.NONE;
}
self._mapCollision = function(map){
if (self.y < map[map.length-1]) return false;
var c = false;
for (var i=0;i<map.length-2;i++){ //-2 so we skip the minY as the last element
if (((map[i][1]>self.y) != (map[i+1][1] > self.y)) &&
(self.x < (map[i+1][0]-map[i][0]) *
(self.y - map[i][1]) / (map[i+1][1]-
map[i][1]) + map[i][0]) ){
c = !c;
}
}
return c;
}
While scaling the boundary coordinates to fit the animation area we track the lowest Y coordinate to easily exclude flakes still above it from collision tests.
A second canvas is used to draw the fallen snow, with flakes moved there when they “collide” with a boundary.
There’s also a non-HTML5 version without the settling or collision detection, just falling snow. The loader stub uses jQuery to load either the posh version or boring version based on browser <canvas> support.
Thanks all!

