InParticular – Three JS WebGL test


So I finally got around to checking out the brillaint Three.js, the 3D JavaScript Engine, by the incredible Mr Doob. It’s really easy to pick up, even for a JavaScript novice like me – and the performance (WebGL in chrome) is absolultey astounding.
Three JS has a few different renderers –

  • Canvas Renderer
  • DOM Renderer
  • SVG Renderer
  • Sound Renderer
  • WebGL Renderer

In these demos, I’ve been using the Canvas Renderer and the WebGL Renderer.

Here’s a brief wikipedia defintion of each –

The Canvas Element

The canvas element is part of HTML5 and allows for dynamic, scriptable rendering of 2D shapes and bitmap images. It is a low level, procedural model that updates a bitmap and does not have a built-in scene graph.

WebGL

“WebGL is a Web-based Graphics Library. It extends the capability of the JavaScript programming language to allow it to generate interactive 3D graphics within any compatible web browser.”

Canvas is nice – but WebGL is fast. Really fast. Here’s a video of my first Three.js experiment – InParticular.

Note – The screncapture software I used caps the video at 22fps – it actually runs faster – usually at around 35 odd FPS and is much smoother than the video appears. I’ve also optimised a little since I recorded the video.

Here’s a few stills i’ve taken from the app –
View the set on Flickr

I really enjoyed using WebGL – but a major drawback is varying browser support and speed. WebGL should be supported by all the latest browsers – although for me, the only browser that ran it at a good framerate was Chrome.


Table from – http://findmebyip.com/litmus

Try it out for yourself – http://lawriecape.co.uk/threejs

The first version you see in the video is a demo file included with Three.js. Not being overly familiar with JS, I used this as my starting point. I’ve put the demo up for you to see too – it’s the “Original Mr Doob demo version”.
In the demo – canvas_lines.html – white particles are placed in a 3d scene and connected by a line.

I built upon this, to set the particle positions using some basic 3d maths, adding some varibles, a GUI and some colour options.
The basic maths used for positioning the particles is based on a question from stackoverflow.com – Plotting points round a sphere

The forula given was –

x = r * cos(s) * sin(t)
y = r * sin(s) * sin(t)
z = r * cos(t)
here, s is the angle around the z-axis, and t is the height angle, measured ‘down’ from the z-axis.

I gave this a go, but I’m not great with maths. I ended up assigning a variables to the following –

  • The initial T-Angle
  • The initial S-Angle
  • A step variable by which to increase the T-Angle by for each step in the loop
  • A step variable by which to increase the S-Angle by for each step in the loop

It’s probably terrible, ActionScript style JS but here’s the loop code I used to lay out the particles –

for ( var i = 1; i < = _numBalls; i ++ ) {
	particle = new THREE.Particle( material );
						
	_tAngle += _tStep;
	_sAngle += _sStep;
					
	particle.position.x = _radius * Math.cos(_sAngle) * Math.sin(_tAngle)
	particle.position.y = _radius * Math.sin(_sAngle) * Math.sin(_tAngle)
	particle.position.z = _radius * Math.cos(_tAngle)
					
	particle.position.normalize();
	particle.position.multiplyScalar( Math.random() * 10 + _radius );
	particle.scale.x = particle.scale.y = 5;

	geometry.vertices.push( new THREE.Vertex( particle.position ) );
	lGeometry.vertices.push( new THREE.Vertex( particle.position ) );

}

Right hand side - What's DAT

For a nice, quick interface for altering varibles and calling functions, I used the brilliant DAT.GUI.
It's a brilliant tool that will sit in with other UI frameworks I love using, such as MinimalComps (for Flash) and ControlP5 for Processing).

I also added in some keyboard shorcuts - indicated by a letter in brackets on the GUI menu.

Pro tips

  • Q decreases the radius
  • W increases the radius
  • A decreases the color variable
  • S increases the color variable

Notes

You have to redraw the scene for changed varibale to take effect.

Randomising the scene clears any current objects.

"Redrawing" draws the scene again over the top of the current scene. So you can try decreasing the radius and color values, then redrawing, to add another copy of the structure inside itself!

The canvas version offers a ghosting effect - deselect "clear canvas" and select "ghosting" to see it in action. It gives the nice smooth, blur images seen in the Flickr Set. For some reason though, it removes the particles. I'd love to get the ghosting to work in the speedy WebGL version - but that's still a work in progress.

The canvas version's image export does not include the solid black background color.

TIP TOPS - Further development links

https://github.com/mrdoob/three.js/
http://www.aerotwist.com/lab/getting-started-with-three-js/
http://www.aerotwist.com/lab/ten-things-i-learned/
http://dataarts.github.com/dat.gui/

