## Cheap-Ass Metaballs

timmeh42
Citizen
Posts: 90
Joined: Wed Mar 07, 2012 7:32 pm
Location: Cape Town, South Africa

### Cheap-Ass Metaballs

Something I did a while ago in Gamemaker and recently decided to port to Löve (only actually ported it this afternoon) was a small demo of metaballs. Now, I have absolutely no idea how metaballs are meant to be made, but I hear that it involves maths. And I hate maths. Therefore, when I was working on it in GM, I found that a similar effect could be achieved with gradiented images being additively drawn onto a surface, which is then put through a threshold function reducing it to pure black-and-white. The threshold function in GM I used was a really hacked-together thing using recursive rendering in certain blend modes, but when I first thought of doing it in Löve I realised that PixelEffects would be perfect for the job.
SO today I looked through some simple PixelEffect tutorials, and made a very simple BnW shader (it rounds the rgb values to the nearest whole number).
Using this I then went and coded up the rest of the stuff needed for the demo.
I therefore present to you
the Cheap-Ass Metaball System (no, I'm not going to work a Löve-pun into this. EDIT: "Cheap, Ugly Metaballs"?)
so-called because it is a cheap-ass, hackish method of faking metaballs.

Here is a shot showing it giving a cool 985 FPS.

Note that the edges are not anti-aliased at all; this could be remedied by using a more advanced shader. Someone else can do that part.

The way I achieve this is I draw a 64x64 image of a radial sinusoidal gradient from black to white at the points where the metaballs are, using additive blending. This is then passed through the afore-mentioned shader. The gradient must be sinusoidal to achieve the proper effect of the metaballs interacting; a straight-line gradient will give flat edges where they interact.

If you look at the code, be aware that the for-loop is made for a table of only 10 metaballs. This was out of laziness, so just expand it or make it detect the size of the table if you want to use it. Auto-detects table size now, it just assumes the xvalue table is the same length as the yvalue table.

UPDATE:
+ Auto-detects the size of the tables, not hard-coded anymore.
+ The shader now uses 'floor' instead of 'round', which isn't supported by some computers.
Attachments
CAMetaballsB.love
Autodetects table size, now uses floor instead of round in the shader
CAMetaballs.love
Old version, rather use the B version
Last edited by timmeh42 on Tue May 01, 2012 6:21 pm, edited 1 time in total.

slime
Solid Snayke
Posts: 2923
Joined: Mon Aug 23, 2010 6:45 am
Contact:

### Re: Cheap-Ass Metaballs

It looks like your shader code uses a function which isn't officially supported in GLSL 1.20 (which LÖVE uses). http://www.opengl.org/sdk/docs/manglsl/xhtml/round.xml
It will possibly work on some of the more lenient graphics drivers, but it doesn't seem to for me in OSX with an ATI video card and an intel one (both of which do support later GLSL versions).

Adding this to the top of the shader code made it work for me, but I'm not sure what will happen on systems where the driver allows access to the built-in round, so you might want to rename it.

Code: Select all

float round(float x)
{
return floor(x + 0.5);
}

timmeh42
Citizen
Posts: 90
Joined: Wed Mar 07, 2012 7:32 pm
Location: Cape Town, South Africa

### Re: Cheap-Ass Metaballs

Ah, I didn't realise Löve used such an old version. Too much trouble to use newer version?
Works on my pc, which uses ATI video card (HD4670) but pretty up-to-date drivers (updated about a month ago). Thanks for that bit of code tho, will add it when I get time.

bartbes
Sex machine
Posts: 4946
Joined: Fri Aug 29, 2008 10:35 am
Location: The Netherlands
Contact:

### Re: Cheap-Ass Metaballs

timmeh42 wrote:Ah, I didn't realise Löve used such an old version. Too much trouble to use newer version?
It's a matter of compatibility, by aiming low, you include as many people as possible.

timmeh42
Citizen
Posts: 90
Joined: Wed Mar 07, 2012 7:32 pm
Location: Cape Town, South Africa

### Re: Cheap-Ass Metaballs

Right, updated.

UPDATE:
+ Auto-detects the size of the tables, not hard-coded anymore.
+ The shader now uses 'floor' instead of 'round', which isn't supported by some computers.

Also added some line spacing and changed the names of some variables.

Tesselode
Party member
Posts: 555
Joined: Fri Jul 23, 2010 7:55 pm

### Re: Cheap-Ass Metaballs

Why are people so obsessed with metaballs?

ishkabible
Party member
Posts: 241
Joined: Sat Oct 23, 2010 7:34 pm
Location: Kansas USA

### Re: Cheap-Ass Metaballs

I want to use this for a fluid simulator; get a bunch of physics balls and display them using meatballs that you created and presto; instance fluid simulator

Tesselode
Party member
Posts: 555
Joined: Fri Jul 23, 2010 7:55 pm

### Re: Cheap-Ass Metaballs

Haha, "meatballs."

slime
Solid Snayke
Posts: 2923
Joined: Mon Aug 23, 2010 6:45 am
Contact:

### Re: Cheap-Ass Metaballs

Yeah, portal 2's gels use 3d metaballs for visualization I think.

ishkabible
Party member
Posts: 241
Joined: Sat Oct 23, 2010 7:34 pm
Location: Kansas USA

### Re: Cheap-Ass Metaballs

proof of concept: this looks just a lot like portal 2's goo. I'm still tweaking the numbers. maybe adding surface tension(attraction between close balls) will give it a better effect.

Code: Select all

function love.load()
mb_img = love.graphics.newImage("metaball.png")
canv = love.graphics.newCanvas(800,600)
effect = love.graphics.newPixelEffect [[
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) {
vec4 pixel = Texel(texture, texture_coords);
pixel.r = floor(pixel.r+0.5);
pixel.g = floor(pixel.g+0.5);
pixel.b = floor(pixel.b+0.5);
return  pixel;
}
]]

