Browse the notebook

Category: Graphics

Wave Trick

[SWF(width=800, height=600)]
var dupes:int = 5
var pntNum:int = 180;

var hh:Number = stage.stageHeight / 2;
var points:Vector.<Number> = new Vector.<Number>();
var vel:Vector.<Number> = new Vector.<Number>();
var cmds:Vector.<int> = new Vector.<int>();
var vectors:Vector.<Shape> = new Vector.<Shape>();

for (var i:int = 0; i<dupes; i++){
	vectors[i] = Shape(addChildAt(new Shape(),0));
	vectors[i].x = 10 * i;
	vectors[i].y = -10 * i;
}
var index:int = 0;
for (i = 0; i<pntNum; i++){
	points[index++] = 10 + i * 4;
	points[index++] = hh;
	cmds[i] = 2;
	vel[i] = 0;
}
cmds[0] = 1;
addEventListener(Event.ENTER_FRAME, onLoop);
function onLoop(evt:Event):void {	
   index = 1;
   points[index] += (mouseY - points[index]) / 12;
   for (var i:int = 3; i<points.length; i+=2){
         points[i] += vel[index];
		 vel[index] += ((points[i] - points[i - 2]) * -0.16 - vel[index]) / 4;
		 index++;
   }
   for (i = 0; i<dupes; i++){
	   var c:uint =  255 - 255 / i;
	   with (vectors[i].graphics){
		   clear();
		   lineStyle(0,c << 16 | c << 8 | c);
		   drawPath(cmds, points);
	   }
   }
}

This snippet uses elasticity to create an interesting wave effect. I first stumbled upon this technique back in my director days…


Have a look at the swf here…

Read snippet: Wave Trick

transformVectors() and drawPath()

var squareNum:int  = 100;
// verts defines a single square
var verts:Vector.<Number> = Vector.<Number>([-20, 0, 0, 20, 0, 0, 20, 0, 40, -20, 0, 40, -20, 0, 0]);
var cmds:Vector.<int> = Vector.<int>([1,2,2,2,2]);
var tempVerts:Vector.<Number> = new Vector.<Number>();
var newVerts:Vector.<Number> = new Vector.<Number>();
var pVerts:Vector.<Number> = new Vector.<Number>(10 * squareNum);
var uv:Vector.<Number> = new Vector.<Number>(15 * squareNum);

var m:Matrix3D = new Matrix3D();

// duplicate the verts array a bunch of times
// each time randomly rotating, scaling and translating it
for (var i:int = 0; i<squareNum; i++){
	m.identity();
	m.appendRotation(Math.random()*360,Vector3D.X_AXIS);
	m.appendRotation(Math.random()*360,Vector3D.Y_AXIS);
	m.appendRotation(Math.random()*360,Vector3D.Z_AXIS);
	var s:Number = Math.random()*2 + .1;
    m.appendScale(s, s, s);
	m.appendTranslation(Math.random()*400 - 200, Math.random()*400 - 200, Math.random()*400 - 200);
	m.transformVectors(verts,tempVerts);
	newVerts = newVerts.concat(tempVerts);
	cmds = cmds.concat(Vector.<int>([1,2,2,2,2]));
}
newVerts.fixed = pVerts.fixed = uv.fixed = true;

var dx:Number = 0, dy:Number = 0;
addEventListener(Event.ENTER_FRAME, onLoop);
function onLoop(evt:Event):void {
       dx += (mouseX - dx) / 4;
	   dy += (mouseY - dy) / 4;
	   
       m.identity();
       m.appendRotation(dx,Vector3D.Z_AXIS);
       m.appendRotation(dy,Vector3D.X_AXIS);
	   m.appendTranslation(stage.stageWidth / 2,stage.stageHeight / 2, 0);

       Utils3D.projectVectors(m, newVerts, pVerts, uv);
	   
	   graphics.clear();
	   graphics.lineStyle(0,0x000000);
	   graphics.drawPath(cmds, pVerts);
}

This is a demo showing how to use transformVectors() and drawPath() together. It creates 100 wireframe squares in 3D:


Have a look at the swf…

Read snippet: transformVectors() and drawPath()

BitmapData & Particles

stage.frameRate = 30;
stage.quality = StageQuality.LOW;

var canvas:BitmapData = new BitmapData(400,400,false, 0x000000);
addChild(new Bitmap(canvas));
var overlay:BitmapData = new BitmapData(400,400,true, 0x12000000);
var particles:Dictionary = new Dictionary(true);

var blur:Array = [new BlurFilter(8,8,1)];
	
