In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

BeatPort + Metaballs 3D

Ok, looks like I don't have right idea to improve this,
so maybe someone of you, oh wonderfl people, will..?

P.S. click anywhere for next track.
Get Adobe Flash player
by makc3d 22 May 2011
/**
 * Copyright makc3d ( http://wonderfl.net/user/makc3d )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/ffFX
 */

// forked from makc3d's Metaballs 3D
package {
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Loader;
	import flash.display.LoaderInfo;
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	import flash.geom.Vector3D;
	import flash.media.SoundMixer;
	import flash.net.URLRequest;
	import flash.system.LoaderContext;
	import flash.utils.ByteArray;
	import flash.utils.getTimer;
	[SWF(width=465,height=465)]
	/**
	 * Ok, looks like I don't have right idea to improve this,
	 * so maybe someone of you, oh wonderfl people, will..?
	 * 
	 * P.S. click anywhere for next track.
	 */
	public class MetaBalls3D extends Sprite {
		private var texture:BitmapData;
		private var sphere:BauerSphere;
		private var metaballs:MetaBalls;
		private var canvas:Shape;
		private var waves:Wave2;
		private var player:BeatPortPlayer;

		public function MetaBalls3D () {
			BeatPortPlayer (player = new BeatPortPlayer).play (18);

			waves = new Wave2;
			waves.w1 = new Wave;
			waves.w2 = new Wave;

			graphics.beginFill (0);
			graphics.drawRect (0, 0, 465, 465);

			addChild (canvas = new Shape);
			canvas.x = 465 / 2;
			canvas.y = 465 / 2;

			texture = new BitmapData (67, 68, false, 0);
			var loader:Loader = new Loader;
			loader.contentLoaderInfo.addEventListener (Event.COMPLETE, gotImage);
			loader.load (
				new URLRequest ("http://assets.wonderfl.net/images/related_images/d/dc/dc74/dc7487fc4b520bab015e52601a11101ec283167d"),
				new LoaderContext (true)
			);

			sphere = new BauerSphere;
			sphere.debug = false;
			sphere.build (2000);

			metaballs = new MetaBalls;
			metaballs.a = 0;

			addEventListener (Event.ENTER_FRAME, draw);

			stage.addEventListener (MouseEvent.CLICK, nextTrack);
		}

		private function nextTrack (e:MouseEvent):void {
			player.next ();
		}

		private function gotImage (e:Event):void {
			texture.draw (LoaderInfo (e.target).content);
		}

		private var m:Matrix4 = new Matrix4;
		private var s:Number = 0;
		private var ba:ByteArray = new ByteArray;
		private function draw (e:Event):void {
			// animate waves
			waves.w1.P = 5e-3 * getTimer ();
			waves.w2.P = waves.w1.P;

			// update wave amplitudes based on sound
			var aLt:Number = 0, aLt2:Number = 0;
			var aRt:Number = 0, aRt2:Number = 0;
			if (!SoundMixer.areSoundsInaccessible ()) {
				var i:int;
				SoundMixer.computeSpectrum (ba, true);

				// left
				ba.position = 0;
				for (i = 0; i < 64; i++) {
					aLt += ba.readFloat ();
				}
				for (i = 64; i < 128; i++) {
					aLt2 += ba.readFloat ();
				}
				aLt /= 100;
				aLt2 /= 100;

				// right
				ba.position = 4 * 256;
				for (i = 0; i < 64; i++) {
					aRt += ba.readFloat ();
				}
				for (i = 64; i < 128; i++) {
					aRt2 += ba.readFloat ();
				}
				aRt /= 100;
				aRt2 /= 100;

				waves.w1.A = maxOrMix (waves.w1.A, 0.1 * aLt); waves.w1.A2 = maxOrMix (waves.w1.A2, 10 * aLt2);
				waves.w2.A = maxOrMix (waves.w2.A, 0.1 * aRt); waves.w2.A2 = maxOrMix (waves.w2.A2, 10 * aRt2);
			}

			// swing balls
			metaballs.a = 1 + Math.sin (2.1e-3 * getTimer ());

			// rotate scene
			m.rotate (3.3e-4 * getTimer ());

			var ranges:Vector.<Range> = metaballs.ranges ();
			if (ranges.length > 1) {
				// render sphere twice
				if (m.Zz < 0) {
					morphSphere (ranges [0], waves.w1);

					sphere.transformVertices (m);
					sphere.render (canvas.graphics, texture);

					morphSphere (ranges [1], waves.w2);

					sphere.transformVertices (m);
					sphere.render (canvas.graphics, texture, false);
				} else {
					morphSphere (ranges [1], waves.w2);

					sphere.transformVertices (m);
					sphere.render (canvas.graphics, texture);

					morphSphere (ranges [0], waves.w1);

					sphere.transformVertices (m);
					sphere.render (canvas.graphics, texture, false);
				}
			} else {
				// render sphere once
				morphSphere (ranges [0], waves);

				sphere.transformVertices (m);
				sphere.render (canvas.graphics, texture);
			}
		}

		private function maxOrMix (a0:Number, a1:Number):Number {
			if (a0 < a1) return a1;
			return 0.9 * a0 + 0.1 * a1;
		}

		private var p:Point = new Point, q:Vector3D = new Vector3D;
		private function morphSphere (range:Range, wave:Wave):void {

			var aa:Number = (metaballs.a - 4 / 3) / 0.3;
			var cc:Number = 1.6 / (1 + aa * aa) - 0.8; // 0 to 0.8 to 0 when aa is -1 to 0 to +1

			sphere.resetVertices ();
			var i:int, L:int = sphere.tvertices.length;
			for (i = 0; i < L; i++) {
				var v:Vector3D = sphere.tvertices [i];
				p.x = v.x; p.y = v.y;
				var vz:Number = v.z;
				if (cc > 0) {
					// to get better triangulation near a = 4/3 map z to 0.2*z*|z| + 0.8*z
					if (vz < 0) {
						vz = (1 - cc) * vz - cc * vz * vz;
					} else {
						vz = (1 - cc) * vz + cc * vz * vz;
					}
				}
				var t:Number = 0.5 + 0.5 * vz;
				// shape as metaball
				v.z = range.x (t);
				p.normalize (metaballs.y (v.z));
				v.x = p.x; v.y = p.y;
				// metaball normal
				p.normalize (1);
				q.x = p.x; q.y = p.y; q.z = -metaballs.y1 ();
				q.normalize ();
				// apply wave modifier
				q.scaleBy (wave.f (t));
				v.incrementBy (q);
			}
		}
	}
}

