Metaballs

Metaballs

Metaballs came around in the 90s with the Demo Effect. What made metaballs so cool was their squishy look and melding into other nearby metaballs.

First I found a nice article and stuck it into as3. It turned out really slow (even after the suggested optimizations), but it worked and made some cool pictures.

A metaball, according to that article, is defined by the function:

F(x,y) = R / sqrt( (x-x0)^2 + (y-y0)^2)

Where x and y is any point on the stage, x0 and y0 is the location of the metaball, and R is the metaball’s radius. In order to make metaballs, I created a Metaball.as class.


package com.rocketmandevelopment.graphics.effects.metaballs {

public class Metaball {
public var radius:Number;
public var x:int;
public var y:int;

public function Metaball(x:Number,y:Number,radius:Number){
this.x = x; //set the x and y to assigned values
this.y = y;
this.radius = radius*radius; //set radius to radius squared for an optimization in the equasion
}

public function equation(tx:Number, ty:Number):Number {
//the important part of the class. It is the equation of a metaball
return radius/((x-tx)*(x-tx)+(y-ty)*(y-ty));//no square root is used because radius is squared and it is an optimization
}

}
}

Nothing in that class is too complex. The equation is the important part, but how it’s used is more important.

Now to actually draw the metaballs. We will use a BitmapData named “canvas” to draw the metaballs onto.

public function draw_metaballs(e:Event):void {
canvas.floodFill(0,0,0);
// Value to act as a summation of all Metaballs' fields applied to this particular pixel
var sum:Number = 0;
// Iterate over every pixel on the screen
for(var ty:int = 0; ty < stage.stageHeight; ty++) {
for(var tx:int = 0; tx < stage.stageWidth; tx++) {
// Reset the summation
sum = 0;

// Iterate through every Metaball
for(var i:int = 0; i < metaballs.length; i++) {
sum += metaballs[i].equation(tx,ty);
}

// Decide whether to draw a pixel
if(sum >= minThreshold && sum <= maxThreshold) canvas.setPixel(tx, ty, 0xFFFFFF);
}
}
}

min and max threshold will have to be messed with to get the metaballs to be drawn properly. The min and max depend upon the size of the metaballs.

It generates cool pictures like this:

Metaballs

And with some modifications:

Glowing MetaballsGlowing MetaballsGlowing Red Metaballs

These pictures took too long to generate to be of any use. I want something usable and animated. Time to try something else.

This article seems like it may work. It defines metaballs as:

    • mn is the location of metaball n
  • sn is the size of metaball n

 

  • k is the number of metaballs

 

  • g is the gooey-ness which affects how the balls are drawn

 

  • r is the threshold for metaballs

 

  • p is the place vector

 

Metaballs Try 2

Second attempt. Click to play with it

Click here to see what it creates.

This still isn’t what I want. It does work fast and it does make a pretty neat effect, but it isn’t a super awesome metaball.

There are also a few problems with it. If you get the balls in the right spot, you’ll notice a “hole” forms in the middle of the metaball group.

It was a good attempt, but not good enough.

Now that we know enough about how metaballs work, how they are created, and a good idea on how to make it work fast, it’s time to try to make our own metaballs!

Back to the first, slow one. It worked well, created a nice effect and everything, it was just slow. Maybe there’s a way to speed it up. Can Pixel Bender save the day?

Here it is! Metaballs from pixelbender, nice and fast! Taken straight from the code in the first example.

For the rest of this article, I will assume you are familiar with pixel bender. If you aren’t, here is some sources to help you get started.

http://www.adobe.com/devnet/flash/articles/pixel_bender_basics.html

http://gotoandlearn.com/play.php?id=83

http://gotoandlearn.com/play.php?id=84

Now, the pixel bender kernal:

<languageVersion : 1.0;>

kernel Metaballs
<   namespace : "com.rocketmandevelopment";
 vendor : "Rocketman Development";
 version : 1;
 description : "Fast Metaballs";
