forked from: Oil Slick
/**
* Copyright bradsedito ( http://wonderfl.net/user/bradsedito )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/xviH
*/
// forked from yonatan's Oil Slick
// forked from makc3d's BeatPort + Metaballs 3D
// 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.geom.Matrix;
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 skyBmd:BitmapData = new BitmapData(465, 232, false, 0);
private var sea:Sea = new Sea(skyBmd);
private var waves:Wave2;
private var player:BeatPortPlayer;
public function MetaBalls3D () {
stage.quality = "medium";
BeatPortPlayer (player = new BeatPortPlayer).play (18);
waves = new Wave2;
waves.w1 = new Wave;
waves.w2 = new Wave;
//graphics.beginFill (0x202020);
graphics.beginGradientFill("linear",[0x202020,0x181818,0x101010,0x080808],[1,1,1,1],[0,64,128,255],new Matrix(0.0001,0.0977,-0.1416,0.0001,80.0000,116.0000),"pad","rgb",0);
graphics.drawRect (0, 0, 465, 465);
addChild (sea);
sea.y = 232;
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 ());
m.Ty = -2/3;
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);
}
// update sea
skyBmd.fillRect(skyBmd.rect, 0);
skyBmd.draw(canvas, new Matrix(1,0,0,1,232,150));//32+30));
//sea.amp = 10 + 40 * (player.channel.leftPeak + player.channel.rightPeak);
sea.update();
}
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.display.*;
import flash.filters.*;
import flash.geom.*;
import flash.utils.*;
class Sea extends Sprite {
private const nearScale:Number = 0.1;
private var vertices:Vector.<Number> = new Vector.<Number>;
private var uvt:Vector.<Number> = new Vector.<Number>;
private var indices:Vector.<int> = new Vector.<int>;
private const octaves:int = 3;
private const seed:int = 1;
private var offsets:Array = [];
private var output:BitmapData = new BitmapData(465, (465+1)/2, false);
private var noise:BitmapData = new BitmapData(465/2, (465+3)/4, true);
private var upsideDown:BitmapData = new BitmapData(465, (465+1)/2, true);
private var gradient:BitmapData = new BitmapData(465, (465+1)/2, false);
private var seaShape:Shape = new Shape;
private var seaBmd:BitmapData = new BitmapData(465, (465+1)/2, true);
private var seaBmp:Bitmap;
private var ct:ColorTransform = new ColorTransform(1.5, 0.8, 0.75, 1, -128, 48, 64);
private const seaMtx:Matrix = new Matrix(1, 0, 0, 1, 0, 0);
private const upsideDownMtx:Matrix = new Matrix(1, 0, 0, -1, 0, 465/2);
private var dispFilter:DisplacementMapFilter = new DisplacementMapFilter(seaBmd, null, 0, 8, 0, 150, "clamp");
private var backdrop:BitmapData;
private var cachedOctaves:Array = []; // precalculated perlin noise
public function Sea(backdrop:BitmapData) {
vertices.push(0, 0, 465-1, 0, 0, 465/2-1, 465-1, 465/2-1);
uvt.push(
0, 0, nearScale,
1, 0, nearScale,
(1-nearScale)/2, 1, 1,
(1+nearScale)/2, 1, 1);
indices.push(0, 1, 2, 2, 1, 3);
this.backdrop = backdrop;
seaBmp = new Bitmap(seaBmd);
initNoise();
addChild(new Bitmap(output));
var s:Shape = new Shape;
s.graphics.beginGradientFill("linear",[0x808080,0xc0c0c0,0xe0e0e0,0xffffff],[1,1,1,1],[0,64,128,255],new Matrix(0.0001,0.0977,-0.1416,0.0001,80.0000,116.0000),"pad","rgb",0);
s.graphics.drawRect (0, 0, 465, 465);
gradient.draw(s);
}
private function initNoise():void {
for(var i:int = 0; i < octaves; i++) {
var bmd:BitmapData = new BitmapData(465/2, (465+3)/4, true, 0);
var pr:Number = 1/Math.pow(2, i);
bmd.perlinNoise(24*pr, 2*pr, 1, 0, true, true, 0xF, false);
pr /= 2;
bmd.colorTransform(bmd.rect, new ColorTransform(pr, pr, pr, pr));
cachedOctaves.push(bmd);
}
}
private function fastPerlin(dst:BitmapData, xOffsets:Array):void {
dst.fillRect(dst.rect, 0);
for(var i:int = 0; i < octaves; i++) {
var offset:int = xOffsets[i];
if(offset >= 0) {
offset %= dst.width;
} else {
offset = dst.width - (-offset % dst.width);
}
var mtx:Matrix = new Matrix;
mtx.tx = offset;
dst.draw(cachedOctaves[i], mtx, null, "add");
mtx.tx = offset - dst.width;
dst.draw(cachedOctaves[i], mtx, null, "add");
}
}
public function update():void {
for(var i:int = 0; i < octaves; i++) offsets[i] = ((i&1)*2-1) * (1+i)*getTimer()/150;
fastPerlin(noise, offsets);
seaShape.graphics.clear();
seaShape.graphics.beginBitmapFill(noise, null, false, true);
seaShape.graphics.drawTriangles(vertices, indices, uvt);
seaShape.graphics.endFill();
seaBmd.fillRect(seaBmd.rect, 0);
seaBmd.draw(seaShape, seaMtx);
// reflection
upsideDown.fillRect(upsideDown.rect, 0);
upsideDown.draw(backdrop, new Matrix(1, 0, 0, -1, 0, 465/2), new ColorTransform(0.25,0.25,0.25));
upsideDown.draw(seaBmd, null, null, "add");
output.applyFilter(upsideDown, upsideDown.rect, upsideDown.rect.topLeft, dispFilter);
output.draw(gradient, null, null, "multiply");
}
}
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 ();
}
}