class Wave {
	public var A:Number = 0, A2:Number = 0;
	public var P:Number = 0;
	public function f (t:Number):Number {
		var X:Number = 2 * t - 1;
		var Y:Number = ((X < 1) && (X > -1)) ? 0.5 + 0.5 * X * X : ((X > 0) ? X : -X);
		var S:Number = Math.sin (10 * Y - P) / Y;
		var C:Number = Math.cos (10000 * X);
		return A * S * S * (1 + A2 * C * C);
	}
}

class Wave2 extends Wave {
	public var w1:Wave;
	public var w2:Wave;
	override public function f (t:Number):Number {
		if (t < 0.5) return w1.f (2 * t);
		return w2.f (2 * t - 1);
	}
}

import flash.display.BitmapData;
import flash.display.Graphics;
import flash.display.TriangleCulling;
import flash.geom.Vector3D;

class Matrix4 {
	public var Xx:Number = 1, Yx:Number = 0, Zx:Number = 0, Tx:Number = 0;
	public var Xy:Number = 0, Yy:Number = 1, Zy:Number = 0, Ty:Number = 0;
	public var Xz:Number = 0, Yz:Number = 0, Zz:Number = 1, Tz:Number = 5;

	public function rotate (a:Number):void {
		Xx =  Math.cos (a); Xz = Math.sin (a);
		Zx = -Math.sin (a); Zz = Math.cos (a);
	}
}

class BauerSphere {
	public var debug:Boolean;

	// original
	public var overtices:Vector.<Vector3D>;
	// transformed
	public var tvertices:Vector.<Vector3D>;

	// face normals
	public var faceNormals:Vector.<Vector3D>;
	// face map
	public var faceMap:Vector.<Vector.<int>>;