Phew! That turned out to be a rather long post - by my standards anyway.
If you get any nice screenshots, I'd love to see them. Or if you have any requests, or optimisation suggestions - get in touch.
Cheers!

SION AS3 Sound library instruments

Another quick – post. I’ve been playing with the great SION AS3 sound library lately, but couldn’t find a list of all the instruments available. So here they are – all the ones that worked for me at least –

var waveArray:Array = ["sine","saw","triangle8","triangle","square","noise","snoise","konami","ma1","beep","ramp","valsound.bass1","valsound.bell1","valsound.brass1","valsound.guitar1","valsound.lead1","valsound.percus1","valsound.piano1","valsound.se1","valsound.special1","valsound.strpad1","valsound.wind1","valsound.world1","midi.piano1","midi.chrom1","midi.organ1","midi.guitar1","midi.bass1","midi.strings1","midi.ensemble1","midi.brass1","midi.reed1","midi.pipe1","midi.lead1","midi.pad1","midi.fx1","midi.world1","midi.percus1","midi.se1","midi.drum24"];

More details on SION here – http://sites.google.com/site/sioncenter/

And here’s a great Wonderfl demo of what you can do with SION –

SiON SoundObject Quartet – wonderfl build flash online

Musical Scales as midi note numbers.

For a couple of audio projects, I needed some nice musical scales, in midi note format (0-127). I couldn’t find a good list online, so had to knock together a quick script to trace some out. Here’s some that I’m using in a current project, and also the AS source to find any other ones you may want.

Using this PDF as a guide – http://8.brm.sk/Scales_Chords.pdf – enter the scale name, and the notes which are active in that scale. For example, the first scale in the image below, the Adonai Malakh (Israel), we would set the code as –

//Enter scale name - 
var scaleName:String="AdonaiMalakh(Israel)";
//Enter the notes in each octave to use - see - http://8.brm.sk/Scales_Chords.pdf
var acceptedArray:Array = [0,1,2,3,5,7,9,10];

Which would give us the output –

var AdonaiMalakh(Israel):Array =[48,49,50,51,53,55,57,58,60,61,62,63,65,67,69,70,72,73,74,75,77,79,81,82,84,85,86,87,89,91,93,94];

//Enter scale name - 
var scaleName:String="Zirafkend";
//Enter the notes in each octave to use - see - http://8.brm.sk/Scales_Chords.pdf
var acceptedArray:Array = [0,2,3,5,7,8,9,11];

var octaveStep:int=0;
var finalArray:Array = [];

//You can limit the scales to any length - it will retrieve the middle section of the scale.
var limitArray:int = 32;

