Flash Smoke Effect using DisplacementMapFilter
Inspired by David Lenaerts’ awesome smoke simulation using Alchemy, I wondered if a similar effect – pushing smoke around with the mouse – could be achieved using Flash’s DisplacementMapFilter. As with David’s simulation, click to add smoke and move the mouse to create wind.
There’s a great explanation of how DisplacementMapFilter works over at Emanuele Feronato’s blog, so I won’t repeat it here. The smoke effect is on the left, and on the right is the displacement bitmap that gets applied to the smoke image on each frame. To simulate wind, as the mouse is moved I add or subtract from the red and green channels using ColorTransform. The faster the mouse is moved, the more I add or subtract. To simulate the wind settling down, I apply a BlurFilter and another ColorTransform to cause the displacement map to converge towards 0×80C000 (no horizontal movement, slight upwards movement).
While not nearly as impressive as David’s more realistic simulation, I’m pretty happy with what can be achieved with simple bitmap displacement. I experimented with adding Perlin noise to prevent the smoke from getting too static when the mouse isn’t moving and got some really cool results, but have decided to keep things simple for now. Maybe in a future post.
Here’s the source. It was built in FlexBuilder as an ActionScript project, but wouldn’t be hard to convert to compile in the Flash IDE.
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.filters.BlurFilter;
import flash.filters.DisplacementMapFilter;
import flash.geom.ColorTransform;
import flash.geom.Point;
import flash.geom.Rectangle;
[SWF(width='1024', height='512', frameRate='30')]
public class DisplacementSmoke extends Sprite
{
internal static var BITMAP_WIDTH:Number = 512;
internal static var BITMAP_HEIGHT:Number = 512;
internal static var WIND_SIZE:Number = 80;
internal static var SMOKE_SIZE:Number = 10;
internal var smokeBitmap:Bitmap;
internal var displacementBitmap:Bitmap;
internal var drawing:Boolean = false;
internal var xPos:Number;
internal var yPos:Number;
internal var displacementFilter:DisplacementMapFilter;
internal var blurFilter:BlurFilter = new BlurFilter(3, 3, 1);
internal var windColorTransform:ColorTransform;
internal var smokeColorTransform:ColorTransform = new ColorTransform(1, 1, 1, 1, 20, 20, 20);
internal var dampingColorTransform:ColorTransform = new ColorTransform(.97, .96, 0, 1, 5, 9, 0, 0);
internal var heatColorTransform:ColorTransform = new ColorTransform(1, 1, 1, 1, 0, 2, 0, 0);
internal var bitmapRect:Rectangle = new Rectangle(0, 0, BITMAP_WIDTH, BITMAP_HEIGHT);
internal var rect:Rectangle;
internal var p:Point = new Point(0, 0);
public function DisplacementSmoke()
{
smokeBitmap = new Bitmap(new BitmapData(BITMAP_WIDTH, BITMAP_HEIGHT, false, 0x000000));
displacementBitmap = new Bitmap(new BitmapData(BITMAP_WIDTH, BITMAP_HEIGHT, false, 0x80C000));
addChild(smokeBitmap);
addChild(displacementBitmap);
displacementBitmap.x = BITMAP_WIDTH;
stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
displacementFilter = new DisplacementMapFilter(displacementBitmap.bitmapData, p, 1, 2, 5, 5);
smokeBitmap.filters = [displacementFilter];
addEventListener(Event.ENTER_FRAME, enterFrameHandler);
}
internal function mouseDownHandler(event:MouseEvent):void
{
drawing = true;
}
internal function mouseUpHandler(event:MouseEvent):void
{
drawing = false;
}
internal function enterFrameHandler(event:Event):void
{
var i:Number;
var j:Number;
var mx:Number = stage.mouseX;
var my:Number = stage.mouseY;
var dx:Number = mx - xPos;
var dy:Number = my - yPos;
var d:Number = Math.sqrt(dx*dx + dy*dy);
var step:Number = .6 - Math.min(.5, d/100);
var xp:Number;
var yp:Number;
windColorTransform = new ColorTransform(1, 1, 1, 1, -dx*.5, -dy*.5);
for(i=0; i<1; i+=step)
{
xp = xPos + dx*i;
yp = yPos + dy*i;
xp = Math.max(xp, WIND_SIZE/2);
xp = Math.min(xp, BITMAP_WIDTH - WIND_SIZE/2);
yp = Math.max(yp, WIND_SIZE/2);
yp = Math.min(yp, BITMAP_HEIGHT - WIND_SIZE/2);
if(!(xPos==0 && yPos==0))
{
rect = new Rectangle(xp - WIND_SIZE/2 + Math.random()*10-5, yp - WIND_SIZE/2 + Math.random()*10-5, WIND_SIZE, WIND_SIZE);
displacementBitmap.bitmapData.colorTransform(rect, windColorTransform);
if(drawing)
{
displacementBitmap.bitmapData.colorTransform(rect, heatColorTransform);
for(j=0; j<4; j++)
{
rect = new Rectangle(xp - Math.random()*SMOKE_SIZE, yp - Math.random()*SMOKE_SIZE, SMOKE_SIZE, SMOKE_SIZE);
smokeBitmap.bitmapData.colorTransform(rect, smokeColorTransform);
}
}
}
}
displacementBitmap.bitmapData.colorTransform(bitmapRect, dampingColorTransform);
displacementBitmap.bitmapData.applyFilter(displacementBitmap.bitmapData, bitmapRect, p, blurFilter);
xPos = mx;
yPos = my;
smokeBitmap.bitmapData.draw(smokeBitmap);
}
}
}
6 Comments
zedia.net on April 1st, 2009
This is really cool!
There is just one problem; I think your application is evil!
Every time I press the mouse button and randomly move an evil distorted face appears.
I think you need to exorcise your swf.
EnzoGames on August 24th, 2009
The code has some html corruption. For instance for(i=0; i<1; i+=step), its easy for me to fix if I reference HTML.
Rob Muller on August 24th, 2009
Yep – the code-colourer plugin I’m using seems to be double-encoding less-than characters. There doesn’t seem to be anything I can do about it. Any suggestions for a better code-colourer that supports AS3 & MXML?
Rob Muller on August 24th, 2009
Ignore that – the HTML encoding issue should be fixed now. I really should read the documentation. :)
Smoke #1 • Blog Archive • Visual Gratis on December 8th, 2009
[...] via Perlin Noise applied as Displacement Map. Got a little intrigued after seeing this experiment and this. While mine has no real physics behind it, it does render a rather pleasing effect. Simply [...]


David on April 1st, 2009
Hey, cool to see I inspired! :D A DisplacementMap, is a pretty original take on the subject. I love the swirly effect when moving the mouse around fast in circles!