	// faces
	public var faceList1:Vector.<int>;
	public var faceList2:Vector.<int>;
	public var xy:Vector.<Number>;
	public var uv:Vector.<Number>;

	public function addFace (a:int, b:int, c:int):void {
		var L:int = faceNormals.length;
		faceMap [a].push (L);
		faceMap [b].push (L);
		faceMap [c].push (L);
		faceNormals.push (new Vector3D);
		faceList1.push (a, b, c);
		faceList2.unshift (c, a, b);
	}

	public function build (N:int = 456):void {
		overtices = new Vector.<Vector3D> (N, true);
		tvertices = new Vector.<Vector3D> (N, true);

		faceNormals = new <Vector3D> [];
		faceMap = new <Vector.<int>> [];

		faceList1 = new <int> [];
		faceList2 = new <int> [];
		xy = new Vector.<Number> (2 * N, true);
		uv = new Vector.<Number> (2 * N, true);

		// uniformly distributed directions:
		// Bauer, Robert, "Distribution of Points on a Sphere with Application to Star Catalogs",
		// Journal of Guidance, Control, and Dynamics, January-February 2000, vol.23 no.1 (130-137).
		var radius:Number = 1;
		for (var i:int = 1; i <= N; i++) {
			var phi:Number = Math.acos ( -1 + (2 * i -1) / N);
			var theta:Number = Math.sqrt (N * Math.PI) * phi;

			var rxy:Number = radius * Math.sin (phi);
			overtices [i - 1] = new Vector3D ( -rxy * Math.sin (theta), rxy * Math.cos (theta), radius * Math.cos (phi) );

			tvertices [i - 1] = overtices [i - 1].clone ();
			faceMap [i - 1] = new <int> [];
		}

		// make faces (and edges), 1st and last by hand
		addFace (0, 1, 2);

		var lastEdgeA:int = 0;
		var lastEdgeB:int = 2;

		while ((lastEdgeB < N - 1) || (lastEdgeA < N - 3)) {
			var vA:Vector3D = tvertices [lastEdgeA];
			var vB:Vector3D = tvertices [lastEdgeB];
			var vA1:Vector3D = tvertices [lastEdgeA + 1];
			var vB1:Vector3D; if (lastEdgeB < N - 1) vB1 = tvertices [lastEdgeB + 1];

			var canIncA:Boolean = (lastEdgeA < lastEdgeB - 2) &&
				(lastEdgeA < N - 3) &&
				// only if B-A-A1 angle < 90°
				(vB.subtract (vA).dotProduct (vA1.subtract (vA)) > 0);

			var canIncB:Boolean = (vB1 != null) &&
				(lastEdgeB < N - 1) && 
				// only if B1-B-A angle < 90°
				(vB1.subtract (vB).dotProduct (vA.subtract (vB)) > 0);

			if (!(canIncA || canIncB)) break;

			if (  canIncA && canIncB ) {
				// prefer shortest edge
				canIncA = (
					vB1.subtract (vA).lengthSquared > vA1.subtract (vB).lengthSquared
				);
			}

			if (canIncA) {
				// add face A-B-A1
				addFace (lastEdgeA, lastEdgeB, lastEdgeA + 1);

				// inc A
				lastEdgeA++;
			} else {
				// add face A-B-B1
				addFace (lastEdgeA, lastEdgeB, lastEdgeB + 1);

				// inc B
				lastEdgeB++;
			}
		}

		// last face
		addFace (N - 1, N - 2, N - 3);

		if (debug) trace ("Sphere stats:", tvertices.length, "vertices,", faceNormals.length, "faces");
	}

	public function resetVertices ():void {
		var N:int = overtices.length;
		for (var i:int = 0; i < N; i++) {
			var t:Vector3D = tvertices [i];
			var o:Vector3D = overtices [i];
			t.x = o.x;
			t.y = o.y;
			t.z = o.z;
		}
	}