for(var i:int=0;i<12;i++){
	for(var j:int=0;j

And here's some scales. These are chopped to the middle 32 notes in each sequence.

var BiYu:Array =[24,27,31,34,36,39,43,46,48,51,55,58,60,63,67,70,72,75,79,82,84,87,91,94,96,99,103,106,108,111,115,118];
var Blues:Array =[41,42,43,46,48,51,53,54,55,58,60,63,65,66,67,70,72,75,77,78,79,82,84,87,89,90,91,94,96,99,101,102];
var BluesDiminished:Array =[48,49,51,52,54,55,56,58,60,61,63,64,66,67,68,70,72,73,75,76,78,79,80,82,84,85,87,88,90,91,92,94];
var Dorian:Array =[25,27,30,32,34,37,39,42,44,46,49,51,54,56,58,61,63,66,68,70,73,75,78,80,82,85,87,90,92,94,97,99];
var FullMinor:Array =[51,53,55,56,57,58,59,60,62,63,65,67,68,69,70,71,72,74,75,77,79,80,81,82,83,84,86,87,89,91,92,93];
var HarmonicMajor:Array =[44,47,48,50,52,53,55,56,59,60,62,64,65,67,68,71,72,74,76,77,79,80,83,84,86,88,89,91,92,95,96,98];
var Hawaiian:Array =[39,43,45,47,48,50,51,55,57,59,60,62,63,67,69,71,72,74,75,79,81,83,84,86,87,91,93,95,96,98,99,103];
var IonianSharp5:Array =[45,47,48,50,52,53,56,57,59,60,62,64,65,68,69,71,72,74,76,77,80,81,83,84,86,88,89,92,93,95,96,98];
var JazzMinor:Array =[45,47,48,50,51,53,55,57,59,60,62,63,65,67,69,71,72,74,75,77,79,81,83,84,86,87,89,91,93,95,96,98];
var Lydian:Array =[45,47,48,50,52,54,55,57,59,60,62,64,66,67,69,71,72,74,76,78,79,81,83,84,86,88,90,91,93,95,96,98];
var Major:Array =[43,45,48,50,51,52,54,55,57,60,62,63,64,66,67,69,72,74,75,76,78,79,81,84,86,87,88,90,91,93,96,98];
var Mixolydian:Array =[45,46,48,50,52,53,55,57,58,60,62,64,65,67,69,70,72,74,76,77,79,81,82,84,86,88,89,91,93,94,96,98];
var Oriental:Array =[45,46,48,49,52,53,54,57,58,60,61,64,65,66,69,70,72,73,76,77,78,81,82,84,85,88,89,90,93,94,96,97];
var SuperLocrian:Array =[44,46,48,49,51,52,54,56,58,60,61,63,64,66,68,70,72,73,75,76,78,80,82,84,85,87,88,90,92,94,96,97];
var VerdiEnigmaticAscending:Array =[46,47,48,49,52,54,56,58,59,60,61,64,66,68,70,71,72,73,76,78,80,82,83,84,85,88,90,92,94,95,96,97];
var Zirafkend:Array =[48,50,51,53,55,56,57,59,60,62,63,65,67,68,69,71,72,74,75,77,79,80,81,83,84,86,87,89,91,92,93,95];

I've also put the code up on Wonderfl - so you can use it straight from your browser.

UK Postcode Validation in AS3

Let me file this post under boring but useful. Recently I worked on a project where I had to validate UK postcodes in flash. The method I used in the end was a regular expression I found, with a bit of formatting logic added. This should work for all UK postcodes.

var validPostcode:String   =  "LS12ED";
var validPostcode2:String   = "LS1 2ED";
var invalidPostcode:String =  "NOTAPOSTCODE";

trace(validateUkPostcode(validPostcode));//true
trace(validateUkPostcode(validPostcode2));//true
trace(validateUkPostcode(invalidPostcode));//false

function validateUkPostcode(str:String):Boolean {
	
	if(str.indexOf(" ")==-1){
		trace("Adding postcode space");
		var l:int = str.length;
		str=str.substr(0,l-3)+" "+str.substr(l-3,l);
	}

	var pattern:RegExp =  /[A-Z]{1,2}[0-9R][0-9A-Z]? [0-9][ABD-HJLNP-UW-Z]{2}/i

	var result:Object = pattern.exec(str);
	if(result == null) {
		return false;
	} else {
		return true;
	}
}

Constellation – A flocking experiment

This week I saw Flight 404’s “Swarm Behavior” on vimeo –

Swarm behavior from flight404 on Vimeo.

Yet another awesome video from Robert, using his Cinder framework. Cool!
In the description, he mentions the particle’s behavioral rules –

1) If I am far away from my neighbors, move towards them.
2) If I am too close to my neighbors, move away from them.
3) If I am neither too close or too far from my neighbors, move with them.

So I decided to try and implement these rules in Flash. The idea changed a few times, and ended up looking like an interactive constellation app. So here it is! It’s still pretty rough around the edges – but I’m quite pleased with how it’s developing.

See and edit the code over on Wonderfl

CODE