>
{
parameter float minThreshold
<
 minValue:float(0.0);
 maxValue:float(2.0);
 defaultValue:float(0.9);
 description: "minThreshold";
>;
//no max threshold because I want these balls completely filled
parameter float3 ball1
<//this is where it gets odd. Pixel bender with flash doesn't support loops, so you must pass in each ball individually. This limits the amount of balls to the amount defined in the filter
 minValue:float3(0.0,0.0,0.0); //storing the data is also odd. We'll use a float3, which is like an array of 3 float values.
 maxValue:float3(640.0,480.0,50.0); //this first is the x, second is y, and the third is radius.
 defaultValue:float3(50.0,50.0,20.0);
 description: "ball1, params, x,y,radius";
>;

parameter float3 ball2
<
 minValue:float3(0.0,0.0,0.0);
 maxValue:float3(640.0,480.0,50.0);//this example only supports two balls
 defaultValue:float3(100.0,100.0,20.0);
 description: "ball2, params, x,y,radius";
>;

 input image4 src;
 output pixel4 dst;

 void
 evaluatePixel()
 {
 dst = sampleNearest(src,outCoord());
 dst.rbg = float3(0,0,0);//sets the current pixel to black so the image is cleared before redrawing
 float2 coord = outCoord(); //get the coordinate of the pixel
 float sum = 0.0; //get the sum and set it to 0
 sum += (ball1.z)/((ball1.x-coord.x)*(ball1.x-coord.x)+(ball1.y-coord.y)*(ball1.y-coord.y)); //add to the sum using the formula from the first example
 sum += (ball2.z)/((ball2.x-coord.x)*(ball2.x-coord.x)+(ball2.y-coord.y)*(ball2.y-coord.y));
 if(sum >= minThreshold){
 dst.rgb = float3(255,255,255); //set it to black if its within the threshold
 }

 }
}

Pixel Bender and flash make a odd combination. No loops are allowed. The balls are passed in as float3 with x as x, y as y, and z as z.

Now for the AS3 that displays it:


public class PixelBenderMetaballs extends Sprite {
[Embed(source='../MetaballsFilter.pbj', mimeType='application/octet-stream')] //this is how you embed a pixelbender filter in flash
private static const MetaballsFilter:Class;

private const canvas:BitmapData = new BitmapData(640,480,false,0);//set up the bitmapdata
private const blur:BlurFilter = new BlurFilter(4,4,1);//set up a blurfilter to soften the edges
private var metaballsFilter:ShaderFilter;
private var b1:Metaball;//same metaball class as before
private var b:Bitmap;
private var b2:Metaball;

public function PixelBenderMetaballs(){
super();
b = new Bitmap(canvas);
b.smoothing = true;
addChild(b);
var ba:ByteArray = new MetaballsFilter() as ByteArray; //create a bytearray from the filter
var s:Shader = new Shader(ba);//create a shader
metaballsFilter = new ShaderFilter(s);//create a shaderfilter
metaballsFilter.shader.data.src.image = canvas;//set the source to the canvas bitmapdata
b1 = new Metaball(100,100,20);//create the metaballs
b2 = new Metaball(150,150,20);
metaballsFilter.shader.data.ball1.value = [b1.x,b1.y,b1.radius];//set the data in the filter
metaballsFilter.shader.data.ball2.value = [b2.x,b2.y,b2.radius];
metaballsFilter.shader.precisionHint = ShaderPrecision.FAST;//speed it up, accuracy isn't needed
b.filters = [metaballsFilter];//apply the filter
addEventListener(Event.ENTER_FRAME, ef);
}

private function ef(e:Event):void {
b1.x += 2;//move the ball
metaballsFilter.shader.data.ball1.value = [b1.x,b1.y,b1.radius];//reapply the filter
metaballsFilter.shader.data.ball2.value = [b2.x,b2.y,b2.radius];
b.filters = [metaballsFilter,blur];
}
}

Pixel Bender Version. Click to watch.

I enhanced the movement and got this. It’s pretty cool.

It’s pretty cool, but I can’t leave it there. It needs more balls!

Many Metaballs!

5 Metaballs. Click to watch!

Glowing Metaballs?!? Click to watch!

Thats all this post. I plan to do more with metaballs soon, like color and shadows, maybe some gradients.