	public function transformVertices (m:Matrix4):void {
		var N:int = overtices.length;
		for (var i:int = 0; i < N; i++) {
			var t:Vector3D = tvertices [i];
			var x:Number = t.x;
			var y:Number = t.y;
			var z:Number = t.z;
			t.x = m.Xx * x + m.Yx * y + m.Zx * z + m.Tx;
			t.y = m.Xy * x + m.Yy * y + m.Zy * z + m.Ty;
			t.z = m.Xz * x + m.Yz * y + m.Zz * z + m.Tz;
		}

		// update face normals
		N = faceNormals.length;
		for (i = 0; i < N; i++) {
			var i3:int = i * 3;
			var a:Vector3D = tvertices [faceList1 [i3]];
			var b:Vector3D = tvertices [faceList1 [i3 + 1]];
			var c:Vector3D = tvertices [faceList1 [i3 + 2]];
			var x1:Number = b.x - a.x, y1:Number = b.y - a.y, z1:Number = b.z - a.z;
			var x2:Number = c.x - a.x, y2:Number = c.y - a.y, z2:Number = c.z - a.z;
			t = faceNormals [i];
			t.x = y1 * z2 - y2 * z1;
			t.y = z1 * x2 - z2 * x1;
			t.z = x1 * y2 - x2 * y1;
		}
	}

	public var normal:Vector3D = new Vector3D;
	public function render (gfx:Graphics, bd:BitmapData, clear:Boolean = true):void {
		var focalLength:Number = 400;
		var N:int = tvertices.length;
		for (var i:int = 0; i < N; i++) {
			var j:int = 2 * i;
			var t:Vector3D = tvertices [i];

			// project
			var zoom:Number = focalLength / t.z;
			xy [j]     = t.x * zoom;
			xy [j + 1] = t.y * zoom;

			// estimate vertex normals
			normal.scaleBy (0);
			var map:Vector.<int> = faceMap [i];
			var mapL:int = map.length;
			for (var k:int = 0; k < mapL; k++) {
				normal.incrementBy (faceNormals [map [k]]);
			}
			normal.normalize ();

			// env. mapping
			uv [j]     = 0.5 + 0.5 * normal.x;
			uv [j + 1] = 0.5 + 0.5 * normal.y;
		}

		if (clear) gfx.clear ();
		if (debug) gfx.lineStyle (0, 0xFF0000);
		gfx.beginBitmapFill (bd, null, true, true);
		gfx.drawTriangles (xy,
			// half-assed distance sorting
			(tvertices[1].z > tvertices[tvertices.length - 2].z) ? faceList1 : faceList2,
		uv, TriangleCulling.POSITIVE);
	}
}

class Range {
	public var a:Number;
	public var b:Number;
	public function Range (from:Number = 0, to:Number = 0) {
		a = from; b = to;
	}
	public function x (t:Number):Number {
		return a + (b - a) * t;
	}
}

/**
 * Metaballs calculator.
 * @see http://wonderfl.net/c/1TYy/read
 */
class MetaBalls {
	private var a1:Number, a2:Number, b2:Number;
	private var d1:Number, d2:Number;

	public function get a ():Number {
		return a1;
	}
	public function set a (A:Number):void {
		a1 = A;
		a2 = A * A;
		b2 = 1 + (A + a2) / 4; b2 *= b2;
		d1 = 3 * a2 - A - 4;
		d2 = 5 * a2 + A + 4;
	}

	private var x1:Number, x2:Number;
	private var s1:Number, s2:Number;

	public function y (x:Number):Number {
		x1 = x;
		x2 = x * x;
		s1 = Math.sqrt (Math.max (0, 4 * a2 * x2 + b2));
		s2 = Math.sqrt (Math.max (0, s1 - a2 - x2));
		return s2;
	}

	/** 1st derivative, dy/dx. y(x) is expected to be called 1st. */
	public function y1 ():Number {
		return (2 * a2 * x1 / s1 - x1) / s2;
	}

	private var r1:Vector.<Range> = new <Range> [ new Range ];
	private var r2:Vector.<Range> = new <Range> [ new Range, new Range ];

	public function ranges ():Vector.<Range> {
		var S2:Number = 0.5 * Math.sqrt (d2);
		if (d1 > 0) {
			var S1:Number = 0.5 * Math.sqrt (d1);
			r2 [0].a = -S2; r2 [0].b = -S1;
			r2 [1].a =  S1; r2 [1].b =  S2;
			return r2;
		}
		r1 [0].a = -S2; r1 [0].b = S2;
		return r1;
	}
}