package{

	[SWF(width="465", height="465", frameRate="50")]

	import flash.events.*;
	import flash.events.KeyboardEvent;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.DisplayObject;
	import flash.display.MovieClip;
	import flash.display.Stage;
	import flash.geom.Point;
	import flash.filters.BlurFilter;
	import flash.filters.ColorMatrixFilter;
	import flash.filters.ConvolutionFilter;
	import flash.filters.GlowFilter
	import flash.text.TextField;
	import com.bit101.components.*;
	
 	public class BFCWonderfl extends MovieClip {
		
		//Vars - 
		//You can play with these ones - 
		private var numBoids:int=20;
		//Boids closer than this will feel crowded!
		private var minDist:int=90;
		//Boid further away than another than this will be lonely!
		private var maxDist:int=100;
		//This is how fast the boids can move. TOP SPEED!
		private var maxSpeed:Number = 3;
		//This is how much boids influence each other - lower number = more influence
		private var divideBy:int=100;
		//Shall the boids slow down with friction?
		private var enableFriction:Boolean = true;
		private var friction:Number = 0.9;
		//Settle down - if they're not too close and not too far away from their friends - they'll just stop
		private var settleDown:Boolean = false;
		
		private var boidColour:uint = 0xffffff;
		private var lineColour:uint = 0xffffff;
		private var bgColour:uint   = 0x000033;
		
		private var glow:GlowFilter = new GlowFilter();
		private var glowColour:uint = 0x99ffff;

		//But you should probably leave these as is - 
		private var boidArray:Array=[];
		private var menuHeight:Number = 85;
		private var doMidi:Boolean = false;
		private var BMD:BitmapData = new BitmapData(stage.stageWidth,stage.stageHeight-menuHeight, false, bgColour);
		private var BF:BlurFilter = new BlurFilter(5,5,1);
		private var Bit:Bitmap = new Bitmap(BMD);
		private var bitHolder:MovieClip = new MovieClip;
		private var ballHolder:MovieClip = new MovieClip;
		private var isMouseDown:Boolean = false;
		private var TB:TextField = new TextField();
		private var myCheckBox1:CheckBox;
		private var myCheckBox2:CheckBox;
		private var mySlider1:HSlider;
		private var mySlider2:HSlider;
		//Boid Flock Class
		public function BFCWonderfl():void{
			trace("Init!");
			addChild(bitHolder);
			bitHolder.addChild(Bit);
			addChild(ballHolder);
			
			glow.color = glowColour;
			glow.alpha = .5;
			glow.blurX = 5;
			glow.blurY = 5;
			
			ballHolder.filters = [glow];
			
			addChild(TB);
			TB.y = 0;
			TB.width = stage.stageWidth-10;
			TB.height = 75;
			TB.selectable = false;
			TB.text = "Constellation by Lawrie\nhttp://www.LawrieCape.co.uk\nSpace to add a boid at mouse - click to attract - any other key to reset.\n\nADD MINIMAL COMPS HERE - MIN MAX sliders and Friction Settle checkboxes";
			
			myCheckBox1 = new CheckBox(this,5,50,"Friction?",updateVals);
			myCheckBox1.selected = true;
			myCheckBox2 = new CheckBox(this,5,70,"Settle?",updateVals);
			var sLabel:Label  = new Label(this,75,45,"Min size");
			var sLabel2:Label = new Label(this,75,65,"Max size");
				
			mySlider1 = new HSlider(this,125,50,updateVals);
			mySlider2 = new HSlider(this,125,70,updateVals);
			
			mySlider1.setSliderParams(1,200,75);
			mySlider2.setSliderParams(1,200,100);
			
			addChild(myCheckBox1);
			addChild(myCheckBox2);
			
			addChild(mySlider1);
			addChild(mySlider2);	
			
			bitHolder.y = menuHeight;
			ballHolder.y = menuHeight;
			ballHolder.mouseEnabled  = false;
			ballHolder.mouseChildren = false;
			bitHolder.alpha=1;
			
			BMD.fillRect(BMD.rect, bgColour);

			bitHolder.addEventListener(MouseEvent.MOUSE_DOWN, mouseIsDown);
			bitHolder.addEventListener(MouseEvent.MOUSE_UP,   mouseIsNotDown);			
			stage.addEventListener(KeyboardEvent.KEY_UP, makeASingleBoid);

			addEventListener(Event.ENTER_FRAME, updateBoids);
			//makeBoids(numBoids);
		}
		
		private function makeASingleBoid(e:KeyboardEvent):void{
			if(e.keyCode==32){
				makeBoids(1,mouseX,mouseY);
			}
			else{
				reset();
			}
		}
		
		private function mouseIsDown(e:Event):void{
			isMouseDown = true;
		}
		private function mouseIsNotDown(e:Event):void{
			isMouseDown = false;
		}
		
		private function makeBoids(makeXBoids:int, bX:Number = 0, bY:Number = 0):void {
			for (var i:int=0; imaxDist) {
					B.speedX-=B.distToB2X/divideBy;
					B.speedY-=B.distToB2Y/divideBy;
				}
				//2) If I am too close to my neighbors, move away from them.
				else if (B.distToB2stage.stageWidth){
					B.x=stage.stageWidth-B.speedX;
					B.speedX*=-friction;
				}
				else if(B.x<0){
					B.x=-B.speedX;
					B.speedX*=-friction;
				}
				if (B.y>(stage.stageHeight-menuHeight)){
					B.y=(stage.stageHeight-menuHeight)-B.speedY;
					B.speedY*=-friction;
				}
				else if(B.y<0){
					B.y=-B.speedY;
					B.speedY*=-friction;
				}
				
				//Limit them to a top speed - 
				if(B.speedX>maxSpeed){
					B.speedX = maxSpeed;
				}
				else if(B.speedX< -maxSpeed){
					B.speedX=-maxSpeed;
				}
				if(B.speedY>maxSpeed){
					B.speedY = maxSpeed;
				}
				else if(B.speedY< -maxSpeed){
					B.speedY=-maxSpeed;
				}		
				
				//Add friction?
				if(enableFriction){
					B.speedX*=friction;
					B.speedY*=friction;		
				}
				
				//Move them 
				B.x+=B.speedX;
				B.y+=B.speedY;
			
			}
			//Draw into the BMD

			BMD.draw(ballHolder);
			//Apply a blur?
			BMD.applyFilter(BMD,BMD.rect,new Point(0,0),BF);
		}
		
		private function reset():void{
			removeEventListener(Event.ENTER_FRAME, updateBoids);
			for (var i:int=0; i