love.physics.setMeter(64) --the height of a meter our worlds will be 64px
world = love.physics.newWorld(0, 9.81*64, true) --create a world for the bodies to exist in with horizontal gravity of 0 and vertical gravity of 9.81

--let's create the ground
ground = {}
ground.body = love.physics.newBody(world, 800/2, 600-50/2) --remember, the shape (the rectangle we create next) anchors to the body from its center, so we have to move it to (650/2, 650-50/2)
ground.shape = love.physics.newRectangleShape(800, 50) --make a rectangle with a width of 650 and a height of 50
ground.fixture = love.physics.newFixture(ground.body, ground.shape); --attach shape to body

--let's create a ball
balls = {}
for i=1, 300, 1 do
local x = math.random(-100, 100)
local y = math.random(-100, 100)
balls[i] = {}
balls[i].body = love.physics.newBody(world, 800/2 + x, 600/2 + y, "dynamic") --place the body in the center of the world and make it dynamic, so it can move around
balls[i].body:setMass(75) --give it a mass of 15
balls[i].shape = love.physics.newCircleShape(2.75) --the ball's shape has a radius of 10
balls[i].fixture = love.physics.newFixture(balls[i].body, balls[i].shape, 10) --attach shape to body and give it a friction of 1
balls[i].fixture:setRestitution(.9) --let the ball bounce
end
end

function love.update(dt)
world:update(dt) --this puts the world into motion
end

function love.draw()
canv:clear(0,0,0,0)
love.graphics.setCanvas(canv)

for i=1, 100, 1 do
love.graphics.draw(mb_img, balls[i].body:getX(), balls[i].body:getY())
end

love.graphics.setBlendMode("alpha")
love.graphics.setCanvas()

love.graphics.setPixelEffect(effect)
love.graphics.draw(canv,0,0,0,1,1,0,0,0,0)
love.graphics.setPixelEffect()

love.graphics.setColor(255,255,255,255)
love.graphics.print(love.timer.getFPS(),32,32,0,1,1,0,0,0,0)
end
if this was really going to be done right; I would use a faster fluid dynamics algorithm that had a 2D array of density for a given area of the screen; the more pressure the more intense the color(alpha value) and the larger the circle. that would give a REALLY nice effect.

### Who is online

Users browsing this forum: No registered users and 54 guests