import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.media.Sound;
import flash.media.SoundChannel;
import flash.media.SoundLoaderContext;
import flash.net.URLLoader;
import flash.net.URLLoaderDataFormat;
import flash.net.URLRequest;

class BeatPortPlayer {
	private var gid:int;

	public function next ():void {
		if (channel) {
			channel.stop ();
			playRandomSample (new Event ("whatever"))
		}
	}

	/**
	 * Plays random samples by genre non-stop.
	 * @see http://api.beatport.com/catalog/genres?format=xml&v=1.0
	 */
	public function play (genreId:int):void {
		// 1st we need to get total number of tracks
		var loader:URLLoader = new URLLoader;
		loader.dataFormat = URLLoaderDataFormat.BINARY;
		subscribeLoader (loader, getCount, onIOFailure1);
		loader.load (new URLRequest (makeUrl (gid = genreId, 1)));
	}

	private function makeUrl (genreId:int, page:int):String {
		return "http://api.beatport.com/catalog/tracks?genreId=" + genreId + "&perPage=1&page=" + page + "&format=xml&v=1.0";
	}

	private var count:int, sound:Sound, channel:SoundChannel;
	private var context:SoundLoaderContext = new SoundLoaderContext (10, true);

	private function getCount (e:Event):void {
		var loader:URLLoader = URLLoader (e.target);
		unsubscribeLoader (loader, getCount, onIOFailure1);

		var result:XML = XML (loader.data);
		count = parseInt (result.result.@count);

		playRandomSample ();
	}

	private function playRandomSample (e:Event = null):void {
		if (e != null) {
			unsubscribeSoundStuff (); channel = null;
		}

		var loader:URLLoader = new URLLoader;
		loader.dataFormat = URLLoaderDataFormat.BINARY;
		subscribeLoader (loader, getRandomSampleUrl, onIOFailure2);
		loader.load (new URLRequest (makeUrl (gid, 1 + (count - 1) * Math.random ())));
	}

	private function getRandomSampleUrl (e:Event):void {
		var loader:URLLoader = URLLoader (e.target);
		unsubscribeLoader (loader, getRandomSampleUrl, onIOFailure2);

		var result:XML = XML (loader.data);

		channel = Sound (sound = new Sound (
			new URLRequest (result.result.document.track.@url), context
		)).play ();

		subscribeSoundStuff ();
	}

	private function subscribeLoader (loader:URLLoader, onComplete:Function, onIOFailure:Function):void {
		loader.addEventListener (Event.COMPLETE, onComplete);
		loader.addEventListener (IOErrorEvent.IO_ERROR, onIOFailure);
	}

	private function unsubscribeLoader (loader:URLLoader, onComplete:Function, onIOFailure:Function):void {
		loader.removeEventListener (Event.COMPLETE, onComplete);
		loader.removeEventListener (IOErrorEvent.IO_ERROR, onIOFailure);
	}

	private function subscribeSoundStuff ():void {
		sound.addEventListener (IOErrorEvent.IO_ERROR, onIOFailure3);
		channel.addEventListener (Event.SOUND_COMPLETE, playRandomSample);
	}

	private function unsubscribeSoundStuff ():void {
		sound.removeEventListener (IOErrorEvent.IO_ERROR, onIOFailure3);
		channel.removeEventListener (Event.SOUND_COMPLETE, playRandomSample);
	}

	private function onIOFailure1 (e:IOErrorEvent):void {
		unsubscribeLoader (URLLoader (e.target), getCount, onIOFailure1);
		play (gid);
	}

	private function onIOFailure2 (e:IOErrorEvent):void {
		unsubscribeLoader (URLLoader (e.target), getRandomSampleUrl, onIOFailure2);
		playRandomSample ();
	}

	private function onIOFailure3 (e:IOErrorEvent):void {
		// assumes sound's ioError comes before channel's soundComplete
		unsubscribeSoundStuff ();
		playRandomSample ();
	}
}