addEventListener(Event.ENTER_FRAME, onLoop);
function onLoop(evt:Event):void {
	for (var i:int= 0; i<3; i++) createParticle();
	canvas.copyPixels(overlay, canvas.rect, new Point(0,0), null, null, true);
}
 
function createParticle():void{
	var s:MovieClip = new MovieClip();
	var diameter:Number = Math.random()*50 + 2;
	var radius:Number = diameter / 2;
	var mat:Matrix = new Matrix();
    mat.createGradientBox(diameter, diameter, 0, -radius, -radius);
	mat.createGradientBox(diameter, diameter, 0, -radius, -radius);
    s.graphics.beginGradientFill(GradientType.RADIAL, [ 0xFFCC32, 0xFF0000], [0.5,0.8], [0, 255], mat, SpreadMethod.PAD);
	s.graphics.drawCircle(0,0,radius);
	s.velX = 0;
	s.velY =  -Math.random()*6 + 3;
	s.posX = s.x = 150+ Math.random() * 100;
	s.posY = s.y = 200;
	s.theta= Math.random() * Math.PI * 2;
	s.inc = Math.random() * 0.4 + 0.01;
	s.rad = Math.random() * 4 + 1;
	s.filters = blur;
	// since the particles aren't on the display list, we need to keep a reference to them
	particles[s] = s;
	s.addEventListener(Event.ENTER_FRAME, onRunParticle);
}
function onRunParticle(evt:Event):void {
	var s:MovieClip = MovieClip(evt.currentTarget);
	s.posX += s.velX;
	s.posY += s.velY;
	s.velX = s.rad * Math.cos(s.theta);
	s.theta += s.inc;
	s.scaleX = s.scaleY -=  .03;
	if (s.scaleX < 0){
		particles[s] = null;
		s.removeEventListener(Event.ENTER_FRAME, onRunParticle);
	}
	s.x = s.posX;
	s.y = s.posY;
	canvas.draw(s, s.transform.matrix, null, BlendMode.ADD);
}

This is a slight variation on a simple particle system that I show in my intermediate class. It is almost the same as this example. But it makes use of filters and BitmapData.

Here is a still generated with this snippet. You’ll notice it vaguely resembles fire.

If you need more particles, there are other approaches that are more appropriate.

Read snippet: BitmapData & Particles

drawTriangles() 2D Textured Plane

[SWF(width = 700, height = 700)]

var loader:Loader = new Loader();
loader.load(new URLRequest("https://actionsnippet.com/wp-content/chair.jpg"));
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoaded);
var tex:BitmapData;
function onLoaded(evt:Event):void{
	tex = Bitmap(loader.content).bitmapData;
	addEventListener(Event.ENTER_FRAME, onLoop);
}

var plane:Shape = Shape(addChild(new Shape()));
plane.x = plane.y = 50;

var verts:Vector.<Number> = new Vector.<Number>();
var uvs:Vector.<Number> = new Vector.<Number>();
var indices:Vector.<int> = new Vector.<int>();
var rows:int = 30;
var size:Number = rows + 1;
var vertNum:Number = size * size;
var polySize:Number = 20;
var vIndex:int = 0;
var uvIndex:int = 0;
var gridSize:Number = rows * polySize;
for (var i:Number = 0; i<vertNum; i++){
	var xp:Number = i % size * polySize;
	var yp:Number =  int(i / size) * polySize;
	verts[vIndex++] = xp
	verts[vIndex++] = yp;
	uvs[uvIndex++] = xp / gridSize;
	uvs[uvIndex++] = yp / gridSize;
	if (i % size != rows){
		  indices.push(i, i+1, i+size, i+size, i+size+1, i+1);
	}
}

// render and show that the verts can be changed around
function onLoop(evt:Event):void {
	// shake the verts
	for (var i:int = 0; i<verts.length; i++){
		verts[i] += Math.random() - 0.5;
	}
	with(plane.graphics){
		clear();
		beginBitmapFill(tex,null, false, true);
		drawTriangles(verts, indices,uvs);
	}
}

This snippet is shows how to draw a textured 2D plane using drawTriangles(). This is similar to the wireframe 2D plane post from yesterday… the main difference is that you need to calculate UV coordinates if you want to use a bitmap fill with drawTriangles().

Here is a still of this snippet:

Read snippet: drawTriangles() 2D Textured Plane

Wireframe Plane 2D

var plane:Shape = Shape(addChild(new Shape()));
plane.x = plane.y = 50;

