MetaDonuts

MetaDonuts, click to watch

The next MetaShape is the MetaDonut. Its like the metaballs, the MetaEllipses, and the MetaDiamonds but with donuts instead.

MetaDonuts are based off the pixel bender filter used in the previous MetaShape posts.

MetaDonuts are defined by the function:

M(x,y) = Radius1 / |Radius2 - sqrt( (x0-x)^2 + (y0-y)^2 )|

Where Radius1 is the outer radius. Radius2 is the inner radius. x0 and y0 are the donut’s coordinates.

The pixel bender filter looks like this:

<languageVersion : 1.0;>

kernel MetaDonuts
<   namespace : "com.rocketmandevelopment";
 vendor : "Rocketman Development";
 version : 1;
 description : "MetaDonuts";
>
{
parameter float minThreshold
<
 minValue:float(0.0);
 maxValue:float(5.0);
 defaultValue:float(3.0);
 description: "minThreshold";
>;

parameter float4 ball1
<
 minValue:float4(0.0,0.0,0.0,0.0);
 maxValue:float4(640.0,480.0,100.0,100.0);
 defaultValue:float4(50.0,50.0,50.0,50.0);
 description: "ball1, params, x,y,radius";
>;

parameter float4 ball2
<
 minValue:float4(0.0,0.0,0.0,0.0);
 maxValue:float4(640.0,480.0,100.0,100.0);
 defaultValue:float4(50.0,50.0,50.0,50.0);
 description: "ball2, params, x,y,radius";
>;

parameter float4 ball3
<
 minValue:float4(0.0,0.0,0.0,0.0);
 maxValue:float4(640.0,480.0,100.0,100.0);
 defaultValue:float4(50.0,50.0,50.0,50.0);
 description: "ball3, params, x,y,radius";
>;

parameter float4 ball4
<
 minValue:float4(0.0,0.0,0.0,0.0);
 maxValue:float4(640.0,480.0,100.0,100.0);
 defaultValue:float4(50.0,50.0,50.0,50.0);
 description: "ball4, params, x,y,radius";
>;

parameter float4 ball5
<
 minValue:float4(0.0,0.0,0.0,0.0);
 maxValue:float4(640.0,480.0,100.0,100.0);
 defaultValue:float4(50.0,50.0,50.0,55.0);
 description: "ball5, params, x,y,radius";
>;
 input image4 src;
 output pixel4 dst;

 void
 evaluatePixel()
 {
 dst = sampleNearest(src,outCoord());
 dst.rbg = float3(0,0,0);
 float2 coord = outCoord();
 float sum = 0.0;
 sum += ball1.z / (abs(ball1.w - sqrt( (ball1.x-coord.x)*(ball1.x-coord.x) + (ball1.y-coord.y)*(ball1.y-coord.y) )));
 sum += ball2.z / (abs(ball2.w - sqrt( (ball2.x-coord.x)*(ball2.x-coord.x) + (ball2.y-coord.y)*(ball2.y-coord.y) )));
 sum += ball3.z / (abs(ball3.w - sqrt( (ball3.x-coord.x)*(ball3.x-coord.x) + (ball3.y-coord.y)*(ball3.y-coord.y) )));
 sum += ball4.z / (abs(ball4.w - sqrt( (ball4.x-coord.x)*(ball4.x-coord.x) + (ball4.y-coord.y)*(ball4.y-coord.y) )));
 sum += ball5.z / (abs(ball5.w - sqrt( (ball5.x-coord.x)*(ball5.x-coord.x) + (ball5.y-coord.y)*(ball5.y-coord.y) )));
 if(sum >= minThreshold){
 dst.rgb = float3(100,75,96);
 }

 }
}

This one is a bit crazy compared to the others.

Just look at this line:

 sum += ball1.z / (abs(ball1.w - sqrt( (ball1.x-coord.x)*(ball1.x-coord.x) + (ball1.y-coord.y)*(ball1.y-coord.y))));

What is going on there? First, the balls are defined as float4 because they need 4 properties (the inner radius being the fourth). So ball1.w is the inner radius. Pixel Bender has odd properties. In a float4, which is like an array of 4 floating point number (4 is the longest pixel bender supports, remember no loops in pixel bender for flash). A float4 has x,y,z, and w properties. Its a bit different, but not too hard to remember.

Now using this in flash will be a bit different:


public class PixelBenderMetaballs extends Sprite {
[Embed(source='../MetaDonuts.pbj', mimeType='application/octet-stream')]//embedding the filter
private static const MetaballsFilter:Class;

private const canvas:BitmapData = new BitmapData(640,480,false,0);
private const blur:BlurFilter = new BlurFilter(2,2,1);
private var metaballsFilter:ShaderFilter;
private var b1:Metaball;
private var b:Bitmap;
private var b2:Metaball;
private var b3:Metaball;
private var b4:Metaball;
private var b5:Metaball;

public function PixelBenderMetaballs(){
super();
b = new Bitmap(canvas);
b.smoothing = true;
addChild(b);
var ba:ByteArray = new MetaballsFilter() as ByteArray;
var s:Shader = new Shader(ba);
metaballsFilter = new ShaderFilter(s);
metaballsFilter.shader.data.src.image = canvas;
b1 = new Metaball(100,100,10);
b2 = new Metaball(150,150,12);
b3 = new Metaball(200,200,10);
b4 = new Metaball(250,250,14);
b5 = new Metaball(300,300,9);
b1.stage = b2.stage = b4.stage = b5.stage = b3.stage = stage;
metaballsFilter.shader.data.minThreshold.value = [15.0];
metaballsFilter.shader.data.ball1.value = [b1.x,b1.y,b1.radius,50];//This is where it gets different. The fourth number is the inner radius
metaballsFilter.shader.data.ball2.value = [b2.x,b2.y,b2.radius,30];
metaballsFilter.shader.data.ball3.value = [b3.x,b3.y,b3.radius,80];
metaballsFilter.shader.data.ball4.value = [b4.x,b4.y,b4.radius,30];
metaballsFilter.shader.data.ball5.value = [b5.x,b5.y,b5.radius,90];
metaballsFilter.shader.precisionHint = ShaderPrecision.FAST;
b.filters = [metaballsFilter];
addEventListener(Event.ENTER_FRAME, ef);
}

private function ef(e:Event):void {
b1.update();
b2.update();
b3.update();
b4.update();
b5.update();
metaballsFilter.shader.data.ball1.value = [b1.x,b1.y,b1.radius,50];
metaballsFilter.shader.data.ball2.value = [b2.x,b2.y,b2.radius,30];
metaballsFilter.shader.data.ball3.value = [b3.x,b3.y,b3.radius,80];
metaballsFilter.shader.data.ball4.value = [b4.x,b4.y,b4.radius,30];
metaballsFilter.shader.data.ball5.value = [b5.x,b5.y,b5.radius,90];
b.filters = [metaballsFilter,blur];
}

}

There is one difference between this meteshape and all the others:

metaballsFilter.shader.data.ball5.value = [b5.x,b5.y,b5.radius,90];

The fourth number in this array is the inner radius. It defines how much of the middle is not drawn.

Click here to watch the MetaDonuts!