AS3 Metaballs

Posted by: rocketman on June 15, 2010

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 2Second 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.

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.

EffectPixel bender

Current rating: 5


Comments

  • Pedram 4 years, 2 months ago

    excellent, thanks.

    Link / Reply
  • Dan 3 years, 10 months ago

    Hi, it's awesome work you did here, Congratulations!
    I wonder if you can share the code of the second attempt:
    (http://rocketmandevelopment.com/wp-content/uploads/2010/06/Metaballs.swf?width=640&amp;height=480)
    I'm not that good at maths
    Thank you very Much

    Link / Reply
  • SyntheCypher 2 years, 4 months ago

    I was looking at you're pixel blender example and the edges are extremely blurry is there any way to get the same effect but with hard edges.

    Link / Reply
  • Marc-Andre Weibezahn 2 years, 2 months ago

    Hi, nice post!
    I know that your experiment was all about the formula and performance optimization (pixel bender is great, but still a bit cumbersome) – but I wanted to add that you can have very similar glowing metaball effects with a simple GlowFilter on the balls. Just set the blur size and the strength right (from my experience, half the size for the former, 5 for the latter) and the blendMode to BlendMode.ADD.
    The cool thing with this is that you are not limited to circles this way and have cool blend effects when the balls have different colors :)

    Link / Reply
  • Rocketman Development &raquo; AS3 MetaEllipses 1 year, 8 months ago

    [...] will be based off the same pixel bender filter used in the metaballs [...]

    Link / Reply
  • YopSolo 7 months, 4 weeks ago

    http://rocketmandevelopment.com/wp-content/uploads/2010/06/PixelBenderMetaballsGlowing.swf?width-640&height=480

    page not found :(

    Link / Reply

New Comment

required
required (not published)
optional
  • Back in July, we looked at how to use Ray Casting for collision detection. We also learned how to use the Separation of Axis Theorem. I recently had a request for a way to use the Shapes we created here with the Ray Casting method. First, lets do a quick review of the shape classes. read more
    3 years, 7 months ago
  • Today we will finally be detecting a collision with SAT. We know how SAT works, we've built classes to work with SAT, now we can use all of this to detect a real, live collision! read more
    3 years, 9 months ago
  • A few months ago, I posted on the separation of axis theorem. You can learn all about SAT and how it works here. What that post failed to do was use the SAT. We will explore using SAT for collision detection in this post. read more
    3 years, 10 months ago
  • Some of you have asked for the entire steering behavior source as well as the A* source. Here is the zip with all the classes. In there is the vehicle... read more
    3 years, 10 months ago
  • In the last three posts, we explored the how A* works, then we put A-star into code, then we looked at different heuristics for A*. Now we will combine A*... read more
    3 years, 10 months ago
RSS / Atom