var verts:Vector.<Number> = new Vector.<Number>();
var indices:Vector.<int> = new Vector.<int>();
var rows:int = 20;
var size:Number = rows + 1;
var vertNum:Number = size * size;
var polySize:Number = 20;
var vIndex:int = 0;
for (var i:Number = 0; i<vertNum; i++){
	verts[vIndex++] = i % size * polySize;
	verts[vIndex++] = int(i / size) * polySize;
	if (i % size != rows){
		  indices.push(i, i+1, i+size, i+size, i+size+1, i+1);
	}
}

// render and show that the verts can be changed around
addEventListener(Event.ENTER_FRAME, onLoop);
function onLoop(evt:Event):void {
	// shake the verts
	for (var i:int = 0; i<verts.length; i++){
		verts[i] += Math.random() - 0.5;
	}
	with(plane.graphics){
		clear();
		lineStyle(0,0);
		drawTriangles(verts, indices);
	}
}

This snippet shows a simple way to draw a 2D wireframe plane using drawTriangles(). To demonstrate the flexibility of the plane… the vertices are randomly shaken on enterframe.

Read snippet: Wireframe Plane 2D

drawTriangles() Terrain

[SWF(width=500,height=500,backgroundColor=0x333333, frameRate=40)]
var terrain:Shape = Shape(addChild(new Shape()));
terrain.x = terrain.y = 250;
var rows:int = 60;
var size:int = rows + 1;
var vertNum:int = size * size;
var polySize:Number = 5;
var gridWidth:Number = polySize * rows;
var halfWidth:Number = gridWidth / 2;
var verts:Vector.<Number> = new Vector.<Number>();
var pVerts:Vector.<Number> = new Vector.<Number>();
var indices:Vector.<int> = new Vector.<int>();
var uvs:Vector.<Number> = new Vector.<Number>();
var uvts:Vector.<Number> = new Vector.<Number>();
var tex:BitmapData = new BitmapData(gridWidth, gridWidth, false, 0x000000);
var pix:int = gridWidth * gridWidth;
var perlin:BitmapData = new BitmapData(gridWidth, gridWidth, false, 0x000000);
// generate the texture and the terrain
function generate():void{
	tex.fillRect(tex.rect, 0x000000);
	var i:Number, xp:Number, yp:Number;
	for (i = 0; i<pix; i++){
		xp = i % gridWidth;
		yp= int(i / gridWidth);
		var dx:Number = xp - gridWidth / 2;
		var dy:Number = yp - gridWidth / 2;
		var d:Number = 255 - Math.sqrt(dx * dx + dy * dy) / halfWidth* 255;
		if (d <0) d = 0;
		if (d> 255) d = 255;
		var c:uint = uint(d);
		tex.setPixel(xp, yp, c <<16 | c <<8 | c);
	}
	perlin.perlinNoise(100,100,3,Math.random()*100,false,false,7,true);
	perlin.draw(perlin, null, null, BlendMode.SCREEN);
	tex.draw(perlin, null, null, BlendMode.MULTIPLY);
	// calculate verts, uvs and indices
	var vIndex:int = 0;
	var uvIndex:int = 0;
	indices = new Vector.<int>();
	for (i = 0; i<vertNum; i++){
		var xMod:Number = i % size;
		xp = xMod * polySize;
		yp = int(i / size) * polySize;
		verts[vIndex++] = xp - halfWidth;
		verts[vIndex++] = yp - halfWidth;
		verts[vIndex++] = tex.getPixel(xp, yp) & 0xFF;
		uvs[uvIndex++] = xp /  gridWidth;
		uvs[uvIndex++] = yp / gridWidth;
		if (xMod != rows){
			  indices.push(i, i+1, i+size, i+1, i+size+1, i+size);
		}
	}
}
generate();
stage.addEventListener(MouseEvent.MOUSE_DOWN, onGenerate);
function onGenerate(evt:MouseEvent):void{ generate() };
var m:Matrix3D = new Matrix3D();
var rot:Number = 0;
addEventListener(Event.ENTER_FRAME, onLoop);
function onLoop(evt:Event):void {
		m.identity();
		var destRot:Number = mouseX / stage.stageWidth * 90;
		if (destRot <0) destRot = 0;
		if (destRot> 90) destRot = 90;
		rot += (destRot - rot) * 0.2;
		m.appendRotation(rot,Vector3D.Z_AXIS);
		m.appendRotation(60,Vector3D.X_AXIS);
		Utils3D.projectVectors(m, verts, pVerts, uvts);
		with(terrain.graphics){
			clear();
			beginBitmapFill(tex, null, false, true);
			drawTriangles(pVerts, indices, uvs, TriangleCulling.NEGATIVE);
		}
}

This snippet draws a pretty simple isometric terrain using fp10 graphics stuff and perlin noise.


Have a look at the swf here…

Read snippet: drawTriangles() Terrain

Variable Line Resolution #2

var loc:Vector.<Point> = new Vector.<Point>();
var lifts:Vector.<int> = new Vector.<int>();
var index:int = 0;
var resolution:int = 1;
var down:Boolean;
stage.addEventListener(MouseEvent.MOUSE_DOWN, onDown);
stage.addEventListener(MouseEvent.MOUSE_UP, onUp);
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyReleased);
addEventListener(Event.ENTER_FRAME, onLoop);
function onKeyReleased(evt:KeyboardEvent):void{
	if (evt.keyCode == Keyboard.RIGHT){
		resolution -= 1;
		if (resolution < 1) resolution = 1;
	}else 
	if (evt.keyCode == Keyboard.LEFT){
		resolution += 1;
	}
}
function onDown(evt:MouseEvent):void{
	down = true;
	lifts.push(index);
}
function onUp(evt:MouseEvent):void{
	down = false;
}
function onLoop(evt:Event):void {
	if (down){
		loc[index] = new Point(mouseX, mouseY);
		index++;
	}
	graphics.clear();
	graphics.lineStyle(0,0x000000);
	var j:int = 0;
	var lift:int;
	var liftLength:int = lifts.length;
	var lastLoc:int =  loc.length  - 1;
	for (var i:int = 0; i<liftLength; i++){
		j =  lifts[i];
		graphics.moveTo(loc[j].x, loc[j].y);
		if (i < liftLength - 1){
			lift = lifts[i + 1] - 1;
		}else{
			lift = lastLoc;
		}
		while (j < lift){
			j++;
			if (j % resolution == 1 || resolution == 1){
			  graphics.lineTo(loc[j].x, loc[j].y);
			}
		}
		graphics.lineTo(loc[j].x, loc[j].y);
	}
}

This snippet shows a simple approach to creating variable resolution graphics.

This is an improved version of yesterdays post. In yesterdays example the end points of the lines would sometimes be removed… this version doesn’t have that problem…

Below is a drawing created using this snippet. The left arrow key is used to decrease resolution and the right arrow key is used to increase resolution:

Have a look at the swf…

Read snippet: Variable Line Resolution #2

Variable Line Resolution

var loc:Vector.<Point> = new Vector.<Point>();
var lifts:Vector.<int> = new Vector.<int>();
var index:int = 0;
var resolution:int = 1;
var down:Boolean;
stage.addEventListener(MouseEvent.MOUSE_DOWN, onDown);
stage.addEventListener(MouseEvent.MOUSE_UP, onUp);
stage.addEventListener(KeyboardEvent.KEY_UP, onKeyReleased);
addEventListener(Event.ENTER_FRAME, onLoop);
function onKeyReleased(evt:KeyboardEvent):void{
	if (evt.keyCode == Keyboard.RIGHT){
		resolution -= 1;
		if (resolution < 1) resolution = 1;
	}else 
	if (evt.keyCode == Keyboard.LEFT){
		resolution += 1;
	}
}
function onDown(evt:MouseEvent):void{
	down = true;
	lifts.push(index);
}
function onUp(evt:MouseEvent):void{
	down = false;
}
function onLoop(evt:Event):void {
	if (down){
		loc[index] = new Point(mouseX, mouseY);
		index++;
	}
	graphics.clear();
	graphics.lineStyle(0,0x000000);
	var liftIndex:int = 0;
	for (var i:int = 0; i<loc.length; i++){
		if (liftIndex == lifts.length) liftIndex = 0;
		if (i == lifts[liftIndex]){
			graphics.moveTo(loc[i].x, loc[i].y);
		    liftIndex++;
		}else{
			if (i % resolution == 1 || resolution == 1){
				graphics.lineTo(loc[i].x, loc[i].y);
			}
		}
	}
}

This snippet shows a simple approach to creating variable resolution graphics.

Below is a drawing done using this snippet. The left arrow key is used to decrease resolution and the right arrow key is used to increase resolution:


Click to view swf…

Read snippet: Variable Line Resolution

Better BitmapData Brush

var canvas:BitmapData = new BitmapData(stage.stageWidth,stage.stageHeight,false, 0xCCCCCC);
addChild(new Bitmap(canvas));
var prevX:Number;
var prevY:Number;
var brush:Shape = new Shape();

stage.addEventListener(MouseEvent.MOUSE_DOWN, onDown);
stage.addEventListener(MouseEvent.MOUSE_UP, onUp);
function onDown(evt:MouseEvent):void{
	prevX = mouseX;
	prevY = mouseY;
	addEventListener(Event.ENTER_FRAME, onLoop);
}

function onUp(evt:MouseEvent):void{
	removeEventListener(Event.ENTER_FRAME, onLoop);
}

function onLoop(evt:Event):void {
      brush.x = mouseX;
	  brush.y = mouseY;
	  with (brush.graphics){
		  clear();
		  lineStyle(3, 0x000000);
		  lineTo(prevX-mouseX, prevY-mouseY);
	  }
	  canvas.draw(brush, brush.transform.matrix);
	  prevX = mouseX;
	  prevY = mouseY;
}

If you write a drawing program with the Graphics class alone, you’ll notice that eventually the flash player will start to slow down. This could take quite some time, but it will eventually happen. Because the Graphics class is entirely vector based, the flash player needs to keep track of every single point that makes up any line in your drawing. If you draw something and then erase it by drawing white vector lines over it, flash still needs to know about that thing that you erased.

The snippet I wrote yesterday reminded me of the technique used in the above snippet. In yesterdays post every line is drawn using the graphics class. As a result, eventually the flash player begins to choke.

Today’s snippet draws a vector between the previous mouse location and the current mouse location – this vector is then drawn onto a BitmapData object and the vector is cleared. So rather than creating vector graphics that are continuously increasing in complexity, your just changing the color of pixels around on a BitmapData.

To see a visual explanation take a look at the below swf. I slowed it down to 5fps, I tinted the vector red and scaled up the stage so you can differentiate between what’s vector and what’s bitmap:


Click to see the swf…

Read snippet: Better BitmapData Brush

Gesture Capture

var canvas:Shape = Shape(addChild(new Shape()));
var gestures:Array=[];
var gestureNum:int = 0;
var capGesture:Array;
stage.addEventListener(MouseEvent.MOUSE_DOWN, onDown);
stage.addEventListener(MouseEvent.MOUSE_UP, onUp);
function onDown(evt:MouseEvent):void{
	 capGesture=[];
	 addEventListener(Event.ENTER_FRAME, onCapture);
	
	 canvas.graphics.lineStyle(3, 0xFF0000);
	 canvas.x = mouseX;
	 canvas.y = mouseY;
	 canvas.graphics.moveTo(0, 0);
}
function onUp(evt:MouseEvent):void{
	gestures.push(capGesture.concat());
	gestureNum++;
	 canvas.graphics.clear();
	removeEventListener(Event.ENTER_FRAME, onCapture);
}
function onCapture(evt:Event):void{
	capGesture.push(new Point(canvas.mouseX, canvas.mouseY));
	canvas.graphics.lineTo(canvas.mouseX, canvas.mouseY);
}

var currGesture:Array;
var drawing:Boolean = false;
var lineThickness:Number = 0;
var lineColor:Number = 0x000000;
var index:int = 0;
var pnt:Point;
var trans:Matrix = new Matrix();
var i:int
addEventListener(Event.ENTER_FRAME, onLoop);
function onLoop(evt:Event):void{
	if (gestureNum > 0){
		if (!drawing){
		  currGesture = gestures[int(Math.random() * gestureNum)].concat();
		  trans.identity();
		  trans.rotate(Math.random()*6.28);
		  var scale:Number = Math.random() * 2 + .1;
		  trans.scale(scale, scale);
		  trans.tx = Math.random() * stage.stageWidth 
		  trans.ty = Math.random() * stage.stageHeight
		  for (i = 0; i<currGesture.length; i++){
			 currGesture[i] = trans.transformPoint(currGesture[i]);  
		  }
		  lineThickness = Math.random() * Math.random() * 50;
		  if (int(Math.random()*10) ==1){
			 var col:uint = uint(Math.random()*255);
		     lineColor = col << 16 | col << 8 | col;
		  }
		  index = 0;
		  drawing = true;
		  graphics.lineStyle(lineThickness, lineColor);
		}else{
		   for (i = 0; i<10; i++){
			    if (drawing == true){
				   pnt = currGesture[index];
				   if (index == 0){
					  graphics.moveTo(pnt.x, pnt.y);  
				   }else{
					  graphics.lineTo(pnt.x, pnt.y);
				   }
				   index++;
				   if (index == currGesture.length){
					   drawing = false;
				   }
		  	   }
		   }
		}
	}
}

This snippet is an idea I have been meaning to try for sometime. It’s a mini-drawing program. You can draw single gestures (shapes, letters etc…) and the program then randomly scales, rotates, tints and translates these gestures repeatedly on the canvas. You can continue to draw as it does this, the more gestures you draw, the more the program will have to randomly choose from.


Have a look at the swf here…

Read snippet: Gesture Capture